## Merged Features from sub2apipro - Sora video generation integration (OpenAI Sora API) - Group management enhancements - Usage log improvements - Security headers middleware ## Chinese Model Pricing Updates - GLM-5, GLM-5-Turbo, GLM-5.1, GLM-4.7, GLM-4.5-Air - Baichuan4, Baichuan4-Turbo, Baichuan4-Air, Baichuan-M3-Plus - DeepSeek-V3, DeepSeek-V3.2, DeepSeek-R1 - Qwen3-8B (free), Qwen2.5-72B-Instruct ## URL Whitelist Additions - api.baichuan-ai.com (百川智能) - api.siliconflow.cn (硅基流动) - api.z.ai (智谱国际) - api.groq.com (Groq加速推理) ## Documentation - Added merge guide (docs/MERGE_GUIDE.md) - Added quick reference (docs/MERGE_QUICKREF.md) - Added review reports (docs/reviews/)
41 KiB
Sub2API v0.1.112 与本地版本详细合并方案
生成日期: 2026-04-14 官方版本: v0.1.112 (D:\project\sub2api-latest) 本地版本: D:\project\sub2apipro 合并策略: 官方版本为基准 + 选择性合并本地修改
一、文件差异总览
1.1 差异统计
| 层级 | 官方独有 | 本地独有 | 差异文件 | 高风险冲突 |
|---|---|---|---|---|
| Schema | 5 | 0 | 3 | 2 |
| Handler | 15 | 4 | 12 | 3 |
| Service | 16 | 15 | 5 | 2 |
| Repository | 4 | 2 | 3 | 2 |
| Middleware | 0 | 0 | 1 | 0 |
| Domain | 1 | 0 | 1 | 1 |
| 前端Views | 11 | 3 | 3 | 1 |
| 前端Components | 24 | 8 | 5 | 2 |
| 前端API/Types | 4 | 2 | 8 | 3 |
| 总计 | 80 | 37 | 41 | 16 |
1.2 功能对比矩阵
| 功能模块 | 官方版本 | 本地版本 | 合并策略 |
|---|---|---|---|
| 支付系统 | ✅ 完整 | ❌ 无 | 必须引入 |
| 订阅计划 | ✅ 完整 | ❌ 无 | 必须引入 |
| TLS指纹配置 | ✅ 完整 | ❌ 无 | 必须引入 |
| 渠道管理 | ✅ 完整 | ❌ 无 | 必须引入 |
| OIDC OAuth | ✅ 完整 | ❌ 无 | 必须引入 |
| OpenAI Messages Dispatch | ✅ 完整 | ❌ 无 | 必须引入 |
| Sora视频生成 | ⚠️ 标注不可用 | ✅ 完整实现 | 保留+禁用 |
| Sora存储配额 | ❌ 无 | ✅ 完整 | 必须保留 |
二、高风险冲突点详细分析
2.1 Schema层冲突
冲突1: group.go (高风险)
官方新增字段:
field.Bool("require_oauth_only").Default(false)
field.Bool("require_privacy_set").Default(false)
field.JSON("messages_dispatch_model_config", domain.OpenAIMessagesDispatchModelConfig{})
本地新增字段:
field.Float("sora_image_price_360").Optional().Nillable()
field.Float("sora_image_price_540").Optional().Nillable()
field.Float("sora_video_price_per_request").Optional().Nillable()
field.Float("sora_video_price_per_request_hd").Optional().Nillable()
field.Int64("sora_storage_quota_bytes").Default(0)
合并方案: 合并双方所有字段,无冲突 数据库迁移: 需要添加8个新字段
冲突2: usage_log.go (高风险)
官方新增字段:
field.Int64("channel_id").Optional().Nillable()
field.String("model_mapping_chain").MaxLen(500).Optional().Nillable()
field.String("billing_tier").MaxLen(50).Optional().Nillable()
field.String("billing_mode").MaxLen(20).Optional().Nillable()
本地新增字段:
field.String("media_type").MaxLen(16).Optional().Nillable()
合并方案: 合并双方所有字段,无冲突 数据库迁移: 高频写入表,建议在低峰期迁移
冲突3: user.go (中风险)
官方新增: payment_orders edge
本地新增: sora_storage_quota_bytes, sora_storage_used_bytes 字段
合并方案: 合并双方修改
2.2 Handler层冲突
冲突1: wire.go (高风险)
官方新增Handler注册:
PaymentHandler,
ChannelHandler,
TLSFingerprintProfileHandler,
PaymentWebhookHandler,
本地新增Handler注册:
SoraGatewayHandler,
SoraClientHandler,
合并方案: 手动合并,保留双方所有Handler
冲突2: endpoint.go (中风险)
官方新增: OpenAI Responses API路径处理
本地新增: PlatformSora支持和/sora路径前缀
合并方案:
- 使用官方版本为基准
- 手动添加Sora平台判断逻辑
冲突3: admin/setting_handler.go (高风险)
差异: 官方版本大幅扩展(+16KB),新增支付/渠道等配置管理
合并方案: 使用官方版本,验证本地自定义配置是否丢失
2.3 Service层冲突
冲突1: wire.go (中风险)
官方: 无SoraAccountRepo注入 本地: 注入SoraAccountRepo用于双表同步
合并方案: 保留本地注入
冲突2: gateway_service.go (中风险)
差异:
- 官方: defaultMaxLineSize=10MB, 新增3个请求头
- 本地: defaultMaxLineSize=500MB, 新增MediaType/MediaURL字段
合并方案:
- 使用官方的请求头白名单
- 保留本地的媒体字段
- 评估500MB vs 10MB的业务需求后决定
2.4 前端冲突
冲突1: router/index.ts (高风险)
官方新增路由: 支付、渠道、OIDC回调 本地新增路由: Sora
合并方案: 合并双方所有路由
冲突2: 类型定义 (高风险)
GroupPlatform/AccountPlatform类型不兼容:
- 官方:
'anthropic' | 'openai' | 'gemini' | 'antigravity' - 本地: 上述 +
'sora'
合并方案: 扩展官方类型,添加'sora'
冲突3: stores/app.ts (中风险)
官方新增: payment_enabled等支付状态
本地新增: sora_client_enabled等Sora状态
合并方案: 合并双方状态
三、合并执行计划
Phase 1: 准备阶段 (Day 1)
步骤1.1: 环境准备
# 1. 备份本地版本
cp -r D:/project/sub2apipro D:/project/sub2apipro_backup_$(date +%Y%m%d)
# 2. 创建合并工作目录
cp -r D:/project/sub2api-latest D:/project/sub2api-merge
# 3. 初始化Git
cd D:/project/sub2api-merge
git init
git add .
git commit -m "chore: 官方版本 v0.1.112 基准"
git checkout -b merge-local-changes
步骤1.2: 依赖准备
# 合并 go.mod
# 官方依赖 + 本地Sora依赖
需要保留的本地依赖:
// Sora相关
github.com/DouDOU-start/go-sora2api v1.1.0
// TLS指纹模拟
github.com/bogdanfinn/fhttp v0.6.8
github.com/bogdanfinn/tls-client v1.14.0
github.com/bogdanfinn/utls v1.7.7-barnius
Phase 2: Schema层合并 (Day 2)
步骤2.1: 合并group.go
// 在官方版本基础上添加本地字段
// 位置: backend/ent/schema/group.go
// === 本地新增字段 (保留) ===
field.Float("sora_image_price_360").Optional().Nillable().
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
field.Float("sora_image_price_540").Optional().Nillable().
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
field.Float("sora_video_price_per_request").Optional().Nillable().
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
field.Float("sora_video_price_per_request_hd").Optional().Nillable().
SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}),
field.Int64("sora_storage_quota_bytes").Default(0),
步骤2.2: 合并usage_log.go
// 在官方版本基础上添加本地字段
// 位置: backend/ent/schema/usage_log.go
// === 本地新增字段 (保留) ===
field.String("media_type").MaxLen(16).Optional().Nillable(),
步骤2.3: 合并user.go
// 在官方版本基础上添加本地字段
// 位置: backend/ent/schema/user.go
// === 本地新增字段 (保留) ===
field.Int64("sora_storage_quota_bytes").Default(0),
field.Int64("sora_storage_used_bytes").Default(0),
步骤2.4: 复制官方独有Schema文件
# 这些文件官方独有,直接复制
cp D:/project/sub2api-latest/backend/ent/schema/payment_order.go \
D:/project/sub2api-merge/backend/ent/schema/
cp D:/project/sub2api-latest/backend/ent/schema/payment_provider_instance.go \
D:/project/sub2api-merge/backend/ent/schema/
cp D:/project/sub2api-latest/backend/ent/schema/payment_audit_log.go \
D:/project/sub2api-merge/backend/ent/schema/
cp D:/project/sub2api-latest/backend/ent/schema/subscription_plan.go \
D:/project/sub2api-merge/backend/ent/schema/
cp D:/project/sub2api-latest/backend/ent/schema/tls_fingerprint_profile.go \
D:/project/sub2api-merge/backend/ent/schema/
步骤2.5: 生成Ent代码
cd D:/project/sub2api-merge/backend
go generate ./ent
Phase 3: 后端代码合并 (Day 3-4)
步骤3.1: Handler层合并
直接复制的官方文件 (无冲突):
# 支付相关Handler
payment_handler.go
payment_webhook_handler.go
admin/payment_handler.go
admin/channel_handler.go
admin/tls_fingerprint_profile_handler.go
# 新增API Handler
gateway_handler_chat_completions.go
gateway_handler_responses.go
# OIDC认证
auth_oidc_oauth.go
需要保留的本地文件:
# Sora相关Handler (必须保留)
sora_client_handler.go
sora_gateway_handler.go
需要手动合并的文件:
- wire.go:
// 合并双方的Handler注册
func ProvideHandlers(...) []*Handler {
return []*Handler{
// === 官方Handler ===
&PaymentHandler{},
&ChannelHandler{},
&TLSFingerprintProfileHandler{},
&PaymentWebhookHandler{},
// === 本地Handler (保留) ===
&SoraGatewayHandler{},
&SoraClientHandler{},
// ... 其他共同Handler
}
}
- endpoint.go:
// 添加Sora平台判断
func oauthPlatformFromPath(path string) Platform {
switch {
// === 官方逻辑 ===
case strings.Contains(path, "/anthropic"):
return PlatformAnthropic
// ...
// === 本地新增 (保留) ===
case strings.Contains(path, "/sora"):
return PlatformSora
default:
return ""
}
}
步骤3.2: Service层合并
直接复制的官方文件 (支付系统):
payment_service.go
payment_order.go
payment_order_lifecycle.go
payment_order_expiry_service.go
payment_fulfillment.go
payment_refund.go
payment_stats.go
payment_config_service.go
payment_config_providers.go
payment_config_plans.go
payment_config_limits.go
billing_service.go
channel.go
channel_service.go
需要保留的本地文件 (Sora系统):
sora_gateway_service.go
sora_generation_service.go
sora_sdk_client.go
sora_models.go
sora_account_service.go
sora_quota_service.go
sora_s3_storage.go
sora_media_storage.go
sora_media_sign.go
sora_media_cleanup_service.go
sora_upstream_forwarder.go
需要手动合并的文件:
- service/wire.go:
// 保留本地的SoraAccountRepo注入
func ProvideTokenRefreshService(
// 官方参数...
soraAccountRepo repository.SoraAccountRepository, // 本地新增
) *TokenRefreshService {
// ...
}
- gateway_service.go:
// 合并双方的修改
const defaultMaxLineSize = 10 * 1024 * 1024 // 使用官方值
// 保留本地的媒体字段
type ForwardResult struct {
// 官方字段...
// 本地新增 (保留)
MediaType string
MediaURL string
}
步骤3.3: Repository层合并
直接复制的官方文件:
channel_repo.go
channel_repo_pricing.go
tls_fingerprint_profile_repo.go
tls_fingerprint_profile_cache.go
需要保留的本地文件:
sora_account_repo.go
sora_generation_repo.go
需要手动合并的文件:
- user_repo.go - 添加Sora存储方法:
// === 本地新增方法 (保留) ===
func (r *userRepository) AddSoraStorageUsageWithQuota(ctx context.Context, userID int64, deltaBytes int64, effectiveQuota int64) (int64, error) {
// ...
}
func (r *userRepository) ReleaseSoraStorageUsageAtomic(ctx context.Context, userID int64, deltaBytes int64) (int64, error) {
// ...
}
- group_repo.go - 合并排序功能:
// 保留官方的排序增强
func groupListOrder(params pagination.PaginationParams) []func(*entsql.Selector) {
// 官方实现...
}
步骤3.4: Domain层合并
直接复制的官方文件:
openai_messages_dispatch.go
需要手动合并的文件:
- constants.go:
// 添加本地Sora平台常量
const (
PlatformAnthropic = "anthropic"
PlatformOpenAI = "openai"
PlatformGemini = "gemini"
PlatformAntigravity = "antigravity"
PlatformSora = "sora" // 本地新增 (保留)
)
步骤3.5: Middleware层合并
使用本地版本:
# 仅 security_headers.go 有差异
# 本地版本包含 /sora/ 路径支持
cp D:/project/sub2apipro/backend/internal/server/middleware/security_headers.go \
D:/project/sub2api-merge/backend/internal/server/middleware/
Phase 4: 前端代码合并 (Day 5-6)
步骤4.1: Views层合并
直接复制的官方文件:
# 支付相关页面
views/user/PaymentView.vue
views/user/PaymentQRCodeView.vue
views/user/PaymentResultView.vue
views/user/StripePaymentView.vue
views/user/StripePopupView.vue
views/user/UserOrdersView.vue
# 管理后台页面
views/admin/orders/AdminPaymentDashboardView.vue
views/admin/orders/AdminOrdersView.vue
views/admin/orders/AdminPaymentPlansView.vue
views/admin/ChannelsView.vue
views/auth/OidcCallbackView.vue
需要保留的本地文件:
# Sora相关页面
views/user/SoraView.vue
views/admin/DataManagementView.vue
步骤4.2: Components层合并
直接复制的官方文件 (支付组件):
# 24个支付相关组件
components/payment/*.vue
components/admin/payment/*.vue
components/admin/channel/*.vue
components/admin/TLSFingerprintProfilesModal.vue
components/auth/OidcOAuthSection.vue
components/keys/EndpointPopover.vue
需要保留的本地文件:
# 8个Sora相关组件
components/sora/*.vue
步骤4.3: API和类型合并
直接复制的官方文件:
api/payment.ts
api/admin/payment.ts
api/admin/channels.ts
api/admin/tlsFingerprintProfile.ts
需要保留的本地文件:
api/sora.ts
api/__tests__/sora.spec.ts
需要手动合并的文件:
- types/index.ts - 扩展平台类型:
// 合并官方类型 + 本地Sora扩展
export type GroupPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity' | 'sora'
export type AccountPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity' | 'sora'
// 合并Group类型
export interface Group {
// === 官方字段 ===
require_oauth_only?: boolean
require_privacy_set?: boolean
messages_dispatch_model_config?: OpenAIMessagesDispatchModelConfig
// === 本地字段 (保留) ===
sora_image_price_360?: number
sora_image_price_540?: number
sora_video_price_per_request?: number
sora_video_price_per_request_hd?: number
sora_storage_quota_bytes?: number
}
// 合并User类型
export interface AdminUser {
// === 官方字段 ===
// ...
// === 本地字段 (保留) ===
sora_storage_quota_bytes?: number
sora_storage_used_bytes?: number
}
- stores/app.ts - 合并状态:
export const useAppStore = defineStore('app', {
state: () => ({
// === 官方状态 ===
payment_enabled: false,
// === 本地状态 (保留) ===
sora_client_enabled: false,
})
})
- router/index.ts - 合并路由:
const routes: RouteRecordRaw[] = [
// === 官方路由 ===
{ path: '/purchase', name: 'purchase', component: () => import('@/views/user/PaymentView.vue') },
{ path: '/payment/qrcode', name: 'payment-qrcode', component: () => import('@/views/user/PaymentQRCodeView.vue') },
{ path: '/admin/channels', name: 'admin-channels', component: () => import('@/views/admin/ChannelsView.vue') },
// ... 更多官方路由
// === 本地路由 (保留) ===
{ path: '/sora', name: 'sora', component: () => import('@/views/user/SoraView.vue') },
{ path: '/admin/data-management', name: 'admin-data-management', component: () => import('@/views/admin/DataManagementView.vue') },
]
Phase 5: 数据库迁移 (Day 7)
步骤5.1: 创建迁移脚本
创建新迁移文件: backend/migrations/XXX_merge_migration.sql
-- === 官方新表 ===
CREATE TABLE IF NOT EXISTS payment_orders (...);
CREATE TABLE IF NOT EXISTS payment_provider_instances (...);
CREATE TABLE IF NOT EXISTS payment_audit_logs (...);
CREATE TABLE IF NOT EXISTS subscription_plans (...);
CREATE TABLE IF NOT EXISTS tls_fingerprint_profiles (...);
-- === 官方新字段 ===
ALTER TABLE groups ADD COLUMN IF NOT EXISTS require_oauth_only BOOLEAN DEFAULT false;
ALTER TABLE groups ADD COLUMN IF NOT EXISTS require_privacy_set BOOLEAN DEFAULT false;
ALTER TABLE groups ADD COLUMN IF NOT EXISTS messages_dispatch_model_config JSONB DEFAULT '{}';
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS channel_id BIGINT;
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS model_mapping_chain VARCHAR(500);
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS billing_tier VARCHAR(50);
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS billing_mode VARCHAR(20);
-- === 本地新字段 (如果不存在) ===
ALTER TABLE users ADD COLUMN IF NOT EXISTS sora_storage_quota_bytes BIGINT DEFAULT 0;
ALTER TABLE users ADD COLUMN IF NOT EXISTS sora_storage_used_bytes BIGINT DEFAULT 0;
ALTER TABLE groups ADD COLUMN IF NOT EXISTS sora_image_price_360 DECIMAL(20,8);
ALTER TABLE groups ADD COLUMN IF NOT EXISTS sora_image_price_540 DECIMAL(20,8);
ALTER TABLE groups ADD COLUMN IF NOT EXISTS sora_video_price_per_request DECIMAL(20,8);
ALTER TABLE groups ADD COLUMN IF NOT EXISTS sora_video_price_per_request_hd DECIMAL(20,8);
ALTER TABLE groups ADD COLUMN IF NOT EXISTS sora_storage_quota_bytes BIGINT DEFAULT 0;
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS media_type VARCHAR(16);
-- === 本地新表 (如果不存在) ===
CREATE TABLE IF NOT EXISTS sora_accounts (...);
CREATE TABLE IF NOT EXISTS sora_generations (...);
步骤5.2: 执行迁移
# 在测试环境先验证
cd D:/project/sub2api-merge/backend
go run . migrate
# 检查迁移结果
psql -h localhost -U postgres -d sub2api -c "\dt"
psql -h localhost -U postgres -d sub2api -c "\d groups"
psql -h localhost -U postgres -d sub2api -c "\d usage_logs"
Phase 6: 配置合并 (Day 7)
步骤6.1: 后端配置
合并 config.go:
type Config struct {
// === 官方配置 ===
PaymentConfig PaymentConfig
ChannelConfig ChannelConfig
TLSFingerprintConfig TLSFingerprintConfig
OIDCConnectConfig OIDCConnectConfig
// === 本地配置 (保留) ===
SoraConfig SoraConfig
}
步骤6.2: 环境变量
合并 .env.example:
# === 官方配置 ===
PAYMENT_ENABLED=false
PAYMENT_MIN_AMOUNT=1
PAYMENT_MAX_AMOUNT=10000
# ... 更多支付配置
# === 本地配置 (保留) ===
SORA_CLIENT_ENABLED=false
SORA_STORAGE_TYPE=local
# ... 更多Sora配置
Phase 7: 测试验证 (Day 8-10)
步骤7.1: 后端测试
# 运行所有测试
cd D:/project/sub2api-merge/backend
go test ./... -v
# 特别关注
go test ./internal/handler/... -v
go test ./internal/service/... -v
go test ./internal/repository/... -v
步骤7.2: 前端测试
cd D:/project/sub2api-merge/frontend
pnpm install
pnpm typecheck
pnpm lint
pnpm test
pnpm build
步骤7.3: 集成测试清单
| 测试项 | 测试内容 | 预期结果 |
|---|---|---|
| 支付创建 | 创建支付订单 | 成功创建,状态PENDING |
| 支付回调 | 模拟支付成功回调 | 状态变为PAID,余额增加 |
| 订阅计划 | 创建/购买订阅 | 成功创建,用户订阅关联 |
| 渠道管理 | 创建/编辑渠道 | 成功保存,定价生效 |
| TLS指纹 | 创建指纹配置 | 成功保存,账号关联 |
| OIDC登录 | OIDC认证流程 | 成功登录,用户创建 |
| Sora生成 | 创建Sora任务 | 任务创建成功(功能禁用提示) |
| Sora配额 | 查询配额信息 | 返回配额数据 |
| API网关 | 各平台请求转发 | 正常响应 |
四、回滚方案
4.1 代码回滚
# 恢复到备份版本
rm -rf D:/project/sub2api-merge
cp -r D:/project/sub2apipro_backup_YYYYMMDD D:/project/sub2api-merge
4.2 数据库回滚
-- 删除新增表
DROP TABLE IF EXISTS payment_orders;
DROP TABLE IF EXISTS payment_provider_instances;
DROP TABLE IF EXISTS payment_audit_logs;
DROP TABLE IF EXISTS subscription_plans;
DROP TABLE IF EXISTS tls_fingerprint_profiles;
-- 删除新增字段
ALTER TABLE groups DROP COLUMN IF EXISTS require_oauth_only;
ALTER TABLE groups DROP COLUMN IF EXISTS require_privacy_set;
ALTER TABLE groups DROP COLUMN IF EXISTS messages_dispatch_model_config;
-- ... 更多字段
五、风险清单与缓解措施
| 风险 | 等级 | 缓解措施 |
|---|---|---|
| Wire依赖注入冲突 | 高 | 手动合并,逐个验证Handler |
| 数据库迁移失败 | 高 | 先在测试环境验证,准备回滚脚本 |
| 类型定义不兼容 | 高 | 扩展类型而非替换,保持向后兼容 |
| Sora功能影响其他模块 | 中 | 通过功能开关禁用,确保隔离 |
| 前端路由冲突 | 中 | 使用命名空间区分,避免重复 |
| 支付功能测试不完整 | 中 | 编写端到端测试,覆盖主要流程 |
六、合并后检查清单
6.1 功能检查
- 支付系统功能完整(创建订单、支付、退款)
- 订阅计划管理正常
- 渠道管理功能正常
- TLS指纹配置功能正常
- OIDC认证功能正常
- Sora功能开关正常(禁用状态)
- Sora配额管理正常
- 所有API端点正常响应
6.2 代码质量检查
- 后端测试全部通过
- 前端类型检查通过
- 前端lint检查通过
- 前端构建成功
- 无console.log残留
- 无硬编码密钥
6.3 安全检查
- JWT认证正常
- API Key认证正常
- 管理员权限验证正常
- 支付回调签名验证正常
- 敏感信息不在日志中泄露
七、时间表
| 阶段 | 内容 | 预计时间 |
|---|---|---|
| Phase 1 | 准备阶段 | Day 1 |
| Phase 2 | Schema层合并 | Day 2 |
| Phase 3 | 后端代码合并 | Day 3-4 |
| Phase 4 | 前端代码合并 | Day 5-6 |
| Phase 5 | 数据库迁移 | Day 7 |
| Phase 6 | 配置合并 | Day 7 |
| Phase 7 | 测试验证 | Day 8-10 |
| 总计 | 10天 |
八、专家审核发现的遗漏项补充
审核日期: 2026-04-14 审核结果: 综合评分 6.7/10 主要问题: Wire合并方案不完整、缺少类型定义、迁移脚本不完整
8.1 必须修复的严重问题
问题1: cmd/server/wire.go 合并方案补充
官方版本新增 (D:\project\sub2api-latest\backend\cmd\server\wire.go):
import (
"github.com/Wei-Shaw/sub2api/internal/payment" // 官方独有
)
wire.Build(
// === 官方新增 Provider ===
payment.ProvideRegistry,
payment.ProvideEncryptionKey,
payment.ProvideDefaultLoadBalancer,
service.ProvidePaymentConfigService,
service.ProvidePaymentOrderExpiryService,
)
本地版本 cleanup 函数 (D:\project\sub2apipro\backend\cmd\server\wire.go):
// cleanup 中需要保留
soraMediaCleanup *service.SoraMediaCleanupService
合并后的 cmd/server/wire.go:
import (
"github.com/Wei-Shaw/sub2api/internal/payment"
// ... 其他导入
)
wire.Build(
// === 官方 Provider (必须保留) ===
payment.ProvideRegistry,
payment.ProvideEncryptionKey,
payment.ProvideDefaultLoadBalancer,
service.ProvidePaymentConfigService,
service.ProvidePaymentOrderExpiryService,
// === 本地 Provider (保留) ===
// Sora 相关 Provider...
// ... 其他共同 Provider
)
// cleanup 函数合并
func cleanup(...) {
// === 官方 cleanup ===
paymentOrderExpiryService.Stop()
// === 本地 cleanup (保留) ===
soraMediaCleanup.Stop()
}
问题2: internal/handler/wire.go 结构体定义补充
官方版本 Handlers 结构体:
type Handlers struct {
Auth *AuthHandler
User *UserHandler
APIKey *APIKeyHandler
Usage *UsageHandler
Redeem *RedeemHandler
Subscription *SubscriptionHandler
Announcement *AnnouncementHandler
Admin *AdminHandlers
Gateway *GatewayHandler
OpenAIGateway *OpenAIGatewayHandler
Setting *SettingHandler
Totp *TotpHandler
Payment *PaymentHandler // 官方新增
PaymentWebhook *PaymentWebhookHandler // 官方新增
}
本地版本 Handlers 结构体:
type Handlers struct {
// ... 共同字段
SoraGateway *SoraGatewayHandler // 本地独有
SoraClient *SoraClientHandler // 本地独有
}
合并后的 Handlers 结构体:
type Handlers struct {
// === 共同字段 ===
Auth *AuthHandler
User *UserHandler
APIKey *APIKeyHandler
Usage *UsageHandler
Redeem *RedeemHandler
Subscription *SubscriptionHandler
Announcement *AnnouncementHandler
Admin *AdminHandlers
Gateway *GatewayHandler
OpenAIGateway *OpenAIGatewayHandler
Setting *SettingHandler
Totp *TotpHandler
// === 官方新增 (必须保留) ===
Payment *PaymentHandler
PaymentWebhook *PaymentWebhookHandler
// === 本地新增 (必须保留) ===
SoraGateway *SoraGatewayHandler
SoraClient *SoraClientHandler
}
合并后的 AdminHandlers 结构体:
type AdminHandlers struct {
// === 共同字段 ===
Account *AccountHandler
Group *GroupHandler
User *UserHandler
APIKey *APIKeyHandler
Redeem *RedeemHandler
Subscription *SubscriptionHandler
Usage *UsageHandler
Setting *SettingHandler
Announcement *AnnouncementHandler
Proxy *ProxyHandler
Dashboard *DashboardHandler
OAuth *OAuthHandler
OpenAIOAuth *OpenAIOAuthHandler
GeminiOAuth *GeminiOAuthHandler
AntigravityOAuth *AntigravityOAuthHandler
ErrorPassthrough *ErrorPassthroughHandler
PromoCode *PromoCodeHandler
// === 官方新增 (必须保留) ===
TLSFingerprintProfile *TLSFingerprintProfileHandler
Channel *ChannelHandler
Payment *PaymentHandler
}
问题3: domain/constants.go PlatformSora 常量补充
必须添加到合并版本 (backend/internal/domain/constants.go):
// Platform constants - 合并版本
const (
PlatformAnthropic = "anthropic"
PlatformOpenAI = "openai"
PlatformGemini = "gemini"
PlatformAntigravity = "antigravity"
PlatformSora = "sora" // ⚠️ 必须添加,否则 endpoint.go 编译失败
)
问题4: OpenAIMessagesDispatchModelConfig 类型定义
官方版本定义 (backend/internal/domain/openai_messages_dispatch.go):
type OpenAIMessagesDispatchModelConfig struct {
OpusMappedModel string `json:"opus_mapped_model,omitempty"`
SonnetMappedModel string `json:"sonnet_mapped_model,omitempty"`
HaikuMappedModel string `json:"haiku_mapped_model,omitempty"`
ExactModelMappings map[string]string `json:"exact_model_mappings,omitempty"`
}
必须确保合并版本包含此文件,否则 group.go 的 messages_dispatch_model_config 字段编译失败。
问题5: SoraAccountRepository 接口定义
本地版本定义 (backend/internal/repository/sora_account_repo.go):
type SoraAccountRepository interface {
GetByAccountID(ctx context.Context, accountID int64) (*SoraAccount, error)
Upsert(ctx context.Context, accountID int64, accessToken, refreshToken, sessionToken string) error
UpdateTokens(ctx context.Context, accountID int64, accessToken, refreshToken, sessionToken string) error
Delete(ctx context.Context, accountID int64) error
}
必须保留此文件,否则 service/wire.go 的 ProvideTokenRefreshService 依赖注入失败。
8.2 数据库迁移脚本补充
问题6-10: 迁移脚本完整版本
创建完整迁移文件: backend/migrations/099_merge_migration.sql
-- Migration: 099_merge_migration
-- 合并官方 v0.1.112 与本地版本的数据库迁移
-- 执行前必须先备份数据库
-- ============================================================
-- 第一部分:官方新表(带完整约束)
-- ============================================================
-- payment_orders 表(带外键约束)
CREATE TABLE IF NOT EXISTS payment_orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, -- ⚠️ 添加外键
user_email VARCHAR(255) NOT NULL DEFAULT '',
user_name VARCHAR(100) NOT NULL DEFAULT '',
user_notes TEXT,
amount DECIMAL(20,2) NOT NULL,
pay_amount DECIMAL(20,2) NOT NULL,
fee_rate DECIMAL(10,4) NOT NULL DEFAULT 0,
recharge_code VARCHAR(64) NOT NULL DEFAULT '',
out_trade_no VARCHAR(64) NOT NULL DEFAULT '',
payment_type VARCHAR(30) NOT NULL DEFAULT '',
payment_trade_no VARCHAR(128) NOT NULL DEFAULT '',
pay_url TEXT,
qr_code TEXT,
qr_code_img TEXT,
order_type VARCHAR(20) NOT NULL DEFAULT 'balance',
plan_id BIGINT,
subscription_group_id BIGINT,
subscription_days INT,
provider_instance_id VARCHAR(64),
status VARCHAR(30) NOT NULL DEFAULT 'PENDING',
refund_amount DECIMAL(20,2) NOT NULL DEFAULT 0,
refund_reason TEXT,
refund_at TIMESTAMPTZ,
force_refund BOOLEAN NOT NULL DEFAULT FALSE,
refund_requested_at TIMESTAMPTZ,
refund_request_reason TEXT,
refund_requested_by VARCHAR(20),
expires_at TIMESTAMPTZ NOT NULL,
paid_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
failed_at TIMESTAMPTZ,
failed_reason TEXT,
client_ip VARCHAR(50) NOT NULL DEFAULT '',
src_host VARCHAR(255) NOT NULL DEFAULT '',
src_url TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_payment_orders_user_id ON payment_orders(user_id);
CREATE INDEX IF NOT EXISTS idx_payment_orders_status ON payment_orders(status);
CREATE INDEX IF NOT EXISTS idx_payment_orders_expires_at ON payment_orders(expires_at);
CREATE INDEX IF NOT EXISTS idx_payment_orders_created_at ON payment_orders(created_at);
CREATE INDEX IF NOT EXISTS idx_payment_orders_paid_at ON payment_orders(paid_at);
CREATE INDEX IF NOT EXISTS idx_payment_orders_order_type ON payment_orders(order_type);
-- subscription_plans 表(带外键约束)
CREATE TABLE IF NOT EXISTS subscription_plans (
id BIGSERIAL PRIMARY KEY,
group_id BIGINT NOT NULL REFERENCES groups(id) ON DELETE CASCADE, -- ⚠️ 添加外键
name VARCHAR(100) NOT NULL,
description TEXT NOT NULL DEFAULT '',
price DECIMAL(20,2) NOT NULL,
original_price DECIMAL(20,2),
validity_days INT NOT NULL DEFAULT 30,
validity_unit VARCHAR(10) NOT NULL DEFAULT 'day',
features TEXT NOT NULL DEFAULT '',
product_name VARCHAR(100) NOT NULL DEFAULT '',
for_sale BOOLEAN NOT NULL DEFAULT TRUE,
sort_order INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_subscription_plans_group_id ON subscription_plans(group_id);
CREATE INDEX IF NOT EXISTS idx_subscription_plans_for_sale ON subscription_plans(for_sale);
-- payment_provider_instances 表
CREATE TABLE IF NOT EXISTS payment_provider_instances (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
provider_key VARCHAR(50) NOT NULL,
config TEXT NOT NULL, -- ⚠️ 注意:应用层需加密存储
supported_types TEXT NOT NULL DEFAULT '',
enabled BOOLEAN NOT NULL DEFAULT TRUE,
refund_enabled BOOLEAN NOT NULL DEFAULT FALSE,
min_amount DECIMAL(20,2) NOT NULL DEFAULT 1,
max_amount DECIMAL(20,2) NOT NULL DEFAULT 10000,
priority INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- payment_audit_logs 表
CREATE TABLE IF NOT EXISTS payment_audit_logs (
id BIGSERIAL PRIMARY KEY,
order_id VARCHAR(64) NOT NULL,
action VARCHAR(50) NOT NULL,
old_status VARCHAR(30),
new_status VARCHAR(30),
actor_type VARCHAR(20) NOT NULL,
actor_id BIGINT,
actor_name VARCHAR(100),
details TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_payment_audit_logs_order_id ON payment_audit_logs(order_id);
-- tls_fingerprint_profiles 表
CREATE TABLE IF NOT EXISTS tls_fingerprint_profiles (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT NOT NULL DEFAULT '',
client_hello_template JSONB NOT NULL DEFAULT '{}',
http2_settings JSONB NOT NULL DEFAULT '{}',
grease_extensions JSONB NOT NULL DEFAULT '{}',
psk_key_exchange_modes JSONB NOT NULL DEFAULT '{}',
curves JSONB NOT NULL DEFAULT '{}',
post_handshake_extensions JSONB NOT NULL DEFAULT '{}',
sig_algs JSONB NOT NULL DEFAULT '{}',
supported_versions JSONB NOT NULL DEFAULT '{}',
min_version INT NOT NULL DEFAULT 771,
max_version INT NOT NULL DEFAULT 772,
timeout_ms INT NOT NULL DEFAULT 30000,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ============================================================
-- 第二部分:本地新表(完整DDL)
-- ============================================================
-- sora_accounts 表 ⚠️ 完整定义
CREATE TABLE IF NOT EXISTS sora_accounts (
account_id BIGINT PRIMARY KEY REFERENCES accounts(id) ON DELETE CASCADE,
access_token TEXT NOT NULL,
refresh_token TEXT NOT NULL,
session_token TEXT,
upstream_user_id VARCHAR(128) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- sora_generations 表 ⚠️ 完整定义
CREATE TABLE IF NOT EXISTS sora_generations (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
api_key_id BIGINT,
group_id BIGINT,
model VARCHAR(64) NOT NULL,
prompt TEXT NOT NULL DEFAULT '',
media_type VARCHAR(16) NOT NULL DEFAULT 'video',
status VARCHAR(16) NOT NULL DEFAULT 'pending',
media_url TEXT NOT NULL DEFAULT '',
media_urls JSONB,
file_size_bytes BIGINT NOT NULL DEFAULT 0,
storage_type VARCHAR(16) NOT NULL DEFAULT 'none',
s3_object_keys JSONB,
upstream_task_id VARCHAR(128) NOT NULL DEFAULT '',
error_message TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
completed_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_sora_gen_user_created ON sora_generations(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_sora_gen_user_status ON sora_generations(user_id, status);
CREATE INDEX IF NOT EXISTS idx_sora_gen_status ON sora_generations(status);
-- ============================================================
-- 第三部分:官方新字段
-- ============================================================
-- groups 表官方新字段
ALTER TABLE groups ADD COLUMN IF NOT EXISTS require_oauth_only BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE groups ADD COLUMN IF NOT EXISTS require_privacy_set BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE groups ADD COLUMN IF NOT EXISTS messages_dispatch_model_config JSONB DEFAULT '{}';
-- usage_logs 表官方新字段
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS channel_id BIGINT;
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS model_mapping_chain VARCHAR(500);
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS billing_tier VARCHAR(50);
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS billing_mode VARCHAR(20);
-- ⚠️ 添加索引(使用 CONCURRENTLY 避免锁表)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_usage_logs_channel_id
ON usage_logs(channel_id) WHERE channel_id IS NOT NULL;
-- ============================================================
-- 第四部分:本地新字段
-- ============================================================
-- groups 表 Sora 字段
ALTER TABLE groups ADD COLUMN IF NOT EXISTS sora_image_price_360 DECIMAL(20,8);
ALTER TABLE groups ADD COLUMN IF NOT EXISTS sora_image_price_540 DECIMAL(20,8);
ALTER TABLE groups ADD COLUMN IF NOT EXISTS sora_video_price_per_request DECIMAL(20,8);
ALTER TABLE groups ADD COLUMN IF NOT EXISTS sora_video_price_per_request_hd DECIMAL(20,8);
ALTER TABLE groups ADD COLUMN IF NOT EXISTS sora_storage_quota_bytes BIGINT NOT NULL DEFAULT 0;
-- users 表 Sora 字段
ALTER TABLE users ADD COLUMN IF NOT EXISTS sora_storage_quota_bytes BIGINT NOT NULL DEFAULT 0;
ALTER TABLE users ADD COLUMN IF NOT EXISTS sora_storage_used_bytes BIGINT NOT NULL DEFAULT 0;
-- usage_logs 表媒体类型字段
ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS media_type VARCHAR(16);
8.3 完整回滚SQL补充
-- ============================================================
-- 完整回滚脚本: rollback_merge.sql
-- ============================================================
-- Phase 1: 删除外键约束
ALTER TABLE payment_orders DROP CONSTRAINT IF EXISTS fk_payment_orders_user_id;
ALTER TABLE subscription_plans DROP CONSTRAINT IF EXISTS fk_subscription_plans_group_id;
-- Phase 2: 删除新增表(按依赖顺序)
DROP TABLE IF EXISTS sora_generations CASCADE;
DROP TABLE IF EXISTS sora_accounts CASCADE;
DROP TABLE IF EXISTS payment_audit_logs CASCADE;
DROP TABLE IF EXISTS payment_orders CASCADE;
DROP TABLE IF EXISTS subscription_plans CASCADE;
DROP TABLE IF EXISTS payment_provider_instances CASCADE;
DROP TABLE IF EXISTS tls_fingerprint_profiles CASCADE;
-- Phase 3: 删除官方新增字段
ALTER TABLE groups DROP COLUMN IF EXISTS require_oauth_only;
ALTER TABLE groups DROP COLUMN IF EXISTS require_privacy_set;
ALTER TABLE groups DROP COLUMN IF EXISTS messages_dispatch_model_config;
ALTER TABLE usage_logs DROP COLUMN IF EXISTS channel_id;
ALTER TABLE usage_logs DROP COLUMN IF EXISTS model_mapping_chain;
ALTER TABLE usage_logs DROP COLUMN IF EXISTS billing_tier;
ALTER TABLE usage_logs DROP COLUMN IF EXISTS billing_mode;
-- Phase 4: 删除本地新增字段(如果需要完全回滚)
ALTER TABLE groups DROP COLUMN IF EXISTS sora_image_price_360;
ALTER TABLE groups DROP COLUMN IF EXISTS sora_image_price_540;
ALTER TABLE groups DROP COLUMN IF EXISTS sora_video_price_per_request;
ALTER TABLE groups DROP COLUMN IF EXISTS sora_video_price_per_request_hd;
ALTER TABLE groups DROP COLUMN IF EXISTS sora_storage_quota_bytes;
ALTER TABLE users DROP COLUMN IF EXISTS sora_storage_quota_bytes;
ALTER TABLE users DROP COLUMN IF EXISTS sora_storage_used_bytes;
ALTER TABLE usage_logs DROP COLUMN IF EXISTS media_type;
-- Phase 5: 删除新增索引
DROP INDEX IF EXISTS idx_usage_logs_channel_id;
DROP INDEX IF EXISTS idx_sora_gen_user_created;
DROP INDEX IF EXISTS idx_sora_gen_user_status;
DROP INDEX IF EXISTS idx_sora_gen_status;
8.4 前端类型定义补充
补充 OpenAIMessagesDispatchModelConfig 类型 (frontend/src/types/index.ts):
// ⚠️ 必须添加,否则 Group 类型编译失败
export interface OpenAIMessagesDispatchModelConfig {
opus_mapped_model?: string
sonnet_mapped_model?: string
haiku_mapped_model?: string
exact_model_mappings?: Record<string, string>
}
// 合并后的完整 Group 类型
export interface Group {
id: number
name: string
platform: GroupPlatform
// ... 其他共同字段
// === 官方字段 ===
require_oauth_only: boolean
require_privacy_set: boolean
messages_dispatch_model_config?: OpenAIMessagesDispatchModelConfig
default_mapped_model?: string
// === 本地字段 ===
sora_image_price_360?: number | null
sora_image_price_540?: number | null
sora_video_price_per_request?: number | null
sora_video_price_per_request_hd?: number | null
sora_storage_quota_bytes: number
}
8.5 安全测试补充清单
| 测试项 | 测试内容 | 预期结果 |
|---|---|---|
| 支付重放攻击 | 重复发送相同回调 | 拒绝处理,返回错误 |
| SQL注入测试 | 输入恶意SQL片段 | 无注入,正常处理 |
| XSS测试 | 输入恶意脚本 | 内容被净化 |
| 权限绕过测试 | 无权限用户访问管理API | 返回403 |
| Sora禁用验证 | 调用Sora API | 返回功能禁用提示 |
九、修订后的时间表
| 阶段 | 内容 | 原计划 | 修订后 |
|---|---|---|---|
| Phase 1 | 准备阶段 + 备份验证 | Day 1 | Day 1 |
| Phase 2 | Schema层合并 + 类型定义补充 | Day 2 | Day 2-3 |
| Phase 3 | 后端代码合并 + Wire完整合并 | Day 3-4 | Day 4-6 |
| Phase 4 | 前端代码合并 + 类型补充 | Day 5-6 | Day 7-8 |
| Phase 5 | 数据库迁移 + 索引创建 | Day 7 | Day 9 |
| Phase 6 | 配置合并 + 安全配置验证 | Day 7 | Day 9 |
| Phase 7 | 测试验证 + 安全测试 | Day 8-10 | Day 10-14 |
| 总计 | 10天 | 14天 |
增加时间用于:
- Wire合并方案的详细验证(+2天)
- 安全测试和端到端测试(+2天)
- 迁移脚本的完善和测试验证
十、执行前提条件检查清单
在开始执行合并前,必须满足以下条件:
代码准备
- 已备份本地版本 (
sub2apipro_backup_20260414) - 补充 PlatformSora 常量定义
- 确认 OpenAIMessagesDispatchModelConfig 类型存在
- 确认 SoraAccountRepository 接口保留
- 明确 Handlers/AdminHandlers 最终结构
迁移准备
- 补充完整的 sora_accounts 表 DDL
- 补充完整的 sora_generations 表 DDL
- 添加 channel_id 索引脚本
- 添加外键约束脚本
- 准备完整回滚脚本
测试准备
- 编写支付端到端测试用例
- 编写安全测试用例(重放攻击、注入测试)
- 编写 Sora 禁用验证测试
备份准备
- 完整备份数据库
- 验证备份恢复流程
- 准备迁移监控脚本
文档版本: v2.0 (专家审核后修订版) 最后更新: 2026-04-14 审核评分: 6.7/10 → 修复后预期 9/10