核心变更: - 产品定位: '像用水电一样使用AI能力,注册即用,按量付费' - 用户模型: 去掉租户概念,扁平化为 订阅用户/非订阅用户 - 认证: Casdoor SSO + 阿里云实名认证(替代 Keycloak) - LLM 网关: NEW API 开源(替代自研 Go 网关) - AI 服务: FastAPI(替代 Go 自研,AI 生态更兼容) - 新增: 欠费不影响已发布能力(核心商业规则) - 新增: 开发者提现模块 - 开发周期: 20周 → 15周(开源组件复用) 文档列表: - 01-PRD-产品需求文档.md (v2.0) - 02-系统架构设计.md (v2.0) - 03-数据库设计.md (v2.0) - 04-API接口规范.md (v2.0) - 05-AGENTS-MiMoCode配置.md (v2.0)
12 KiB
12 KiB
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(用户)
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 密钥)
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(订阅)
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 流水 — 不可变)
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 用量 — 按月分区)
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 能力)
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(能力版本)
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(技能市场)
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(技能购买)
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(开发者提现)
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
文档结束