新增功能: - 新增 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>
264 lines
5.5 KiB
Go
264 lines
5.5 KiB
Go
package sora
|
|
|
|
// ModelConfig 定义 Sora 模型配置。
|
|
type ModelConfig struct {
|
|
Type string
|
|
Width int
|
|
Height int
|
|
Orientation string
|
|
NFrames int
|
|
Model string
|
|
Size string
|
|
RequirePro bool
|
|
ExpansionLevel string
|
|
DurationS int
|
|
}
|
|
|
|
// ModelConfigs 定义所有模型配置。
|
|
var ModelConfigs = map[string]ModelConfig{
|
|
"gpt-image": {
|
|
Type: "image",
|
|
Width: 360,
|
|
Height: 360,
|
|
},
|
|
"gpt-image-landscape": {
|
|
Type: "image",
|
|
Width: 540,
|
|
Height: 360,
|
|
},
|
|
"gpt-image-portrait": {
|
|
Type: "image",
|
|
Width: 360,
|
|
Height: 540,
|
|
},
|
|
"sora2-landscape-10s": {
|
|
Type: "video",
|
|
Orientation: "landscape",
|
|
NFrames: 300,
|
|
},
|
|
"sora2-portrait-10s": {
|
|
Type: "video",
|
|
Orientation: "portrait",
|
|
NFrames: 300,
|
|
},
|
|
"sora2-landscape-15s": {
|
|
Type: "video",
|
|
Orientation: "landscape",
|
|
NFrames: 450,
|
|
},
|
|
"sora2-portrait-15s": {
|
|
Type: "video",
|
|
Orientation: "portrait",
|
|
NFrames: 450,
|
|
},
|
|
"sora2-landscape-25s": {
|
|
Type: "video",
|
|
Orientation: "landscape",
|
|
NFrames: 750,
|
|
Model: "sy_8",
|
|
Size: "small",
|
|
RequirePro: true,
|
|
},
|
|
"sora2-portrait-25s": {
|
|
Type: "video",
|
|
Orientation: "portrait",
|
|
NFrames: 750,
|
|
Model: "sy_8",
|
|
Size: "small",
|
|
RequirePro: true,
|
|
},
|
|
"sora2pro-landscape-10s": {
|
|
Type: "video",
|
|
Orientation: "landscape",
|
|
NFrames: 300,
|
|
Model: "sy_ore",
|
|
Size: "small",
|
|
RequirePro: true,
|
|
},
|
|
"sora2pro-portrait-10s": {
|
|
Type: "video",
|
|
Orientation: "portrait",
|
|
NFrames: 300,
|
|
Model: "sy_ore",
|
|
Size: "small",
|
|
RequirePro: true,
|
|
},
|
|
"sora2pro-landscape-15s": {
|
|
Type: "video",
|
|
Orientation: "landscape",
|
|
NFrames: 450,
|
|
Model: "sy_ore",
|
|
Size: "small",
|
|
RequirePro: true,
|
|
},
|
|
"sora2pro-portrait-15s": {
|
|
Type: "video",
|
|
Orientation: "portrait",
|
|
NFrames: 450,
|
|
Model: "sy_ore",
|
|
Size: "small",
|
|
RequirePro: true,
|
|
},
|
|
"sora2pro-landscape-25s": {
|
|
Type: "video",
|
|
Orientation: "landscape",
|
|
NFrames: 750,
|
|
Model: "sy_ore",
|
|
Size: "small",
|
|
RequirePro: true,
|
|
},
|
|
"sora2pro-portrait-25s": {
|
|
Type: "video",
|
|
Orientation: "portrait",
|
|
NFrames: 750,
|
|
Model: "sy_ore",
|
|
Size: "small",
|
|
RequirePro: true,
|
|
},
|
|
"sora2pro-hd-landscape-10s": {
|
|
Type: "video",
|
|
Orientation: "landscape",
|
|
NFrames: 300,
|
|
Model: "sy_ore",
|
|
Size: "large",
|
|
RequirePro: true,
|
|
},
|
|
"sora2pro-hd-portrait-10s": {
|
|
Type: "video",
|
|
Orientation: "portrait",
|
|
NFrames: 300,
|
|
Model: "sy_ore",
|
|
Size: "large",
|
|
RequirePro: true,
|
|
},
|
|
"sora2pro-hd-landscape-15s": {
|
|
Type: "video",
|
|
Orientation: "landscape",
|
|
NFrames: 450,
|
|
Model: "sy_ore",
|
|
Size: "large",
|
|
RequirePro: true,
|
|
},
|
|
"sora2pro-hd-portrait-15s": {
|
|
Type: "video",
|
|
Orientation: "portrait",
|
|
NFrames: 450,
|
|
Model: "sy_ore",
|
|
Size: "large",
|
|
RequirePro: true,
|
|
},
|
|
"prompt-enhance-short-10s": {
|
|
Type: "prompt_enhance",
|
|
ExpansionLevel: "short",
|
|
DurationS: 10,
|
|
},
|
|
"prompt-enhance-short-15s": {
|
|
Type: "prompt_enhance",
|
|
ExpansionLevel: "short",
|
|
DurationS: 15,
|
|
},
|
|
"prompt-enhance-short-20s": {
|
|
Type: "prompt_enhance",
|
|
ExpansionLevel: "short",
|
|
DurationS: 20,
|
|
},
|
|
"prompt-enhance-medium-10s": {
|
|
Type: "prompt_enhance",
|
|
ExpansionLevel: "medium",
|
|
DurationS: 10,
|
|
},
|
|
"prompt-enhance-medium-15s": {
|
|
Type: "prompt_enhance",
|
|
ExpansionLevel: "medium",
|
|
DurationS: 15,
|
|
},
|
|
"prompt-enhance-medium-20s": {
|
|
Type: "prompt_enhance",
|
|
ExpansionLevel: "medium",
|
|
DurationS: 20,
|
|
},
|
|
"prompt-enhance-long-10s": {
|
|
Type: "prompt_enhance",
|
|
ExpansionLevel: "long",
|
|
DurationS: 10,
|
|
},
|
|
"prompt-enhance-long-15s": {
|
|
Type: "prompt_enhance",
|
|
ExpansionLevel: "long",
|
|
DurationS: 15,
|
|
},
|
|
"prompt-enhance-long-20s": {
|
|
Type: "prompt_enhance",
|
|
ExpansionLevel: "long",
|
|
DurationS: 20,
|
|
},
|
|
}
|
|
|
|
// ModelListItem 返回模型列表条目。
|
|
type ModelListItem struct {
|
|
ID string `json:"id"`
|
|
Object string `json:"object"`
|
|
OwnedBy string `json:"owned_by"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
// ListModels 生成模型列表。
|
|
func ListModels() []ModelListItem {
|
|
models := make([]ModelListItem, 0, len(ModelConfigs))
|
|
for id, cfg := range ModelConfigs {
|
|
description := ""
|
|
switch cfg.Type {
|
|
case "image":
|
|
description = "Image generation"
|
|
if cfg.Width > 0 && cfg.Height > 0 {
|
|
description += " - " + itoa(cfg.Width) + "x" + itoa(cfg.Height)
|
|
}
|
|
case "video":
|
|
description = "Video generation"
|
|
if cfg.Orientation != "" {
|
|
description += " - " + cfg.Orientation
|
|
}
|
|
case "prompt_enhance":
|
|
description = "Prompt enhancement"
|
|
if cfg.ExpansionLevel != "" {
|
|
description += " - " + cfg.ExpansionLevel
|
|
}
|
|
if cfg.DurationS > 0 {
|
|
description += " (" + itoa(cfg.DurationS) + "s)"
|
|
}
|
|
default:
|
|
description = "Sora model"
|
|
}
|
|
models = append(models, ModelListItem{
|
|
ID: id,
|
|
Object: "model",
|
|
OwnedBy: "sora",
|
|
Description: description,
|
|
})
|
|
}
|
|
return models
|
|
}
|
|
|
|
func itoa(val int) string {
|
|
if val == 0 {
|
|
return "0"
|
|
}
|
|
neg := false
|
|
if val < 0 {
|
|
neg = true
|
|
val = -val
|
|
}
|
|
buf := [12]byte{}
|
|
i := len(buf)
|
|
for val > 0 {
|
|
i--
|
|
buf[i] = byte('0' + val%10)
|
|
val /= 10
|
|
}
|
|
if neg {
|
|
i--
|
|
buf[i] = '-'
|
|
}
|
|
return string(buf[i:])
|
|
}
|