108 lines
3.9 KiB
Go
108 lines
3.9 KiB
Go
|
|
package domain
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"errors"
|
|||
|
|
"time"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
StatusPending = "pending"
|
|||
|
|
StatusQueued = "queued"
|
|||
|
|
StatusRunning = "running"
|
|||
|
|
StatusSucceeded = "succeeded"
|
|||
|
|
StatusFailed = "failed"
|
|||
|
|
StatusCancelled = "cancelled"
|
|||
|
|
|
|||
|
|
TemplateDemo = "demo"
|
|||
|
|
TemplateThreadsTokenRenew = "threads_token_renew"
|
|||
|
|
TemplatePersonaAnalyzeAccount = "persona_analyze_account"
|
|||
|
|
TemplatePersonaAnalyzeText = "persona_analyze_text"
|
|||
|
|
TemplateComposeMimic = "compose_mimic"
|
|||
|
|
// TemplatePlayGenerateScript — 互回/串場:一次產完整劇本(背景 job,避免 HTTP 卡住)
|
|||
|
|
TemplatePlayGenerateScript = "play_generate_script"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Job 生命週期契約(所有模板必須遵守,worker / API 入列時):
|
|||
|
|
// 1. 建立/入列 → StatusQueued + notify(鈴鐺「已排程」)
|
|||
|
|
// 2. ClaimNext → StatusRunning + notify(至少 ~5%)
|
|||
|
|
// 3. 執行中每有進度 → MarkRunningProgress → notify(同一 job upsert 一則通知)
|
|||
|
|
// 4. 終態 SucceedJob / FailJob → notify(已完成/失敗)
|
|||
|
|
// 前端靠全域 JobLive 輪詢列表 + 通知;遠期 run_after 不進頂欄即時條。
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
ErrNotFound = errors.New("job not found")
|
|||
|
|
ErrForbidden = errors.New("job access denied")
|
|||
|
|
ErrIllegalStatus = errors.New("illegal job status transition")
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
type Job struct {
|
|||
|
|
ID string `bson:"_id" json:"id"`
|
|||
|
|
OwnerUID int64 `bson:"owner_uid" json:"owner_uid"`
|
|||
|
|
TemplateType string `bson:"template_type" json:"template_type"`
|
|||
|
|
Status string `bson:"status" json:"status"`
|
|||
|
|
ProgressSummary string `bson:"progress_summary" json:"progress_summary"`
|
|||
|
|
ProgressPercent int `bson:"progress_percent" json:"progress_percent"`
|
|||
|
|
Error string `bson:"error,omitempty" json:"error,omitempty"`
|
|||
|
|
WorkerID string `bson:"worker_id,omitempty" json:"worker_id,omitempty"`
|
|||
|
|
// RefID e.g. threads account id for renew / persona id for analyze
|
|||
|
|
RefID string `bson:"ref_id,omitempty" json:"ref_id,omitempty"`
|
|||
|
|
// Payload JSON body for job worker (e.g. {"username":"x"})
|
|||
|
|
Payload string `bson:"payload,omitempty" json:"payload,omitempty"`
|
|||
|
|
// RunAfter unix nanoseconds; worker only claims when now >= RunAfter (0 = due now)
|
|||
|
|
RunAfter int64 `bson:"run_after,omitempty" json:"run_after,omitempty"`
|
|||
|
|
CreatedAt int64 `bson:"created_at" json:"created_at"`
|
|||
|
|
UpdatedAt int64 `bson:"updated_at" json:"updated_at"`
|
|||
|
|
CompletedAt int64 `bson:"completed_at,omitempty" json:"completed_at,omitempty"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DefaultTokenRenewDelayNs — 連帳後約 30 天做第一次 renew(長效 token ~60 天)
|
|||
|
|
const DefaultTokenRenewDelayNs = int64(30 * 24 * time.Hour)
|
|||
|
|
|
|||
|
|
// TerminalRetentionNs — 終態任務(已完成/失敗/取消)保留多久後自動清除
|
|||
|
|
const TerminalRetentionNs = int64(2 * 24 * time.Hour)
|
|||
|
|
|
|||
|
|
func NowNano() int64 { return time.Now().UTC().UnixNano() }
|
|||
|
|
|
|||
|
|
// IsTerminal — 不可再轉移的狀態
|
|||
|
|
func IsTerminal(status string) bool {
|
|||
|
|
return status == StatusSucceeded || status == StatusFailed || status == StatusCancelled
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TerminalCutoff — 早於此時間的終態任務應被 purge
|
|||
|
|
func TerminalCutoff(now int64) int64 {
|
|||
|
|
if now <= 0 {
|
|||
|
|
now = NowNano()
|
|||
|
|
}
|
|||
|
|
return now - TerminalRetentionNs
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// CanTransition enforces JB-10 guarded status.
|
|||
|
|
func CanTransition(from, to string) bool {
|
|||
|
|
switch from {
|
|||
|
|
case StatusPending:
|
|||
|
|
return to == StatusQueued || to == StatusRunning || to == StatusFailed
|
|||
|
|
case StatusQueued:
|
|||
|
|
return to == StatusRunning || to == StatusFailed || to == StatusCancelled
|
|||
|
|
case StatusRunning:
|
|||
|
|
return to == StatusSucceeded || to == StatusFailed || to == StatusCancelled
|
|||
|
|
default:
|
|||
|
|
return false // terminal
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ApplyProgress enforces monotonic percent (JB-02).
|
|||
|
|
func ApplyProgress(j *Job, percent int, summary string) error {
|
|||
|
|
if percent < j.ProgressPercent {
|
|||
|
|
return ErrIllegalStatus
|
|||
|
|
}
|
|||
|
|
if percent > 100 {
|
|||
|
|
percent = 100
|
|||
|
|
}
|
|||
|
|
j.ProgressPercent = percent
|
|||
|
|
if summary != "" {
|
|||
|
|
j.ProgressSummary = summary
|
|||
|
|
}
|
|||
|
|
j.UpdatedAt = NowNano()
|
|||
|
|
return nil
|
|||
|
|
}
|