# 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` (高风险) **官方新增字段**: ```go field.Bool("require_oauth_only").Default(false) field.Bool("require_privacy_set").Default(false) field.JSON("messages_dispatch_model_config", domain.OpenAIMessagesDispatchModelConfig{}) ``` **本地新增字段**: ```go 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` (高风险) **官方新增字段**: ```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() ``` **本地新增字段**: ```go 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注册**: ```go PaymentHandler, ChannelHandler, TLSFingerprintProfileHandler, PaymentWebhookHandler, ``` **本地新增Handler注册**: ```go SoraGatewayHandler, SoraClientHandler, ``` **合并方案**: 手动合并,保留双方所有Handler #### 冲突2: `endpoint.go` (中风险) **官方新增**: OpenAI Responses API路径处理 **本地新增**: `PlatformSora`支持和`/sora`路径前缀 **合并方案**: 1. 使用官方版本为基准 2. 手动添加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字段 **合并方案**: 1. 使用官方的请求头白名单 2. 保留本地的媒体字段 3. 评估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: 环境准备 ```bash # 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: 依赖准备 ```bash # 合并 go.mod # 官方依赖 + 本地Sora依赖 ``` **需要保留的本地依赖**: ```go // 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 ```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 ```go // 在官方版本基础上添加本地字段 // 位置: backend/ent/schema/usage_log.go // === 本地新增字段 (保留) === field.String("media_type").MaxLen(16).Optional().Nillable(), ``` #### 步骤2.3: 合并user.go ```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文件 ```bash # 这些文件官方独有,直接复制 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代码 ```bash cd D:/project/sub2api-merge/backend go generate ./ent ``` --- ### Phase 3: 后端代码合并 (Day 3-4) #### 步骤3.1: Handler层合并 **直接复制的官方文件** (无冲突): ```bash # 支付相关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 ``` **需要保留的本地文件**: ```bash # Sora相关Handler (必须保留) sora_client_handler.go sora_gateway_handler.go ``` **需要手动合并的文件**: 1. **wire.go**: ```go // 合并双方的Handler注册 func ProvideHandlers(...) []*Handler { return []*Handler{ // === 官方Handler === &PaymentHandler{}, &ChannelHandler{}, &TLSFingerprintProfileHandler{}, &PaymentWebhookHandler{}, // === 本地Handler (保留) === &SoraGatewayHandler{}, &SoraClientHandler{}, // ... 其他共同Handler } } ``` 2. **endpoint.go**: ```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层合并 **直接复制的官方文件** (支付系统): ```bash 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系统): ```bash 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 ``` **需要手动合并的文件**: 1. **service/wire.go**: ```go // 保留本地的SoraAccountRepo注入 func ProvideTokenRefreshService( // 官方参数... soraAccountRepo repository.SoraAccountRepository, // 本地新增 ) *TokenRefreshService { // ... } ``` 2. **gateway_service.go**: ```go // 合并双方的修改 const defaultMaxLineSize = 10 * 1024 * 1024 // 使用官方值 // 保留本地的媒体字段 type ForwardResult struct { // 官方字段... // 本地新增 (保留) MediaType string MediaURL string } ``` #### 步骤3.3: Repository层合并 **直接复制的官方文件**: ```bash channel_repo.go channel_repo_pricing.go tls_fingerprint_profile_repo.go tls_fingerprint_profile_cache.go ``` **需要保留的本地文件**: ```bash sora_account_repo.go sora_generation_repo.go ``` **需要手动合并的文件**: 1. **user_repo.go** - 添加Sora存储方法: ```go // === 本地新增方法 (保留) === 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) { // ... } ``` 2. **group_repo.go** - 合并排序功能: ```go // 保留官方的排序增强 func groupListOrder(params pagination.PaginationParams) []func(*entsql.Selector) { // 官方实现... } ``` #### 步骤3.4: Domain层合并 **直接复制的官方文件**: ```bash openai_messages_dispatch.go ``` **需要手动合并的文件**: 1. **constants.go**: ```go // 添加本地Sora平台常量 const ( PlatformAnthropic = "anthropic" PlatformOpenAI = "openai" PlatformGemini = "gemini" PlatformAntigravity = "antigravity" PlatformSora = "sora" // 本地新增 (保留) ) ``` #### 步骤3.5: Middleware层合并 **使用本地版本**: ```bash # 仅 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层合并 **直接复制的官方文件**: ```bash # 支付相关页面 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 ``` **需要保留的本地文件**: ```bash # Sora相关页面 views/user/SoraView.vue views/admin/DataManagementView.vue ``` #### 步骤4.2: Components层合并 **直接复制的官方文件** (支付组件): ```bash # 24个支付相关组件 components/payment/*.vue components/admin/payment/*.vue components/admin/channel/*.vue components/admin/TLSFingerprintProfilesModal.vue components/auth/OidcOAuthSection.vue components/keys/EndpointPopover.vue ``` **需要保留的本地文件**: ```bash # 8个Sora相关组件 components/sora/*.vue ``` #### 步骤4.3: API和类型合并 **直接复制的官方文件**: ```bash api/payment.ts api/admin/payment.ts api/admin/channels.ts api/admin/tlsFingerprintProfile.ts ``` **需要保留的本地文件**: ```bash api/sora.ts api/__tests__/sora.spec.ts ``` **需要手动合并的文件**: 1. **types/index.ts** - 扩展平台类型: ```typescript // 合并官方类型 + 本地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 } ``` 2. **stores/app.ts** - 合并状态: ```typescript export const useAppStore = defineStore('app', { state: () => ({ // === 官方状态 === payment_enabled: false, // === 本地状态 (保留) === sora_client_enabled: false, }) }) ``` 3. **router/index.ts** - 合并路由: ```typescript 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` ```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: 执行迁移 ```bash # 在测试环境先验证 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**: ```go type Config struct { // === 官方配置 === PaymentConfig PaymentConfig ChannelConfig ChannelConfig TLSFingerprintConfig TLSFingerprintConfig OIDCConnectConfig OIDCConnectConfig // === 本地配置 (保留) === SoraConfig SoraConfig } ``` #### 步骤6.2: 环境变量 **合并 .env.example**: ```bash # === 官方配置 === 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: 后端测试 ```bash # 运行所有测试 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: 前端测试 ```bash 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 代码回滚 ```bash # 恢复到备份版本 rm -rf D:/project/sub2api-merge cp -r D:/project/sub2apipro_backup_YYYYMMDD D:/project/sub2api-merge ``` ### 4.2 数据库回滚 ```sql -- 删除新增表 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`): ```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`): ```go // cleanup 中需要保留 soraMediaCleanup *service.SoraMediaCleanupService ``` **合并后的 cmd/server/wire.go**: ```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 结构体**: ```go 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 结构体**: ```go type Handlers struct { // ... 共同字段 SoraGateway *SoraGatewayHandler // 本地独有 SoraClient *SoraClientHandler // 本地独有 } ``` **合并后的 Handlers 结构体**: ```go 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 结构体**: ```go 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`): ```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`): ```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`): ```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` ```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补充 ```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`): ```typescript // ⚠️ 必须添加,否则 Group 类型编译失败 export interface OpenAIMessagesDispatchModelConfig { opus_mapped_model?: string sonnet_mapped_model?: string haiku_mapped_model?: string exact_model_mappings?: Record } // 合并后的完整 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