# AI PaaS 商业平台 — 数据库设计 > 版本: 2.0 | 日期: 2026-06-27 > 状态: 已修订(扁平化:去掉租户,直接挂在用户下) > 数据库: PostgreSQL 16+ | 缓存: Redis 7+ --- ## 1. ER 关系图 ``` users ──1:N──→ api_keys users ──1:1──→ subscriptions users ──1:N──→ credit_transactions users ──1:N──→ token_usage users ──1:N──→ capabilities (开发者发布) users ──1:N──→ skills (开发者发布) users ──1:N──→ skill_purchases (用户购买) skills ──1:N──→ skill_purchases capabilities ──1:N──→ capability_versions ``` --- ## 2. 表定义 ### 2.1 users(用户) ```sql CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), phone VARCHAR(20) UNIQUE, -- 手机号(主登录方式) email VARCHAR(255) UNIQUE, -- 邮箱(备用登录) password_hash VARCHAR(255), -- 密码哈希(邮箱登录时) name VARCHAR(255) NOT NULL, -- 显示名称 real_name VARCHAR(100), -- 真实姓名(实名认证后) id_card_hash VARCHAR(64), -- 身份证号哈希(实名认证后) avatar_url VARCHAR(500), -- 头像 role VARCHAR(20) NOT NULL DEFAULT 'user', -- user/admin verified BOOLEAN DEFAULT FALSE, -- 是否完成实名认证 subscribed BOOLEAN DEFAULT FALSE, -- 是否为订阅用户 status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/suspended/deleted casdoor_id VARCHAR(255), -- Casdoor 关联 ID last_login_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_users_phone ON users(phone); CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_users_status ON users(status); CREATE INDEX idx_users_verified ON users(verified); CREATE INDEX idx_users_subscribed ON users(subscribed); CREATE INDEX idx_users_casdoor ON users(casdoor_id); ``` ### 2.2 api_keys(API 密钥) ```sql CREATE TABLE api_keys ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, -- 用途标识 key_hash VARCHAR(64) NOT NULL UNIQUE, -- SHA-256 哈希 key_prefix VARCHAR(10) NOT NULL, -- 前缀 aip_xxxx scopes TEXT[] DEFAULT '{}', -- 权限范围 last_used_at TIMESTAMP WITH TIME ZONE, expires_at TIMESTAMP WITH TIME ZONE, status VARCHAR(20) NOT NULL DEFAULT 'active', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_api_keys_user ON api_keys(user_id); CREATE INDEX idx_api_keys_hash ON api_keys(key_hash); ``` ### 2.3 subscriptions(订阅) ```sql CREATE TABLE subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, plan VARCHAR(20) NOT NULL DEFAULT 'free', -- free/developer/pro/flagship credits_included BIGINT NOT NULL DEFAULT 0, -- 月包含 Token 数 credits_used BIGINT NOT NULL DEFAULT 0, -- 本月已用 Token price_cents INTEGER NOT NULL DEFAULT 0, -- 月费 (分) billing_cycle VARCHAR(10) NOT NULL DEFAULT 'monthly', period_start TIMESTAMP WITH TIME ZONE NOT NULL, period_end TIMESTAMP WITH TIME ZONE NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/cancelled/expired/grace grace_end TIMESTAMP WITH TIME ZONE, -- 欠费保护期截止 created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(user_id) ); CREATE INDEX idx_subscriptions_user ON subscriptions(user_id); CREATE INDEX idx_subscriptions_status ON subscriptions(status); CREATE INDEX idx_subscriptions_period ON subscriptions(period_start, period_end); ``` ### 2.4 credit_transactions(Credit 流水 — 不可变) ```sql CREATE TABLE credit_transactions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id), type VARCHAR(30) NOT NULL, -- usage/topup/subscription/bonus/refund amount BIGINT NOT NULL, -- 变动量 (分),正=加,负=减 balance_after BIGINT NOT NULL, -- 变动后余额 description TEXT, trace_id VARCHAR(64), idempotency_key VARCHAR(64) UNIQUE, reference_type VARCHAR(30), -- llm_call/skill_purchase/topup reference_id UUID, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_credit_tx_user ON credit_transactions(user_id); CREATE INDEX idx_credit_tx_type ON credit_transactions(type); CREATE INDEX idx_credit_tx_created ON credit_transactions(created_at); CREATE RULE credit_tx_no_update AS ON UPDATE TO credit_transactions DO INSTEAD NOTHING; CREATE RULE credit_tx_no_delete AS ON DELETE TO credit_transactions DO INSTEAD NOTHING; ``` ### 2.5 token_usage(Token 用量 — 按月分区) ```sql CREATE TABLE token_usage ( id UUID NOT NULL DEFAULT gen_random_uuid(), user_id UUID NOT NULL, capability_id UUID, model VARCHAR(100) NOT NULL, provider VARCHAR(50) NOT NULL, prompt_tokens INTEGER NOT NULL, completion_tokens INTEGER NOT NULL, total_tokens INTEGER NOT NULL, cost_cents BIGINT NOT NULL, -- 消耗费用 (分) trace_id VARCHAR(64), metadata JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), PRIMARY KEY (id, created_at) ) PARTITION BY RANGE (created_at); CREATE TABLE token_usage_2026_07 PARTITION OF token_usage FOR VALUES FROM ('2026-07-01') TO ('2026-08-01'); CREATE INDEX idx_token_usage_user ON token_usage(user_id, created_at); CREATE INDEX idx_token_usage_model ON token_usage(model, created_at); CREATE RULE token_usage_no_update AS ON UPDATE TO token_usage DO INSTEAD NOTHING; CREATE RULE token_usage_no_delete AS ON DELETE TO token_usage DO INSTEAD NOTHING; ``` ### 2.6 capabilities(AI 能力) ```sql CREATE TABLE capabilities ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, type VARCHAR(30) NOT NULL, -- agent/mcp/skill/llm/custom version VARCHAR(50) NOT NULL, description TEXT, tags TEXT[] DEFAULT '{}', endpoint VARCHAR(500), protocol VARCHAR(20) DEFAULT 'rest', pricing_config JSONB DEFAULT '{}', dependencies JSONB DEFAULT '[]', status VARCHAR(20) NOT NULL DEFAULT 'draft', review_note TEXT, view_count INTEGER DEFAULT 0, invoke_count BIGINT DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(user_id, name, version) ); CREATE INDEX idx_capabilities_user ON capabilities(user_id); CREATE INDEX idx_capabilities_type ON capabilities(type); CREATE INDEX idx_capabilities_status ON capabilities(status); CREATE INDEX idx_capabilities_tags ON capabilities USING GIN(tags); ``` ### 2.7 capability_versions(能力版本) ```sql CREATE TABLE capability_versions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), capability_id UUID NOT NULL REFERENCES capabilities(id) ON DELETE CASCADE, version VARCHAR(50) NOT NULL, endpoint VARCHAR(500), changelog TEXT, status VARCHAR(20) NOT NULL DEFAULT 'active', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(capability_id, version) ); ``` ### 2.8 skills(技能市场) ```sql CREATE TABLE skills ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), developer_id UUID NOT NULL REFERENCES users(id), name VARCHAR(255) NOT NULL, description TEXT, category VARCHAR(50), tags TEXT[] DEFAULT '{}', pricing_model VARCHAR(20) NOT NULL DEFAULT 'per_call', price_credits BIGINT NOT NULL DEFAULT 0, platform_share NUMERIC(3,2) DEFAULT 0.25, -- 平台分成比例 developer_share NUMERIC(3,2) DEFAULT 0.75, -- 开发者分成比例 endpoint VARCHAR(500), documentation TEXT, icon_url VARCHAR(500), status VARCHAR(20) NOT NULL DEFAULT 'draft', rating NUMERIC(3,2) DEFAULT 0, rating_count INTEGER DEFAULT 0, view_count INTEGER DEFAULT 0, purchase_count INTEGER DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_skills_developer ON skills(developer_id); CREATE INDEX idx_skills_category ON skills(category); CREATE INDEX idx_skills_status ON skills(status); CREATE INDEX idx_skills_rating ON skills(rating DESC); CREATE INDEX idx_skills_name_search ON skills USING GIN( to_tsvector('simple', name || ' ' || COALESCE(description, '')) ); ``` ### 2.9 skill_purchases(技能购买) ```sql CREATE TABLE skill_purchases ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id), skill_id UUID NOT NULL REFERENCES skills(id), developer_id UUID NOT NULL, amount_cents BIGINT NOT NULL, platform_share BIGINT NOT NULL, developer_share BIGINT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'active', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX idx_skill_purchases_user ON skill_purchases(user_id); CREATE INDEX idx_skill_purchases_skill ON skill_purchases(skill_id); ``` ### 2.10 developer_payouts(开发者提现) ```sql CREATE TABLE developer_payouts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), developer_id UUID NOT NULL REFERENCES users(id), amount_cents BIGINT NOT NULL, method VARCHAR(20) NOT NULL, -- alipay/bank account_info JSONB NOT NULL, -- 加密的收款信息 status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending/paid/rejected paid_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); ``` --- ## 3. Redis 数据结构 ``` # Credit 余额 (分) billing:balance:{user_id} → "125800" # 幂等键 billing:idempotency:{user_id}:{key} → "OK" # TTL: 24h # 每日统计 billing:daily:{user_id}:{YYYY-MM-DD} → { calls, tokens, cost } # TTL: 7d # 订阅信息缓存 billing:subscription:{user_id} → { plan, credits_included, credits_used, period_end } # TTL: 永不过期 # 限流 ratelimit:{user_id}:{window} → count # TTL: 窗口大小 # IM 会话上下文 im:session:{platform}:{user_id} → { messages: [...] } # TTL: 24h ``` --- ## 4. Migration 策略 ``` migrations/ ├── 001_init.sql # 所有表 ├── 002_seed_data.sql # 开发环境种子数据 ├── 003_add_partitions.sql # token_usage 分区 └── 004_add_fulltext.sql # 全文搜索索引 ``` **规则**: - Migration 文件名: `{序号}_{描述}.sql` - 不允许修改已执行的 Migration - 每个 Migration 必须可回滚 - 不可变表 (credit_transactions, token_usage): 无 UPDATE/DELETE --- *文档结束*