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

12 KiB
Raw Blame History

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

文档结束