# 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<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
