核心变更: - 产品定位: '像用水电一样使用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)
320 lines
12 KiB
Markdown
320 lines
12 KiB
Markdown
# 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
|
||
|
||
---
|
||
|
||
*文档结束*
|