Files
ai-paas-comparison/docs/03-数据库设计.md
T
qyyfw 8209f15d0a docs: v2.0 全面修订 — 开源优先 + 扁平化用户模型
核心变更:
- 产品定位: '像用水电一样使用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)
2026-06-27 11:35:53 +00:00

320 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
---
*文档结束*