No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 

512 líneas
16 KiB

  1. package model
  2. import (
  3. "bytes"
  4. "database/sql/driver"
  5. "encoding/json"
  6. "time"
  7. "github.com/QuantumNous/new-api/common"
  8. "github.com/QuantumNous/new-api/constant"
  9. "github.com/QuantumNous/new-api/dto"
  10. commonRelay "github.com/QuantumNous/new-api/relay/common"
  11. )
  12. type TaskStatus string
  13. func (t TaskStatus) ToVideoStatus() string {
  14. var status string
  15. switch t {
  16. case TaskStatusQueued, TaskStatusSubmitted:
  17. status = dto.VideoStatusQueued
  18. case TaskStatusInProgress:
  19. status = dto.VideoStatusInProgress
  20. case TaskStatusSuccess:
  21. status = dto.VideoStatusCompleted
  22. case TaskStatusFailure:
  23. status = dto.VideoStatusFailed
  24. default:
  25. status = dto.VideoStatusUnknown // Default fallback
  26. }
  27. return status
  28. }
  29. const (
  30. TaskStatusNotStart TaskStatus = "NOT_START"
  31. TaskStatusSubmitted = "SUBMITTED"
  32. TaskStatusQueued = "QUEUED"
  33. TaskStatusInProgress = "IN_PROGRESS"
  34. TaskStatusFailure = "FAILURE"
  35. TaskStatusSuccess = "SUCCESS"
  36. TaskStatusUnknown = "UNKNOWN"
  37. )
  38. type Task struct {
  39. ID int64 `json:"id" gorm:"primary_key;AUTO_INCREMENT"`
  40. CreatedAt int64 `json:"created_at" gorm:"index"`
  41. UpdatedAt int64 `json:"updated_at"`
  42. TaskID string `json:"task_id" gorm:"type:varchar(191);index"` // 第三方id,不一定有/ song id\ Task id
  43. Platform constant.TaskPlatform `json:"platform" gorm:"type:varchar(30);index"` // 平台
  44. UserId int `json:"user_id" gorm:"index"`
  45. Group string `json:"group" gorm:"type:varchar(50)"` // 修正计费用
  46. ChannelId int `json:"channel_id" gorm:"index"`
  47. Quota int `json:"quota"`
  48. Action string `json:"action" gorm:"type:varchar(40);index"` // 任务类型, song, lyrics, description-mode
  49. Status TaskStatus `json:"status" gorm:"type:varchar(20);index"` // 任务状态
  50. FailReason string `json:"fail_reason"`
  51. SubmitTime int64 `json:"submit_time" gorm:"index"`
  52. StartTime int64 `json:"start_time" gorm:"index"`
  53. FinishTime int64 `json:"finish_time" gorm:"index"`
  54. Progress string `json:"progress" gorm:"type:varchar(20);index"`
  55. Properties Properties `json:"properties" gorm:"type:json"`
  56. Username string `json:"username,omitempty" gorm:"-"`
  57. // 禁止返回给用户,内部可能包含key等隐私信息
  58. PrivateData TaskPrivateData `json:"-" gorm:"column:private_data;type:json"`
  59. Data json.RawMessage `json:"data" gorm:"type:json"`
  60. }
  61. func (t *Task) SetData(data any) {
  62. b, _ := common.Marshal(data)
  63. t.Data = json.RawMessage(b)
  64. }
  65. func (t *Task) GetData(v any) error {
  66. return common.Unmarshal(t.Data, &v)
  67. }
  68. type Properties struct {
  69. Input string `json:"input"`
  70. UpstreamModelName string `json:"upstream_model_name,omitempty"`
  71. OriginModelName string `json:"origin_model_name,omitempty"`
  72. }
  73. func (m *Properties) Scan(val interface{}) error {
  74. bytesValue, _ := val.([]byte)
  75. if len(bytesValue) == 0 {
  76. *m = Properties{}
  77. return nil
  78. }
  79. return common.Unmarshal(bytesValue, m)
  80. }
  81. func (m Properties) Value() (driver.Value, error) {
  82. if m == (Properties{}) {
  83. return nil, nil
  84. }
  85. return common.Marshal(m)
  86. }
  87. type TaskPrivateData struct {
  88. Key string `json:"key,omitempty"`
  89. UpstreamTaskID string `json:"upstream_task_id,omitempty"` // 上游真实 task ID
  90. ResultURL string `json:"result_url,omitempty"` // 任务成功后的结果 URL(视频地址等)
  91. // 计费上下文:用于异步退款/差额结算(轮询阶段读取)
  92. BillingSource string `json:"billing_source,omitempty"` // "wallet" 或 "subscription"
  93. SubscriptionId int `json:"subscription_id,omitempty"` // 订阅 ID,用于订阅退款
  94. TokenId int `json:"token_id,omitempty"` // 令牌 ID,用于令牌额度退款
  95. BillingContext *TaskBillingContext `json:"billing_context,omitempty"` // 计费参数快照(用于轮询阶段重新计算)
  96. }
  97. // TaskBillingContext 记录任务提交时的计费参数,以便轮询阶段可以重新计算额度。
  98. type TaskBillingContext struct {
  99. ModelPrice float64 `json:"model_price,omitempty"` // 模型单价
  100. GroupRatio float64 `json:"group_ratio,omitempty"` // 分组倍率
  101. ModelRatio float64 `json:"model_ratio,omitempty"` // 模型倍率
  102. OtherRatios map[string]float64 `json:"other_ratios,omitempty"` // 附加倍率(时长、分辨率等)
  103. OriginModelName string `json:"origin_model_name,omitempty"` // 模型名称,必须为OriginModelName
  104. PerCallBilling bool `json:"per_call_billing,omitempty"` // 按次计费:跳过轮询阶段的差额结算
  105. BillingMode string `json:"billing_mode,omitempty"`
  106. PricingSnapshot map[string]any `json:"pricing_snapshot,omitempty"`
  107. BillingUnit string `json:"billing_unit,omitempty"`
  108. TokenUnitPriceUSD float64 `json:"token_unit_price_usd,omitempty"`
  109. }
  110. // GetUpstreamTaskID 获取上游真实 task ID(用于与 provider 通信)
  111. // 旧数据没有 UpstreamTaskID 时,TaskID 本身就是上游 ID
  112. func (t *Task) GetUpstreamTaskID() string {
  113. if t.PrivateData.UpstreamTaskID != "" {
  114. return t.PrivateData.UpstreamTaskID
  115. }
  116. return t.TaskID
  117. }
  118. // GetResultURL 获取任务结果 URL(视频地址等)
  119. // 新数据存在 PrivateData.ResultURL 中;旧数据回退到 FailReason(历史兼容)
  120. func (t *Task) GetResultURL() string {
  121. if t.PrivateData.ResultURL != "" {
  122. return t.PrivateData.ResultURL
  123. }
  124. return t.FailReason
  125. }
  126. // GenerateTaskID 生成对外暴露的 task_xxxx 格式 ID
  127. func GenerateTaskID() string {
  128. key, _ := common.GenerateRandomCharsKey(32)
  129. return "task_" + key
  130. }
  131. func (p *TaskPrivateData) Scan(val interface{}) error {
  132. bytesValue, _ := val.([]byte)
  133. if len(bytesValue) == 0 {
  134. return nil
  135. }
  136. return common.Unmarshal(bytesValue, p)
  137. }
  138. func (p TaskPrivateData) Value() (driver.Value, error) {
  139. if (p == TaskPrivateData{}) {
  140. return nil, nil
  141. }
  142. return common.Marshal(p)
  143. }
  144. // SyncTaskQueryParams 用于包含所有搜索条件的结构体,可以根据需求添加更多字段
  145. type SyncTaskQueryParams struct {
  146. Platform constant.TaskPlatform
  147. ChannelID string
  148. TaskID string
  149. UserID string
  150. Action string
  151. Status string
  152. StartTimestamp int64
  153. EndTimestamp int64
  154. UserIDs []int
  155. }
  156. func InitTask(platform constant.TaskPlatform, relayInfo *commonRelay.RelayInfo) *Task {
  157. properties := Properties{}
  158. privateData := TaskPrivateData{}
  159. if relayInfo != nil && relayInfo.ChannelMeta != nil {
  160. if relayInfo.ChannelMeta.ChannelType == constant.ChannelTypeGemini {
  161. privateData.Key = relayInfo.ChannelMeta.ApiKey
  162. }
  163. if relayInfo.UpstreamModelName != "" {
  164. properties.UpstreamModelName = relayInfo.UpstreamModelName
  165. }
  166. if relayInfo.OriginModelName != "" {
  167. properties.OriginModelName = relayInfo.OriginModelName
  168. }
  169. }
  170. // 使用预生成的公开 ID(如果有),否则新生成
  171. taskID := ""
  172. if relayInfo.TaskRelayInfo != nil && relayInfo.TaskRelayInfo.PublicTaskID != "" {
  173. taskID = relayInfo.TaskRelayInfo.PublicTaskID
  174. } else {
  175. taskID = GenerateTaskID()
  176. }
  177. t := &Task{
  178. TaskID: taskID,
  179. UserId: relayInfo.UserId,
  180. Group: relayInfo.UsingGroup,
  181. SubmitTime: time.Now().Unix(),
  182. Status: TaskStatusNotStart,
  183. Progress: "0%",
  184. ChannelId: relayInfo.ChannelId,
  185. Platform: platform,
  186. Properties: properties,
  187. PrivateData: privateData,
  188. }
  189. return t
  190. }
  191. func TaskGetAllUserTask(userId int, startIdx int, num int, queryParams SyncTaskQueryParams) []*Task {
  192. var tasks []*Task
  193. var err error
  194. // 初始化查询构建器
  195. query := DB.Where("user_id = ?", userId)
  196. if queryParams.TaskID != "" {
  197. query = query.Where("task_id = ?", queryParams.TaskID)
  198. }
  199. if queryParams.Action != "" {
  200. query = query.Where("action = ?", queryParams.Action)
  201. }
  202. if queryParams.Status != "" {
  203. query = query.Where("status = ?", queryParams.Status)
  204. }
  205. if queryParams.Platform != "" {
  206. query = query.Where("platform = ?", queryParams.Platform)
  207. }
  208. if queryParams.StartTimestamp != 0 {
  209. // 假设您已将前端传来的时间戳转换为数据库所需的时间格式,并处理了时间戳的验证和解析
  210. query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
  211. }
  212. if queryParams.EndTimestamp != 0 {
  213. query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
  214. }
  215. // 获取数据
  216. err = query.Omit("channel_id").Order("id desc").Limit(num).Offset(startIdx).Find(&tasks).Error
  217. if err != nil {
  218. return nil
  219. }
  220. return tasks
  221. }
  222. func TaskGetAllTasks(startIdx int, num int, queryParams SyncTaskQueryParams) []*Task {
  223. var tasks []*Task
  224. var err error
  225. // 初始化查询构建器
  226. query := DB
  227. // 添加过滤条件
  228. if queryParams.ChannelID != "" {
  229. query = query.Where("channel_id = ?", queryParams.ChannelID)
  230. }
  231. if queryParams.Platform != "" {
  232. query = query.Where("platform = ?", queryParams.Platform)
  233. }
  234. if queryParams.UserID != "" {
  235. query = query.Where("user_id = ?", queryParams.UserID)
  236. }
  237. if len(queryParams.UserIDs) != 0 {
  238. query = query.Where("user_id in (?)", queryParams.UserIDs)
  239. }
  240. if queryParams.TaskID != "" {
  241. query = query.Where("task_id = ?", queryParams.TaskID)
  242. }
  243. if queryParams.Action != "" {
  244. query = query.Where("action = ?", queryParams.Action)
  245. }
  246. if queryParams.Status != "" {
  247. query = query.Where("status = ?", queryParams.Status)
  248. }
  249. if queryParams.StartTimestamp != 0 {
  250. query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
  251. }
  252. if queryParams.EndTimestamp != 0 {
  253. query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
  254. }
  255. // 获取数据
  256. err = query.Order("id desc").Limit(num).Offset(startIdx).Find(&tasks).Error
  257. if err != nil {
  258. return nil
  259. }
  260. return tasks
  261. }
  262. func GetTimedOutUnfinishedTasks(cutoffUnix int64, limit int) []*Task {
  263. var tasks []*Task
  264. err := DB.Where("progress != ?", "100%").
  265. Where("status NOT IN ?", []string{TaskStatusFailure, TaskStatusSuccess}).
  266. Where("submit_time < ?", cutoffUnix).
  267. Order("submit_time").
  268. Limit(limit).
  269. Find(&tasks).Error
  270. if err != nil {
  271. return nil
  272. }
  273. return tasks
  274. }
  275. func GetAllUnFinishSyncTasks(limit int) []*Task {
  276. var tasks []*Task
  277. var err error
  278. // get all tasks progress is not 100%
  279. err = DB.Where("progress != ?", "100%").Where("status != ?", TaskStatusFailure).Where("status != ?", TaskStatusSuccess).Limit(limit).Order("id").Find(&tasks).Error
  280. if err != nil {
  281. return nil
  282. }
  283. return tasks
  284. }
  285. func GetByOnlyTaskId(taskId string) (*Task, bool, error) {
  286. if taskId == "" {
  287. return nil, false, nil
  288. }
  289. var task *Task
  290. var err error
  291. err = DB.Where("task_id = ?", taskId).First(&task).Error
  292. exist, err := RecordExist(err)
  293. if err != nil {
  294. return nil, false, err
  295. }
  296. return task, exist, err
  297. }
  298. func GetByTaskId(userId int, taskId string) (*Task, bool, error) {
  299. if taskId == "" {
  300. return nil, false, nil
  301. }
  302. var task *Task
  303. var err error
  304. err = DB.Where("user_id = ? and task_id = ?", userId, taskId).
  305. First(&task).Error
  306. exist, err := RecordExist(err)
  307. if err != nil {
  308. return nil, false, err
  309. }
  310. return task, exist, err
  311. }
  312. func GetByTaskIds(userId int, taskIds []any) ([]*Task, error) {
  313. if len(taskIds) == 0 {
  314. return nil, nil
  315. }
  316. var task []*Task
  317. var err error
  318. err = DB.Where("user_id = ? and task_id in (?)", userId, taskIds).
  319. Find(&task).Error
  320. if err != nil {
  321. return nil, err
  322. }
  323. return task, nil
  324. }
  325. func (Task *Task) Insert() error {
  326. var err error
  327. err = DB.Create(Task).Error
  328. return err
  329. }
  330. type taskSnapshot struct {
  331. Status TaskStatus
  332. Progress string
  333. StartTime int64
  334. FinishTime int64
  335. FailReason string
  336. ResultURL string
  337. Data json.RawMessage
  338. }
  339. func (s taskSnapshot) Equal(other taskSnapshot) bool {
  340. return s.Status == other.Status &&
  341. s.Progress == other.Progress &&
  342. s.StartTime == other.StartTime &&
  343. s.FinishTime == other.FinishTime &&
  344. s.FailReason == other.FailReason &&
  345. s.ResultURL == other.ResultURL &&
  346. bytes.Equal(s.Data, other.Data)
  347. }
  348. func (t *Task) Snapshot() taskSnapshot {
  349. return taskSnapshot{
  350. Status: t.Status,
  351. Progress: t.Progress,
  352. StartTime: t.StartTime,
  353. FinishTime: t.FinishTime,
  354. FailReason: t.FailReason,
  355. ResultURL: t.PrivateData.ResultURL,
  356. Data: t.Data,
  357. }
  358. }
  359. func (Task *Task) Update() error {
  360. var err error
  361. err = DB.Save(Task).Error
  362. return err
  363. }
  364. // UpdateWithStatus performs a conditional UPDATE guarded by fromStatus (CAS).
  365. // Returns (true, nil) if this caller won the update, (false, nil) if
  366. // another process already moved the task out of fromStatus.
  367. //
  368. // Uses Model().Select("*").Updates() instead of Save() because GORM's Save
  369. // falls back to INSERT ON CONFLICT when the WHERE-guarded UPDATE matches
  370. // zero rows, which silently bypasses the CAS guard.
  371. func (t *Task) UpdateWithStatus(fromStatus TaskStatus) (bool, error) {
  372. result := DB.Model(t).Where("status = ?", fromStatus).Select("*").Updates(t)
  373. if result.Error != nil {
  374. return false, result.Error
  375. }
  376. return result.RowsAffected > 0, nil
  377. }
  378. // TaskBulkUpdateByID performs an unconditional bulk UPDATE by primary key IDs.
  379. // WARNING: This function has NO CAS (Compare-And-Swap) guard — it will overwrite
  380. // any concurrent status changes. DO NOT use in billing/quota lifecycle flows
  381. // (e.g., timeout, success, failure transitions that trigger refunds or settlements).
  382. // For status transitions that involve billing, use Task.UpdateWithStatus() instead.
  383. func TaskBulkUpdateByID(ids []int64, params map[string]any) error {
  384. if len(ids) == 0 {
  385. return nil
  386. }
  387. return DB.Model(&Task{}).
  388. Where("id in (?)", ids).
  389. Updates(params).Error
  390. }
  391. type TaskQuotaUsage struct {
  392. Mode string `json:"mode"`
  393. Count float64 `json:"count"`
  394. }
  395. // TaskCountAllTasks returns total tasks that match the given query params (admin usage)
  396. func TaskCountAllTasks(queryParams SyncTaskQueryParams) int64 {
  397. var total int64
  398. query := DB.Model(&Task{})
  399. if queryParams.ChannelID != "" {
  400. query = query.Where("channel_id = ?", queryParams.ChannelID)
  401. }
  402. if queryParams.Platform != "" {
  403. query = query.Where("platform = ?", queryParams.Platform)
  404. }
  405. if queryParams.UserID != "" {
  406. query = query.Where("user_id = ?", queryParams.UserID)
  407. }
  408. if len(queryParams.UserIDs) != 0 {
  409. query = query.Where("user_id in (?)", queryParams.UserIDs)
  410. }
  411. if queryParams.TaskID != "" {
  412. query = query.Where("task_id = ?", queryParams.TaskID)
  413. }
  414. if queryParams.Action != "" {
  415. query = query.Where("action = ?", queryParams.Action)
  416. }
  417. if queryParams.Status != "" {
  418. query = query.Where("status = ?", queryParams.Status)
  419. }
  420. if queryParams.StartTimestamp != 0 {
  421. query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
  422. }
  423. if queryParams.EndTimestamp != 0 {
  424. query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
  425. }
  426. _ = query.Count(&total).Error
  427. return total
  428. }
  429. // TaskCountAllUserTask returns total tasks for given user
  430. func TaskCountAllUserTask(userId int, queryParams SyncTaskQueryParams) int64 {
  431. var total int64
  432. query := DB.Model(&Task{}).Where("user_id = ?", userId)
  433. if queryParams.TaskID != "" {
  434. query = query.Where("task_id = ?", queryParams.TaskID)
  435. }
  436. if queryParams.Action != "" {
  437. query = query.Where("action = ?", queryParams.Action)
  438. }
  439. if queryParams.Status != "" {
  440. query = query.Where("status = ?", queryParams.Status)
  441. }
  442. if queryParams.Platform != "" {
  443. query = query.Where("platform = ?", queryParams.Platform)
  444. }
  445. if queryParams.StartTimestamp != 0 {
  446. query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
  447. }
  448. if queryParams.EndTimestamp != 0 {
  449. query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
  450. }
  451. _ = query.Count(&total).Error
  452. return total
  453. }
  454. func (t *Task) ToOpenAIVideo() *dto.OpenAIVideo {
  455. openAIVideo := dto.NewOpenAIVideo()
  456. openAIVideo.ID = t.TaskID
  457. openAIVideo.Status = t.Status.ToVideoStatus()
  458. openAIVideo.Model = t.Properties.OriginModelName
  459. openAIVideo.SetProgressStr(t.Progress)
  460. openAIVideo.CreatedAt = t.CreatedAt
  461. openAIVideo.CompletedAt = t.UpdatedAt
  462. openAIVideo.SetMetadata("url", t.GetResultURL())
  463. return openAIVideo
  464. }