From 13b95049c3195262bfb7c324d3f1ffaaf9cbd21b Mon Sep 17 00:00:00 2001 From: huangzhenpc Date: Sun, 4 Jan 2026 18:11:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=81=A2=E5=A4=8D=E8=BF=81=E7=A7=BB?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=88=B0=E5=8E=9F=E5=A7=8B=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=A0=A1=E9=AA=8C=E5=92=8C=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration files should never be modified after being applied. Reverted comment-only changes to fix checksum mismatch. 修复错误: migration 001_init.sql checksum mismatch 解决方案: 将迁移文件恢复到修改前的状态 --- backend/migrations/001_init.sql | 344 +++++++++--------- .../migrations/002_account_type_migration.sql | 66 ++-- backend/migrations/003_subscription.sql | 130 +++---- 3 files changed, 270 insertions(+), 270 deletions(-) diff --git a/backend/migrations/001_init.sql b/backend/migrations/001_init.sql index 0fa60b12..64078c42 100644 --- a/backend/migrations/001_init.sql +++ b/backend/migrations/001_init.sql @@ -1,172 +1,172 @@ --- TianShuAPI 初始化数据库迁移脚本 --- PostgreSQL 15+ - --- 1. proxies 代理IP表(无外键依赖) -CREATE TABLE IF NOT EXISTS proxies ( - id BIGSERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - protocol VARCHAR(20) NOT NULL, -- http/https/socks5 - host VARCHAR(255) NOT NULL, - port INT NOT NULL, - username VARCHAR(100), - password VARCHAR(100), - status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/disabled - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - deleted_at TIMESTAMPTZ -); - -CREATE INDEX IF NOT EXISTS idx_proxies_status ON proxies(status); -CREATE INDEX IF NOT EXISTS idx_proxies_deleted_at ON proxies(deleted_at); - --- 2. groups 分组表(无外键依赖) -CREATE TABLE IF NOT EXISTS groups ( - id BIGSERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL UNIQUE, - description TEXT, - rate_multiplier DECIMAL(10, 4) NOT NULL DEFAULT 1.0, -- 费率倍率 - is_exclusive BOOLEAN NOT NULL DEFAULT FALSE, -- 是否专属分组 - status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/disabled - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - deleted_at TIMESTAMPTZ -); - -CREATE INDEX IF NOT EXISTS idx_groups_name ON groups(name); -CREATE INDEX IF NOT EXISTS idx_groups_status ON groups(status); -CREATE INDEX IF NOT EXISTS idx_groups_is_exclusive ON groups(is_exclusive); -CREATE INDEX IF NOT EXISTS idx_groups_deleted_at ON groups(deleted_at); - --- 3. users 用户表(无外键依赖) -CREATE TABLE IF NOT EXISTS users ( - id BIGSERIAL PRIMARY KEY, - email VARCHAR(255) NOT NULL UNIQUE, - password_hash VARCHAR(255) NOT NULL, - role VARCHAR(20) NOT NULL DEFAULT 'user', -- admin/user - balance DECIMAL(20, 8) NOT NULL DEFAULT 0, -- 余额(可为负数) - concurrency INT NOT NULL DEFAULT 5, -- 并发数限制 - status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/disabled - allowed_groups BIGINT[] DEFAULT NULL, -- 允许绑定的分组ID列表 - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - deleted_at TIMESTAMPTZ -); - -CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); -CREATE INDEX IF NOT EXISTS idx_users_status ON users(status); -CREATE INDEX IF NOT EXISTS idx_users_deleted_at ON users(deleted_at); - --- 4. accounts 上游账号表(依赖proxies) -CREATE TABLE IF NOT EXISTS accounts ( - id BIGSERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - platform VARCHAR(50) NOT NULL, -- anthropic/openai/gemini - type VARCHAR(20) NOT NULL, -- oauth/apikey - credentials JSONB NOT NULL DEFAULT '{}', -- 凭证信息(加密存储) - extra JSONB NOT NULL DEFAULT '{}', -- 扩展信息 - proxy_id BIGINT REFERENCES proxies(id) ON DELETE SET NULL, - concurrency INT NOT NULL DEFAULT 3, -- 账号并发限制 - priority INT NOT NULL DEFAULT 50, -- 调度优先级(1-100,越小越高) - status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/disabled/error - error_message TEXT, - last_used_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - deleted_at TIMESTAMPTZ -); - -CREATE INDEX IF NOT EXISTS idx_accounts_platform ON accounts(platform); -CREATE INDEX IF NOT EXISTS idx_accounts_type ON accounts(type); -CREATE INDEX IF NOT EXISTS idx_accounts_status ON accounts(status); -CREATE INDEX IF NOT EXISTS idx_accounts_proxy_id ON accounts(proxy_id); -CREATE INDEX IF NOT EXISTS idx_accounts_priority ON accounts(priority); -CREATE INDEX IF NOT EXISTS idx_accounts_last_used_at ON accounts(last_used_at); -CREATE INDEX IF NOT EXISTS idx_accounts_deleted_at ON accounts(deleted_at); - --- 5. api_keys API密钥表(依赖users, groups) -CREATE TABLE IF NOT EXISTS api_keys ( - id BIGSERIAL PRIMARY KEY, - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - key VARCHAR(64) NOT NULL UNIQUE, -- sk-xxx格式 - name VARCHAR(100) NOT NULL, - group_id BIGINT REFERENCES groups(id) ON DELETE SET NULL, - status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/disabled - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - deleted_at TIMESTAMPTZ -); - -CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(key); -CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id); -CREATE INDEX IF NOT EXISTS idx_api_keys_group_id ON api_keys(group_id); -CREATE INDEX IF NOT EXISTS idx_api_keys_status ON api_keys(status); -CREATE INDEX IF NOT EXISTS idx_api_keys_deleted_at ON api_keys(deleted_at); - --- 6. account_groups 账号-分组关联表(依赖accounts, groups) -CREATE TABLE IF NOT EXISTS account_groups ( - account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, - group_id BIGINT NOT NULL REFERENCES groups(id) ON DELETE CASCADE, - priority INT NOT NULL DEFAULT 50, -- 分组内优先级 - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - PRIMARY KEY (account_id, group_id) -); - -CREATE INDEX IF NOT EXISTS idx_account_groups_group_id ON account_groups(group_id); -CREATE INDEX IF NOT EXISTS idx_account_groups_priority ON account_groups(priority); - --- 7. redeem_codes 卡密表(依赖users) -CREATE TABLE IF NOT EXISTS redeem_codes ( - id BIGSERIAL PRIMARY KEY, - code VARCHAR(32) NOT NULL UNIQUE, -- 兑换码 - type VARCHAR(20) NOT NULL DEFAULT 'balance', -- balance - value DECIMAL(20, 8) NOT NULL, -- 面值(USD) - status VARCHAR(20) NOT NULL DEFAULT 'unused', -- unused/used - used_by BIGINT REFERENCES users(id) ON DELETE SET NULL, - used_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX IF NOT EXISTS idx_redeem_codes_code ON redeem_codes(code); -CREATE INDEX IF NOT EXISTS idx_redeem_codes_status ON redeem_codes(status); -CREATE INDEX IF NOT EXISTS idx_redeem_codes_used_by ON redeem_codes(used_by); - --- 8. usage_logs 使用记录表(依赖users, api_keys, accounts) -CREATE TABLE IF NOT EXISTS usage_logs ( - id BIGSERIAL PRIMARY KEY, - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - api_key_id BIGINT NOT NULL REFERENCES api_keys(id) ON DELETE CASCADE, - account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, - request_id VARCHAR(64), - model VARCHAR(100) NOT NULL, - - -- Token使用量(4类) - input_tokens INT NOT NULL DEFAULT 0, - output_tokens INT NOT NULL DEFAULT 0, - cache_creation_tokens INT NOT NULL DEFAULT 0, - cache_read_tokens INT NOT NULL DEFAULT 0, - - -- 详细的缓存创建分类 - cache_creation_5m_tokens INT NOT NULL DEFAULT 0, - cache_creation_1h_tokens INT NOT NULL DEFAULT 0, - - -- 费用(USD) - input_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, - output_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, - cache_creation_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, - cache_read_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, - total_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, -- 原始总费用 - actual_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, -- 实际扣除费用 - - -- 元数据 - stream BOOLEAN NOT NULL DEFAULT FALSE, - duration_ms INT, - - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX IF NOT EXISTS idx_usage_logs_user_id ON usage_logs(user_id); -CREATE INDEX IF NOT EXISTS idx_usage_logs_api_key_id ON usage_logs(api_key_id); -CREATE INDEX IF NOT EXISTS idx_usage_logs_account_id ON usage_logs(account_id); -CREATE INDEX IF NOT EXISTS idx_usage_logs_model ON usage_logs(model); -CREATE INDEX IF NOT EXISTS idx_usage_logs_created_at ON usage_logs(created_at); -CREATE INDEX IF NOT EXISTS idx_usage_logs_user_created ON usage_logs(user_id, created_at); +-- Sub2API 初始化数据库迁移脚本 +-- PostgreSQL 15+ + +-- 1. proxies 代理IP表(无外键依赖) +CREATE TABLE IF NOT EXISTS proxies ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + protocol VARCHAR(20) NOT NULL, -- http/https/socks5 + host VARCHAR(255) NOT NULL, + port INT NOT NULL, + username VARCHAR(100), + password VARCHAR(100), + status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/disabled + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +CREATE INDEX IF NOT EXISTS idx_proxies_status ON proxies(status); +CREATE INDEX IF NOT EXISTS idx_proxies_deleted_at ON proxies(deleted_at); + +-- 2. groups 分组表(无外键依赖) +CREATE TABLE IF NOT EXISTS groups ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL UNIQUE, + description TEXT, + rate_multiplier DECIMAL(10, 4) NOT NULL DEFAULT 1.0, -- 费率倍率 + is_exclusive BOOLEAN NOT NULL DEFAULT FALSE, -- 是否专属分组 + status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/disabled + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +CREATE INDEX IF NOT EXISTS idx_groups_name ON groups(name); +CREATE INDEX IF NOT EXISTS idx_groups_status ON groups(status); +CREATE INDEX IF NOT EXISTS idx_groups_is_exclusive ON groups(is_exclusive); +CREATE INDEX IF NOT EXISTS idx_groups_deleted_at ON groups(deleted_at); + +-- 3. users 用户表(无外键依赖) +CREATE TABLE IF NOT EXISTS users ( + id BIGSERIAL PRIMARY KEY, + email VARCHAR(255) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + role VARCHAR(20) NOT NULL DEFAULT 'user', -- admin/user + balance DECIMAL(20, 8) NOT NULL DEFAULT 0, -- 余额(可为负数) + concurrency INT NOT NULL DEFAULT 5, -- 并发数限制 + status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/disabled + allowed_groups BIGINT[] DEFAULT NULL, -- 允许绑定的分组ID列表 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); +CREATE INDEX IF NOT EXISTS idx_users_status ON users(status); +CREATE INDEX IF NOT EXISTS idx_users_deleted_at ON users(deleted_at); + +-- 4. accounts 上游账号表(依赖proxies) +CREATE TABLE IF NOT EXISTS accounts ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + platform VARCHAR(50) NOT NULL, -- anthropic/openai/gemini + type VARCHAR(20) NOT NULL, -- oauth/apikey + credentials JSONB NOT NULL DEFAULT '{}', -- 凭证信息(加密存储) + extra JSONB NOT NULL DEFAULT '{}', -- 扩展信息 + proxy_id BIGINT REFERENCES proxies(id) ON DELETE SET NULL, + concurrency INT NOT NULL DEFAULT 3, -- 账号并发限制 + priority INT NOT NULL DEFAULT 50, -- 调度优先级(1-100,越小越高) + status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/disabled/error + error_message TEXT, + last_used_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +CREATE INDEX IF NOT EXISTS idx_accounts_platform ON accounts(platform); +CREATE INDEX IF NOT EXISTS idx_accounts_type ON accounts(type); +CREATE INDEX IF NOT EXISTS idx_accounts_status ON accounts(status); +CREATE INDEX IF NOT EXISTS idx_accounts_proxy_id ON accounts(proxy_id); +CREATE INDEX IF NOT EXISTS idx_accounts_priority ON accounts(priority); +CREATE INDEX IF NOT EXISTS idx_accounts_last_used_at ON accounts(last_used_at); +CREATE INDEX IF NOT EXISTS idx_accounts_deleted_at ON accounts(deleted_at); + +-- 5. api_keys API密钥表(依赖users, groups) +CREATE TABLE IF NOT EXISTS api_keys ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + key VARCHAR(64) NOT NULL UNIQUE, -- sk-xxx格式 + name VARCHAR(100) NOT NULL, + group_id BIGINT REFERENCES groups(id) ON DELETE SET NULL, + status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/disabled + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + deleted_at TIMESTAMPTZ +); + +CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(key); +CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id); +CREATE INDEX IF NOT EXISTS idx_api_keys_group_id ON api_keys(group_id); +CREATE INDEX IF NOT EXISTS idx_api_keys_status ON api_keys(status); +CREATE INDEX IF NOT EXISTS idx_api_keys_deleted_at ON api_keys(deleted_at); + +-- 6. account_groups 账号-分组关联表(依赖accounts, groups) +CREATE TABLE IF NOT EXISTS account_groups ( + account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + group_id BIGINT NOT NULL REFERENCES groups(id) ON DELETE CASCADE, + priority INT NOT NULL DEFAULT 50, -- 分组内优先级 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (account_id, group_id) +); + +CREATE INDEX IF NOT EXISTS idx_account_groups_group_id ON account_groups(group_id); +CREATE INDEX IF NOT EXISTS idx_account_groups_priority ON account_groups(priority); + +-- 7. redeem_codes 卡密表(依赖users) +CREATE TABLE IF NOT EXISTS redeem_codes ( + id BIGSERIAL PRIMARY KEY, + code VARCHAR(32) NOT NULL UNIQUE, -- 兑换码 + type VARCHAR(20) NOT NULL DEFAULT 'balance', -- balance + value DECIMAL(20, 8) NOT NULL, -- 面值(USD) + status VARCHAR(20) NOT NULL DEFAULT 'unused', -- unused/used + used_by BIGINT REFERENCES users(id) ON DELETE SET NULL, + used_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_redeem_codes_code ON redeem_codes(code); +CREATE INDEX IF NOT EXISTS idx_redeem_codes_status ON redeem_codes(status); +CREATE INDEX IF NOT EXISTS idx_redeem_codes_used_by ON redeem_codes(used_by); + +-- 8. usage_logs 使用记录表(依赖users, api_keys, accounts) +CREATE TABLE IF NOT EXISTS usage_logs ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + api_key_id BIGINT NOT NULL REFERENCES api_keys(id) ON DELETE CASCADE, + account_id BIGINT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE, + request_id VARCHAR(64), + model VARCHAR(100) NOT NULL, + + -- Token使用量(4类) + input_tokens INT NOT NULL DEFAULT 0, + output_tokens INT NOT NULL DEFAULT 0, + cache_creation_tokens INT NOT NULL DEFAULT 0, + cache_read_tokens INT NOT NULL DEFAULT 0, + + -- 详细的缓存创建分类 + cache_creation_5m_tokens INT NOT NULL DEFAULT 0, + cache_creation_1h_tokens INT NOT NULL DEFAULT 0, + + -- 费用(USD) + input_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, + output_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, + cache_creation_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, + cache_read_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, + total_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, -- 原始总费用 + actual_cost DECIMAL(20, 10) NOT NULL DEFAULT 0, -- 实际扣除费用 + + -- 元数据 + stream BOOLEAN NOT NULL DEFAULT FALSE, + duration_ms INT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_usage_logs_user_id ON usage_logs(user_id); +CREATE INDEX IF NOT EXISTS idx_usage_logs_api_key_id ON usage_logs(api_key_id); +CREATE INDEX IF NOT EXISTS idx_usage_logs_account_id ON usage_logs(account_id); +CREATE INDEX IF NOT EXISTS idx_usage_logs_model ON usage_logs(model); +CREATE INDEX IF NOT EXISTS idx_usage_logs_created_at ON usage_logs(created_at); +CREATE INDEX IF NOT EXISTS idx_usage_logs_user_created ON usage_logs(user_id, created_at); diff --git a/backend/migrations/002_account_type_migration.sql b/backend/migrations/002_account_type_migration.sql index 65bd4976..b1c955ef 100644 --- a/backend/migrations/002_account_type_migration.sql +++ b/backend/migrations/002_account_type_migration.sql @@ -1,33 +1,33 @@ --- TianShuAPI 账号类型迁移脚本 --- 将 'official' 类型账号迁移为 'oauth' 或 'setup-token' --- 根据 credentials->>'scope' 字段判断: --- - 包含 'user:profile' 的是 'oauth' 类型 --- - 只有 'user:inference' 的是 'setup-token' 类型 - --- 1. 将包含 profile scope 的 official 账号迁移为 oauth -UPDATE accounts -SET type = 'oauth', - updated_at = NOW() -WHERE type = 'official' - AND credentials->>'scope' LIKE '%user:profile%'; - --- 2. 将只有 inference scope 的 official 账号迁移为 setup-token -UPDATE accounts -SET type = 'setup-token', - updated_at = NOW() -WHERE type = 'official' - AND ( - credentials->>'scope' = 'user:inference' - OR credentials->>'scope' NOT LIKE '%user:profile%' - ); - --- 3. 处理没有 scope 字段的旧账号(默认为 oauth) -UPDATE accounts -SET type = 'oauth', - updated_at = NOW() -WHERE type = 'official' - AND (credentials->>'scope' IS NULL OR credentials->>'scope' = ''); - --- 4. 验证迁移结果(查询是否还有 official 类型账号) --- SELECT COUNT(*) FROM accounts WHERE type = 'official'; --- 如果结果为 0,说明迁移成功 +-- Sub2API 账号类型迁移脚本 +-- 将 'official' 类型账号迁移为 'oauth' 或 'setup-token' +-- 根据 credentials->>'scope' 字段判断: +-- - 包含 'user:profile' 的是 'oauth' 类型 +-- - 只有 'user:inference' 的是 'setup-token' 类型 + +-- 1. 将包含 profile scope 的 official 账号迁移为 oauth +UPDATE accounts +SET type = 'oauth', + updated_at = NOW() +WHERE type = 'official' + AND credentials->>'scope' LIKE '%user:profile%'; + +-- 2. 将只有 inference scope 的 official 账号迁移为 setup-token +UPDATE accounts +SET type = 'setup-token', + updated_at = NOW() +WHERE type = 'official' + AND ( + credentials->>'scope' = 'user:inference' + OR credentials->>'scope' NOT LIKE '%user:profile%' + ); + +-- 3. 处理没有 scope 字段的旧账号(默认为 oauth) +UPDATE accounts +SET type = 'oauth', + updated_at = NOW() +WHERE type = 'official' + AND (credentials->>'scope' IS NULL OR credentials->>'scope' = ''); + +-- 4. 验证迁移结果(查询是否还有 official 类型账号) +-- SELECT COUNT(*) FROM accounts WHERE type = 'official'; +-- 如果结果为 0,说明迁移成功 diff --git a/backend/migrations/003_subscription.sql b/backend/migrations/003_subscription.sql index b039a411..d9c54a32 100644 --- a/backend/migrations/003_subscription.sql +++ b/backend/migrations/003_subscription.sql @@ -1,65 +1,65 @@ --- TianShuAPI 订阅功能迁移脚本 --- 添加订阅分组和用户订阅功能 - --- 1. 扩展 groups 表添加订阅相关字段 -ALTER TABLE groups ADD COLUMN IF NOT EXISTS platform VARCHAR(50) NOT NULL DEFAULT 'anthropic'; -ALTER TABLE groups ADD COLUMN IF NOT EXISTS subscription_type VARCHAR(20) NOT NULL DEFAULT 'standard'; -ALTER TABLE groups ADD COLUMN IF NOT EXISTS daily_limit_usd DECIMAL(20, 8) DEFAULT NULL; -ALTER TABLE groups ADD COLUMN IF NOT EXISTS weekly_limit_usd DECIMAL(20, 8) DEFAULT NULL; -ALTER TABLE groups ADD COLUMN IF NOT EXISTS monthly_limit_usd DECIMAL(20, 8) DEFAULT NULL; -ALTER TABLE groups ADD COLUMN IF NOT EXISTS default_validity_days INT NOT NULL DEFAULT 30; - --- 添加索引 -CREATE INDEX IF NOT EXISTS idx_groups_platform ON groups(platform); -CREATE INDEX IF NOT EXISTS idx_groups_subscription_type ON groups(subscription_type); - --- 2. 创建 user_subscriptions 用户订阅表 -CREATE TABLE IF NOT EXISTS user_subscriptions ( - id BIGSERIAL PRIMARY KEY, - user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, - group_id BIGINT NOT NULL REFERENCES groups(id) ON DELETE CASCADE, - - -- 订阅有效期 - starts_at TIMESTAMPTZ NOT NULL, - expires_at TIMESTAMPTZ NOT NULL, - status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/expired/suspended - - -- 滑动窗口起始时间(NULL=未激活) - daily_window_start TIMESTAMPTZ, - weekly_window_start TIMESTAMPTZ, - monthly_window_start TIMESTAMPTZ, - - -- 当前窗口已用额度(USD,基于 total_cost 计算) - daily_usage_usd DECIMAL(20, 10) NOT NULL DEFAULT 0, - weekly_usage_usd DECIMAL(20, 10) NOT NULL DEFAULT 0, - monthly_usage_usd DECIMAL(20, 10) NOT NULL DEFAULT 0, - - -- 管理员分配信息 - assigned_by BIGINT REFERENCES users(id) ON DELETE SET NULL, - assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - notes TEXT, - - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - - -- 唯一约束:每个用户对每个分组只能有一个订阅 - UNIQUE(user_id, group_id) -); - --- user_subscriptions 索引 -CREATE INDEX IF NOT EXISTS idx_user_subscriptions_user_id ON user_subscriptions(user_id); -CREATE INDEX IF NOT EXISTS idx_user_subscriptions_group_id ON user_subscriptions(group_id); -CREATE INDEX IF NOT EXISTS idx_user_subscriptions_status ON user_subscriptions(status); -CREATE INDEX IF NOT EXISTS idx_user_subscriptions_expires_at ON user_subscriptions(expires_at); -CREATE INDEX IF NOT EXISTS idx_user_subscriptions_assigned_by ON user_subscriptions(assigned_by); - --- 3. 扩展 usage_logs 表添加分组和订阅关联 -ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS group_id BIGINT REFERENCES groups(id) ON DELETE SET NULL; -ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS subscription_id BIGINT REFERENCES user_subscriptions(id) ON DELETE SET NULL; -ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS rate_multiplier DECIMAL(10, 4) NOT NULL DEFAULT 1; -ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS first_token_ms INT; - --- usage_logs 新索引 -CREATE INDEX IF NOT EXISTS idx_usage_logs_group_id ON usage_logs(group_id); -CREATE INDEX IF NOT EXISTS idx_usage_logs_subscription_id ON usage_logs(subscription_id); -CREATE INDEX IF NOT EXISTS idx_usage_logs_sub_created ON usage_logs(subscription_id, created_at); +-- Sub2API 订阅功能迁移脚本 +-- 添加订阅分组和用户订阅功能 + +-- 1. 扩展 groups 表添加订阅相关字段 +ALTER TABLE groups ADD COLUMN IF NOT EXISTS platform VARCHAR(50) NOT NULL DEFAULT 'anthropic'; +ALTER TABLE groups ADD COLUMN IF NOT EXISTS subscription_type VARCHAR(20) NOT NULL DEFAULT 'standard'; +ALTER TABLE groups ADD COLUMN IF NOT EXISTS daily_limit_usd DECIMAL(20, 8) DEFAULT NULL; +ALTER TABLE groups ADD COLUMN IF NOT EXISTS weekly_limit_usd DECIMAL(20, 8) DEFAULT NULL; +ALTER TABLE groups ADD COLUMN IF NOT EXISTS monthly_limit_usd DECIMAL(20, 8) DEFAULT NULL; +ALTER TABLE groups ADD COLUMN IF NOT EXISTS default_validity_days INT NOT NULL DEFAULT 30; + +-- 添加索引 +CREATE INDEX IF NOT EXISTS idx_groups_platform ON groups(platform); +CREATE INDEX IF NOT EXISTS idx_groups_subscription_type ON groups(subscription_type); + +-- 2. 创建 user_subscriptions 用户订阅表 +CREATE TABLE IF NOT EXISTS user_subscriptions ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + group_id BIGINT NOT NULL REFERENCES groups(id) ON DELETE CASCADE, + + -- 订阅有效期 + starts_at TIMESTAMPTZ NOT NULL, + expires_at TIMESTAMPTZ NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/expired/suspended + + -- 滑动窗口起始时间(NULL=未激活) + daily_window_start TIMESTAMPTZ, + weekly_window_start TIMESTAMPTZ, + monthly_window_start TIMESTAMPTZ, + + -- 当前窗口已用额度(USD,基于 total_cost 计算) + daily_usage_usd DECIMAL(20, 10) NOT NULL DEFAULT 0, + weekly_usage_usd DECIMAL(20, 10) NOT NULL DEFAULT 0, + monthly_usage_usd DECIMAL(20, 10) NOT NULL DEFAULT 0, + + -- 管理员分配信息 + assigned_by BIGINT REFERENCES users(id) ON DELETE SET NULL, + assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + notes TEXT, + + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + -- 唯一约束:每个用户对每个分组只能有一个订阅 + UNIQUE(user_id, group_id) +); + +-- user_subscriptions 索引 +CREATE INDEX IF NOT EXISTS idx_user_subscriptions_user_id ON user_subscriptions(user_id); +CREATE INDEX IF NOT EXISTS idx_user_subscriptions_group_id ON user_subscriptions(group_id); +CREATE INDEX IF NOT EXISTS idx_user_subscriptions_status ON user_subscriptions(status); +CREATE INDEX IF NOT EXISTS idx_user_subscriptions_expires_at ON user_subscriptions(expires_at); +CREATE INDEX IF NOT EXISTS idx_user_subscriptions_assigned_by ON user_subscriptions(assigned_by); + +-- 3. 扩展 usage_logs 表添加分组和订阅关联 +ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS group_id BIGINT REFERENCES groups(id) ON DELETE SET NULL; +ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS subscription_id BIGINT REFERENCES user_subscriptions(id) ON DELETE SET NULL; +ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS rate_multiplier DECIMAL(10, 4) NOT NULL DEFAULT 1; +ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS first_token_ms INT; + +-- usage_logs 新索引 +CREATE INDEX IF NOT EXISTS idx_usage_logs_group_id ON usage_logs(group_id); +CREATE INDEX IF NOT EXISTS idx_usage_logs_subscription_id ON usage_logs(subscription_id); +CREATE INDEX IF NOT EXISTS idx_usage_logs_sub_created ON usage_logs(subscription_id, created_at);