1411 lines
41 KiB
Markdown
1411 lines
41 KiB
Markdown
|
|
# 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
|