docs: 补全基础设施文档 (DB设计/API规范/MiMoCode配置)

- 03-数据库设计.md: ER图 + 12张表定义 + Redis数据结构 + Migration策略
- 04-API接口规范.md: OpenAPI 3.0 约定 + 全部接口定义 (Auth/APIKey/LLM/Billing/Registry/Skills/IM/Admin)
- 05-AGENTS-MiMoCode配置.md: MiMo Code 核心配置文件,包含项目结构/技术栈/开发规范/模块说明

至此 docs/ 目录包含完整的6份基础设计文档,可作为 MiMo Code 的上下文输入
This commit is contained in:
qyyfw committed 2026-06-27 10:51:13 +00:00
1 parent 67c3270aad
commit d9a8a0e93c
5 files changed
+2099

No files matched your search

+443
View File
@@ -0,0 +1,443 @@
# AI PaaS 商业平台 — 数据库设计
> 版本: 1.0 | 日期: 2026-06-27
> 数据库: PostgreSQL 16+ | 缓存: Redis 7+
---
## 1. ER 关系图
```
tenants ──1:N──→ users
tenants ──1:N──→ capabilities
tenants ──1:N──→ subscriptions
tenants ──1:N──→ credit_transactions
tenants ──1:N──→ token_usage
tenants ──1:N──→ skills
tenants ──1:N──→ im_sessions
users ──1:N──→ api_keys
users ──1:N──→ skill_purchases
capabilities ──1:N──→ capability_versions
skills ──1:N──→ skill_purchases
im_sessions ──1:N──→ im_messages
```
---
## 2. 表定义
### 2.1 tenants(租户)
```sql
CREATE TABLE tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE, -- URL 友好标识
plan VARCHAR(20) NOT NULL DEFAULT 'free', -- free/pro/team/enterprise
credit_balance BIGINT NOT NULL DEFAULT 0, -- 毫 Credit (1 Credit = 1000)
credit_included BIGINT NOT NULL DEFAULT 100000, -- 月包含 Credit (毫)
credit_used BIGINT NOT NULL DEFAULT 0, -- 本月已用 (毫)
status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/suspended/deleted
settings JSONB DEFAULT '{}', -- 租户配置
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_tenants_slug ON tenants(slug);
CREATE INDEX idx_tenants_status ON tenants(status);
-- RLS
ALTER TABLE tenants ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON tenants
USING (id = current_setting('app.tenant_id')::uuid);
```
### 2.2 users(用户)
```sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
email VARCHAR(255) NOT NULL,
password_hash VARCHAR(255), -- Keycloak 管理时可为空
name VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'user', -- owner/admin/developer/user
avatar_url VARCHAR(500),
status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/suspended/deleted
last_login_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(tenant_id, email)
);
CREATE INDEX idx_users_tenant ON users(tenant_id);
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_role ON users(role);
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON users
USING (tenant_id = current_setting('app.tenant_id')::uuid);
```
### 2.3 api_keys(API 密钥)
```sql
CREATE TABLE api_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
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', -- active/revoked
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_api_keys_tenant ON api_keys(tenant_id);
CREATE INDEX idx_api_keys_hash ON api_keys(key_hash);
CREATE INDEX idx_api_keys_user ON api_keys(user_id);
ALTER TABLE api_keys ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON api_keys
USING (tenant_id = current_setting('app.tenant_id')::uuid);
```
### 2.4 subscriptions(订阅)
```sql
CREATE TABLE subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
plan VARCHAR(20) NOT NULL, -- free/pro/team/enterprise
credits_included BIGINT NOT NULL, -- 月包含 Credit (毫)
credits_used BIGINT NOT NULL DEFAULT 0, -- 本月已用
price_cents INTEGER NOT NULL DEFAULT 0, -- 月费 (分)
billing_cycle VARCHAR(10) NOT NULL DEFAULT 'monthly', -- monthly/yearly
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
stripe_sub_id VARCHAR(255), -- Stripe 订阅 ID
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_subscriptions_tenant ON subscriptions(tenant_id);
CREATE INDEX idx_subscriptions_status ON subscriptions(status);
CREATE INDEX idx_subscriptions_period ON subscriptions(period_start, period_end);
ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON subscriptions
USING (tenant_id = current_setting('app.tenant_id')::uuid);
```
### 2.5 credit_transactions(Credit 流水 — 不可变)
```sql
CREATE TABLE credit_transactions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
type VARCHAR(30) NOT NULL, -- usage/topup/subscription/bonus/refund
amount BIGINT NOT NULL, -- 变动量 (毫 Credit),正=加,负=减
balance_after BIGINT NOT NULL, -- 变动后余额
description TEXT, -- 描述
trace_id VARCHAR(64), -- 关联的调用 trace
idempotency_key VARCHAR(64) UNIQUE, -- 幂等键 (防重复扣费)
reference_type VARCHAR(30), -- llm_call/skill_purchase/topup/subscription
reference_id UUID, -- 关联 ID
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
-- 不可变: 无 updated_at, 无 DELETE
);
CREATE INDEX idx_credit_tx_tenant ON credit_transactions(tenant_id);
CREATE INDEX idx_credit_tx_type ON credit_transactions(type);
CREATE INDEX idx_credit_tx_created ON credit_transactions(created_at);
CREATE INDEX idx_credit_tx_trace ON credit_transactions(trace_id);
-- 不可变表: 禁用 UPDATE 和 DELETE
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;
ALTER TABLE credit_transactions ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON credit_transactions
USING (tenant_id = current_setting('app.tenant_id')::uuid);
```
### 2.6 token_usage(Token 用量 — 按月分区)
```sql
-- 主表 (自动分区)
CREATE TABLE token_usage (
id UUID NOT NULL DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
capability_id UUID,
user_id UUID,
model VARCHAR(100) NOT NULL, -- deepseek-chat, qwen-turbo
provider VARCHAR(50) NOT NULL, -- deepseek, qianwen, doubao
prompt_tokens INTEGER NOT NULL,
completion_tokens INTEGER NOT NULL,
total_tokens INTEGER NOT NULL,
cost_credits BIGINT NOT NULL, -- 消耗 Credit (毫)
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 TABLE token_usage_2026_08 PARTITION OF token_usage
FOR VALUES FROM ('2026-08-01') TO ('2026-09-01');
-- ... 自动创建未来分区
CREATE INDEX idx_token_usage_tenant ON token_usage(tenant_id, created_at);
CREATE INDEX idx_token_usage_model ON token_usage(model, created_at);
CREATE INDEX idx_token_usage_trace ON token_usage(trace_id);
-- 不可变表
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.7 capabilities(能力)
```sql
CREATE TABLE capabilities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
type VARCHAR(30) NOT NULL, -- agent/mcp/skill/llm/custom
version VARCHAR(50) NOT NULL, -- SemVer
description TEXT,
tags TEXT[] DEFAULT '{}',
endpoint VARCHAR(500), -- 调用地址
protocol VARCHAR(20) DEFAULT 'rest', -- rest/grpc/websocket
pricing_config JSONB DEFAULT '{}', -- 计费配置
dependencies JSONB DEFAULT '[]', -- 依赖的其他能力
owner_id UUID REFERENCES users(id),
status VARCHAR(20) NOT NULL DEFAULT 'draft', -- draft/pending_review/active/deprecated/rejected
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(tenant_id, name, version)
);
CREATE INDEX idx_capabilities_tenant ON capabilities(tenant_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);
ALTER TABLE capabilities ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON capabilities
USING (tenant_id = current_setting('app.tenant_id')::uuid);
```
### 2.8 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', -- active/deprecated
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(capability_id, version)
);
CREATE INDEX idx_cap_versions_cap ON capability_versions(capability_id);
```
### 2.9 skills(技能)
```sql
CREATE TABLE skills (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(50), -- 分类
tags TEXT[] DEFAULT '{}',
pricing_model VARCHAR(20) NOT NULL DEFAULT 'per_call', -- per_call/per_token/subscription/free
price_credits BIGINT NOT NULL DEFAULT 0, -- 价格 (毫 Credit)
developer_share NUMERIC(3,2) DEFAULT 0.75, -- 开发者分成比例
endpoint VARCHAR(500), -- API 地址
documentation TEXT, -- 使用文档
icon_url VARCHAR(500),
status VARCHAR(20) NOT NULL DEFAULT 'draft', -- draft/active/suspended/deleted
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_tenant ON skills(tenant_id);
CREATE INDEX idx_skills_category ON skills(category);
CREATE INDEX idx_skills_status ON skills(status);
CREATE INDEX idx_skills_tags ON skills USING GIN(tags);
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, '')));
ALTER TABLE skills ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON skills
USING (tenant_id = current_setting('app.tenant_id')::uuid);
```
### 2.10 skill_purchases(技能购买)
```sql
CREATE TABLE skill_purchases (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
user_id UUID NOT NULL REFERENCES users(id),
skill_id UUID NOT NULL REFERENCES skills(id),
developer_id UUID NOT NULL, -- 开发者 user_id
amount_credits BIGINT NOT NULL, -- 支付金额 (毫 Credit)
platform_share BIGINT NOT NULL, -- 平台分成
developer_share BIGINT NOT NULL, -- 开发者分成
status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/refunded
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);
CREATE INDEX idx_skill_purchases_tenant ON skill_purchases(tenant_id);
```
### 2.11 im_sessions(IM 会话)
```sql
CREATE TABLE im_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
platform VARCHAR(20) NOT NULL, -- wechat/qq/dingtalk/feishu
user_identifier VARCHAR(255) NOT NULL, -- 平台用户标识
user_name VARCHAR(255), -- 平台用户名
bot_id VARCHAR(255), -- 机器人标识
model VARCHAR(100) DEFAULT 'deepseek-chat', -- 当前使用模型
status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/closed
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
last_active_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_im_sessions_tenant ON im_sessions(tenant_id);
CREATE INDEX idx_im_sessions_platform ON im_sessions(platform, user_identifier);
ALTER TABLE im_sessions ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON im_sessions
USING (tenant_id = current_setting('app.tenant_id')::uuid);
```
### 2.12 im_messages(IM 消息)
```sql
CREATE TABLE im_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
session_id UUID NOT NULL REFERENCES im_sessions(id),
role VARCHAR(10) NOT NULL, -- user/assistant/system
content TEXT NOT NULL,
model VARCHAR(100), -- 使用的模型
tokens_used INTEGER,
cost_credits BIGINT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_im_messages_session ON im_messages(session_id, created_at);
```
---
## 3. Redis 数据结构
### 3.1 计费相关
```
# Credit 余额 (毫 Credit)
billing:balance:{tenant_id} → "12580000"
# 类型: String
# 操作: GET / DECRBY (Lua 原子)
# TTL: 永不过期
# 幂等键
billing:idempotency:{tenant_id}:{idempotency_key} → {
"result": "success",
"amount": -1280,
"trace_id": "abc-123"
}
# 类型: Hash
# TTL: 24 小时
# 每日统计
billing:daily:{tenant_id}:{YYYY-MM-DD} → {
"calls": "342",
"tokens": "456000",
"credits": "1280000"
}
# 类型: Hash
# TTL: 7 天
# 订阅信息
billing:subscription:{tenant_id} → {
"plan": "pro",
"credits_included": "50000000",
"credits_used": "12800000",
"period_end": "2026-07-27T00:00:00Z"
}
# 类型: Hash
# TTL: 永不过期
```
### 3.2 限流相关
```
# 滑动窗口限流
ratelimit:{tenant_id}:{window_key} → "1" (INCR)
# 类型: String
# TTL: 窗口大小 (如 60s)
```
### 3.3 IM 会话
```
# 会话上下文
im:session:{platform}:{user_id} → {
"tenant_id": "xxx",
"model": "deepseek-chat",
"messages": "[{role,content}, ...]" # 最近 20 条
}
# 类型: Hash
# TTL: 24 小时
```
---
## 4. Migration 策略
```
migrations/
├── 001_init.sql # 所有表 + RLS + 索引
├── 002_seed_data.sql # 开发环境种子数据
├── 003_add_partitions.sql # token_usage 分区
└── 004_add_fulltext.sql # 全文搜索索引
```
**规则**:
- Migration 文件名: `{序号}_{描述}.sql`
- 不允许修改已执行的 Migration
- 每个 Migration 必须可回滚
- 生产环境 Migration 必须在低峰期执行
---
*文档结束*