feat(sora): 新增 Sora 平台支持并修复高危安全和性能问题
新增功能: - 新增 Sora 账号管理和 OAuth 认证 - 新增 Sora 视频/图片生成 API 网关 - 新增 Sora 任务调度和缓存机制 - 新增 Sora 使用统计和计费支持 - 前端增加 Sora 平台配置界面 安全修复(代码审核): - [SEC-001] 限制媒体下载响应体大小(图片 20MB、视频 200MB),防止 DoS 攻击 - [SEC-002] 限制 SDK API 响应大小(1MB),防止内存耗尽 - [SEC-003] 修复 SSRF 风险,添加 URL 验证并强制使用代理配置 BUG 修复(代码审核): - [BUG-001] 修复 for 循环内 defer 累积导致的资源泄漏 - [BUG-002] 修复图片并发槽位获取失败时已持有锁未释放的永久泄漏 性能优化(代码审核): - [PERF-001] 添加 Sentinel Token 缓存(3 分钟有效期),减少 PoW 计算开销 技术细节: - 使用 io.LimitReader 限制所有外部输入的大小 - 添加 urlvalidator 验证防止 SSRF 攻击 - 使用 sync.Map 实现线程安全的包级缓存 - 优化并发槽位管理,添加 releaseAll 模式防止泄漏 影响范围: - 后端:新增 Sora 相关数据模型、服务、网关和管理接口 - 前端:新增 Sora 平台配置、账号管理和监控界面 - 配置:新增 Sora 相关配置项和环境变量 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -434,6 +434,172 @@ var (
|
||||
Columns: SettingsColumns,
|
||||
PrimaryKey: []*schema.Column{SettingsColumns[0]},
|
||||
}
|
||||
// SoraAccountsColumns holds the columns for the "sora_accounts" table.
|
||||
SoraAccountsColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "account_id", Type: field.TypeInt64},
|
||||
{Name: "access_token", Type: field.TypeString, Nullable: true},
|
||||
{Name: "session_token", Type: field.TypeString, Nullable: true},
|
||||
{Name: "refresh_token", Type: field.TypeString, Nullable: true},
|
||||
{Name: "client_id", Type: field.TypeString, Nullable: true},
|
||||
{Name: "email", Type: field.TypeString, Nullable: true},
|
||||
{Name: "username", Type: field.TypeString, Nullable: true},
|
||||
{Name: "remark", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
|
||||
{Name: "use_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "plan_type", Type: field.TypeString, Nullable: true},
|
||||
{Name: "plan_title", Type: field.TypeString, Nullable: true},
|
||||
{Name: "subscription_end", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "sora_supported", Type: field.TypeBool, Default: false},
|
||||
{Name: "sora_invite_code", Type: field.TypeString, Nullable: true},
|
||||
{Name: "sora_redeemed_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "sora_remaining_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "sora_total_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "sora_cooldown_until", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "cooled_until", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "image_enabled", Type: field.TypeBool, Default: true},
|
||||
{Name: "video_enabled", Type: field.TypeBool, Default: true},
|
||||
{Name: "image_concurrency", Type: field.TypeInt, Default: -1},
|
||||
{Name: "video_concurrency", Type: field.TypeInt, Default: -1},
|
||||
{Name: "is_expired", Type: field.TypeBool, Default: false},
|
||||
}
|
||||
// SoraAccountsTable holds the schema information for the "sora_accounts" table.
|
||||
SoraAccountsTable = &schema.Table{
|
||||
Name: "sora_accounts",
|
||||
Columns: SoraAccountsColumns,
|
||||
PrimaryKey: []*schema.Column{SoraAccountsColumns[0]},
|
||||
Indexes: []*schema.Index{
|
||||
{
|
||||
Name: "soraaccount_account_id",
|
||||
Unique: true,
|
||||
Columns: []*schema.Column{SoraAccountsColumns[3]},
|
||||
},
|
||||
{
|
||||
Name: "soraaccount_plan_type",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{SoraAccountsColumns[12]},
|
||||
},
|
||||
{
|
||||
Name: "soraaccount_sora_supported",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{SoraAccountsColumns[15]},
|
||||
},
|
||||
{
|
||||
Name: "soraaccount_image_enabled",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{SoraAccountsColumns[22]},
|
||||
},
|
||||
{
|
||||
Name: "soraaccount_video_enabled",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{SoraAccountsColumns[23]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// SoraCacheFilesColumns holds the columns for the "sora_cache_files" table.
|
||||
SoraCacheFilesColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||
{Name: "task_id", Type: field.TypeString, Nullable: true, Size: 120},
|
||||
{Name: "account_id", Type: field.TypeInt64},
|
||||
{Name: "user_id", Type: field.TypeInt64},
|
||||
{Name: "media_type", Type: field.TypeString, Size: 32},
|
||||
{Name: "original_url", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
|
||||
{Name: "cache_path", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
|
||||
{Name: "cache_url", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
|
||||
{Name: "size_bytes", Type: field.TypeInt64, Default: 0},
|
||||
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
}
|
||||
// SoraCacheFilesTable holds the schema information for the "sora_cache_files" table.
|
||||
SoraCacheFilesTable = &schema.Table{
|
||||
Name: "sora_cache_files",
|
||||
Columns: SoraCacheFilesColumns,
|
||||
PrimaryKey: []*schema.Column{SoraCacheFilesColumns[0]},
|
||||
Indexes: []*schema.Index{
|
||||
{
|
||||
Name: "soracachefile_account_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{SoraCacheFilesColumns[2]},
|
||||
},
|
||||
{
|
||||
Name: "soracachefile_user_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{SoraCacheFilesColumns[3]},
|
||||
},
|
||||
{
|
||||
Name: "soracachefile_media_type",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{SoraCacheFilesColumns[4]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// SoraTasksColumns holds the columns for the "sora_tasks" table.
|
||||
SoraTasksColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||
{Name: "task_id", Type: field.TypeString, Unique: true, Size: 120},
|
||||
{Name: "account_id", Type: field.TypeInt64},
|
||||
{Name: "model", Type: field.TypeString, Size: 120},
|
||||
{Name: "prompt", Type: field.TypeString, SchemaType: map[string]string{"postgres": "text"}},
|
||||
{Name: "status", Type: field.TypeString, Size: 32, Default: "processing"},
|
||||
{Name: "progress", Type: field.TypeFloat64, Default: 0},
|
||||
{Name: "result_urls", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
|
||||
{Name: "error_message", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"postgres": "text"}},
|
||||
{Name: "retry_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "completed_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
}
|
||||
// SoraTasksTable holds the schema information for the "sora_tasks" table.
|
||||
SoraTasksTable = &schema.Table{
|
||||
Name: "sora_tasks",
|
||||
Columns: SoraTasksColumns,
|
||||
PrimaryKey: []*schema.Column{SoraTasksColumns[0]},
|
||||
Indexes: []*schema.Index{
|
||||
{
|
||||
Name: "soratask_account_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{SoraTasksColumns[2]},
|
||||
},
|
||||
{
|
||||
Name: "soratask_status",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{SoraTasksColumns[5]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// SoraUsageStatsColumns holds the columns for the "sora_usage_stats" table.
|
||||
SoraUsageStatsColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||
{Name: "created_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "updated_at", Type: field.TypeTime, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "account_id", Type: field.TypeInt64},
|
||||
{Name: "image_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "video_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "error_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "last_error_at", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "timestamptz"}},
|
||||
{Name: "today_image_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "today_video_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "today_error_count", Type: field.TypeInt, Default: 0},
|
||||
{Name: "today_date", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{"postgres": "date"}},
|
||||
{Name: "consecutive_error_count", Type: field.TypeInt, Default: 0},
|
||||
}
|
||||
// SoraUsageStatsTable holds the schema information for the "sora_usage_stats" table.
|
||||
SoraUsageStatsTable = &schema.Table{
|
||||
Name: "sora_usage_stats",
|
||||
Columns: SoraUsageStatsColumns,
|
||||
PrimaryKey: []*schema.Column{SoraUsageStatsColumns[0]},
|
||||
Indexes: []*schema.Index{
|
||||
{
|
||||
Name: "sorausagestat_account_id",
|
||||
Unique: true,
|
||||
Columns: []*schema.Column{SoraUsageStatsColumns[3]},
|
||||
},
|
||||
{
|
||||
Name: "sorausagestat_today_date",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{SoraUsageStatsColumns[11]},
|
||||
},
|
||||
},
|
||||
}
|
||||
// UsageCleanupTasksColumns holds the columns for the "usage_cleanup_tasks" table.
|
||||
UsageCleanupTasksColumns = []*schema.Column{
|
||||
{Name: "id", Type: field.TypeInt64, Increment: true},
|
||||
@@ -843,6 +1009,10 @@ var (
|
||||
ProxiesTable,
|
||||
RedeemCodesTable,
|
||||
SettingsTable,
|
||||
SoraAccountsTable,
|
||||
SoraCacheFilesTable,
|
||||
SoraTasksTable,
|
||||
SoraUsageStatsTable,
|
||||
UsageCleanupTasksTable,
|
||||
UsageLogsTable,
|
||||
UsersTable,
|
||||
@@ -890,6 +1060,18 @@ func init() {
|
||||
SettingsTable.Annotation = &entsql.Annotation{
|
||||
Table: "settings",
|
||||
}
|
||||
SoraAccountsTable.Annotation = &entsql.Annotation{
|
||||
Table: "sora_accounts",
|
||||
}
|
||||
SoraCacheFilesTable.Annotation = &entsql.Annotation{
|
||||
Table: "sora_cache_files",
|
||||
}
|
||||
SoraTasksTable.Annotation = &entsql.Annotation{
|
||||
Table: "sora_tasks",
|
||||
}
|
||||
SoraUsageStatsTable.Annotation = &entsql.Annotation{
|
||||
Table: "sora_usage_stats",
|
||||
}
|
||||
UsageCleanupTasksTable.Annotation = &entsql.Annotation{
|
||||
Table: "usage_cleanup_tasks",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user