将全库资金金额从 float64 迁移到 shopspring/decimal,落定边界架构: 入口 NewFromFloat → 内部多步运算全程 decimal → 出口 InexactFloat64; DB NUMERIC(20,10) 走 driver.Valuer 精确直传,Redis 钱包用整数 micro-USD, CNY→USD 换汇仅在 payment.FulfillOrder recharge 分支。 - money.go 重写为 decimal 原生(运算/收敛/比较/边界/micro 工具) - billing/tenant/admin/payment/open/task 各层金额运算 decimal 化 - entity/do 资金列重生成为 decimal.Decimal(可空列 *decimal.Decimal) - 000013 迁移:非金额 NUMERIC 列 → double precision(5 表 13 列) - Res 出口 .InexactFloat64() 收敛,JSONB 快照缝隙显式转 float64 - CLAUDE.md 新增 decimal 强约束;补充迁移基线/完成总结文档
88 lines
3.1 KiB
Go
88 lines
3.1 KiB
Go
package common
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"time"
|
||
|
||
"github.com/shopspring/decimal"
|
||
)
|
||
|
||
// TaskDataProvider 异步任务数据持久化接口
|
||
type TaskDataProvider interface {
|
||
// CreateTask 创建异步任务记录
|
||
CreateTask(ctx context.Context, task *AsyncTask) error
|
||
|
||
// UpdateTask 更新任务记录
|
||
UpdateTask(ctx context.Context, task *AsyncTask) error
|
||
|
||
// UpdateTaskCAS CAS 状态更新(防重复结算)
|
||
UpdateTaskCAS(ctx context.Context, task *AsyncTask, oldStatus string) error
|
||
|
||
// GetTaskByPublicID 根据公开任务 ID 查询任务
|
||
GetTaskByPublicID(ctx context.Context, publicTaskID string) (*AsyncTask, error)
|
||
|
||
// GetTaskByPublicIDAndUser 根据公开任务 ID + 用户 ID 查询(客户端查询)
|
||
GetTaskByPublicIDAndUser(ctx context.Context, publicTaskID string, userID int64, tenantID int64) (*AsyncTask, error)
|
||
|
||
// GetNonTerminalTasks 获取所有非终态任务(轮询用)
|
||
GetNonTerminalTasks(ctx context.Context, limit int) ([]*AsyncTask, error)
|
||
|
||
// GetTimedOutTasks 获取超时未完成任务
|
||
GetTimedOutTasks(ctx context.Context, cutoffUnix int64, limit int) ([]*AsyncTask, error)
|
||
|
||
// GetUnsettledTasks 获取终态但未结算的任务(结算重试)
|
||
GetUnsettledTasks(ctx context.Context, limit int) ([]*AsyncTask, error)
|
||
|
||
// GetChannelByID 获取渠道基本信息
|
||
GetChannelByID(ctx context.Context, channelID int64) (*ChannelBasicInfo, error)
|
||
}
|
||
|
||
// AsyncTask 异步任务数据结构
|
||
type AsyncTask struct {
|
||
ID int64 `json:"id"`
|
||
PublicTaskID string `json:"public_task_id"` // task_xxxxx
|
||
RequestID string `json:"request_id"` // 原始请求 ID(req_xxxxx)
|
||
Platform string `json:"platform"` // sora, kling, suno
|
||
Action string `json:"action"` // generate, music, lyrics
|
||
Status string `json:"status"`
|
||
Progress string `json:"progress"`
|
||
FailReason string `json:"fail_reason,omitempty"`
|
||
|
||
TenantID int64 `json:"tenant_id"`
|
||
UserID int64 `json:"user_id"`
|
||
ApiKeyID int64 `json:"api_key_id"`
|
||
ChannelID int64 `json:"channel_id"`
|
||
|
||
ModelName string `json:"model_name"`
|
||
UpstreamModel string `json:"upstream_model,omitempty"`
|
||
|
||
PreDeductAmount decimal.Decimal `json:"pre_deduct_amount"`
|
||
ActualCost decimal.Decimal `json:"actual_cost"`
|
||
BillingSettled bool `json:"billing_settled"`
|
||
|
||
PromptTokens int `json:"prompt_tokens,omitempty"`
|
||
CompletionTokens int `json:"completion_tokens,omitempty"`
|
||
TotalTokens int `json:"total_tokens,omitempty"`
|
||
|
||
ResultURL string `json:"result_url,omitempty"`
|
||
Data json.RawMessage `json:"data,omitempty"` // 上游原始响应
|
||
PrivateData json.RawMessage `json:"private_data,omitempty"` // 上游任务 ID、API Key 等敏感数据
|
||
|
||
SubmitTime *time.Time `json:"submit_time,omitempty"`
|
||
StartTime *time.Time `json:"start_time,omitempty"`
|
||
FinishTime *time.Time `json:"finish_time,omitempty"`
|
||
CreatedAt time.Time `json:"created_at"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
}
|
||
|
||
// ChannelBasicInfo 渠道基本信息(轮询时查询)
|
||
type ChannelBasicInfo struct {
|
||
ID int64
|
||
Type int // ProviderType
|
||
Name string
|
||
BaseURL string
|
||
ApiKey string
|
||
Settings json.RawMessage
|
||
}
|