gatway
This commit is contained in:
35
README.md
35
README.md
@@ -1,4 +1,4 @@
|
||||
# model-asynch(模型异步中间件)
|
||||
# model-asynch(模型异步中间件)[2026.5.12前,暂时弃置]
|
||||
|
||||
一个独立的异步中间件服务:按模型配置路由调用不同模型服务,统一生成 `task_id`,后台异步执行,结果上传 OSS,并提供查询/批量领取/自动重试/自动清理能力,便于业务方“拿走结果并转移”。
|
||||
|
||||
@@ -69,6 +69,16 @@
|
||||
参数说明:
|
||||
- `modelName`:模型名称(唯一标识/路由键)
|
||||
- `modelsType`:模型类型ID列表(逗号分隔),示例:`1,2,3`(关联 `asynch_models_type.type_id`)
|
||||
|
||||
### 模型类型同步
|
||||
- `POST /model/type/createModelType` 创建成功后,会同步 `POST` 到 `prompts-core` 的 `/prompt/createPrompt`
|
||||
- 同步字段映射:
|
||||
- `typeId` -> `modelTypeId`
|
||||
- `type` -> `modelType`
|
||||
- `promptInfo` -> `promptInfo`
|
||||
- `responseJsonSchema` -> `responseJsonSchema`
|
||||
- `version` -> `version`
|
||||
- 若 `prompts-core` 同步失败,`model-gateway` 会回滚本地新建的模型类型,避免两边数据不一致
|
||||
- `form`:动态表单配置(JSON数组),用于前端按模型渲染参数表单(字段示例:field/label/type/required)
|
||||
- `baseUrl`:模型服务地址(Base URL)
|
||||
- `route`:模型服务路由(拼接到 baseUrl 后)
|
||||
@@ -94,7 +104,7 @@
|
||||
>
|
||||
> `callbackUrl` 用于任务成功后的回调通知:当任务 `state=2` 成功时,中间件会发起一次 GET 请求:
|
||||
> - 实际回调地址:`callbackUrl/{bizName}`
|
||||
> - query 参数:`task_id/state/oss_file/file_type`
|
||||
> - query 参数:`task_id/state/oss_file/file_type/text(可选,最多2000字符)`
|
||||
|
||||
### 第三步:同步任务进度(推荐批量)
|
||||
业务方通过轮询/定时任务同步进度:
|
||||
@@ -107,9 +117,28 @@
|
||||
|
||||
### 后台执行(由上层定时任务控制)
|
||||
本项目不再在服务进程内常驻轮询 worker/cleaner,而是提供两个接口供上层定时任务触发:
|
||||
- `POST /task/runWork`:执行一次 Worker(抢占并处理一批排队任务)
|
||||
- `POST /task/runWork`:执行一次 Worker(抢占并处理一批排队任务;适合处理 createTask 立即执行时未处理到的任务和积压队列)
|
||||
- `POST /task/cleanWork`:执行一次 Cleaner(清理过期任务、失败重试、超时任务失败等)
|
||||
|
||||
创建任务执行策略:
|
||||
- `POST /task/createTask` 成功入库后,会立即异步尝试执行当前任务。
|
||||
- 若当前模型并发已满,或当前任务未成功抢占,则会按 `asynch.worker.intervalSeconds` 对当前任务做轻量级定向轮询;只要任务仍为 `state=0` 就继续尝试,一旦进入 `state=1/2/3/4` 就立即停止,不会一直轮询。
|
||||
- 若任务执行成功且配置了 `callbackUrl + bizName`,会在成功落库后异步触发回调钩子。
|
||||
|
||||
本地调试(可选):
|
||||
可在 `config.yml` 中开启自动执行,避免手工频繁调用接口:
|
||||
```yml
|
||||
asynch:
|
||||
worker:
|
||||
enabled: true
|
||||
intervalSeconds: 5
|
||||
batchSize: 10
|
||||
goroutines: 1
|
||||
cleaner:
|
||||
enabled: true
|
||||
intervalSeconds: 30
|
||||
```
|
||||
|
||||
### 动态并发/队列调参(接口请求控制)
|
||||
为支持根据最近一段时间的耗时与吞吐对 `max_concurrency/queue_limit` 做动态调整,本项目提供接口供上层定时任务触发(建议每小时一次):
|
||||
- `POST /model/autoTune`
|
||||
|
||||
29
config.yml
29
config.yml
@@ -1,6 +1,6 @@
|
||||
server:
|
||||
address: ":8001"
|
||||
name: "model-asynch"
|
||||
name: "model-gateway"
|
||||
workerId: 1 # 雪花算法worker ID(用于 common/db/gfdb)
|
||||
|
||||
# PostgreSQL(GoFrame driver pgsql)
|
||||
@@ -11,7 +11,7 @@ database:
|
||||
port: "15432"
|
||||
user: "postgres"
|
||||
pass: "Bjang09@686^*^"
|
||||
name: "model-asynch"
|
||||
name: "model-gateway"
|
||||
prefix: "" # (可选)表名前缀
|
||||
role: "master" # (可选)数据库主从角色(master/slave),默认为master。如果不使用应用主从机制请不配置或留空即可。
|
||||
debug: true # (可选)开启调试模式
|
||||
@@ -29,11 +29,30 @@ database:
|
||||
|
||||
redis:
|
||||
default:
|
||||
address: 192.168.3.30:6379
|
||||
address: 116.204.74.41:6379
|
||||
db: 0
|
||||
|
||||
consul:
|
||||
address: 192.168.3.30:8500
|
||||
address: 116.204.74.41:8500
|
||||
|
||||
jaeger:
|
||||
addr: 192.168.3.30:4318
|
||||
addr: 116.204.74.41:4318
|
||||
|
||||
# 本地调试用:可选自动执行 worker/cleaner(默认关闭)
|
||||
asynch:
|
||||
worker:
|
||||
enabled: false
|
||||
intervalSeconds: 5
|
||||
batchSize: 10
|
||||
goroutines: 1
|
||||
cleaner:
|
||||
enabled: false
|
||||
intervalSeconds: 30
|
||||
|
||||
modelType:
|
||||
types:
|
||||
1: "推理模型"
|
||||
2: "图片模型"
|
||||
3: "音频模型"
|
||||
4: "向量化模型"
|
||||
5: "全模态模型"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package public
|
||||
|
||||
const (
|
||||
TableNameModel = "asynch_models" // 异步模型表
|
||||
TableNameModel = "asynch_models" // 模型表
|
||||
TableNameModelType = "asynch_models_type" // 模型类型表
|
||||
TableNameTask = "asynch_task" // 异步任务表
|
||||
TableNameOpLog = "asynch_op_log" // 异步操作日志表
|
||||
TableNameStat = "asynch_model_stat" // 按天统计表(请求次数)
|
||||
TableNameTask = "asynch_task" // 任务表
|
||||
TableNameOpLog = "logs_model_op" // 操作日志表
|
||||
TableNameStat = "logs_model_stat" // 按天统计表(请求次数)
|
||||
)
|
||||
|
||||
@@ -1,24 +1 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// ensureUser 用于本地/无网关环境下的兜底用户信息,避免 gfdb Hook 因缺少用户上下文而报错。
|
||||
// 生产环境建议由网关透传 X-User-Info 或鉴权中间件注入 ctx.Value("user")。
|
||||
func ensureUser(ctx context.Context) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if ctx.Value("user") != nil {
|
||||
return ctx
|
||||
}
|
||||
u := &beans.User{
|
||||
UserName: "admin",
|
||||
TenantId: 1,
|
||||
}
|
||||
return context.WithValue(ctx, "user", u)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"model-asynch/model/dto"
|
||||
"model-asynch/model/entity"
|
||||
"model-asynch/service"
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
@@ -16,51 +17,42 @@ var Model = new(model)
|
||||
|
||||
// CreateModel 添加配置
|
||||
func (c *model) CreateModel(ctx context.Context, req *dto.CreateModelReq) (res *dto.CreateModelRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
return service.Model.Create(ctx, req)
|
||||
}
|
||||
|
||||
// UpdateModel 更改配置
|
||||
func (c *model) UpdateModel(ctx context.Context, req *dto.UpdateModelReq) (res *beans.ResponseEmpty, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
err = service.Model.Update(ctx, req)
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteModel 删除配置
|
||||
func (c *model) DeleteModel(ctx context.Context, req *dto.DeleteModelReq) (res *beans.ResponseEmpty, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
err = service.Model.Delete(ctx, req.ID)
|
||||
return
|
||||
}
|
||||
|
||||
// GetModel 获取配置详情(按 modelName)
|
||||
func (c *model) GetModel(ctx context.Context, req *dto.GetModelReq) (res *dto.GetModelRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
m, err := service.Model.Get(ctx, req.ID)
|
||||
model, err := service.Model.Get(ctx, req.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.GetModelRes{Model: m}, nil
|
||||
return &dto.GetModelRes{Model: model}, nil
|
||||
}
|
||||
|
||||
// ListModel 配置列表
|
||||
func (c *model) ListModel(ctx context.Context, req *dto.ListModelReq) (res *dto.ListModelRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
pageNum, pageSize := 1, 10 //默认分页参数
|
||||
if req != nil && req.Page != nil {
|
||||
if req.Page.PageNum > 0 {
|
||||
pageNum = int(req.Page.PageNum)
|
||||
}
|
||||
if req.Page.PageSize > 0 {
|
||||
pageSize = int(req.Page.PageSize)
|
||||
}
|
||||
}
|
||||
modelName := ""
|
||||
if req != nil {
|
||||
modelName = req.ModelName
|
||||
if req.PageNum > 0 {
|
||||
pageNum = req.PageNum
|
||||
}
|
||||
if req.PageSize > 0 {
|
||||
pageSize = req.PageSize
|
||||
}
|
||||
}
|
||||
list, total, err := service.Model.List(ctx, pageNum, pageSize, modelName)
|
||||
list, total, err := service.Model.List(ctx, pageNum, pageSize, req.ModelName, req.ModelType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -72,7 +64,6 @@ func (c *model) ListModel(ctx context.Context, req *dto.ListModelReq) (res *dto.
|
||||
|
||||
// AutoTune 动态调参(由上层定时任务每小时触发一次)
|
||||
func (c *model) AutoTune(ctx context.Context, req *dto.AutoTuneReq) (res *dto.AutoTuneRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
windowSeconds := 3600
|
||||
if req != nil && req.WindowSeconds > 0 {
|
||||
windowSeconds = req.WindowSeconds
|
||||
@@ -83,3 +74,20 @@ func (c *model) AutoTune(ctx context.Context, req *dto.AutoTuneReq) (res *dto.Au
|
||||
}
|
||||
return &dto.AutoTuneRes{List: list}, nil
|
||||
}
|
||||
|
||||
func (c *model) ListType(ctx context.Context, req *dto.ListTypeReq) (res dto.TypeItem, err error) {
|
||||
modelType := service.GetModelTypesFromConfig(ctx)
|
||||
res.Type = modelType
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// UpdateChatModel 更新是否为聊天模型
|
||||
func (c *model) UpdateChatModel(ctx context.Context, req *dto.UpdateChatModelReq) (res *beans.ResponseEmpty, err error) {
|
||||
err = service.Model.UpdateChatModel(ctx, req)
|
||||
return
|
||||
}
|
||||
|
||||
// GetIsChatModel 获取是否为聊天模型
|
||||
func (c *model) GetIsChatModel(ctx context.Context, req *dto.GetIsChatModelReq) (res *entity.AsynchModel, err error) {
|
||||
return service.Model.GetIsChatModel(ctx)
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"model-asynch/model/dto"
|
||||
"model-asynch/service"
|
||||
|
||||
"gitea.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
type modelType struct{}
|
||||
|
||||
// ModelType 模型类型控制器
|
||||
var ModelType = new(modelType)
|
||||
|
||||
func (c *modelType) CreateModelType(ctx context.Context, req *dto.CreateModelTypeReq) (res *dto.CreateModelTypeRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
return service.ModelType.Create(ctx, req)
|
||||
}
|
||||
|
||||
func (c *modelType) UpdateModelType(ctx context.Context, req *dto.UpdateModelTypeReq) (res *beans.ResponseEmpty, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
err = service.ModelType.Update(ctx, req)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *modelType) DeleteModelType(ctx context.Context, req *dto.DeleteModelTypeReq) (res *beans.ResponseEmpty, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
err = service.ModelType.Delete(ctx, req.ID)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *modelType) GetModelType(ctx context.Context, req *dto.GetModelTypeReq) (res *dto.GetModelTypeRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
t, err := service.ModelType.Get(ctx, req.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.GetModelTypeRes{Type: t}, nil
|
||||
}
|
||||
|
||||
func (c *modelType) ListModelType(ctx context.Context, req *dto.ListModelTypeReq) (res *dto.ListModelTypeRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
pageNum, pageSize := 1, 10
|
||||
if req != nil && req.Page != nil {
|
||||
if req.Page.PageNum > 0 {
|
||||
pageNum = int(req.Page.PageNum)
|
||||
}
|
||||
if req.Page.PageSize > 0 {
|
||||
pageSize = int(req.Page.PageSize)
|
||||
}
|
||||
}
|
||||
typeName := ""
|
||||
if req != nil {
|
||||
typeName = req.TypeName
|
||||
}
|
||||
list, total, err := service.ModelType.List(ctx, pageNum, pageSize, typeName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.ListModelTypeRes{List: list, Total: total}, nil
|
||||
}
|
||||
|
||||
func (c *modelType) ListModelTypeWithModels(ctx context.Context, req *dto.ListModelTypeWithModelsReq) (res []dto.ModelTypeWithModelsItem, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
return service.ModelType.ListWithModels(ctx, req)
|
||||
}
|
||||
@@ -14,7 +14,5 @@ var Stat = new(stat)
|
||||
|
||||
// ListModelStat 统计列表
|
||||
func (c *stat) ListModelStat(ctx context.Context, req *dto.ListModelStatReq) (res *dto.ListModelStatRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
return service.Stat.List(ctx, req)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,31 +14,26 @@ var Task = new(task)
|
||||
|
||||
// CreateTask 根据 modelName 创建异步任务,返回 taskId
|
||||
func (c *task) CreateTask(ctx context.Context, req *dto.CreateTaskReq) (res *dto.CreateTaskRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
return service.Task.Create(ctx, req)
|
||||
}
|
||||
|
||||
// GetTaskResult 获取任务结果(只返回 oss 地址 + state)
|
||||
func (c *task) GetTaskResult(ctx context.Context, req *dto.GetTaskResultReq) (res *dto.GetTaskResultRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
return service.Task.GetResult(ctx, req.TaskID)
|
||||
}
|
||||
|
||||
// GetTaskBatch 批量查询任务(成功任务标记为已下载)
|
||||
func (c *task) GetTaskBatch(ctx context.Context, req *dto.GetTaskBatchReq) (res *dto.GetTaskBatchRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
return service.Task.GetBatch(ctx, req)
|
||||
}
|
||||
|
||||
// ListTask 任务列表分页查询
|
||||
func (c *task) ListTask(ctx context.Context, req *dto.ListTaskReq) (res *dto.ListTaskRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
return service.Task.List(ctx, req)
|
||||
}
|
||||
|
||||
// RunWork 手动触发一次 worker(由上层定时任务调用)
|
||||
func (c *task) RunWork(ctx context.Context, req *dto.RunWorkReq) (res *dto.RunWorkRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
batchSize, goroutines := 10, 1
|
||||
if req != nil {
|
||||
if req.BatchSize > 0 {
|
||||
@@ -57,7 +52,6 @@ func (c *task) RunWork(ctx context.Context, req *dto.RunWorkReq) (res *dto.RunWo
|
||||
|
||||
// CleanWork 手动触发一次 cleaner(由上层定时任务调用)
|
||||
func (c *task) CleanWork(ctx context.Context, req *dto.CleanWorkReq) (res *dto.CleanWorkRes, err error) {
|
||||
ctx = ensureUser(ctx)
|
||||
service.Cleaner.RunOnce(ctx)
|
||||
return &dto.CleanWorkRes{Ok: true}, nil
|
||||
}
|
||||
|
||||
114
dao/model_dao.go
114
dao/model_dao.go
@@ -2,11 +2,14 @@ package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"model-asynch/consts/public"
|
||||
"model-asynch/model/dto"
|
||||
"model-asynch/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/db/gfdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
@@ -22,12 +25,12 @@ func (d *modelDao) Insert(ctx context.Context, m *entity.AsynchModel) (id int64,
|
||||
return r.LastInsertId()
|
||||
}
|
||||
|
||||
func (d *modelDao) UpdateByID(ctx context.Context, id int64, data map[string]any) (rows int64, err error) {
|
||||
func (d *modelDao) Update(ctx context.Context, m *dto.UpdateModelReq) (rows int64, err error) {
|
||||
// 触发 gfdb 的 updateHook 自动填充 updater,需要显式带 updater 字段
|
||||
data[entity.AsynchModelCol.Updater] = ""
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
|
||||
Where(entity.AsynchModelCol.Id, id).
|
||||
Data(data).
|
||||
OmitEmpty().
|
||||
Where(entity.AsynchModelCol.Id, m.ID).
|
||||
Data(m).
|
||||
Update()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -35,7 +38,21 @@ func (d *modelDao) UpdateByID(ctx context.Context, id int64, data map[string]any
|
||||
return r.RowsAffected()
|
||||
}
|
||||
|
||||
func (d *modelDao) DeleteByID(ctx context.Context, id int64) (rows int64, err error) {
|
||||
func (d *modelDao) UpdateByID(ctx context.Context, m *dto.UpdateModelReq) (rows int64, err error) {
|
||||
// 专用于切换会话模型,只更新 is_chat_model 字段
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
|
||||
Where(entity.AsynchModelCol.Id, m.ID).
|
||||
Data(g.Map{
|
||||
"is_chat_model": m.IsChatModel,
|
||||
}).
|
||||
Update()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return r.RowsAffected()
|
||||
}
|
||||
|
||||
func (d *modelDao) DeleteByID(ctx context.Context, id string) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
|
||||
Where(entity.AsynchModelCol.Id, id).
|
||||
Delete()
|
||||
@@ -59,7 +76,7 @@ func (d *modelDao) GetByModelName(ctx context.Context, modelName string) (m *ent
|
||||
return
|
||||
}
|
||||
|
||||
func (d *modelDao) GetByID(ctx context.Context, id int64) (m *entity.AsynchModel, err error) {
|
||||
func (d *modelDao) Get(ctx context.Context, id int64) (m *entity.AsynchModel, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
|
||||
Where(entity.AsynchModelCol.Id, id).
|
||||
One()
|
||||
@@ -73,11 +90,15 @@ func (d *modelDao) GetByID(ctx context.Context, id int64) (m *entity.AsynchModel
|
||||
return
|
||||
}
|
||||
|
||||
func (d *modelDao) List(ctx context.Context, pageNum, pageSize int, modelNameLike string) (list []*entity.AsynchModel, total int64, err error) {
|
||||
model := gfdb.DB(ctx).Model(ctx, public.TableNameModel).Where("deleted_at IS NULL").OrderDesc(entity.AsynchModelCol.CreatedAt)
|
||||
func (d *modelDao) List(ctx context.Context, pageNum, pageSize int, modelNameLike string, modelType int) (list []*entity.AsynchModel, total int64, err error) {
|
||||
model := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
|
||||
OrderDesc(entity.AsynchModelCol.CreatedAt)
|
||||
if modelNameLike != "" {
|
||||
model = model.WhereLike(entity.AsynchModelCol.ModelName, "%"+modelNameLike+"%")
|
||||
}
|
||||
if modelType != 0 {
|
||||
model = model.Where(entity.AsynchModelCol.ModelsType, modelType)
|
||||
}
|
||||
if pageNum > 0 && pageSize > 0 {
|
||||
model = model.Page(pageNum, pageSize)
|
||||
}
|
||||
@@ -90,10 +111,85 @@ func (d *modelDao) List(ctx context.Context, pageNum, pageSize int, modelNameLik
|
||||
return
|
||||
}
|
||||
|
||||
// ListByCreatorAndPlatform 普通用户:平台公共(tenant_id=0) + 自己创建的(creator=xxx)
|
||||
func (d *modelDao) ListByCreatorAndPlatform(ctx context.Context, creator string, pageNum, pageSize int, modelNameLike string) (list []*entity.AsynchModel, total int64, err error) {
|
||||
// 构建 Where 条件
|
||||
whereSQL := "deleted_at IS NULL AND (tenant_id = 1 OR creator = ?)" //1 代表超级管理员
|
||||
args := []any{creator}
|
||||
|
||||
if modelNameLike != "" {
|
||||
whereSQL += " AND model_name LIKE ?"
|
||||
args = append(args, "%"+modelNameLike+"%")
|
||||
}
|
||||
|
||||
// 查总数
|
||||
countSQL := fmt.Sprintf("SELECT COUNT(1) FROM %s WHERE %s", public.TableNameModel, whereSQL)
|
||||
countResult, err := gfdb.DB(ctx).GetAll(ctx, countSQL, args...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if len(countResult) > 0 {
|
||||
total = gconv.Int64(countResult[0]["count"])
|
||||
}
|
||||
|
||||
// 查列表
|
||||
querySQL := fmt.Sprintf("SELECT * FROM %s WHERE %s ORDER BY created_at DESC", public.TableNameModel, whereSQL)
|
||||
if pageNum > 0 && pageSize > 0 {
|
||||
offset := (pageNum - 1) * pageSize
|
||||
querySQL += fmt.Sprintf(" LIMIT %d OFFSET %d", pageSize, offset)
|
||||
}
|
||||
|
||||
r, err := gfdb.DB(ctx).GetAll(ctx, querySQL, args...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = r.Structs(&list)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *modelDao) GetByCreatorAndPlatform(ctx context.Context, creator string, modelNameLike string, modelType int) (list []*entity.AsynchModel, err error) {
|
||||
whereSQL := "deleted_at IS NULL AND (tenant_id = 1 OR creator = ?)"
|
||||
args := []any{creator}
|
||||
|
||||
if modelNameLike != "" {
|
||||
whereSQL += " AND model_name LIKE ?"
|
||||
args = append(args, "%"+modelNameLike+"%")
|
||||
}
|
||||
if modelType != 0 {
|
||||
whereSQL += " AND models_type = ?"
|
||||
args = append(args, modelType)
|
||||
}
|
||||
|
||||
querySQL := fmt.Sprintf("SELECT * FROM %s WHERE %s ORDER BY created_at DESC", public.TableNameModel, whereSQL)
|
||||
|
||||
r, err := gfdb.DB(ctx).GetAll(ctx, querySQL, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r.Structs(&list)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *modelDao) GetByIsChatModel(ctx context.Context, userName string) (m *entity.AsynchModel, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
|
||||
Where(entity.AsynchModelCol.IsChatModel, 1).
|
||||
Where(entity.AsynchModelCol.Creator, userName).
|
||||
One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.IsEmpty() {
|
||||
return nil, nil
|
||||
}
|
||||
err = r.Struct(&m)
|
||||
return
|
||||
}
|
||||
|
||||
// ListAll 用于分组展示:查询全部模型(不按类型过滤,类型拆分在 service 层处理)
|
||||
func (d *modelDao) ListAll(ctx context.Context) (list []*entity.AsynchModel, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).
|
||||
Where("deleted_at IS NULL").
|
||||
OrderDesc(entity.AsynchModelCol.CreatedAt).
|
||||
All()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"model-asynch/consts/public"
|
||||
"model-asynch/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/db/gfdb"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type modelTypeDao struct{}
|
||||
|
||||
var ModelType = &modelTypeDao{}
|
||||
|
||||
func (d *modelTypeDao) Insert(ctx context.Context, t *entity.AsynchModelType) (id int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModelType).Data(t).Insert()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return r.LastInsertId()
|
||||
}
|
||||
|
||||
func (d *modelTypeDao) UpdateByID(ctx context.Context, id int64, data gdb.Map) (rows int64, err error) {
|
||||
// 触发 gfdb 的 updateHook 自动填充 updater,需要显式带 updater 字段
|
||||
data[entity.AsynchModelTypeCol.Updater] = ""
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModelType).Where(entity.AsynchModelTypeCol.Id, id).Data(data).Update()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, _ := r.RowsAffected()
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (d *modelTypeDao) DeleteByID(ctx context.Context, id int64) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModelType).Where(entity.AsynchModelTypeCol.Id, id).Delete()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, _ := r.RowsAffected()
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (d *modelTypeDao) GetByID(ctx context.Context, id int64) (*entity.AsynchModelType, error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModelType).Where(entity.AsynchModelTypeCol.Id, id).One()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.IsEmpty() {
|
||||
return nil, nil
|
||||
}
|
||||
var t *entity.AsynchModelType
|
||||
_ = r.Struct(&t)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (d *modelTypeDao) List(ctx context.Context, pageNum, pageSize int, typeNameLike string) (list []*entity.AsynchModelType, total int64, err error) {
|
||||
m := gfdb.DB(ctx).Model(ctx, public.TableNameModelType).Where("deleted_at IS NULL").OrderAsc(entity.AsynchModelTypeCol.TypeID)
|
||||
if typeNameLike != "" {
|
||||
m = m.WhereLike(entity.AsynchModelTypeCol.TypeName, "%"+typeNameLike+"%")
|
||||
}
|
||||
if pageNum > 0 && pageSize > 0 {
|
||||
m = m.Page(pageNum, pageSize)
|
||||
}
|
||||
r, totalInt, err := m.AllAndCount(false)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
total = gconv.Int64(totalInt)
|
||||
err = r.Structs(&list)
|
||||
return
|
||||
}
|
||||
@@ -13,7 +13,7 @@ type opLogDao struct{}
|
||||
|
||||
var OpLog = &opLogDao{}
|
||||
|
||||
func (d *opLogDao) Insert(ctx context.Context, log *entity.AsynchOpLog) (id int64, err error) {
|
||||
func (d *opLogDao) Insert(ctx context.Context, log *entity.LogsModelOp) (id int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, public.TableNameOpLog).Data(log).Insert()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
||||
@@ -29,7 +29,7 @@ DO UPDATE SET request_count = %s.request_count + 1, updated_at = NOW()`,
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *statDao) List(ctx context.Context, pageNum, pageSize int, startDay, endDay string, tenantId *int64, creator, modelName string) (list []*entity.AsynchModelStat, total int64, err error) {
|
||||
func (d *statDao) List(ctx context.Context, pageNum, pageSize int, startDay, endDay string, tenantId *int64, creator, modelName string) (list []*entity.LogsModelStat, total int64, err error) {
|
||||
m := gfdb.DB(ctx).Model(ctx, public.TableNameStat).Where("1=1")
|
||||
if startDay != "" {
|
||||
m = m.Where("day >= ?", startDay)
|
||||
@@ -58,4 +58,3 @@ func (d *statDao) List(ctx context.Context, pageNum, pageSize int, startDay, end
|
||||
err = r.Structs(&list)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ func (d *taskDao) ClaimPendingGlobal(ctx context.Context, batchSize int) (tasks
|
||||
}
|
||||
err = gfdb.DB(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
sql := fmt.Sprintf(
|
||||
`SELECT id, tenant_id, creator, model_name, task_id, model_key, input_ref, request_payload, phase, tmp_file
|
||||
`SELECT id, tenant_id, creator, model_name, task_id, biz_name, callback_url, model_key, input_ref, request_payload, phase, tmp_file
|
||||
FROM %s
|
||||
WHERE deleted_at IS NULL AND state = 0
|
||||
ORDER BY enqueue_at ASC
|
||||
@@ -55,13 +55,51 @@ func (d *taskDao) ClaimPendingGlobal(ctx context.Context, batchSize int) (tasks
|
||||
return
|
||||
}
|
||||
|
||||
func (d *taskDao) UpdateSuccessGlobal(ctx context.Context, id int64, ossFile, fileType string, fileSize int64, expireAt *gtime.Time) error {
|
||||
// ClaimPendingByTaskIDGlobal 按 task_id 定向抢占单个 pending 任务(不加 tenant 过滤)
|
||||
// 用于 createTask 创建成功后立即异步尝试执行当前任务,避免只依赖后续 runWork 扫描队列。
|
||||
func (d *taskDao) ClaimPendingByTaskIDGlobal(ctx context.Context, taskID string) (task *entity.AsynchTask, err error) {
|
||||
if taskID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
err = gfdb.DB(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
sql := fmt.Sprintf(
|
||||
`SELECT id, tenant_id, creator, model_name, task_id, biz_name, callback_url, model_key, input_ref, request_payload, phase, tmp_file
|
||||
FROM %s
|
||||
WHERE deleted_at IS NULL AND state = 0 AND task_id = ?
|
||||
LIMIT 1
|
||||
FOR UPDATE SKIP LOCKED`,
|
||||
public.TableNameTask,
|
||||
)
|
||||
r, err := tx.GetOne(sql, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.IsEmpty() {
|
||||
task = nil
|
||||
return nil
|
||||
}
|
||||
if err := r.Struct(&task); err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = tx.Exec(
|
||||
fmt.Sprintf(`UPDATE %s SET state=1, started_at=?, updated_at=? WHERE id=?`, public.TableNameTask),
|
||||
now, now, task.Id,
|
||||
)
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (d *taskDao) UpdateSuccessGlobal(ctx context.Context, id int64, ossFile, fileType, textResult string, fileSize int64, expireAt *gtime.Time, expendTokens int) error {
|
||||
now := gtime.Now()
|
||||
_, err := gfdb.DB(ctx).Exec(ctx,
|
||||
fmt.Sprintf(`UPDATE %s
|
||||
SET state=2,
|
||||
oss_file=?,
|
||||
file_type=?,
|
||||
text_result=?,
|
||||
expend_tokens=?,
|
||||
file_size=?,
|
||||
error_msg='',
|
||||
finished_at=?,
|
||||
@@ -71,7 +109,7 @@ SET state=2,
|
||||
tmp_file='',
|
||||
updated_at=?
|
||||
WHERE id=?`, public.TableNameTask),
|
||||
ossFile, fileType, fileSize, now, now, now, id,
|
||||
ossFile, fileType, textResult, expendTokens, fileSize, now, now, now, id,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
15
go.mod
15
go.mod
@@ -3,14 +3,21 @@ module model-asynch
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
gitea.com/red-future/common v0.0.12
|
||||
gitea.com/red-future/common v0.0.19 // indirect
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0
|
||||
github.com/gogf/gf/v2 v2.10.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/tidwall/gjson v1.14.2
|
||||
)
|
||||
|
||||
// replace gitea.com/red-future/common v0.0.12 => ../common
|
||||
require (
|
||||
github.com/r3labs/diff/v2 v2.15.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
@@ -29,7 +36,6 @@ require (
|
||||
github.com/go-ego/gse v1.0.2 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gogf/gf v1.16.9 // indirect
|
||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 // indirect
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
@@ -39,7 +45,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/flatbuffers v1.12.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||
github.com/hashicorp/consul/api v1.26.1 // indirect
|
||||
@@ -65,6 +71,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/redis/go-redis/v9 v9.12.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/tidwall/sjson v1.2.5
|
||||
github.com/tiger1103/gfast-token v1.0.10 // indirect
|
||||
github.com/vcaesar/cedar v0.30.0 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.4.0 // indirect
|
||||
|
||||
185
go.sum
185
go.sum
@@ -1,14 +1,20 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
gitea.com/red-future/common v0.0.12 h1:whaCAiH33orl0P+oDpxzC4VoNluHKNYKGZ+FcUWw85Q=
|
||||
gitea.com/red-future/common v0.0.12/go.mod h1:3a7cwZNvgpKw5FzE8x5MZImd7NBePGXRGFSMjt90158=
|
||||
gitea.com/red-future/common v0.0.19 h1:9/WrfCFUCeFUYwuhBYF+JOQi5F5xuOy+gVnf2ZvHZu4=
|
||||
gitea.com/red-future/common v0.0.19/go.mod h1:6/nqIucVzmjOyqDTIq71feYBXXFNBy0rFwzaQ0/Ueoo=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
|
||||
@@ -25,6 +31,7 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
@@ -33,10 +40,15 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4=
|
||||
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -44,15 +56,23 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs=
|
||||
github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak=
|
||||
github.com/dgraph-io/badger/v4 v4.9.1 h1:DocZXZkg5JJHJPtUErA0ibyHxOVUDVoXLSCV6t8NC8w=
|
||||
github.com/dgraph-io/badger/v4 v4.9.1/go.mod h1:5/MEx97uzdPUHR4KtkNt8asfI2T4JiEiQlV7kWUo8c0=
|
||||
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
|
||||
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0 h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuMyNy0XlU=
|
||||
github.com/dgraph-io/ristretto/v2 v2.4.0/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
|
||||
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -65,15 +85,21 @@ github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/go-ego/gse v1.0.2 h1:+27lYFPhQEhA9igtdOsJPRKYL/k3TwYsxBF5jr6KFv4=
|
||||
github.com/go-ego/gse v1.0.2/go.mod h1:Fy35G+q7VV7Et1zIKO8o/sW1kkugV3znXap/lF/11zc=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@@ -81,6 +107,10 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gogf/gf v1.16.9 h1:Q803UmmRo59+Ws08sMVFOcd8oNpkSWL9vS33hlo/Cyk=
|
||||
github.com/gogf/gf v1.16.9/go.mod h1:8Q/kw05nlVRp+4vv7XASBsMe9L1tsVKiGoeP2AHnlkk=
|
||||
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0 h1:39+jbTenm7KBj4hO2C8ANAxVHpX/7OuRDs1VcGC9ylA=
|
||||
@@ -89,8 +119,12 @@ github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0 h1:N/F9CuDdUZLoM1nVRqrDE/33pDZ
|
||||
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.0/go.mod h1:x6uoJGfZOtirIRQls8xUlYzC6f7T/eULPUa9er368X0=
|
||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 h1:eUqwJ/qNH8lJ6yssiqskazgp1ACQuNU6zXlLOZVuXTQ=
|
||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5/go.mod h1:sjQyMry9+0POYZCA6lHXBxO77WoNKkruJpRB4xKqk5k=
|
||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.10.0 h1:NF3xO+/bJ0Jve+BBVLX/f80aOmAtIVQPdoNk1IvaPs0=
|
||||
github.com/gogf/gf/contrib/registry/consul/v2 v2.10.0/go.mod h1:tF3JjImw346aLSVNRpmYyMukLQGivBOpuAU39TvF6i0=
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 h1:tHUEZYB5GTqEYYVDYnlGobf1xISARKDE4KHVlgjwTec=
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5/go.mod h1:cfzTn2HS9RDX8f5pUVkbGxUWcSosouqfNQ1G6cY0V88=
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.10.0 h1:9uQ29GvNTWBngPnltV+2C+FbofHbmcaiEdLgqhcHgu0=
|
||||
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.10.0/go.mod h1:wRPkw0CqBUe3DPHH2IA5en+Il7nPQpFhHDPqvuDNdjU=
|
||||
github.com/gogf/gf/v2 v2.10.0 h1:rzDROlyqGMe/eM6dCalSR8dZOuMIdLhmxKSH1DGhbFs=
|
||||
github.com/gogf/gf/v2 v2.10.0/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -104,6 +138,8 @@ github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwm
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -114,7 +150,10 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
@@ -123,14 +162,19 @@ github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUz
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
|
||||
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
|
||||
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
@@ -141,15 +185,22 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
|
||||
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||
github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM=
|
||||
github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A=
|
||||
github.com/hashicorp/consul/api v1.34.2 h1:B5jqSSKwWyY8U8WiGS5vmPEPkkF0bAvrECykdZkDR80=
|
||||
github.com/hashicorp/consul/api v1.34.2/go.mod h1:+gAdHQa2zvgYX3ZfcgITtnYCSj6AgS/cgotvCKaE+b8=
|
||||
github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU=
|
||||
github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo=
|
||||
github.com/hashicorp/consul/sdk v0.18.1 h1:RDTeBvAeOveI2xI86sV+8WkaN7OkP4zz+cG3fOobDCM=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -158,9 +209,13 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
||||
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY=
|
||||
github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
@@ -174,6 +229,7 @@ github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR3
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
@@ -188,16 +244,26 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
|
||||
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
|
||||
github.com/hashicorp/memberlist v0.5.2 h1:rJoNPWZ0juJBgqn48gjy59K5H4rNgvUoM1kUD7bXiuI=
|
||||
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
|
||||
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
|
||||
github.com/hashicorp/serf v0.10.2 h1:m5IORhuNSjaxeljg5DeQVDlQyVkhRIjJDimbkCa8aAc=
|
||||
github.com/hashicorp/serf v0.10.2/go.mod h1:T1CmSGfSeGfnfNy/w0odXQUR1rfECGd2Qdsp84DjOiY=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -208,6 +274,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
@@ -225,9 +293,13 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
@@ -244,13 +316,22 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/errors v1.3.0 h1:teJvgLGUEqMzBUms+Dj3/3szNqCG/Jdw9iDbum8fR6U=
|
||||
github.com/olekukonko/errors v1.3.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8=
|
||||
github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
||||
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
|
||||
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
@@ -266,29 +347,42 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg=
|
||||
github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc=
|
||||
github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg=
|
||||
github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
|
||||
github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
|
||||
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
@@ -297,9 +391,26 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
|
||||
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s=
|
||||
github.com/tiger1103/gfast-token v1.0.10/go.mod h1:a/21mxmj7zFeNvjhZSC0XpEAFHfb1aT2k6DXnufFU1s=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
@@ -307,32 +418,58 @@ github.com/vcaesar/cedar v0.30.0 h1:9fSDpM7FTjjUdPiBUUa0MWYMRGSEcqgFXvppZcZ4d7Y=
|
||||
github.com/vcaesar/cedar v0.30.0/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik=
|
||||
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
|
||||
github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo=
|
||||
go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI=
|
||||
go.mongodb.org/mongo-driver/v2 v2.6.0 h1:b9sJOYrkmt4l8bY43ZenFBcPlhYIjaOfYHLtbB/5qi8=
|
||||
go.mongodb.org/mongo-driver/v2 v2.6.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
|
||||
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
|
||||
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
|
||||
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
|
||||
go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
|
||||
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
@@ -340,43 +477,58 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw=
|
||||
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -389,29 +541,45 @@ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -421,23 +589,33 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 h1:U8orV30l6KpDsi9dxU0CoJZGbjS8EEpw+6ba+XwGPQA=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348/go.mod h1:Yzdzr5OOZFgSsEV2D/Xi9NL3bszpXFAg0hFJiRohcD8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
@@ -445,6 +623,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
|
||||
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -454,8 +634,12 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -465,6 +649,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
53
main.go
53
main.go
@@ -5,10 +5,11 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"model-asynch/controller"
|
||||
"model-asynch/service"
|
||||
|
||||
_ "gitea.com/red-future/common/config"
|
||||
"gitea.com/red-future/common/http"
|
||||
"gitea.com/red-future/common/jaeger"
|
||||
_ "gitea.com/red-future/common/swagger"
|
||||
@@ -25,11 +26,13 @@ func main() {
|
||||
// 注册路由
|
||||
http.RouteRegister([]interface{}{
|
||||
controller.Model,
|
||||
controller.ModelType,
|
||||
controller.Task,
|
||||
controller.Stat,
|
||||
})
|
||||
|
||||
// 本地调试:可选自动触发 worker/cleaner(由配置文件控制)
|
||||
startAutoRunner(ctx)
|
||||
|
||||
// 监听退出信号,确保 Ctrl+C 能完整退出(停止 worker/cleaner 并关闭 http server)
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
||||
@@ -40,3 +43,49 @@ func main() {
|
||||
// 关闭 http server(RouteRegister 内部是 go Httpserver.Run() 启动的)
|
||||
_ = http.Httpserver.Shutdown()
|
||||
}
|
||||
|
||||
func startAutoRunner(ctx context.Context) {
|
||||
// worker
|
||||
if g.Cfg().MustGet(ctx, "asynch.worker.enabled").Bool() {
|
||||
interval := g.Cfg().MustGet(ctx, "asynch.worker.intervalSeconds").Int()
|
||||
if interval <= 0 {
|
||||
interval = 5
|
||||
}
|
||||
batchSize := g.Cfg().MustGet(ctx, "asynch.worker.batchSize").Int()
|
||||
goroutines := g.Cfg().MustGet(ctx, "asynch.worker.goroutines").Int()
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
go func() {
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
if _, err := service.AsyncWorker.RunOnce(ctx, batchSize, goroutines); err != nil {
|
||||
g.Log().Warningf(ctx, "[auto-worker] run once failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// cleaner
|
||||
if g.Cfg().MustGet(ctx, "asynch.cleaner.enabled").Bool() {
|
||||
interval := g.Cfg().MustGet(ctx, "asynch.cleaner.intervalSeconds").Int()
|
||||
if interval <= 0 {
|
||||
interval = 30
|
||||
}
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
go func() {
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
service.Cleaner.RunOnce(ctx)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"gitea.com/red-future/common/beans"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
@@ -9,20 +8,26 @@ import (
|
||||
type CreateModelReq struct {
|
||||
g.Meta `path:"/createModel" method:"post" tags:"模型管理" summary:"创建模型配置" dc:"添加新的模型配置"`
|
||||
ModelName string `p:"modelName" json:"modelName" v:"required#modelName不能为空" dc:"模型名称(唯一标识)"`
|
||||
ModelsType string `p:"modelsType" json:"modelsType" dc:"模型类型ID列表(逗号分隔),示例:1,2,3(关联 asynch_models_type.type_id,可选)"`
|
||||
ModelsType int `p:"modelsType" json:"modelsType" v:"required#modelsType不能为空" dc:"模型类型:1-文本生成 2-图像生成 3-语音 4-视频 5-多模态"`
|
||||
BaseURL string `p:"baseUrl" json:"baseUrl" v:"required#baseUrl不能为空" dc:"模型服务基础地址(如 http(s)://host:port)"`
|
||||
Route string `p:"route" json:"route" dc:"路由/路径(拼接到 BaseURL 之后的可选路径)"`
|
||||
HttpMethod string `p:"httpMethod" json:"httpMethod" dc:"请求方式:GET/POST(默认POST)"`
|
||||
HeadMsg string `p:"headMsg" json:"headMsg" dc:"请求头绑定(支持多个,逗号分隔),示例:X-API-Key:xxx,operation:true"`
|
||||
Form any `p:"form" json:"form" dc:"动态表单配置(JSON),用于前端渲染配置项,示例:[{field,label,type,required},...]"`
|
||||
Enabled int `p:"enabled" json:"enabled" dc:"是否启用:0-禁用,1-启用"`
|
||||
MaxConcurrency int `p:"maxConcurrency" json:"maxConcurrency" dc:"最大并发数"`
|
||||
QueueLimit int `p:"queueLimit" json:"queueLimit" dc:"排队队列上限(超过则拒绝/限流)"`
|
||||
TimeoutSeconds int `p:"timeoutSeconds" json:"timeoutSeconds" dc:"请求超时时间(秒)"`
|
||||
ExpectedSeconds int `p:"expectedSeconds" json:"expectedSeconds" dc:"模型预计执行时间(秒,用于超时判定/排队策略等)"`
|
||||
RetryTimes int `p:"retryTimes" json:"retryTimes" dc:"失败重试次数"`
|
||||
RetryQueueMaxSeconds int `p:"retryQueueMaxSeconds" json:"retryQueueMaxSeconds" dc:"失败重试最大排队时间(秒);0表示失败重试插队到队首;>0表示排队超过该时间后插队,否则仍到队尾"`
|
||||
AutoCleanSeconds int `p:"autoCleanSeconds" json:"autoCleanSeconds" dc:"自动清理间隔(秒)(如清理超时任务/队列)"`
|
||||
HeadMsg string `p:"headMsg" json:"headMsg" dc:"请求头绑定(支持多个,逗号分隔),示例:Authorization:Bearer xxx,Content-Type:application/json"`
|
||||
IsPrivate int `p:"isPrivate" json:"isPrivate" v:"in:0,1#私有化参数只能为0或1" dc:"是否私有化:0-私有(默认) 1-公共"`
|
||||
Enabled int `p:"enabled" json:"enabled" v:"in:0,1#启用参数只能为0或1" dc:"是否启用:0-禁用,1-启用(默认1)"`
|
||||
IsChatModel int `p:"isChatModel" json:"isChatModel" v:"in:0,1#对话模型参数只能为0或1" dc:"是否为对话模型:0-否,1-是(默认0)"`
|
||||
ApiKey string `p:"apiKey" json:"apiKey" v:"required-if:isPrivate,1#公共模型必须填写API密钥" dc:"调用凭证/密钥,用于模型认证"`
|
||||
Form any `p:"form" json:"form" dc:"动态表单配置(JSON),用于前端渲染配置项"`
|
||||
RequestMapping any `p:"requestMapping" json:"requestMapping" dc:"请求映射"`
|
||||
ResponseMapping any `p:"responseMapping" json:"responseMapping" dc:"返回映射"`
|
||||
ResponseBody any `p:"responseBody" json:"responseBody" dc:"返回主体"`
|
||||
TokenMapping string `p:"tokenMapping" json:"tokenMapping" dc:"token映射"`
|
||||
MaxConcurrency int `p:"maxConcurrency" json:"maxConcurrency" dc:"最大并发数(默认10)"`
|
||||
QueueLimit int `p:"queueLimit" json:"queueLimit" dc:"排队队列上限(默认1000)"`
|
||||
TimeoutSeconds int `p:"timeoutSeconds" json:"timeoutSeconds" dc:"请求超时时间(秒,默认600)"`
|
||||
ExpectedSeconds int `p:"expectedSeconds" json:"expectedSeconds" dc:"模型预计执行时间(秒,默认600)"`
|
||||
RetryTimes int `p:"retryTimes" json:"retryTimes" dc:"失败重试次数(默认3)"`
|
||||
RetryQueueMaxSeconds int `p:"retryQueueMaxSeconds" json:"retryQueueMaxSeconds" dc:"失败重试最大排队时间(秒,默认600)"`
|
||||
AutoCleanSeconds int `p:"autoCleanSeconds" json:"autoCleanSeconds" dc:"任务完成后自动清理时间(秒,默认86400)"`
|
||||
Remark string `p:"remark" json:"remark" dc:"备注说明"`
|
||||
}
|
||||
|
||||
@@ -30,36 +35,39 @@ type CreateModelRes struct {
|
||||
ID int64 `json:"id,string" dc:"配置ID"`
|
||||
}
|
||||
|
||||
// UpdateModelReq 更新模型配置
|
||||
type UpdateModelReq struct {
|
||||
g.Meta `path:"/updateModel" method:"put" tags:"模型管理" summary:"更新模型配置" dc:"更新指定ID的模型配置"`
|
||||
ID int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"配置ID"`
|
||||
ModelsType *string `p:"modelsType" json:"modelsType" dc:"模型类型ID列表(逗号分隔)(可选更新)"`
|
||||
BaseURL string `p:"baseUrl" json:"baseUrl" dc:"模型服务基础地址"`
|
||||
Route string `p:"route" json:"route" dc:"路由/路径"`
|
||||
HttpMethod *string `p:"httpMethod" json:"httpMethod" dc:"请求方式:GET/POST(可选更新)"`
|
||||
HeadMsg *string `p:"headMsg" json:"headMsg" dc:"请求头绑定(可选更新)"`
|
||||
Form any `p:"form" json:"form" dc:"动态表单配置(JSON)(可选更新)"`
|
||||
Enabled *int `p:"enabled" json:"enabled" dc:"是否启用:0-禁用,1-启用(可选更新)"`
|
||||
MaxConcurrency *int `p:"maxConcurrency" json:"maxConcurrency" dc:"最大并发数(可选更新)"`
|
||||
QueueLimit *int `p:"queueLimit" json:"queueLimit" dc:"排队队列上限(可选更新)"`
|
||||
TimeoutSeconds *int `p:"timeoutSeconds" json:"timeoutSeconds" dc:"请求超时时间(秒)(可选更新)"`
|
||||
ExpectedSeconds *int `p:"expectedSeconds" json:"expectedSeconds" dc:"模型预计执行时间(秒)(可选更新)"`
|
||||
RetryTimes *int `p:"retryTimes" json:"retryTimes" dc:"失败重试次数(可选更新)"`
|
||||
RetryQueueMaxSeconds *int `p:"retryQueueMaxSeconds" json:"retryQueueMaxSeconds" dc:"失败重试最大排队时间(秒)(可选更新)"`
|
||||
AutoCleanSeconds *int `p:"autoCleanSeconds" json:"autoCleanSeconds" dc:"自动清理间隔(秒)(可选更新)"`
|
||||
Remark *string `p:"remark" json:"remark" dc:"备注说明(可选更新)"`
|
||||
ID int64 `p:"id" json:"id" v:"required#id不能为空" dc:"配置ID"`
|
||||
ModelsType string `p:"modelsType" json:"modelsType" dc:"模型类型ID列表(逗号分隔)(可选更新)"`
|
||||
BaseURL string `p:"baseUrl" json:"baseUrl" dc:"模型服务基础地址"`
|
||||
HttpMethod string `p:"httpMethod" json:"httpMethod" dc:"请求方式:GET/POST(可选更新)"`
|
||||
HeadMsg string `p:"headMsg" json:"headMsg" dc:"请求头绑定(可选更新)"`
|
||||
Form any `p:"form" json:"form" dc:"动态表单配置(JSON)(可选更新)"`
|
||||
RequestMapping any `p:"requestMapping" json:"requestMapping" dc:"请求参数映射(可选更新)"`
|
||||
ResponseMapping any `p:"responseMapping" json:"responseMapping" dc:"返回参数映射(可选更新)"`
|
||||
ResponseBody any `p:"responseBody" json:"responseBody" dc:"返回主体(可选更新)"`
|
||||
TokenMapping string `p:"tokenMapping" json:"tokenMapping" dc:"token映射(可选更新)"`
|
||||
Enabled int `p:"enabled" json:"enabled" dc:"是否启用:0-禁用,1-启用(可选更新)"`
|
||||
IsChatModel int `p:"isChatModel" json:"isChatModel" v:"in:0,1#对话模型参数只能为0或1" dc:"是否为对话模型:0-否,1-是(默认0)"`
|
||||
MaxConcurrency int `p:"maxConcurrency" json:"maxConcurrency" dc:"最大并发数(可选更新)"`
|
||||
QueueLimit int `p:"queueLimit" json:"queueLimit" dc:"排队队列上限(可选更新)"`
|
||||
TimeoutSeconds int `p:"timeoutSeconds" json:"timeoutSeconds" dc:"请求超时时间(秒)(可选更新)"`
|
||||
ExpectedSeconds int `p:"expectedSeconds" json:"expectedSeconds" dc:"模型预计执行时间(秒)(可选更新)"`
|
||||
RetryTimes int `p:"retryTimes" json:"retryTimes" dc:"失败重试次数(可选更新)"`
|
||||
RetryQueueMaxSeconds int `p:"retryQueueMaxSeconds" json:"retryQueueMaxSeconds" dc:"失败重试最大排队时间(秒)(可选更新)"`
|
||||
AutoCleanSeconds int `p:"autoCleanSeconds" json:"autoCleanSeconds" dc:"自动清理间隔(秒)(可选更新)"`
|
||||
Remark string `p:"remark" json:"remark" dc:"备注说明(可选更新)"`
|
||||
}
|
||||
|
||||
// DeleteModelReq 删除模型配置
|
||||
type DeleteModelReq struct {
|
||||
g.Meta `path:"/deleteModel" method:"delete" tags:"模型管理" summary:"删除模型配置" dc:"删除指定ID的模型配置"`
|
||||
ID int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"配置ID"`
|
||||
ID string `p:"id" json:"id,string" v:"required#id不能为空" dc:"配置ID"`
|
||||
}
|
||||
|
||||
// GetModelReq 获取模型配置详情
|
||||
type GetModelReq struct {
|
||||
g.Meta `path:"/getModel" method:"get" tags:"模型管理" summary:"获取模型配置" dc:"根据模型名称获取配置详情"`
|
||||
g.Meta `path:"/getModel" method:"get" tags:"模型管理" summary:"获取模型配置" dc:"根据模型ID获取配置详情"`
|
||||
ID int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"配置ID"`
|
||||
}
|
||||
|
||||
@@ -69,9 +77,11 @@ type GetModelRes struct {
|
||||
|
||||
// ListModelReq 配置列表
|
||||
type ListModelReq struct {
|
||||
g.Meta `path:"/listModel" method:"post" tags:"模型管理" summary:"模型配置列表" dc:"分页获取模型配置列表"`
|
||||
Page *beans.Page `p:"page" json:"page" dc:"分页参数"`
|
||||
ModelName string `p:"modelName" json:"modelName" dc:"模型名称(模糊查询,可选)"`
|
||||
g.Meta `path:"/listModel" method:"get" tags:"模型管理" summary:"模型配置列表" dc:"分页获取模型配置列表"`
|
||||
PageNum int `p:"pageNum" json:"pageNum" dc:"页码(默认1)"`
|
||||
PageSize int `p:"pageSize" json:"pageSize" dc:"每页条数(默认10)"`
|
||||
ModelName string `p:"modelName" json:"modelName" dc:"模型名称(模糊查询,可选)"`
|
||||
ModelType int `p:"modelType" json:"modelType" dc:"模型类型"`
|
||||
}
|
||||
|
||||
type ListModelRes struct {
|
||||
@@ -81,10 +91,34 @@ type ListModelRes struct {
|
||||
|
||||
// AutoTuneReq 动态调参(由上层定时任务每小时触发一次)
|
||||
type AutoTuneReq struct {
|
||||
g.Meta `path:"/autoTune" method:"post" tags:"模型管理" summary:"动态调参" dc:"按 model_name 维度统计指定时间窗口内执行耗时(P90),动态生成运行时 max_concurrency/queue_limit(不超过配置上限),写入 Redis 供 Worker/CreateTask 使用;windowSeconds 不传默认 3600"`
|
||||
g.Meta `path:"/autoTune" method:"post" tags:"模型管理" summary:"动态调参" dc:"按 model_name 维度统计指定时间窗口内执行耗时(P90),动态生成运行时 max_concurrency/queue_limit(不超过配置上限),写入 Redis 供 Worker/CreateTask 使用;windowSeconds 不传默认 3600"`
|
||||
WindowSeconds int `p:"windowSeconds" json:"windowSeconds" dc:"统计窗口秒数;不传/<=0 默认 3600(1小时)"`
|
||||
}
|
||||
|
||||
type AutoTuneRes struct {
|
||||
List any `json:"list" dc:"调参结果列表"`
|
||||
}
|
||||
|
||||
type ModelTypeModelItem struct {
|
||||
ID int64 `json:"id" dc:"模型主键ID"`
|
||||
Name string `json:"name" dc:"模型名称"`
|
||||
Form any `json:"form" dc:"动态表单配置(JSON数组),用于前端渲染"`
|
||||
}
|
||||
|
||||
// ListModelTypeReq 模型类型列表(分页)
|
||||
type ListTypeReq struct {
|
||||
g.Meta `path:"/listType" method:"get" tags:"模型类型列表" summary:"模型类型列表" dc:"分页获取模型类型列表"`
|
||||
}
|
||||
|
||||
type TypeItem struct {
|
||||
Type map[int]string `json:"type" dc:"模型类型ID到名称的映射"`
|
||||
}
|
||||
|
||||
type UpdateChatModelReq struct {
|
||||
g.Meta `path:"/updateChatModel" method:"post" tags:"模型管理" summary:"更新聊天模型" dc:"更新指定模型的聊天模型"`
|
||||
Id int64 `p:"id" json:"id" v:"required#model不能为空" dc:"模型id"`
|
||||
}
|
||||
|
||||
type GetIsChatModelReq struct {
|
||||
g.Meta `path:"/getIsChatModel" method:"get" tags:"模型管理" summary:"获取模型是否为聊天模型" dc:"根据模型ID获取是否为聊天模型"`
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"gitea.com/red-future/common/beans"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// CreateModelTypeReq 创建模型类型
|
||||
type CreateModelTypeReq struct {
|
||||
g.Meta `path:"/createModelType" method:"post" tags:"模型类型" summary:"创建模型类型" dc:"创建模型类型(图片/音频/视频等)"`
|
||||
TypeID int `p:"typeId" json:"typeId" v:"required#typeId不能为空" dc:"模型类型ID(业务枚举)"`
|
||||
TypeName string `p:"type" json:"type" v:"required#type不能为空" dc:"模型类型名称"`
|
||||
Remark string `p:"remark" json:"remark" dc:"备注"`
|
||||
}
|
||||
|
||||
type CreateModelTypeRes struct {
|
||||
ID int64 `json:"id,string" dc:"主键ID"`
|
||||
}
|
||||
|
||||
// UpdateModelTypeReq 更新模型类型
|
||||
type UpdateModelTypeReq struct {
|
||||
g.Meta `path:"/updateModelType" method:"put" tags:"模型类型" summary:"更新模型类型" dc:"更新模型类型"`
|
||||
ID int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"主键ID"`
|
||||
TypeID *int `p:"typeId" json:"typeId" dc:"模型类型ID(可选更新)"`
|
||||
TypeName *string `p:"type" json:"type" dc:"模型类型名称(可选更新)"`
|
||||
Remark *string `p:"remark" json:"remark" dc:"备注(可选更新)"`
|
||||
}
|
||||
|
||||
// DeleteModelTypeReq 删除模型类型
|
||||
type DeleteModelTypeReq struct {
|
||||
g.Meta `path:"/deleteModelType" method:"delete" tags:"模型类型" summary:"删除模型类型" dc:"删除模型类型"`
|
||||
ID int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"主键ID"`
|
||||
}
|
||||
|
||||
// GetModelTypeReq 获取模型类型
|
||||
type GetModelTypeReq struct {
|
||||
g.Meta `path:"/getModelType" method:"get" tags:"模型类型" summary:"获取模型类型" dc:"获取模型类型详情"`
|
||||
ID int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"主键ID"`
|
||||
}
|
||||
|
||||
type GetModelTypeRes struct {
|
||||
Type any `json:"type" dc:"模型类型详情"`
|
||||
}
|
||||
|
||||
// ListModelTypeReq 模型类型列表(分页)
|
||||
type ListModelTypeReq struct {
|
||||
g.Meta `path:"/listModelType" method:"post" tags:"模型类型" summary:"模型类型列表" dc:"分页获取模型类型列表"`
|
||||
Page *beans.Page `p:"page" json:"page" dc:"分页参数(默认10条)"`
|
||||
TypeName string `p:"type" json:"type" dc:"模型类型名称(模糊查询,可选)"`
|
||||
}
|
||||
|
||||
type ListModelTypeRes struct {
|
||||
List any `json:"list" dc:"列表数据"`
|
||||
Total int64 `json:"total" dc:"总数"`
|
||||
}
|
||||
|
||||
// ListModelTypeWithModelsReq 按类型分组返回模型列表
|
||||
type ListModelTypeWithModelsReq struct {
|
||||
g.Meta `path:"/listModelTypeWithModels" method:"post" tags:"模型类型" summary:"按类型分组的模型列表" dc:"返回模型类型及其下的模型列表(用于前端分组展示)"`
|
||||
TypeID int `p:"typeId" json:"typeId" dc:"按类型ID过滤(可选)"`
|
||||
Type string `p:"type" json:"type" dc:"按类型名称过滤(可选,模糊匹配)"`
|
||||
}
|
||||
|
||||
type ModelTypeModelItem struct {
|
||||
ID int64 `json:"id" dc:"模型主键ID"`
|
||||
Name string `json:"name" dc:"模型名称"`
|
||||
Form any `json:"form" dc:"动态表单配置(JSON数组),用于前端渲染"`
|
||||
}
|
||||
|
||||
type ModelTypeWithModelsItem struct {
|
||||
TypeID int `json:"typeId" dc:"模型类型ID"`
|
||||
Type string `json:"type" dc:"模型类型名称"`
|
||||
Items []ModelTypeModelItem `json:"items" dc:"该类型下模型列表"`
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"gitea.com/red-future/common/beans"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// ListModelStatReq 统计列表
|
||||
type ListModelStatReq struct {
|
||||
g.Meta `path:"/listModelStat" method:"post" tags:"统计" summary:"模型请求统计列表" dc:"按天统计模型请求次数,支持分页与条件筛选"`
|
||||
Page *beans.Page `p:"page" json:"page" dc:"分页参数(默认10条)"`
|
||||
g.Meta `path:"/listModelStat" method:"get" tags:"统计" summary:"模型请求统计列表" dc:"按天统计模型请求次数,支持分页与条件筛选"`
|
||||
PageNum int `p:"pageNum" json:"pageNum" dc:"页码(默认1)"`
|
||||
PageSize int `p:"pageSize" json:"pageSize" dc:"每页条数(默认10)"`
|
||||
StartDay string `p:"startDay" json:"startDay" dc:"开始日期(YYYY-MM-DD,可选)"`
|
||||
EndDay string `p:"endDay" json:"endDay" dc:"结束日期(YYYY-MM-DD,可选)"`
|
||||
TenantID *int64 `p:"tenantId" json:"tenantId" dc:"租户ID(可选)"`
|
||||
@@ -20,4 +18,3 @@ type ListModelStatRes struct {
|
||||
List any `json:"list" dc:"列表数据"`
|
||||
Total int64 `json:"total" dc:"总数"`
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"gitea.com/red-future/common/beans"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// CreateTaskReq 创建异步任务
|
||||
type CreateTaskReq struct {
|
||||
g.Meta `path:"/createTask" method:"post" tags:"任务管理" summary:"创建异步任务" dc:"创建异步任务并返回任务ID"`
|
||||
g.Meta `path:"/createTask" method:"post" tags:"任务管理" summary:"创建异步任务" dc:"创建异步任务并返回任务ID;创建成功后会立即异步尝试执行当前任务,执行成功后按回调配置触发钩子"`
|
||||
ModelName string `p:"modelName" json:"modelName" v:"required#modelName不能为空" dc:"模型名称"`
|
||||
ModelKey string `p:"modelKey" json:"modelKey" dc:"动态请求头(用于覆盖/补充模型配置 head_msg),示例:X-API-Key:xxx"`
|
||||
BizName string `p:"bizName" json:"bizName" dc:"业务名称(调用方模块/系统,用于统计)"`
|
||||
CallbackUrl string `p:"callbackUrl" json:"callbackUrl" dc:"回调地址(可选,用于后续业务通知)"`
|
||||
InputRef string `p:"inputRef" json:"inputRef" dc:"输入引用(如OSS/文件引用等)"`
|
||||
RequestPayload any `p:"requestPayload" json:"requestPayload" dc:"请求负载(透传给模型服务)"`
|
||||
EpicycleId int64 `json:"epicycleId" dc:"轮次ID"`
|
||||
}
|
||||
|
||||
type CreateTaskRes struct {
|
||||
@@ -49,11 +46,12 @@ type GetTaskBatchRes struct {
|
||||
|
||||
// ListTaskReq 任务列表分页查询
|
||||
type ListTaskReq struct {
|
||||
g.Meta `path:"/listTask" method:"post" tags:"任务管理" summary:"任务列表" dc:"分页查询任务列表,支持按状态/模型名称/task_id过滤"`
|
||||
Page *beans.Page `p:"page" json:"page" dc:"分页参数"`
|
||||
ModelName string `p:"modelName" json:"modelName" dc:"模型名称(模糊匹配)"`
|
||||
TaskID string `p:"taskId" json:"taskId" dc:"任务ID(模糊匹配)"`
|
||||
State *int `p:"state" json:"state" dc:"任务状态(0/1/2/3/4,可选)"`
|
||||
g.Meta `path:"/listTask" method:"get" tags:"任务管理" summary:"任务列表" dc:"分页查询任务列表,支持按状态/模型名称/task_id过滤"`
|
||||
PageNum int `p:"pageNum" json:"pageNum" dc:"页码(默认1)"`
|
||||
PageSize int `p:"pageSize" json:"pageSize" dc:"每页条数(默认10)"`
|
||||
ModelName string `p:"modelName" json:"modelName" dc:"模型名称(模糊匹配)"`
|
||||
TaskID string `p:"taskId" json:"taskId" dc:"任务ID(模糊匹配)"`
|
||||
State *int `p:"state" json:"state" dc:"任务状态(0/1/2/3/4,可选)"`
|
||||
}
|
||||
|
||||
type ListTaskRes struct {
|
||||
@@ -63,7 +61,7 @@ type ListTaskRes struct {
|
||||
|
||||
// RunWorkReq 手动触发 worker 执行一次(由上层定时任务调用)
|
||||
type RunWorkReq struct {
|
||||
g.Meta `path:"/runWork" method:"post" tags:"任务管理" summary:"执行一次Worker" dc:"手动触发一次Worker抢占并处理任务(用于由上层定时任务控制)"`
|
||||
g.Meta `path:"/runWork" method:"post" tags:"任务管理" summary:"执行一次Worker" dc:"手动触发一次Worker抢占并处理排队中的任务;适合处理 createTask 立即执行时未处理到的任务以及积压队列"`
|
||||
BatchSize int `p:"batchSize" json:"batchSize" dc:"本次抢占任务数量(默认10)"`
|
||||
Goroutines int `p:"goroutines" json:"goroutines" dc:"本次并发数(默认1)"`
|
||||
}
|
||||
|
||||
@@ -5,12 +5,19 @@ import "gitea.com/red-future/common/beans"
|
||||
type asynchModelCol struct {
|
||||
beans.SQLBaseCol
|
||||
ModelName string
|
||||
ModelsType string
|
||||
BaseURL string
|
||||
Route string
|
||||
HttpMethod string
|
||||
HeadMsg string
|
||||
FormJSON string
|
||||
ModelsType string
|
||||
RequestMapping string
|
||||
ResponseMapping string
|
||||
ResponseBody string
|
||||
TokenMapping string
|
||||
Prompt string
|
||||
IsPrivate string
|
||||
IsChatModel string
|
||||
ApiKey string
|
||||
Enabled string
|
||||
MaxConcurrency string
|
||||
QueueLimit string
|
||||
@@ -25,12 +32,19 @@ type asynchModelCol struct {
|
||||
var AsynchModelCol = asynchModelCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
ModelName: "model_name",
|
||||
ModelsType: "models_type",
|
||||
BaseURL: "base_url",
|
||||
Route: "route",
|
||||
HttpMethod: "http_method",
|
||||
HeadMsg: "head_msg",
|
||||
FormJSON: "form_json",
|
||||
ModelsType: "models_type",
|
||||
RequestMapping: "request_mapping",
|
||||
ResponseMapping: "response_mapping",
|
||||
ResponseBody: "response_body",
|
||||
TokenMapping: "token_mapping",
|
||||
Prompt: "prompt",
|
||||
IsPrivate: "is_private",
|
||||
IsChatModel: "is_chat_model",
|
||||
ApiKey: "api_key",
|
||||
Enabled: "enabled",
|
||||
MaxConcurrency: "max_concurrency",
|
||||
QueueLimit: "queue_limit",
|
||||
@@ -44,21 +58,28 @@ var AsynchModelCol = asynchModelCol{
|
||||
|
||||
// AsynchModel 异步模型配置
|
||||
type AsynchModel struct {
|
||||
beans.SQLBaseDO `orm:",inline"`
|
||||
ModelName string `orm:"model_name" json:"modelName"`
|
||||
BaseURL string `orm:"base_url" json:"baseUrl"`
|
||||
Route string `orm:"route" json:"route"`
|
||||
HttpMethod string `orm:"http_method" json:"httpMethod"`
|
||||
HeadMsg string `orm:"head_msg" json:"headMsg"`
|
||||
Form any `orm:"form_json" json:"form"`
|
||||
ModelsType string `orm:"models_type" json:"modelsType"`
|
||||
Enabled int `orm:"enabled" json:"enabled"`
|
||||
MaxConcurrency int `orm:"max_concurrency" json:"maxConcurrency"`
|
||||
QueueLimit int `orm:"queue_limit" json:"queueLimit"`
|
||||
TimeoutSeconds int `orm:"timeout_seconds" json:"timeoutSeconds"`
|
||||
ExpectedSeconds int `orm:"expected_seconds" json:"expectedSeconds"`
|
||||
RetryTimes int `orm:"retry_times" json:"retryTimes"`
|
||||
RetryQueueMaxSecs int `orm:"retry_queue_max_seconds" json:"retryQueueMaxSeconds"`
|
||||
AutoCleanSeconds int `orm:"auto_clean_seconds" json:"autoCleanSeconds"`
|
||||
Remark string `orm:"remark" json:"remark"`
|
||||
beans.SQLBaseDO `orm:",inline"`
|
||||
ModelName string `orm:"model_name" json:"modelName"`
|
||||
ModelsType int `orm:"models_type" json:"modelsType"`
|
||||
BaseURL string `orm:"base_url" json:"baseUrl"`
|
||||
HttpMethod string `orm:"http_method" json:"httpMethod"`
|
||||
HeadMsg string `orm:"head_msg" json:"headMsg"`
|
||||
Form any `orm:"form_json" json:"form"`
|
||||
RequestMapping any `orm:"request_mapping" json:"requestMapping"`
|
||||
ResponseMapping any `orm:"response_mapping" json:"responseMapping"`
|
||||
ResponseBody any `orm:"response_body" json:"responseBody"`
|
||||
TokenMapping string `orm:"token_mapping" json:"tokenMapping"`
|
||||
Prompt string `orm:"prompt" json:"prompt"`
|
||||
IsPrivate int `orm:"is_private" json:"isPrivate"`
|
||||
IsChatModel int `orm:"is_chat_model" json:"isChatModel"`
|
||||
ApiKey string `orm:"api_key" json:"apiKey"`
|
||||
Enabled int `orm:"enabled" json:"enabled"`
|
||||
MaxConcurrency int `orm:"max_concurrency" json:"maxConcurrency"`
|
||||
QueueLimit int `orm:"queue_limit" json:"queueLimit"`
|
||||
TimeoutSeconds int `orm:"timeout_seconds" json:"timeoutSeconds"`
|
||||
ExpectedSeconds int `orm:"expected_seconds" json:"expectedSeconds"`
|
||||
RetryTimes int `orm:"retry_times" json:"retryTimes"`
|
||||
RetryQueueMaxSeconds int `orm:"retry_queue_max_seconds" json:"retryQueueMaxSeconds"`
|
||||
AutoCleanSeconds int `orm:"auto_clean_seconds" json:"autoCleanSeconds"`
|
||||
Remark string `orm:"remark" json:"remark"`
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
// AsynchModelStat 按天统计:某天/租户/创建人/模型的请求次数
|
||||
// 注:这里不走通用 SQLBaseDO,采用联合唯一键(day,tenant_id,creator,model_name)做 UPSERT 原子累加。
|
||||
type AsynchModelStat struct {
|
||||
Day *gtime.Time `orm:"day" json:"day"` // 日期(建议仅使用日期部分)
|
||||
TenantId int64 `orm:"tenant_id" json:"tenantId,string"`
|
||||
Creator string `orm:"creator" json:"creator"`
|
||||
ModelName string `orm:"model_name" json:"modelName"`
|
||||
RequestCount int64 `orm:"request_count" json:"requestCount"`
|
||||
CreatedAt *gtime.Time `orm:"created_at" json:"createdAt"`
|
||||
UpdatedAt *gtime.Time `orm:"updated_at" json:"updatedAt"`
|
||||
}
|
||||
|
||||
@@ -9,73 +9,81 @@ type asynchTaskCol struct {
|
||||
beans.SQLBaseCol
|
||||
ModelName string
|
||||
TaskID string
|
||||
State string
|
||||
BizName string
|
||||
CallbackURL string
|
||||
ModelKey string
|
||||
State string
|
||||
OssFile string
|
||||
FileType string
|
||||
FileSize string
|
||||
ErrorMsg string
|
||||
StartedAt string
|
||||
FinishedAt string
|
||||
ExpireAt string
|
||||
DurationSeconds string
|
||||
ExpireAt string
|
||||
RetryCount string
|
||||
EnqueueAt string
|
||||
Phase string
|
||||
TmpFile string
|
||||
InputRef string
|
||||
RequestPayload string
|
||||
TextResult string
|
||||
EpicycleId string
|
||||
ExpendTokens string
|
||||
}
|
||||
|
||||
var AsynchTaskCol = asynchTaskCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
ModelName: "model_name",
|
||||
TaskID: "task_id",
|
||||
State: "state",
|
||||
BizName: "biz_name",
|
||||
CallbackURL: "callback_url",
|
||||
ModelKey: "model_key",
|
||||
OssFile: "oss_file",
|
||||
FileType: "file_type",
|
||||
FileSize: "file_size",
|
||||
ErrorMsg: "error_msg",
|
||||
StartedAt: "started_at",
|
||||
FinishedAt: "finished_at",
|
||||
ExpireAt: "expire_at",
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
ModelName: "model_name",
|
||||
TaskID: "task_id",
|
||||
BizName: "biz_name",
|
||||
CallbackURL: "callback_url",
|
||||
ModelKey: "model_key",
|
||||
State: "state",
|
||||
OssFile: "oss_file",
|
||||
FileType: "file_type",
|
||||
FileSize: "file_size",
|
||||
ErrorMsg: "error_msg",
|
||||
StartedAt: "started_at",
|
||||
FinishedAt: "finished_at",
|
||||
DurationSeconds: "duration_seconds",
|
||||
RetryCount: "retry_count",
|
||||
EnqueueAt: "enqueue_at",
|
||||
Phase: "phase",
|
||||
TmpFile: "tmp_file",
|
||||
InputRef: "input_ref",
|
||||
RequestPayload: "request_payload",
|
||||
ExpireAt: "expire_at",
|
||||
RetryCount: "retry_count",
|
||||
EnqueueAt: "enqueue_at",
|
||||
Phase: "phase",
|
||||
TmpFile: "tmp_file",
|
||||
InputRef: "input_ref",
|
||||
RequestPayload: "request_payload",
|
||||
TextResult: "text_result",
|
||||
EpicycleId: "epicycle_id",
|
||||
ExpendTokens: "expend_tokens",
|
||||
}
|
||||
|
||||
// AsynchTask 异步任务
|
||||
type AsynchTask struct {
|
||||
beans.SQLBaseDO `orm:",inline"`
|
||||
ModelName string `orm:"model_name" json:"modelName"`
|
||||
TaskID string `orm:"task_id" json:"taskId"`
|
||||
State int `orm:"state" json:"state"` // 0排队中/1执行中/2成功/3失败/4已下载
|
||||
BizName string `orm:"biz_name" json:"bizName"`
|
||||
CallbackURL string `orm:"callback_url" json:"callbackUrl"`
|
||||
ModelKey string `orm:"model_key" json:"modelKey"`
|
||||
OssFile string `orm:"oss_file" json:"ossFile"`
|
||||
FileType string `orm:"file_type" json:"fileType"`
|
||||
FileSize int64 `orm:"file_size" json:"fileSize"`
|
||||
ErrorMsg string `orm:"error_msg" json:"errorMsg"`
|
||||
StartedAt *gtime.Time `orm:"started_at" json:"startedAt"`
|
||||
FinishedAt *gtime.Time `orm:"finished_at" json:"finishedAt"`
|
||||
ExpireAt *gtime.Time `orm:"expire_at" json:"expireAt"` // 已下载(state=4)后的过期时间
|
||||
DurationSeconds int64 `orm:"duration_seconds" json:"durationSeconds"`
|
||||
RetryCount int `orm:"retry_count" json:"retryCount"`
|
||||
EnqueueAt *gtime.Time `orm:"enqueue_at" json:"enqueueAt"`
|
||||
Phase int `orm:"phase" json:"phase"` // 0模型阶段/1OSS阶段
|
||||
TmpFile string `orm:"tmp_file" json:"tmpFile"` // 临时结果文件路径
|
||||
// RetryQueueMaxSeconds 为 ListFailedRetryableGlobal 的 join 字段(非任务表字段)
|
||||
RetryQueueMaxSeconds int `orm:"retry_queue_max_seconds" json:"-"`
|
||||
InputRef string `orm:"input_ref" json:"inputRef"`
|
||||
RequestPayload any `orm:"request_payload" json:"requestPayload"`
|
||||
beans.SQLBaseDO `orm:",inline"`
|
||||
ModelName string `orm:"model_name" json:"modelName"`
|
||||
TaskID string `orm:"task_id" json:"taskId"`
|
||||
BizName string `orm:"biz_name" json:"bizName"`
|
||||
CallbackURL string `orm:"callback_url" json:"callbackUrl"`
|
||||
ModelKey string `orm:"model_key" json:"modelKey"`
|
||||
State int `orm:"state" json:"state"` // 0排队中/1执行中/2成功/3失败/4已下载
|
||||
OssFile string `orm:"oss_file" json:"ossFile"`
|
||||
FileType string `orm:"file_type" json:"fileType"`
|
||||
FileSize int64 `orm:"file_size" json:"fileSize"`
|
||||
ErrorMsg string `orm:"error_msg" json:"errorMsg"`
|
||||
StartedAt *gtime.Time `orm:"started_at" json:"startedAt"`
|
||||
FinishedAt *gtime.Time `orm:"finished_at" json:"finishedAt"`
|
||||
DurationSeconds int64 `orm:"duration_seconds" json:"durationSeconds"`
|
||||
ExpireAt *gtime.Time `orm:"expire_at" json:"expireAt"` // 已下载(state=4)后的过期时间
|
||||
RetryCount int `orm:"retry_count" json:"retryCount"`
|
||||
EnqueueAt *gtime.Time `orm:"enqueue_at" json:"enqueueAt"`
|
||||
Phase int `orm:"phase" json:"phase"` // 0模型阶段/1OSS阶段
|
||||
TmpFile string `orm:"tmp_file" json:"tmpFile"` // 临时结果文件路径
|
||||
InputRef string `orm:"input_ref" json:"inputRef"`
|
||||
RequestPayload any `orm:"request_payload" json:"requestPayload"`
|
||||
TextResult string `orm:"text_result" json:"text"`
|
||||
EpicycleId int64 `orm:"epicycle_id" json:"epicycleId"` // 轮次ID(用于标识同一轮次的任务)
|
||||
ExpendTokens int64 `orm:"expend_tokens" json:"expendTokens"` // 消耗 token 数
|
||||
RetryQueueMaxSeconds int `orm:"retry_queue_max_seconds" json:"-"`
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"gitea.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
type asynchOpLogCol struct {
|
||||
type LogsModelPpCol struct {
|
||||
beans.SQLBaseCol
|
||||
IP string
|
||||
UserAgent string
|
||||
@@ -21,7 +21,7 @@ type asynchOpLogCol struct {
|
||||
ResponsePayload string
|
||||
}
|
||||
|
||||
var AsynchOpLogCol = asynchOpLogCol{
|
||||
var LogsModelOpCol = LogsModelPpCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
IP: "ip",
|
||||
UserAgent: "user_agent",
|
||||
@@ -38,8 +38,8 @@ var AsynchOpLogCol = asynchOpLogCol{
|
||||
ResponsePayload: "response_payload",
|
||||
}
|
||||
|
||||
// AsynchOpLog 操作日志(创建任务等)
|
||||
type AsynchOpLog struct {
|
||||
// LogsModelOp 操作日志(创建任务等)
|
||||
type LogsModelOp struct {
|
||||
beans.SQLBaseDO `orm:",inline"`
|
||||
IP string `orm:"ip" json:"ip"`
|
||||
UserAgent string `orm:"user_agent" json:"userAgent"`
|
||||
38
model/entity/logs_model_stat.go
Normal file
38
model/entity/logs_model_stat.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// LogsModelStatCol 字段常量
|
||||
type LogsModelStatCol struct {
|
||||
Day string
|
||||
TenantId string
|
||||
Creator string
|
||||
ModelName string
|
||||
RequestCount string
|
||||
CreatedAt string
|
||||
UpdatedAt string
|
||||
}
|
||||
|
||||
var LogsModelStatCols = LogsModelStatCol{
|
||||
Day: "day",
|
||||
TenantId: "tenant_id",
|
||||
Creator: "creator",
|
||||
ModelName: "model_name",
|
||||
RequestCount: "request_count",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// LogsModelStat 按天统计:某天/租户/创建人/模型的请求次数
|
||||
// 注:这里不走通用 SQLBaseDO,采用联合唯一键(day,tenant_id,creator,model_name)做 UPSERT 原子累加。
|
||||
type LogsModelStat struct {
|
||||
Day *gtime.Time `orm:"day" json:"day"` // 日期(建议仅使用日期部分)
|
||||
TenantId int64 `orm:"tenant_id" json:"tenantId"` // 租户ID
|
||||
Creator string `orm:"creator" json:"creator"` // 创建人/操作人
|
||||
ModelName string `orm:"model_name" json:"modelName"` // 模型名称
|
||||
RequestCount int64 `orm:"request_count" json:"requestCount"` // 请求次数
|
||||
CreatedAt *gtime.Time `orm:"created_at" json:"createdAt"` // 创建时间
|
||||
UpdatedAt *gtime.Time `orm:"updated_at" json:"updatedAt"` // 更新时间
|
||||
}
|
||||
@@ -2,87 +2,86 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
"encoding/json"
|
||||
|
||||
"model-asynch/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/http"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// triggerSuccessCallback 任务成功后的回调钩子:
|
||||
// - 使用 GET 请求
|
||||
// - 回调地址为 callbackUrl + "/" + bizName
|
||||
// - query 参数:task_id/state/oss_file/file_type
|
||||
// 注意:回调失败不影响任务主流程,只记录日志。
|
||||
func triggerSuccessCallback(ctx context.Context, t *entity.AsynchTask) {
|
||||
if t == nil {
|
||||
return
|
||||
// triggerCallback 任务成功后的回调:
|
||||
// - JSON body 参数:task_id/state/oss_file/file_type/text(可选)
|
||||
func triggerCallback(ctx context.Context, t *entity.AsynchTask) {
|
||||
callbackURL := t.BizName + t.CallbackURL
|
||||
headers := forwardHeaders(ctx)
|
||||
var req struct{}
|
||||
payload := map[string]interface{}{
|
||||
"task_id": t.TaskID,
|
||||
"state": t.State,
|
||||
"oss_file": t.OssFile,
|
||||
"file_type": t.FileType,
|
||||
"text": t.TextResult,
|
||||
"error_msg": t.ErrorMsg,
|
||||
}
|
||||
callbackURL := strings.TrimSpace(t.CallbackURL)
|
||||
bizName := strings.TrimSpace(t.BizName)
|
||||
if callbackURL == "" || bizName == "" {
|
||||
return
|
||||
}
|
||||
|
||||
u, err := url.Parse(callbackURL)
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[callback] invalid callbackUrl=%s err=%v", callbackURL, err)
|
||||
return
|
||||
}
|
||||
// 必须是可发起 HTTP 请求的绝对地址
|
||||
if u.Scheme == "" || u.Host == "" {
|
||||
g.Log().Warningf(ctx, "[callback] callbackUrl must be absolute http(s) url, got=%s", callbackURL)
|
||||
g.Log().Warningf(ctx, "[回调] JSON序列化失败 taskId=%s 错误=%v", t.TaskID, err)
|
||||
return
|
||||
}
|
||||
g.Log().Infof(ctx, "[回调] 开始发送 taskId=%s 回调地址=%s 请求头数量=%d 消息体大小=%d字节",
|
||||
t.TaskID, callbackURL, len(headers), len(jsonData))
|
||||
|
||||
// path 末尾拼接 bizName
|
||||
bizSeg := url.PathEscape(bizName)
|
||||
if strings.HasSuffix(u.Path, "/") || u.Path == "" {
|
||||
u.Path = strings.TrimRight(u.Path, "/") + "/" + bizSeg
|
||||
} else {
|
||||
u.Path = u.Path + "/" + bizSeg
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
q.Set("task_id", t.TaskID)
|
||||
q.Set("state", fmt.Sprintf("%d", t.State))
|
||||
q.Set("oss_file", t.OssFile)
|
||||
q.Set("file_type", t.FileType)
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
||||
err = http.Post(ctx, callbackURL, headers, &req, jsonData)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[callback] build request failed url=%s err=%v", u.String(), err)
|
||||
g.Log().Warningf(ctx, "[回调] 发送失败 taskId=%s 回调地址=%s 错误=%v", t.TaskID, callbackURL, err)
|
||||
return
|
||||
}
|
||||
// 透传必要头部(如 Authorization / X-User-Info)
|
||||
for k, v := range forwardHeaders(ctx) {
|
||||
if v != "" {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[callback] request failed url=%s err=%v", u.String(), err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
msg := string(b)
|
||||
if len(msg) > 2000 {
|
||||
msg = msg[:2000]
|
||||
}
|
||||
g.Log().Warningf(ctx, "[callback] non-2xx url=%s code=%d body=%s", u.String(), resp.StatusCode, msg)
|
||||
return
|
||||
}
|
||||
g.Log().Infof(ctx, "[callback] success url=%s code=%d", u.String(), resp.StatusCode)
|
||||
g.Log().Infof(ctx, "[回调] 发送成功 taskId=%s 回调地址=%s 消息体大小=%d字节", t.TaskID, callbackURL, len(jsonData))
|
||||
}
|
||||
|
||||
// triggerPromptsCallback 任务成功后的提示词回调
|
||||
// - JSON body 参数:epicycleId(轮次id)/textResult(模型回答消息)
|
||||
func triggerPromptsCallback(ctx context.Context, t *entity.AsynchTask, epicycleId int64) {
|
||||
callbackURL := "prompts-core/session/sessionCallback"
|
||||
headers := forwardHeaders(ctx)
|
||||
var req struct{}
|
||||
payload := map[string]interface{}{
|
||||
"epicycleId": epicycleId,
|
||||
"text": t.TextResult,
|
||||
}
|
||||
jsonData, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[提示词回调] JSON序列化失败 epicycleId=%d 错误=%v", epicycleId, err)
|
||||
return
|
||||
}
|
||||
g.Log().Infof(ctx, "[提示词回调] 开始发送 epicycleId=%d 回调地址=%s 请求头数量=%d 消息体大小=%d字节",
|
||||
t.EpicycleId, callbackURL, len(headers), len(jsonData))
|
||||
|
||||
err = http.Post(ctx, callbackURL, headers, &req, jsonData)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[提示词回调] 发送失败 epicycleId=%d 回调地址=%s 错误=%v", t.EpicycleId, callbackURL, err)
|
||||
return
|
||||
}
|
||||
g.Log().Infof(ctx, "[提示词回调] 发送成功 epicycleId=%d 回调地址=%s 消息体大小=%d字节", t.EpicycleId, callbackURL, len(jsonData))
|
||||
}
|
||||
|
||||
// IsSuperAdmin 调用admin-go服务检查是否是超级管理员
|
||||
func IsSuperAdmin(ctx context.Context) (res bool, err error) {
|
||||
headers := forwardHeaders(ctx)
|
||||
var r = make(map[string]bool)
|
||||
if err = http.Get(ctx, "admin-go/api/v1/system/user/checkIsSuperAdmin", headers, &r); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return r["isSuperAdmin"], err
|
||||
}
|
||||
|
||||
// IsAdmin 调用admin-go服务检查是否是管理员
|
||||
func IsAdmin(ctx context.Context) (res bool, err error) {
|
||||
headers := forwardHeaders(ctx)
|
||||
var r = make(map[string]bool)
|
||||
if err = http.Get(ctx, "admin-go/api/v1/system/user/checkIsSuperAdmin", headers, &r); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return r["isSuperAdmin"], err
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ func DetectFileType(data []byte) (contentType string, ext string) {
|
||||
return "application/octet-stream", ""
|
||||
}
|
||||
ct := http.DetectContentType(data)
|
||||
// http.DetectContentType 可能带 charset 等参数:text/plain; charset=utf-8
|
||||
if idx := strings.Index(ct, ";"); idx > 0 {
|
||||
ct = strings.TrimSpace(ct[:idx])
|
||||
}
|
||||
switch ct {
|
||||
case "audio/mpeg":
|
||||
return ct, ".mp3"
|
||||
@@ -24,12 +28,20 @@ func DetectFileType(data []byte) (contentType string, ext string) {
|
||||
return ct, ".jpg"
|
||||
case "application/pdf":
|
||||
return ct, ".pdf"
|
||||
case "text/plain":
|
||||
return ct, ".txt"
|
||||
case "application/json":
|
||||
return ct, ".json"
|
||||
default:
|
||||
// 兜底:尝试从 ct 截取 subtype 作为后缀(例如 application/json)
|
||||
if parts := strings.Split(ct, "/"); len(parts) == 2 {
|
||||
return ct, "." + parts[1]
|
||||
sub := parts[1]
|
||||
// 避免出现 "plain; charset=utf-8" 之类的后缀
|
||||
if idx := strings.Index(sub, ";"); idx > 0 {
|
||||
sub = strings.TrimSpace(sub[:idx])
|
||||
}
|
||||
return ct, "." + sub
|
||||
}
|
||||
return ct, ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,4 +51,3 @@ func forwardHeaders(ctx context.Context) map[string]string {
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ import (
|
||||
"time"
|
||||
|
||||
"model-asynch/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
// parseHeadMsgHeaders 支持多个 header 绑定,逗号分隔:
|
||||
@@ -100,11 +105,14 @@ func InvokeModel(ctx context.Context, m *entity.AsynchModel, payload any, modelK
|
||||
if m == nil || m.BaseURL == "" {
|
||||
return nil, fmt.Errorf("模型配置不完整")
|
||||
}
|
||||
url := strings.TrimRight(m.BaseURL, "/") + "/" + strings.TrimLeft(m.Route, "/")
|
||||
if strings.TrimSpace(m.Route) == "" {
|
||||
url = strings.TrimRight(m.BaseURL, "/")
|
||||
|
||||
// ============ 新增:请求参数映射 ============
|
||||
mappedPayload, err := mapRequestPayload(m.RequestMapping, payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求参数映射失败: %w", err)
|
||||
}
|
||||
|
||||
url := strings.TrimRight(m.BaseURL, "/")
|
||||
timeout := time.Duration(m.TimeoutSeconds) * time.Second
|
||||
if timeout <= 0 {
|
||||
timeout = 60 * time.Second
|
||||
@@ -118,11 +126,10 @@ func InvokeModel(ctx context.Context, m *entity.AsynchModel, payload any, modelK
|
||||
|
||||
var (
|
||||
req *http.Request
|
||||
err error
|
||||
)
|
||||
switch method {
|
||||
case http.MethodGet:
|
||||
q, err := payloadToQuery(payload)
|
||||
q, err := payloadToQuery(mappedPayload) // 使用映射后的payload
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -135,7 +142,7 @@ func InvokeModel(ctx context.Context, m *entity.AsynchModel, payload any, modelK
|
||||
}
|
||||
req, err = http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
default:
|
||||
bodyBytes, err := json.Marshal(payload)
|
||||
bodyBytes, err := json.Marshal(mappedPayload) // 使用映射后的payload
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -145,20 +152,16 @@ func InvokeModel(ctx context.Context, m *entity.AsynchModel, payload any, modelK
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 先注入模型配置 head_msg(静态头部)
|
||||
// 先注入模型配置 head_msg(静态头部,适合公共模型固定 API Key)
|
||||
for hk, hv := range parseHeadMsgHeaders(m.HeadMsg) {
|
||||
req.Header.Set(hk, hv)
|
||||
}
|
||||
// 透传必要头部(如 Authorization / X-User-Info)
|
||||
for k, v := range forwardHeaders(ctx) {
|
||||
if v != "" {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
// 最后注入动态 modelKey(覆盖/补充静态 head_msg)
|
||||
|
||||
// 最后注入动态 modelKey(允许覆盖/补充静态 head_msg),适合按请求动态传密钥。
|
||||
for hk, hv := range parseHeadMsgHeaders(modelKey) {
|
||||
req.Header.Set(hk, hv)
|
||||
}
|
||||
|
||||
if method != http.MethodGet {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
@@ -174,12 +177,241 @@ func InvokeModel(ctx context.Context, m *entity.AsynchModel, payload any, modelK
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
// 尽量把错误体带回去,方便排查
|
||||
msg := string(b)
|
||||
if len(msg) > 2000 {
|
||||
msg = msg[:2000]
|
||||
}
|
||||
return nil, fmt.Errorf("模型服务返回非2xx: %d, body=%s", resp.StatusCode, msg)
|
||||
}
|
||||
return b, nil
|
||||
|
||||
// ============ 新增:响应参数映射 ============
|
||||
mappedResponse, err := mapResponsePayload(m.ResponseMapping, b)
|
||||
if err != nil {
|
||||
// 响应映射失败不阻塞,返回原始数据
|
||||
g.Log().Warningf(ctx, "响应参数映射失败: %v,返回原始数据", err)
|
||||
return b, nil
|
||||
}
|
||||
// =========================================
|
||||
|
||||
return mappedResponse, nil
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 映射相关函数
|
||||
// ============================================
|
||||
|
||||
// mapRequestPayload 将标准请求映射为模型特定格式
|
||||
func mapRequestPayload(mappingAny any, payload any) (any, error) {
|
||||
// 1. 解析请求映射配置(值是any类型,支持bool、number等)
|
||||
mapping, err := parseRequestMapping(mappingAny)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果没有映射配置,直接返回原始payload
|
||||
if len(mapping) == 0 {
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
// 2. 将payload转为map
|
||||
var payloadMap map[string]any
|
||||
switch v := payload.(type) {
|
||||
case map[string]any:
|
||||
payloadMap = v
|
||||
case []map[string]any:
|
||||
// 如果传进来的是纯messages数组,包装成标准格式
|
||||
payloadMap = map[string]any{
|
||||
"messages": v,
|
||||
}
|
||||
default:
|
||||
// 通过JSON转换
|
||||
jsonBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化payload失败: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(jsonBytes, &payloadMap); err != nil {
|
||||
return nil, fmt.Errorf("反序列化payload失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 用数据库固定参数覆盖/补充
|
||||
for key, value := range mapping {
|
||||
if existingValue, exists := payloadMap[key]; !exists || isEmptyValue(existingValue) {
|
||||
payloadMap[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return payloadMap, nil
|
||||
}
|
||||
|
||||
// mapResponsePayload 将模型响应映射为标准格式
|
||||
func mapResponsePayload(mappingAny any, responseBytes []byte) ([]byte, error) {
|
||||
mapping, err := parseResponseMapping(mappingAny)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(mapping) == 0 {
|
||||
return responseBytes, nil
|
||||
}
|
||||
|
||||
responseStr := string(responseBytes)
|
||||
resultStr := `{}`
|
||||
|
||||
for standardField, modelPath := range mapping {
|
||||
value := gjson.Get(responseStr, modelPath)
|
||||
if !value.Exists() {
|
||||
continue
|
||||
}
|
||||
|
||||
resultStr, err = sjson.SetRaw(resultStr, standardField, value.Raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("提取字段 %s <- %s 失败: %w", standardField, modelPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return []byte(resultStr), nil
|
||||
}
|
||||
|
||||
func parseRequestMapping(mappingAny any) (map[string]any, error) {
|
||||
if mappingAny == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result := make(map[string]any)
|
||||
|
||||
switch v := mappingAny.(type) {
|
||||
case *gvar.Var:
|
||||
if v == nil || v.IsNil() || v.IsEmpty() {
|
||||
return nil, nil
|
||||
}
|
||||
// 尝试转成 map
|
||||
if m := v.Map(); m != nil {
|
||||
for k, val := range m {
|
||||
result[k] = val
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
// 尝试转成 string
|
||||
if s := v.String(); s != "" && s != "{}" && s != "null" {
|
||||
if err := json.Unmarshal([]byte(s), &result); err != nil {
|
||||
return nil, fmt.Errorf("解析请求映射字符串失败: %w", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return nil, nil
|
||||
// =======================================================
|
||||
|
||||
case map[string]interface{}:
|
||||
result = v
|
||||
|
||||
case string:
|
||||
if v == "" || v == "{}" || v == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
if err := json.Unmarshal([]byte(v), &result); err != nil {
|
||||
return nil, fmt.Errorf("解析请求映射字符串失败: %w", err)
|
||||
}
|
||||
|
||||
case []byte:
|
||||
if len(v) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if err := json.Unmarshal(v, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析请求映射字节失败: %w", err)
|
||||
}
|
||||
|
||||
default:
|
||||
jsonBytes, err := json.Marshal(mappingAny)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化映射配置失败: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(jsonBytes, &result); err != nil {
|
||||
return nil, fmt.Errorf("解析映射配置失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// parseResponseMapping 解析响应映射配置
|
||||
// 返回值类型为 map[string]string,值都是JSON路径字符串
|
||||
func parseResponseMapping(mappingAny any) (map[string]string, error) {
|
||||
if mappingAny == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mapping := make(map[string]string)
|
||||
|
||||
switch v := mappingAny.(type) {
|
||||
case *gvar.Var:
|
||||
if v == nil || v.IsNil() || v.IsEmpty() {
|
||||
return nil, nil
|
||||
}
|
||||
if m := v.Map(); m != nil {
|
||||
for k, val := range m {
|
||||
if strVal, ok := val.(string); ok {
|
||||
mapping[k] = strVal
|
||||
}
|
||||
}
|
||||
return mapping, nil
|
||||
}
|
||||
if s := v.String(); s != "" && s != "{}" && s != "null" {
|
||||
if err := json.Unmarshal([]byte(s), &mapping); err != nil {
|
||||
return nil, fmt.Errorf("解析响应映射字符串失败: %w", err)
|
||||
}
|
||||
return mapping, nil
|
||||
}
|
||||
return nil, nil
|
||||
case string:
|
||||
if v == "" || v == "{}" || v == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
if err := json.Unmarshal([]byte(v), &mapping); err != nil {
|
||||
return nil, fmt.Errorf("解析响应映射字符串失败: %w", err)
|
||||
}
|
||||
|
||||
case map[string]interface{}:
|
||||
// 数据库JSONB直接返回的map
|
||||
for k, val := range v {
|
||||
if strVal, ok := val.(string); ok {
|
||||
mapping[k] = strVal
|
||||
}
|
||||
}
|
||||
|
||||
case []byte:
|
||||
if len(v) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if err := json.Unmarshal(v, &mapping); err != nil {
|
||||
return nil, fmt.Errorf("解析响应映射字节失败: %w", err)
|
||||
}
|
||||
|
||||
default:
|
||||
jsonBytes, err := json.Marshal(mappingAny)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化响应映射配置失败: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(jsonBytes, &mapping); err != nil {
|
||||
return nil, fmt.Errorf("解析响应映射配置失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return mapping, nil
|
||||
}
|
||||
|
||||
// isEmptyValue 判断值是否为空
|
||||
func isEmptyValue(v any) bool {
|
||||
if v == nil {
|
||||
return true
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
return val == ""
|
||||
case []any:
|
||||
return len(val) == 0
|
||||
case map[string]any:
|
||||
return len(val) == 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,15 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
"model-asynch/dao"
|
||||
"model-asynch/model/dto"
|
||||
"model-asynch/model/entity"
|
||||
|
||||
"gitea.com/red-future/common/utils"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
var Model = &modelService{}
|
||||
@@ -15,40 +20,28 @@ type modelService struct{}
|
||||
|
||||
func (s *modelService) Create(ctx context.Context, req *dto.CreateModelReq) (res *dto.CreateModelRes, err error) {
|
||||
m := &entity.AsynchModel{
|
||||
ModelName: req.ModelName,
|
||||
ModelsType: normalizeModelsType(req.ModelsType),
|
||||
BaseURL: req.BaseURL,
|
||||
Route: req.Route,
|
||||
HttpMethod: req.HttpMethod,
|
||||
HeadMsg: req.HeadMsg,
|
||||
Form: req.Form,
|
||||
Enabled: req.Enabled,
|
||||
MaxConcurrency: req.MaxConcurrency,
|
||||
QueueLimit: req.QueueLimit,
|
||||
TimeoutSeconds: req.TimeoutSeconds,
|
||||
ExpectedSeconds: req.ExpectedSeconds,
|
||||
RetryTimes: req.RetryTimes,
|
||||
RetryQueueMaxSecs: req.RetryQueueMaxSeconds,
|
||||
AutoCleanSeconds: req.AutoCleanSeconds,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
if m.HttpMethod == "" {
|
||||
m.HttpMethod = "POST"
|
||||
}
|
||||
if m.Enabled == 0 {
|
||||
m.Enabled = 1
|
||||
}
|
||||
if m.MaxConcurrency <= 0 {
|
||||
m.MaxConcurrency = 10
|
||||
}
|
||||
if m.QueueLimit <= 0 {
|
||||
m.QueueLimit = 1000
|
||||
}
|
||||
if m.TimeoutSeconds <= 0 {
|
||||
m.TimeoutSeconds = 60
|
||||
}
|
||||
if m.AutoCleanSeconds <= 0 {
|
||||
m.AutoCleanSeconds = 86400
|
||||
ModelName: req.ModelName,
|
||||
ModelsType: req.ModelsType,
|
||||
BaseURL: req.BaseURL,
|
||||
HttpMethod: req.HttpMethod,
|
||||
HeadMsg: req.HeadMsg,
|
||||
IsPrivate: req.IsPrivate,
|
||||
Enabled: req.Enabled,
|
||||
IsChatModel: req.IsChatModel,
|
||||
ApiKey: req.ApiKey,
|
||||
Form: req.Form,
|
||||
RequestMapping: req.RequestMapping,
|
||||
ResponseMapping: req.ResponseMapping,
|
||||
ResponseBody: req.ResponseBody,
|
||||
TokenMapping: req.TokenMapping,
|
||||
MaxConcurrency: req.MaxConcurrency,
|
||||
QueueLimit: req.QueueLimit,
|
||||
TimeoutSeconds: req.TimeoutSeconds,
|
||||
ExpectedSeconds: req.ExpectedSeconds,
|
||||
RetryTimes: req.RetryTimes,
|
||||
RetryQueueMaxSeconds: req.RetryQueueMaxSeconds,
|
||||
AutoCleanSeconds: req.AutoCleanSeconds,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
id, err := dao.Model.Insert(ctx, m)
|
||||
if err != nil {
|
||||
@@ -58,68 +51,223 @@ func (s *modelService) Create(ctx context.Context, req *dto.CreateModelReq) (res
|
||||
}
|
||||
|
||||
func (s *modelService) Update(ctx context.Context, req *dto.UpdateModelReq) error {
|
||||
data := map[string]any{}
|
||||
if req.BaseURL != "" {
|
||||
data[entity.AsynchModelCol.BaseURL] = req.BaseURL
|
||||
//根据当前 isChatModel 来判断是否更新模型
|
||||
if req.IsChatModel == 1 {
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//判断当前用户是否有会话模型
|
||||
model, err := dao.Model.GetByIsChatModel(ctx, user.UserName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if model != nil {
|
||||
return errors.New("用户已存在会话模型,不能创建新的会话模型")
|
||||
}
|
||||
_, err = dao.Model.Update(ctx, req)
|
||||
return err
|
||||
}
|
||||
if req.Route != "" {
|
||||
data[entity.AsynchModelCol.Route] = req.Route
|
||||
}
|
||||
if req.HttpMethod != nil && *req.HttpMethod != "" {
|
||||
data[entity.AsynchModelCol.HttpMethod] = *req.HttpMethod
|
||||
}
|
||||
if req.HeadMsg != nil {
|
||||
data[entity.AsynchModelCol.HeadMsg] = *req.HeadMsg
|
||||
}
|
||||
if req.Form != nil {
|
||||
data[entity.AsynchModelCol.FormJSON] = req.Form
|
||||
}
|
||||
if req.ModelsType != nil {
|
||||
data[entity.AsynchModelCol.ModelsType] = normalizeModelsType(*req.ModelsType)
|
||||
}
|
||||
if req.Enabled != nil {
|
||||
data[entity.AsynchModelCol.Enabled] = *req.Enabled
|
||||
}
|
||||
if req.MaxConcurrency != nil {
|
||||
data[entity.AsynchModelCol.MaxConcurrency] = *req.MaxConcurrency
|
||||
}
|
||||
if req.QueueLimit != nil {
|
||||
data[entity.AsynchModelCol.QueueLimit] = *req.QueueLimit
|
||||
}
|
||||
if req.TimeoutSeconds != nil {
|
||||
data[entity.AsynchModelCol.TimeoutSeconds] = *req.TimeoutSeconds
|
||||
}
|
||||
if req.ExpectedSeconds != nil {
|
||||
data[entity.AsynchModelCol.ExpectedSeconds] = *req.ExpectedSeconds
|
||||
}
|
||||
if req.RetryTimes != nil {
|
||||
data[entity.AsynchModelCol.RetryTimes] = *req.RetryTimes
|
||||
}
|
||||
if req.RetryQueueMaxSeconds != nil {
|
||||
data[entity.AsynchModelCol.RetryQueueMaxSecs] = *req.RetryQueueMaxSeconds
|
||||
}
|
||||
if req.AutoCleanSeconds != nil {
|
||||
data[entity.AsynchModelCol.AutoCleanSeconds] = *req.AutoCleanSeconds
|
||||
}
|
||||
if req.Remark != nil {
|
||||
data[entity.AsynchModelCol.Remark] = *req.Remark
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return errors.New("无可更新字段")
|
||||
}
|
||||
_, err := dao.Model.UpdateByID(ctx, req.ID, data)
|
||||
_, err := dao.Model.Update(ctx, req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *modelService) Delete(ctx context.Context, id int64) error {
|
||||
func (s *modelService) Delete(ctx context.Context, id string) error {
|
||||
_, err := dao.Model.DeleteByID(ctx, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *modelService) Get(ctx context.Context, id int64) (*entity.AsynchModel, error) {
|
||||
return dao.Model.GetByID(ctx, id)
|
||||
model, err := dao.Model.Get(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
model.Form = ParseJSONField(model.Form)
|
||||
model.RequestMapping = ParseJSONField(model.RequestMapping)
|
||||
model.ResponseMapping = ParseJSONField(model.ResponseMapping)
|
||||
model.ResponseBody = ParseJSONField(model.ResponseBody)
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func (s *modelService) List(ctx context.Context, pageNum, pageSize int, modelNameLike string) (list []*entity.AsynchModel, total int64, err error) {
|
||||
return dao.Model.List(ctx, pageNum, pageSize, modelNameLike)
|
||||
func (s *modelService) List(ctx context.Context, pageNum, pageSize int, modelNameLike string, modelType int) (list []*entity.AsynchModel, total int64, err error) {
|
||||
isSuperAdmin, err := IsSuperAdmin(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var models []*entity.AsynchModel
|
||||
var count int64
|
||||
|
||||
if isSuperAdmin {
|
||||
models, count, err = dao.Model.List(ctx, pageNum, pageSize, modelNameLike, modelType)
|
||||
} else {
|
||||
models, count, err = s.getModelsWithDedup(ctx, user.UserName, pageNum, pageSize, modelNameLike, modelType)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 处理列表中每条记录的 JSONB 字段
|
||||
for _, m := range models {
|
||||
m.Form = ParseJSONField(m.Form)
|
||||
m.RequestMapping = ParseJSONField(m.RequestMapping)
|
||||
m.ResponseMapping = ParseJSONField(m.ResponseMapping)
|
||||
m.ResponseBody = ParseJSONField(m.ResponseBody)
|
||||
}
|
||||
return models, count, nil
|
||||
}
|
||||
|
||||
// getModelsWithDedup 获取普通用户的模型列表并去重
|
||||
func (s *modelService) getModelsWithDedup(ctx context.Context, creator string, pageNum, pageSize int, modelNameLike string, modelType int) (list []*entity.AsynchModel, total int64, err error) {
|
||||
// 1. 查全量数据(不分页,便于去重)
|
||||
allModels, err := dao.Model.GetByCreatorAndPlatform(ctx, creator, modelNameLike, modelType)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 2. 按 modelName 去重,保留当前用户的
|
||||
modelMap := make(map[string]*entity.AsynchModel)
|
||||
for _, m := range allModels {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
name := m.ModelName
|
||||
|
||||
_, ok := modelMap[name]
|
||||
if !ok {
|
||||
// 没有冲突,直接放进去
|
||||
modelMap[name] = m
|
||||
} else {
|
||||
// 有冲突,保留当前用户创建的
|
||||
if m.Creator == creator {
|
||||
modelMap[name] = m
|
||||
}
|
||||
// 如果现有的就是当前用户的,不做任何替换
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 转回切片并排序
|
||||
deduped := make([]*entity.AsynchModel, 0, len(modelMap))
|
||||
for _, m := range modelMap {
|
||||
deduped = append(deduped, m)
|
||||
}
|
||||
sort.Slice(deduped, func(i, j int) bool {
|
||||
return deduped[i].CreatedAt.After(deduped[j].CreatedAt)
|
||||
})
|
||||
|
||||
// 4. 手动分页
|
||||
total = int64(len(deduped))
|
||||
if pageNum > 0 && pageSize > 0 {
|
||||
start := (pageNum - 1) * pageSize
|
||||
if start >= len(deduped) {
|
||||
return []*entity.AsynchModel{}, total, nil
|
||||
}
|
||||
end := start + pageSize
|
||||
if end > len(deduped) {
|
||||
end = len(deduped)
|
||||
}
|
||||
deduped = deduped[start:end]
|
||||
}
|
||||
return deduped, total, nil
|
||||
}
|
||||
|
||||
// GetModelTypesFromConfig 从配置文件读取模型类型
|
||||
func GetModelTypesFromConfig(ctx context.Context) map[int]string {
|
||||
typeMap := make(map[int]string)
|
||||
|
||||
// 读取配置
|
||||
configMap := g.Cfg().MustGet(ctx, "modelType.types").Map()
|
||||
for k, v := range configMap {
|
||||
typeID := gconv.Int(k)
|
||||
typeName := gconv.String(v)
|
||||
if typeID > 0 && typeName != "" {
|
||||
typeMap[typeID] = typeName
|
||||
}
|
||||
}
|
||||
// 如果配置为空,使用默认值
|
||||
if len(typeMap) == 0 {
|
||||
typeMap = map[int]string{
|
||||
1: "推理模型",
|
||||
2: "图片模型",
|
||||
3: "音频模型",
|
||||
4: "向量化模型",
|
||||
5: "全模态模型",
|
||||
}
|
||||
}
|
||||
return typeMap
|
||||
}
|
||||
|
||||
func (s *modelService) UpdateChatModel(ctx context.Context, req *dto.UpdateChatModelReq) error {
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 校验新会话模型是否存在
|
||||
newModel, err := dao.Model.Get(ctx, req.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if newModel == nil {
|
||||
return errors.New("新会话模型不存在")
|
||||
}
|
||||
|
||||
// 获取当前用户会话模型
|
||||
currentModel, err := dao.Model.GetByIsChatModel(ctx, user.UserName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if currentModel.ModelsType != 1 {
|
||||
return errors.New("当前模型为非推理模型,不能设置为会话模型")
|
||||
}
|
||||
|
||||
// 如果点击的就是当前会话模型(已经是1),取消它(设为0)
|
||||
if currentModel != nil && currentModel.Id == req.Id {
|
||||
_, err = dao.Model.UpdateByID(ctx, &dto.UpdateModelReq{
|
||||
ID: req.Id,
|
||||
IsChatModel: 0,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果之前有会话模型,取消它(设为0)
|
||||
if currentModel != nil {
|
||||
_, err = dao.Model.UpdateByID(ctx, &dto.UpdateModelReq{
|
||||
ID: currentModel.Id,
|
||||
IsChatModel: 0,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 设置当前为会话模型(设为1)
|
||||
_, err = dao.Model.UpdateByID(ctx, &dto.UpdateModelReq{
|
||||
ID: req.Id,
|
||||
IsChatModel: 1,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *modelService) GetIsChatModel(ctx context.Context) (*entity.AsynchModel, error) {
|
||||
user, err := utils.GetUserInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
model, err := dao.Model.GetByIsChatModel(ctx, user.UserName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if model == nil {
|
||||
return nil, nil
|
||||
}
|
||||
model.Form = ParseJSONField(model.Form)
|
||||
model.RequestMapping = ParseJSONField(model.RequestMapping)
|
||||
model.ResponseMapping = ParseJSONField(model.ResponseMapping)
|
||||
model.ResponseBody = ParseJSONField(model.ResponseBody)
|
||||
return model, nil
|
||||
}
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"model-asynch/dao"
|
||||
"model-asynch/model/dto"
|
||||
"model-asynch/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
)
|
||||
|
||||
type modelTypeService struct{}
|
||||
|
||||
var ModelType = &modelTypeService{}
|
||||
|
||||
func normalizeFormValue(v any) any {
|
||||
// 目标:对外永远返回 JSON 数组/对象,而不是字符串。
|
||||
if v == nil {
|
||||
return []any{}
|
||||
}
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
s := strings.TrimSpace(t)
|
||||
if s == "" {
|
||||
return []any{}
|
||||
}
|
||||
return normalizeFormValueFromJSONString(s)
|
||||
case []byte:
|
||||
if len(t) == 0 {
|
||||
return []any{}
|
||||
}
|
||||
return normalizeFormValueFromJSONBytes(t)
|
||||
case *gvar.Var:
|
||||
// goframe 常见的 DB 返回类型
|
||||
if t == nil {
|
||||
return []any{}
|
||||
}
|
||||
b := t.Bytes()
|
||||
if len(b) > 0 {
|
||||
return normalizeFormValueFromJSONBytes(b)
|
||||
}
|
||||
s := strings.TrimSpace(t.String())
|
||||
if s == "" {
|
||||
return []any{}
|
||||
}
|
||||
return normalizeFormValueFromJSONString(s)
|
||||
default:
|
||||
// 尝试兼容其他“像 JSON 的值类型”(例如实现了 Bytes/String 的包装类型)
|
||||
if vb, ok := v.(interface{ Bytes() []byte }); ok {
|
||||
if b := vb.Bytes(); len(b) > 0 {
|
||||
return normalizeFormValueFromJSONBytes(b)
|
||||
}
|
||||
}
|
||||
if vs, ok := v.(interface{ String() string }); ok {
|
||||
if s := strings.TrimSpace(vs.String()); s != "" {
|
||||
return normalizeFormValueFromJSONString(s)
|
||||
}
|
||||
}
|
||||
// 已经是 []any / map[string]any 等结构
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容“JSONB 里存了 JSON 字符串”的历史数据:
|
||||
// 例如 form_json = '"[]"' 或 '"[{...}]"'(外层是字符串,内层才是数组/对象)
|
||||
func normalizeFormValueFromJSONString(s string) any {
|
||||
var out any
|
||||
if err := json.Unmarshal([]byte(s), &out); err != nil || out == nil {
|
||||
return []any{}
|
||||
}
|
||||
// 如果解出来还是 string,且看起来是 JSON,再解一层
|
||||
if inner, ok := out.(string); ok {
|
||||
inner = strings.TrimSpace(inner)
|
||||
if inner == "" {
|
||||
return []any{}
|
||||
}
|
||||
if strings.HasPrefix(inner, "[") || strings.HasPrefix(inner, "{") {
|
||||
var out2 any
|
||||
if err := json.Unmarshal([]byte(inner), &out2); err == nil && out2 != nil {
|
||||
return out2
|
||||
}
|
||||
}
|
||||
return []any{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeFormValueFromJSONBytes(b []byte) any {
|
||||
var out any
|
||||
if err := json.Unmarshal(b, &out); err != nil || out == nil {
|
||||
return []any{}
|
||||
}
|
||||
// bytes 解出来也可能是 string(同上)
|
||||
if inner, ok := out.(string); ok {
|
||||
return normalizeFormValueFromJSONString(inner)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *modelTypeService) Create(ctx context.Context, req *dto.CreateModelTypeReq) (res *dto.CreateModelTypeRes, err error) {
|
||||
t := &entity.AsynchModelType{
|
||||
TypeID: req.TypeID,
|
||||
TypeName: req.TypeName,
|
||||
Remark: req.Remark,
|
||||
}
|
||||
id, err := dao.ModelType.Insert(ctx, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.CreateModelTypeRes{ID: id}, nil
|
||||
}
|
||||
|
||||
func (s *modelTypeService) Update(ctx context.Context, req *dto.UpdateModelTypeReq) error {
|
||||
data := map[string]any{}
|
||||
if req.TypeID != nil {
|
||||
data[entity.AsynchModelTypeCol.TypeID] = *req.TypeID
|
||||
}
|
||||
if req.TypeName != nil {
|
||||
data[entity.AsynchModelTypeCol.TypeName] = *req.TypeName
|
||||
}
|
||||
if req.Remark != nil {
|
||||
data[entity.AsynchModelTypeCol.Remark] = *req.Remark
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return errors.New("无可更新字段")
|
||||
}
|
||||
_, err := dao.ModelType.UpdateByID(ctx, req.ID, data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *modelTypeService) Delete(ctx context.Context, id int64) error {
|
||||
_, err := dao.ModelType.DeleteByID(ctx, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *modelTypeService) Get(ctx context.Context, id int64) (*entity.AsynchModelType, error) {
|
||||
return dao.ModelType.GetByID(ctx, id)
|
||||
}
|
||||
|
||||
func (s *modelTypeService) List(ctx context.Context, pageNum, pageSize int, typeNameLike string) (list []*entity.AsynchModelType, total int64, err error) {
|
||||
return dao.ModelType.List(ctx, pageNum, pageSize, typeNameLike)
|
||||
}
|
||||
|
||||
// ListWithModels 按类型分组返回模型(返回数组,便于前端直接渲染)
|
||||
func (s *modelTypeService) ListWithModels(ctx context.Context, req *dto.ListModelTypeWithModelsReq) (res []dto.ModelTypeWithModelsItem, err error) {
|
||||
types, _, err := dao.ModelType.List(ctx, 1, 1000, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 过滤类型(按 typeId / typeName 模糊)
|
||||
filterTypeID := 0
|
||||
filterTypeName := ""
|
||||
if req != nil {
|
||||
filterTypeID = req.TypeID
|
||||
filterTypeName = strings.TrimSpace(req.Type)
|
||||
}
|
||||
typeIDs := make([]int, 0, len(types))
|
||||
typeNameMap := make(map[int]string, len(types))
|
||||
for _, t := range types {
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
if filterTypeID > 0 && t.TypeID != filterTypeID {
|
||||
continue
|
||||
}
|
||||
if filterTypeName != "" && !strings.Contains(t.TypeName, filterTypeName) {
|
||||
continue
|
||||
}
|
||||
typeIDs = append(typeIDs, t.TypeID)
|
||||
typeNameMap[t.TypeID] = t.TypeName
|
||||
}
|
||||
models, err := dao.Model.ListAll(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
itemsMap := map[int][]dto.ModelTypeModelItem{}
|
||||
for _, m := range models {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
form := normalizeFormValue(m.Form)
|
||||
// 一个模型可能支持多个类型:models_type="1,2,3"
|
||||
for _, tid := range parseModelsTypeIDs(m.ModelsType) {
|
||||
// 若请求过滤了类型,则只输出该类型
|
||||
if filterTypeID > 0 && tid != filterTypeID {
|
||||
continue
|
||||
}
|
||||
if filterTypeName != "" {
|
||||
if _, ok := typeNameMap[tid]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
itemsMap[tid] = append(itemsMap[tid], dto.ModelTypeModelItem{
|
||||
ID: m.Id,
|
||||
Name: m.ModelName,
|
||||
Form: form,
|
||||
})
|
||||
}
|
||||
}
|
||||
out := make([]dto.ModelTypeWithModelsItem, 0, len(typeIDs))
|
||||
for _, tid := range typeIDs {
|
||||
items := itemsMap[tid]
|
||||
if items == nil {
|
||||
items = make([]dto.ModelTypeModelItem, 0)
|
||||
}
|
||||
out = append(out, dto.ModelTypeWithModelsItem{
|
||||
TypeID: tid,
|
||||
Type: typeNameMap[tid],
|
||||
Items: items,
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// normalizeModelsType 将 "1, 2,2,3" 归一化为 "1,2,3"
|
||||
// - 去空格
|
||||
// - 去重
|
||||
// - 升序排序
|
||||
func normalizeModelsType(v string) string {
|
||||
ids := parseModelsTypeIDs(v)
|
||||
if len(ids) == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := make([]string, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
parts = append(parts, strconv.Itoa(id))
|
||||
}
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
// parseModelsTypeIDs 解析 models_type 字段(支持 "1,2,3"),返回去重后的 int 列表(升序)。
|
||||
func parseModelsTypeIDs(v string) []int {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
raw := strings.Split(v, ",")
|
||||
seen := map[int]struct{}{}
|
||||
out := make([]int, 0, len(raw))
|
||||
for _, s := range raw {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" || s == "0" {
|
||||
continue
|
||||
}
|
||||
id, err := strconv.Atoi(s)
|
||||
if err != nil || id <= 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[id]; ok {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
out = append(out, id)
|
||||
}
|
||||
sort.Ints(out)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ var Stat = &statService{}
|
||||
|
||||
func (s *statService) List(ctx context.Context, req *dto.ListModelStatReq) (res *dto.ListModelStatRes, err error) {
|
||||
pageNum, pageSize := 1, 10
|
||||
if req != nil && req.Page != nil {
|
||||
if req.Page.PageNum > 0 {
|
||||
pageNum = int(req.Page.PageNum)
|
||||
if req != nil {
|
||||
if req.PageNum > 0 {
|
||||
pageNum = req.PageNum
|
||||
}
|
||||
if req.Page.PageSize > 0 {
|
||||
pageSize = int(req.Page.PageSize)
|
||||
if req.PageSize > 0 {
|
||||
pageSize = req.PageSize
|
||||
}
|
||||
}
|
||||
startDay, endDay := "", ""
|
||||
@@ -37,4 +37,3 @@ func (s *statService) List(ctx context.Context, req *dto.ListModelStatReq) (res
|
||||
}
|
||||
return &dto.ListModelStatRes{List: list, Total: total}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ func (s *ossStorage) UploadByTask(ctx context.Context, _ *entity.AsynchTask, dat
|
||||
if err := commonHttp.Post(ctx, fullURL, headers, &resp, body.Bytes()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Println("打印结果 resp:", resp)
|
||||
g.Log().Infof(ctx, "[OSS] upload success url=%s size=%d format=%s", resp.FileURL, resp.FileSize, resp.FileFormat)
|
||||
return resp.FileURL, nil
|
||||
}
|
||||
|
||||
@@ -58,9 +58,10 @@ func (s *taskService) Create(ctx context.Context, req *dto.CreateTaskReq) (res *
|
||||
State: 0,
|
||||
BizName: req.BizName,
|
||||
CallbackURL: req.CallbackUrl,
|
||||
ModelKey: req.ModelKey,
|
||||
ModelKey: m.ApiKey,
|
||||
InputRef: req.InputRef,
|
||||
RequestPayload: storedPayload,
|
||||
EpicycleId: req.EpicycleId,
|
||||
}
|
||||
_, err = dao.Task.Insert(ctx, t)
|
||||
if err != nil {
|
||||
@@ -80,7 +81,7 @@ func (s *taskService) Create(ctx context.Context, req *dto.CreateTaskReq) (res *
|
||||
apiPath = r.URL.Path
|
||||
httpMethod = r.Method
|
||||
}
|
||||
_, _ = dao.OpLog.Insert(ctx, &entity.AsynchOpLog{
|
||||
_, _ = dao.OpLog.Insert(ctx, &entity.LogsModelOp{
|
||||
IP: ip,
|
||||
UserAgent: ua,
|
||||
APIPath: apiPath,
|
||||
@@ -97,9 +98,80 @@ func (s *taskService) Create(ctx context.Context, req *dto.CreateTaskReq) (res *
|
||||
"taskId": taskID,
|
||||
},
|
||||
})
|
||||
|
||||
// 4) 创建成功后立即异步尝试执行当前任务,并仅在任务仍处于 pending(state=0) 时做定向轮询。
|
||||
// 一旦任务进入 running/success/failed/downloaded,就停止轮询,避免一直空转。
|
||||
go s.pollAndRunUntilPicked(context.WithoutCancel(ctx), taskID, req.EpicycleId)
|
||||
|
||||
return &dto.CreateTaskRes{TaskID: taskID}, nil
|
||||
}
|
||||
|
||||
// pollAndRunUntilPicked 用于 createTask 创建后的“轻量级定向轮询”:
|
||||
// - 目标:尽快把刚创建的任务拉起来执行
|
||||
// - 只在任务仍为 pending(state=0) 时继续尝试抢占
|
||||
// - 一旦任务进入 running(1) / success(2) / failed(3) / downloaded(4),立即停止
|
||||
// - 这样不会无限轮询;runWork 仍负责处理积压队列和未处理到的任务
|
||||
func (s *taskService) pollAndRunUntilPicked(ctx context.Context, taskID string, epicycleId int64) {
|
||||
if taskID == "" {
|
||||
return
|
||||
}
|
||||
interval := g.Cfg().MustGet(ctx, "asynch.worker.intervalSeconds").Int()
|
||||
if interval <= 0 {
|
||||
interval = 5
|
||||
}
|
||||
g.Log().Infof(ctx, "[task-auto-run][start] taskId=%s interval=%ds", taskID, interval)
|
||||
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
tryRun := func() bool {
|
||||
t, err := dao.Task.GetByTaskID(ctx, taskID)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "[task-auto-run][stop] taskId=%s reason=query_failed err=%v", taskID, err)
|
||||
return true
|
||||
}
|
||||
if t == nil {
|
||||
g.Log().Warningf(ctx, "[task-auto-run][stop] taskId=%s reason=task_not_found", taskID)
|
||||
return true
|
||||
}
|
||||
switch t.State {
|
||||
case 0:
|
||||
if err := AsyncWorker.RunByTaskID(ctx, taskID, epicycleId); err != nil {
|
||||
g.Log().Warningf(ctx, "[task-auto-run][retry] taskId=%s state=0 err=%v", taskID, err)
|
||||
} else {
|
||||
g.Log().Infof(ctx, "[task-auto-run][triggered] taskId=%s state=0", taskID)
|
||||
}
|
||||
return false
|
||||
case 1:
|
||||
g.Log().Infof(ctx, "[task-auto-run][stop] taskId=%s reason=running", taskID)
|
||||
return true
|
||||
case 2, 3, 4:
|
||||
g.Log().Infof(ctx, "[task-auto-run][stop] taskId=%s reason=terminal state=%d", taskID, t.State)
|
||||
return true
|
||||
default:
|
||||
g.Log().Infof(ctx, "[task-auto-run][stop] taskId=%s reason=unknown_state state=%d", taskID, t.State)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 先立即尝试一次
|
||||
if stop := tryRun(); stop {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
g.Log().Infof(ctx, "[task-auto-run][stop] taskId=%s reason=context_done", taskID)
|
||||
return
|
||||
case <-ticker.C:
|
||||
if stop := tryRun(); stop {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *taskService) GetResult(ctx context.Context, taskID string) (res *dto.GetTaskResultRes, err error) {
|
||||
t, err := dao.Task.GetByTaskID(ctx, taskID)
|
||||
if err != nil {
|
||||
@@ -168,12 +240,12 @@ func (s *taskService) GetBatch(ctx context.Context, req *dto.GetTaskBatchReq) (r
|
||||
|
||||
func (s *taskService) List(ctx context.Context, req *dto.ListTaskReq) (res *dto.ListTaskRes, err error) {
|
||||
pageNum, pageSize := 1, 10
|
||||
if req != nil && req.Page != nil {
|
||||
if req.Page.PageNum > 0 {
|
||||
pageNum = int(req.Page.PageNum)
|
||||
if req != nil {
|
||||
if req.PageNum > 0 {
|
||||
pageNum = req.PageNum
|
||||
}
|
||||
if req.Page.PageSize > 0 {
|
||||
pageSize = int(req.Page.PageSize)
|
||||
if req.PageSize > 0 {
|
||||
pageSize = req.PageSize
|
||||
}
|
||||
}
|
||||
modelName := ""
|
||||
|
||||
113
service/utils.go
Normal file
113
service/utils.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gvar"
|
||||
)
|
||||
|
||||
func normalizeFormValue(v any) any {
|
||||
// 目标:对外永远返回 JSON 数组/对象,而不是字符串。
|
||||
if v == nil {
|
||||
return []any{}
|
||||
}
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
s := strings.TrimSpace(t)
|
||||
if s == "" {
|
||||
return []any{}
|
||||
}
|
||||
return normalizeFormValueFromJSONString(s)
|
||||
case []byte:
|
||||
if len(t) == 0 {
|
||||
return []any{}
|
||||
}
|
||||
return normalizeFormValueFromJSONBytes(t)
|
||||
case *gvar.Var:
|
||||
// goframe 常见的 DB 返回类型
|
||||
if t == nil {
|
||||
return []any{}
|
||||
}
|
||||
b := t.Bytes()
|
||||
if len(b) > 0 {
|
||||
return normalizeFormValueFromJSONBytes(b)
|
||||
}
|
||||
s := strings.TrimSpace(t.String())
|
||||
if s == "" {
|
||||
return []any{}
|
||||
}
|
||||
return normalizeFormValueFromJSONString(s)
|
||||
default:
|
||||
// 尝试兼容其他“像 JSON 的值类型”(例如实现了 Bytes/String 的包装类型)
|
||||
if vb, ok := v.(interface{ Bytes() []byte }); ok {
|
||||
if b := vb.Bytes(); len(b) > 0 {
|
||||
return normalizeFormValueFromJSONBytes(b)
|
||||
}
|
||||
}
|
||||
if vs, ok := v.(interface{ String() string }); ok {
|
||||
if s := strings.TrimSpace(vs.String()); s != "" {
|
||||
return normalizeFormValueFromJSONString(s)
|
||||
}
|
||||
}
|
||||
// 已经是 []any / map[string]any 等结构
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容“JSONB 里存了 JSON 字符串”的历史数据:
|
||||
// 例如 form_json = '"[]"' 或 '"[{...}]"'(外层是字符串,内层才是数组/对象)
|
||||
func normalizeFormValueFromJSONString(s string) any {
|
||||
var out any
|
||||
if err := json.Unmarshal([]byte(s), &out); err != nil || out == nil {
|
||||
return []any{}
|
||||
}
|
||||
// 如果解出来还是 string,且看起来是 JSON,再解一层
|
||||
if inner, ok := out.(string); ok {
|
||||
inner = strings.TrimSpace(inner)
|
||||
if inner == "" {
|
||||
return []any{}
|
||||
}
|
||||
if strings.HasPrefix(inner, "[") || strings.HasPrefix(inner, "{") {
|
||||
var out2 any
|
||||
if err := json.Unmarshal([]byte(inner), &out2); err == nil && out2 != nil {
|
||||
return out2
|
||||
}
|
||||
}
|
||||
return []any{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeFormValueFromJSONBytes(b []byte) any {
|
||||
var out any
|
||||
if err := json.Unmarshal(b, &out); err != nil || out == nil {
|
||||
return []any{}
|
||||
}
|
||||
// bytes 解出来也可能是 string(同上)
|
||||
if inner, ok := out.(string); ok {
|
||||
return normalizeFormValueFromJSONString(inner)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func ParseJSONField(field any) any {
|
||||
var v *gvar.Var
|
||||
switch val := field.(type) {
|
||||
case *gvar.Var:
|
||||
v = val
|
||||
default:
|
||||
return field
|
||||
}
|
||||
|
||||
if v == nil || v.IsNil() || v.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
str := v.String()
|
||||
var result any
|
||||
if json.Unmarshal([]byte(str), &result) == nil {
|
||||
return result
|
||||
}
|
||||
return str
|
||||
}
|
||||
@@ -5,12 +5,14 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"model-asynch/dao"
|
||||
"model-asynch/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/grpool"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
var AsyncWorker = &asyncWorker{}
|
||||
@@ -43,7 +45,7 @@ func (w *asyncWorker) RunOnce(ctx context.Context, batchSize, goroutines int) (c
|
||||
for _, t := range tasks {
|
||||
task := t
|
||||
_ = pool.AddWithRecover(ctx, func(ctx context.Context) {
|
||||
w.handleOne(ctx, task)
|
||||
w.handleOne(ctx, task, 0)
|
||||
done <- struct{}{}
|
||||
}, func(ctx context.Context, e error) {
|
||||
if e != nil {
|
||||
@@ -59,8 +61,23 @@ func (w *asyncWorker) RunOnce(ctx context.Context, batchSize, goroutines int) (c
|
||||
return claimed, nil
|
||||
}
|
||||
|
||||
func (w *asyncWorker) handleOne(ctx context.Context, t *entity.AsynchTask) {
|
||||
// 从任务入库的 request_payload 里恢复 payload + headers,给 OSS 上传透传鉴权用
|
||||
// RunByTaskID 创建任务后立即异步尝试执行当前任务:
|
||||
// - 只定向抢占当前 taskId 对应的 pending 任务
|
||||
// - 若任务已被其它 worker 抢走/已不在 pending,则直接返回
|
||||
func (w *asyncWorker) RunByTaskID(ctx context.Context, taskID string, epicycleId int64) error {
|
||||
task, err := dao.Task.ClaimPendingByTaskIDGlobal(ctx, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if task == nil {
|
||||
return nil
|
||||
}
|
||||
w.handleOne(ctx, task, epicycleId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *asyncWorker) handleOne(ctx context.Context, t *entity.AsynchTask, epicycleId int64) {
|
||||
// 从任务入库的 request_payload 里恢复 payload + headers
|
||||
payload, headers := parseStoredPayload(t.RequestPayload)
|
||||
if len(headers) > 0 {
|
||||
ctx = setTaskHeadersToCtx(ctx, headers)
|
||||
@@ -71,26 +88,42 @@ func (w *asyncWorker) handleOne(ctx context.Context, t *entity.AsynchTask) {
|
||||
if err != nil {
|
||||
_ = dao.Task.UpdateFailedGlobal(ctx, t.Id, err.Error())
|
||||
ReleaseQueueSlot(ctx, t.ModelName, t.TaskID)
|
||||
// ============ 失败回调 ============
|
||||
t.State = 3
|
||||
t.ErrorMsg = err.Error()
|
||||
go triggerCallback(context.WithoutCancel(ctx), t)
|
||||
// ================================
|
||||
return
|
||||
}
|
||||
if m == nil || m.Enabled != 1 {
|
||||
_ = dao.Task.UpdateFailedGlobal(ctx, t.Id, "模型不存在或未启用")
|
||||
errMsg := "模型不存在或未启用"
|
||||
_ = dao.Task.UpdateFailedGlobal(ctx, t.Id, errMsg)
|
||||
ReleaseQueueSlot(ctx, t.ModelName, t.TaskID)
|
||||
// ============ 失败回调 ============
|
||||
t.State = 3
|
||||
t.ErrorMsg = errMsg
|
||||
go triggerCallback(context.WithoutCancel(ctx), t)
|
||||
// ================================
|
||||
return
|
||||
}
|
||||
|
||||
// 2) 分布式并发限制(按 model_name 全局维度)
|
||||
// 2) 分布式并发限制
|
||||
semKey := fmt.Sprintf("asynch:sem:%s", t.ModelName)
|
||||
leaseSeconds := int64(3600) // 兜底1小时
|
||||
leaseSeconds := int64(3600)
|
||||
maxC := GetRuntimeMaxConcurrency(ctx, t.ModelName, m.MaxConcurrency)
|
||||
acquired, err := acquireSemaphore(ctx, semKey, maxC, leaseSeconds)
|
||||
if err != nil {
|
||||
_ = dao.Task.UpdateFailedGlobal(ctx, t.Id, err.Error())
|
||||
ReleaseQueueSlot(ctx, t.ModelName, t.TaskID)
|
||||
// ============ 失败回调 ============
|
||||
t.State = 3
|
||||
t.ErrorMsg = err.Error()
|
||||
go triggerCallback(context.WithoutCancel(ctx), t)
|
||||
// ================================
|
||||
return
|
||||
}
|
||||
if !acquired {
|
||||
// 并发满了:放回排队(重新置回 state=0),下一轮再抢占
|
||||
// 并发满了:放回排队,不回调(不是失败)
|
||||
_ = w.rollbackToPending(ctx, t.Id)
|
||||
return
|
||||
}
|
||||
@@ -109,30 +142,40 @@ func (w *asyncWorker) handleOne(ctx context.Context, t *entity.AsynchTask) {
|
||||
data []byte
|
||||
contentType string
|
||||
ext string
|
||||
textResult string
|
||||
)
|
||||
|
||||
// phase=1 表示模型已成功但 OSS 上传失败:优先从临时文件加载,避免重复跑模型
|
||||
// phase=1 表示模型已成功但 OSS 上传失败:优先从临时文件加载
|
||||
if t.Phase == 1 && strings.TrimSpace(t.TmpFile) != "" {
|
||||
data, err = loadTmpResult(t.TmpFile)
|
||||
if err == nil && len(data) > 0 {
|
||||
contentType, ext = DetectFileType(data)
|
||||
} else {
|
||||
// 临时文件不可用:回退重新调用模型
|
||||
data = nil
|
||||
}
|
||||
}
|
||||
if data == nil {
|
||||
// 统计:仅在真正请求模型时 +1(OSS 重试不计入)
|
||||
// 统计
|
||||
_ = dao.Stat.IncRequestCount(ctx, time.Now(), int64(t.TenantId), t.Creator, t.ModelName)
|
||||
|
||||
// 核心调用
|
||||
data, err = InvokeModel(ctx, m, payload, t.ModelKey)
|
||||
if err != nil {
|
||||
_ = dao.Task.UpdateFailedGlobal(ctx, t.Id, err.Error())
|
||||
ReleaseQueueSlot(ctx, t.ModelName, t.TaskID)
|
||||
// ============ 失败回调 ============
|
||||
t.State = 3
|
||||
t.ErrorMsg = err.Error()
|
||||
go triggerCallback(context.WithoutCancel(ctx), t)
|
||||
// ================================
|
||||
return
|
||||
}
|
||||
contentType, ext = DetectFileType(data)
|
||||
// 将模型输出写入临时文件,后续若 OSS 失败可只重试 OSS
|
||||
if utf8.Valid(data) && (strings.HasPrefix(contentType, "text/") || contentType == "application/json") {
|
||||
textResult = string(data)
|
||||
if len(textResult) > 20000 {
|
||||
textResult = textResult[:20000]
|
||||
}
|
||||
}
|
||||
tmpPath, err := saveTmpResult(t.TaskID, data, ext)
|
||||
if err == nil && tmpPath != "" {
|
||||
t.TmpFile = tmpPath
|
||||
@@ -147,26 +190,46 @@ func (w *asyncWorker) handleOne(ctx context.Context, t *entity.AsynchTask) {
|
||||
// OSS 阶段失败:保留临时文件,下一轮仅重试 OSS
|
||||
_ = dao.Task.UpdateFailedKeepTmpGlobal(ctx, t.Id, err.Error())
|
||||
ReleaseQueueSlot(ctx, t.ModelName, t.TaskID)
|
||||
// ============ OSS失败不回调(还会重试) ============
|
||||
// 注意:OSS失败保留临时文件,下次重试,所以这里不触发最终回调
|
||||
// 如果已经重试多次还没成功,需要在任务超时或超过最大重试次数时才回调失败
|
||||
return
|
||||
}
|
||||
|
||||
// 5) 更新任务状态成功
|
||||
// 注意:expire_at 的计算改为“已下载(state=4)后开始计时”,因此成功(state=2)不写 expire_at。
|
||||
fileType := strings.TrimPrefix(ext, ".")
|
||||
if fileType == "" {
|
||||
fileType = contentType
|
||||
}
|
||||
if err := dao.Task.UpdateSuccessGlobal(ctx, t.Id, ossURL, fileType, int64(len(data)), nil); err != nil {
|
||||
if err := dao.Task.UpdateSuccessGlobal(
|
||||
ctx,
|
||||
t.Id,
|
||||
ossURL,
|
||||
fileType,
|
||||
textResult,
|
||||
int64(len(data)),
|
||||
nil,
|
||||
GetExpendTokens(m.TokenMapping, textResult),
|
||||
); err != nil {
|
||||
g.Log().Errorf(ctx, "[worker] update success failed: %v", err)
|
||||
return
|
||||
}
|
||||
// 成功/失败均不再占用 queue_limit(state=0/1 才占用)
|
||||
|
||||
// 成功/失败均不再占用 queue_limit
|
||||
ReleaseQueueSlot(ctx, t.ModelName, t.TaskID)
|
||||
// 6) 成功回调(不影响主流程)
|
||||
|
||||
// 6) 成功回调
|
||||
t.State = 2
|
||||
t.OssFile = ossURL
|
||||
t.FileType = fileType
|
||||
go triggerSuccessCallback(context.WithoutCancel(ctx), t)
|
||||
t.TextResult = textResult
|
||||
g.Log().Infof(ctx, "[CALLBACK][DISPATCH] taskId=%s bizName=%s callbackUrl=%s", t.TaskID, t.BizName, t.CallbackURL)
|
||||
go triggerCallback(context.WithoutCancel(ctx), t)
|
||||
// ============ 如果有 epicycleId,也触发业务回调 ============
|
||||
if epicycleId != 0 {
|
||||
go triggerPromptsCallback(context.WithoutCancel(ctx), t, epicycleId)
|
||||
}
|
||||
|
||||
// 成功后清理临时文件
|
||||
deleteTmpResult(t.TmpFile)
|
||||
}
|
||||
@@ -174,3 +237,13 @@ func (w *asyncWorker) handleOne(ctx context.Context, t *entity.AsynchTask) {
|
||||
func (w *asyncWorker) rollbackToPending(ctx context.Context, id int64) error {
|
||||
return dao.Task.RollbackToPendingGlobal(ctx, id)
|
||||
}
|
||||
|
||||
// GetExpendTokens 根据映射路径从 textResult 中提取消耗 token 值
|
||||
func GetExpendTokens(tokenMapping string, textResult string) int {
|
||||
value := gjson.Get(textResult, tokenMapping)
|
||||
if value.Exists() {
|
||||
return int(value.Int())
|
||||
} else {
|
||||
return len(textResult)
|
||||
}
|
||||
}
|
||||
|
||||
366
update.sql
366
update.sql
@@ -1,164 +1,130 @@
|
||||
-- model-asynch 核心表(pgsql)
|
||||
-- 1) asynch_models_type:模型类型
|
||||
-- 2) asynch_models:模型配置
|
||||
-- 3) asynch_task:异步任务
|
||||
-- 4) asynch_op_log:操作日志(统计用)
|
||||
-- 5) asynch_model_stat:按天模型请求统计(限流/监控用)
|
||||
|
||||
-- =========================
|
||||
-- 0) asynch_models_type
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS asynch_models_type (
|
||||
-- 基础字段(与现有表保持一致)
|
||||
id BIGINT PRIMARY KEY, -- 主键ID(非自增)
|
||||
tenant_id BIGINT NOT NULL DEFAULT 0, -- 租户ID
|
||||
creator VARCHAR(64) NOT NULL, -- 创建人
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater VARCHAR(64) NOT NULL, -- 更新人
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
deleted_at TIMESTAMP(6), -- 删除时间(软删)
|
||||
|
||||
-- 业务字段
|
||||
type_id INT NOT NULL, -- 模型类型ID(业务枚举)
|
||||
type_name VARCHAR(64) NOT NULL, -- 模型类型名称(图片模型/音频模型/...)
|
||||
remark TEXT DEFAULT '' -- 备注
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_asynch_models_type_tenant_type_id
|
||||
ON asynch_models_type(tenant_id, type_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_models_type_tenant_id ON asynch_models_type(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_models_type_type_name ON asynch_models_type(type_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_models_type_deleted_at ON asynch_models_type(deleted_at);
|
||||
|
||||
COMMENT ON TABLE asynch_models_type IS '模型类型表';
|
||||
COMMENT ON COLUMN asynch_models_type.id IS '主键ID(非自增)';
|
||||
COMMENT ON COLUMN asynch_models_type.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN asynch_models_type.creator IS '创建人';
|
||||
COMMENT ON COLUMN asynch_models_type.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN asynch_models_type.updater IS '更新人';
|
||||
COMMENT ON COLUMN asynch_models_type.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN asynch_models_type.deleted_at IS '删除时间(软删)';
|
||||
COMMENT ON COLUMN asynch_models_type.type_id IS '模型类型ID(业务枚举)';
|
||||
COMMENT ON COLUMN asynch_models_type.type_name IS '模型类型名称';
|
||||
COMMENT ON COLUMN asynch_models_type.remark IS '备注';
|
||||
|
||||
-- model-asynch 核心表(pgsql)
|
||||
-- 1) asynch_models:模型配置
|
||||
-- 2) asynch_task:异步任务
|
||||
-- 3) logs_model_op:操作日志(统计用)
|
||||
-- 4) logs_model_stat:按天模型请求统计(限流/监控用)
|
||||
|
||||
-- =========================
|
||||
-- 1) asynch_models
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS asynch_models (
|
||||
-- 基础字段(与现有表保持一致)
|
||||
id BIGINT PRIMARY KEY, -- 主键ID(非自增)
|
||||
tenant_id BIGINT NOT NULL DEFAULT 0, -- 租户ID
|
||||
creator VARCHAR(64) NOT NULL, -- 创建人
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater VARCHAR(64) NOT NULL, -- 更新人
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
deleted_at TIMESTAMP(6), -- 删除时间(软删)
|
||||
|
||||
-- 基础字段
|
||||
id BIGINT PRIMARY KEY, -- 主键ID(非自增)
|
||||
tenant_id BIGINT NOT NULL DEFAULT 0, -- 租户ID
|
||||
creator VARCHAR(64) NOT NULL, -- 创建人
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater VARCHAR(64) NOT NULL, -- 更新人
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
deleted_at TIMESTAMP(6), -- 删除时间(软删)
|
||||
-- 业务字段
|
||||
model_name VARCHAR(128) NOT NULL, -- 模型名称(路由键)
|
||||
models_type VARCHAR(128) NOT NULL DEFAULT '', -- 模型类型ID列表(逗号分隔),示例:1,2,3(关联 asynch_models_type.type_id)
|
||||
base_url VARCHAR(256) NOT NULL, -- 模型服务基础地址(如 http://1.2.3.4:8080)
|
||||
route VARCHAR(256) NOT NULL DEFAULT '',-- 模型服务路由(如 /v1/infer)
|
||||
http_method VARCHAR(8) NOT NULL DEFAULT 'POST', -- 请求方式:GET/POST
|
||||
head_msg VARCHAR(1024) DEFAULT '', -- 请求头绑定(支持多个,逗号分隔):X-API-Key:xxx,operation:true
|
||||
form_json JSONB NOT NULL DEFAULT '[]'::jsonb, -- 动态表单配置(JSON数组),用于前端渲染
|
||||
|
||||
enabled SMALLINT NOT NULL DEFAULT 1, -- 是否启用:1启用/0停用
|
||||
max_concurrency INT NOT NULL DEFAULT 10, -- 单模型最大并发
|
||||
queue_limit INT NOT NULL DEFAULT 1000, -- 排队上限(近似控制)
|
||||
timeout_seconds INT NOT NULL DEFAULT 60, -- 调用模型服务超时(秒)
|
||||
expected_seconds INT NOT NULL DEFAULT 0, -- 模型预计执行时间(秒,用于超时判定/排队策略等)
|
||||
|
||||
retry_times SMALLINT NOT NULL DEFAULT 0, -- 失败后最多再重试 N 次(不含首次)
|
||||
retry_queue_max_seconds INT NOT NULL DEFAULT 0, -- 失败重试最大排队时间(秒):0=插队到队首;>0=排队超过该时间后插队,否则仍到队尾
|
||||
|
||||
auto_clean_seconds INT NOT NULL DEFAULT 86400, -- 已下载(state=4)后的保留时间(秒)
|
||||
remark TEXT DEFAULT '' -- 备注
|
||||
model_name VARCHAR(128) NOT NULL, -- 模型名称
|
||||
models_type SMALLINT NOT NULL DEFAULT 0, -- 模型类型
|
||||
base_url VARCHAR(256) NOT NULL, -- 模型地址
|
||||
http_method VARCHAR(8) NOT NULL DEFAULT 'POST', -- 请求方式 GET/POST
|
||||
head_msg VARCHAR(1024) DEFAULT '', -- 请求头绑定(支持多个,逗号分隔)示例 X-API:xxx,operation:true
|
||||
is_private SMALLINT NOT NULL DEFAULT 0, -- 是否私有化 0-私有 1-公共
|
||||
enabled SMALLINT NOT NULL DEFAULT 1, -- 是否启用 0停用 1-启用
|
||||
is_chat_model SMALLINT NOT NULL DEFAULT 0, -- 是否为对话模型 0-否 1-是
|
||||
api_key VARCHAR(256) NOT NULL DEFAULT '', -- 调用凭证,密钥
|
||||
prompt TEXT NOT NULL DEFAULT '', -- 提示词内容(文本)
|
||||
form_json JSONB NOT NULL DEFAULT '{}'::jsonb, -- 表单结构(用于前端渲染)
|
||||
request_mapping JSONB NOT NULL DEFAULT '{}'::jsonb -- 请求映射
|
||||
response_mapping JSONB NOT NULL DEFAULT '{}'::jsonb, -- 返回映射
|
||||
response_body JSONB NOT NULL DEFAULT '{}'::jsonb, -- 返回主体
|
||||
max_concurrency INT NOT NULL DEFAULT 10, -- 单模型最大并发
|
||||
queue_limit INT NOT NULL DEFAULT 1000, -- 排队上限(近似控制)
|
||||
timeout_seconds INT NOT NULL DEFAULT 600, -- 调用模型服务超时(秒)
|
||||
expected_seconds INT NOT NULL DEFAULT 600, -- 模型预计执行时间(秒)
|
||||
retry_times SMALLINT NOT NULL DEFAULT 3, -- 失败重试次数
|
||||
retry_queue_max_seconds INT NOT NULL DEFAULT 600, -- 失败重试最大排队时间(秒 0=插队到队首;>0=排队超过该时间后插队,否则仍到队尾)
|
||||
auto_clean_seconds INT NOT NULL DEFAULT 86400, -- 已下载(state=4 后的保留时间(秒),到期清理)
|
||||
remark TEXT DEFAULT '' -- 备注
|
||||
token_mapping VARCHAR(128) NOT NULL DEFAULT ''; -- token 映射
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_asynch_models_tenant_model_name
|
||||
ON asynch_models(tenant_id, model_name);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_asynch_models_tenant_creator_chat ON asynch_models(tenant_id, creator) WHERE is_chat_model = 1 AND deleted_at IS NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_asynch_models_tenant_model_name ON asynch_models(tenant_id, creator, model_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_models_tenant_id ON asynch_models(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_models_model_name ON asynch_models(model_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_models_models_type ON asynch_models(models_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_models_enabled ON asynch_models(enabled);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_models_deleted_at ON asynch_models(deleted_at);
|
||||
|
||||
COMMENT ON TABLE asynch_models IS '异步模型表(模型服务配置)';
|
||||
COMMENT ON COLUMN asynch_models.id IS '主键ID(非自增)';
|
||||
COMMENT ON TABLE asynch_models IS '模型配置表';
|
||||
COMMENT ON COLUMN asynch_models.id IS '主键ID(非自增)';
|
||||
COMMENT ON COLUMN asynch_models.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN asynch_models.creator IS '创建人';
|
||||
COMMENT ON COLUMN asynch_models.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN asynch_models.updater IS '更新人';
|
||||
COMMENT ON COLUMN asynch_models.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN asynch_models.deleted_at IS '删除时间(软删)';
|
||||
COMMENT ON COLUMN asynch_models.model_name IS '模型名称(路由键)';
|
||||
COMMENT ON COLUMN asynch_models.models_type IS '模型类型ID列表(逗号分隔),示例:1,2,3(关联 asynch_models_type.type_id)';
|
||||
COMMENT ON COLUMN asynch_models.base_url IS '模型服务基础地址(如 http://1.2.3.4:8080)';
|
||||
COMMENT ON COLUMN asynch_models.route IS '模型服务路由(如 /v1/infer)';
|
||||
COMMENT ON COLUMN asynch_models.http_method IS '请求方式:GET/POST';
|
||||
COMMENT ON COLUMN asynch_models.head_msg IS '请求头绑定(支持多个,逗号分隔):X-API-Key:xxx,operation:true';
|
||||
COMMENT ON COLUMN asynch_models.form_json IS '动态表单配置(JSON数组),用于前端渲染';
|
||||
COMMENT ON COLUMN asynch_models.enabled IS '是否启用:1启用/0停用';
|
||||
COMMENT ON COLUMN asynch_models.deleted_at IS '删除时间(软删)';
|
||||
|
||||
COMMENT ON COLUMN asynch_models.model_name IS '模型名称';
|
||||
COMMENT ON COLUMN asynch_models.models_type IS '模型类型';
|
||||
COMMENT ON COLUMN asynch_models.base_url IS '模型地址';
|
||||
COMMENT ON COLUMN asynch_models.http_method IS '请求方式 GET/POST';
|
||||
COMMENT ON COLUMN asynch_models.head_msg IS '请求头绑定(支持多个,逗号分隔)示例 X-API:xxx,operation:true';
|
||||
COMMENT ON COLUMN asynch_models.is_private IS '是否私有化 0-私有 1-公共';
|
||||
COMMENT ON COLUMN asynch_models.enabled IS '是否启用 0停用 1-启用';
|
||||
COMMENT ON COLUMN asynch_models.is_chat_model IS '是否为对话模型 0-否 1-是';
|
||||
COMMENT ON COLUMN asynch_models.api_key IS '调用凭证,密钥';
|
||||
COMMENT ON COLUMN asynch_models.prompt IS '提示词内容(文本)';
|
||||
COMMENT ON COLUMN asynch_models.form_json IS '表单结构(用于前端渲染,也用于后端校验)';
|
||||
COMMENT ON COLUMN asynch_models.request_mapping IS '请求映射';
|
||||
COMMENT ON COLUMN asynch_models.response_mapping IS '返回映射';
|
||||
COMMENT ON COLUMN asynch_models.response_body IS '返回主体';
|
||||
COMMENT ON COLUMN asynch_models.max_concurrency IS '单模型最大并发';
|
||||
COMMENT ON COLUMN asynch_models.queue_limit IS '排队上限(近似控制)';
|
||||
COMMENT ON COLUMN asynch_models.timeout_seconds IS '调用模型服务超时(秒)';
|
||||
COMMENT ON COLUMN asynch_models.expected_seconds IS '模型预计执行时间(秒,用于超时判定/排队策略等)';
|
||||
COMMENT ON COLUMN asynch_models.retry_times IS '失败后最多再重试 N 次(不含首次)';
|
||||
COMMENT ON COLUMN asynch_models.retry_queue_max_seconds IS '失败重试最大排队时间(秒):0=插队到队首;>0=排队超过该时间后插队,否则仍到队尾';
|
||||
COMMENT ON COLUMN asynch_models.auto_clean_seconds IS '已下载(state=4)后的保留时间(秒),到期清理';
|
||||
COMMENT ON COLUMN asynch_models.queue_limit IS '排队上限(近似控制)';
|
||||
COMMENT ON COLUMN asynch_models.timeout_seconds IS '调用模型服务超时(秒)';
|
||||
COMMENT ON COLUMN asynch_models.expected_seconds IS '模型预计执行时间(秒)';
|
||||
COMMENT ON COLUMN asynch_models.retry_times IS '失败重试次数';
|
||||
COMMENT ON COLUMN asynch_models.retry_queue_max_seconds IS '失败重试最大排队时间(秒 0=插队到队首;>0=排队超过该时间后插队,否则仍到队尾)';
|
||||
COMMENT ON COLUMN asynch_models.auto_clean_seconds IS '已下载(state=4 后的保留时间(秒),到期清理)';
|
||||
COMMENT ON COLUMN asynch_models.remark IS '备注';
|
||||
COMMENT ON COLUMN asynch_models.token_mapping IS 'token映射';
|
||||
|
||||
|
||||
|
||||
-- =========================
|
||||
-- 2) asynch_task
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS asynch_task (
|
||||
-- 基础字段(与现有表保持一致)
|
||||
id BIGINT PRIMARY KEY, -- 主键ID(非自增)
|
||||
tenant_id BIGINT NOT NULL DEFAULT 0, -- 租户ID
|
||||
creator VARCHAR(64) NOT NULL, -- 创建人
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater VARCHAR(64) NOT NULL, -- 更新人
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
deleted_at TIMESTAMP(6), -- 删除时间(软删)
|
||||
|
||||
-- 任务核心字段
|
||||
model_name VARCHAR(128) NOT NULL, -- 模型名称
|
||||
task_id VARCHAR(64) NOT NULL, -- 任务ID(对外返回)
|
||||
biz_name VARCHAR(128) NOT NULL DEFAULT '', -- 业务名称(调用方模块/系统)
|
||||
callback_url VARCHAR(512) DEFAULT '', -- 回调地址(可选,用于后续业务通知)
|
||||
model_key VARCHAR(1024) DEFAULT '', -- 动态请求头(用于覆盖/补充模型配置 head_msg),如 X-API-Key:xxx
|
||||
state SMALLINT NOT NULL DEFAULT 0, -- 0排队中/1执行中/2成功/3失败/4已下载
|
||||
|
||||
oss_file VARCHAR(512) DEFAULT '', -- 结果文件OSS地址
|
||||
file_type VARCHAR(32) DEFAULT '', -- 文件类型(mp3/mp4/png/...)
|
||||
file_size BIGINT NOT NULL DEFAULT 0, -- 文件大小(字节)
|
||||
error_msg TEXT DEFAULT '', -- 错误信息
|
||||
|
||||
started_at TIMESTAMP, -- 开始执行时间
|
||||
finished_at TIMESTAMP, -- 执行结束时间
|
||||
duration_seconds BIGINT NOT NULL DEFAULT 0, -- 耗时(秒):从创建到完成(成功/失败)整体耗时
|
||||
|
||||
expire_at TIMESTAMP, -- state=4 后写入,用于清理
|
||||
|
||||
-- 重试/排队
|
||||
retry_count INT NOT NULL DEFAULT 0, -- 已重试次数(不含首次)
|
||||
enqueue_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 入队时间(用于排队顺序)
|
||||
|
||||
-- 任务执行阶段:用于区分“重试模型”与“仅重试 OSS”
|
||||
phase SMALLINT NOT NULL DEFAULT 0, -- 0模型阶段/1OSS阶段
|
||||
tmp_file TEXT DEFAULT '', -- 临时结果文件路径(phase=1 时仅重试 OSS 上传)
|
||||
|
||||
-- 输入信息(可选)
|
||||
input_ref TEXT DEFAULT '', -- 输入引用(如OSS/业务资源ID等)
|
||||
request_payload JSONB -- 请求参数(可选)
|
||||
-- 基础字段
|
||||
id BIGINT PRIMARY KEY, -- 主键ID(非自增)
|
||||
tenant_id BIGINT NOT NULL DEFAULT 0, -- 租户ID
|
||||
creator VARCHAR(64) NOT NULL, -- 创建人
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 创建时间
|
||||
updater VARCHAR(64) NOT NULL, -- 更新人
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 更新时间
|
||||
deleted_at TIMESTAMP(6), -- 删除时间(软删)
|
||||
|
||||
-- 业务字段
|
||||
model_name VARCHAR(128) NOT NULL, -- 模型名称
|
||||
task_id VARCHAR(64) NOT NULL, -- 任务ID(对外返回)
|
||||
biz_name VARCHAR(128) NOT NULL DEFAULT '', -- 业务名称(调用方模块/系统)
|
||||
callback_url VARCHAR(512) DEFAULT '', -- 回调地址(可选,用于后续业务通知)
|
||||
model_key VARCHAR(1024) DEFAULT '', -- 动态请求头(用于覆盖/补充模型配置 head_msg),如 X-API-Key:xxx
|
||||
state SMALLINT NOT NULL DEFAULT 0, -- 0排队中/1执行中/2成功/3失败/4已下载
|
||||
oss_file VARCHAR(512) DEFAULT '', -- 结果文件OSS地址
|
||||
file_type VARCHAR(32) DEFAULT '', -- 文件类型(mp3/mp4/png/...)
|
||||
file_size BIGINT NOT NULL DEFAULT 0, -- 文件大小(字节)
|
||||
error_msg TEXT DEFAULT '', -- 错误信息
|
||||
started_at TIMESTAMP, -- 开始执行时间
|
||||
finished_at TIMESTAMP, -- 执行结束时间
|
||||
duration_seconds BIGINT NOT NULL DEFAULT 0, -- 耗时(秒):从创建到完成(成功/失败)整体耗时
|
||||
expire_at TIMESTAMP, -- state=4 后写入,用于清理
|
||||
retry_count INT NOT NULL DEFAULT 0, -- 已重试次数(不含首次)
|
||||
enqueue_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 入队时间(用于排队顺序)
|
||||
phase SMALLINT NOT NULL DEFAULT 0, -- 0模型阶段/1OSS阶段
|
||||
tmp_file TEXT DEFAULT '', -- 临时结果文件路径(phase=1 时仅重试 OSS 上传)
|
||||
input_ref TEXT DEFAULT '', -- 输入引用(如OSS/业务资源ID等)
|
||||
request_payload JSONB, -- 请求参数(可选)
|
||||
text_result TEXT DEFAULT '', -- 文本类结果(可选,支持直接回调)
|
||||
epicycle_id VARCHAR(64) DEFAULT '', -- 轮次ID
|
||||
expend_tokens BIGINT NOT NULL DEFAULT 0 -- 消耗 token 数
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_asynch_task_tenant_task_id
|
||||
ON asynch_task(tenant_id, task_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uk_asynch_task_tenant_task_id ON asynch_task(tenant_id, task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_task_tenant_id ON asynch_task(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_task_model_name ON asynch_task(model_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_task_biz_name ON asynch_task(biz_name);
|
||||
@@ -168,42 +134,48 @@ CREATE INDEX IF NOT EXISTS idx_asynch_task_enqueue_at ON asynch_task(enqueue_at)
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_task_updated_at ON asynch_task(updated_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_task_expire_at ON asynch_task(expire_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_task_deleted_at ON asynch_task(deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_task_epicycle_id ON asynch_task(epicycle_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_task_expend_tokens ON asynch_task(expend_tokens);
|
||||
|
||||
COMMENT ON TABLE asynch_task IS '异步任务表';
|
||||
COMMENT ON COLUMN asynch_task.id IS '主键ID(非自增)';
|
||||
COMMENT ON COLUMN asynch_task.id IS '主键ID(非自增)';
|
||||
COMMENT ON COLUMN asynch_task.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN asynch_task.creator IS '创建人';
|
||||
COMMENT ON COLUMN asynch_task.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN asynch_task.updater IS '更新人';
|
||||
COMMENT ON COLUMN asynch_task.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN asynch_task.deleted_at IS '删除时间(软删)';
|
||||
COMMENT ON COLUMN asynch_task.deleted_at IS '删除时间(软删)';
|
||||
COMMENT ON COLUMN asynch_task.model_name IS '模型名称';
|
||||
COMMENT ON COLUMN asynch_task.task_id IS '任务ID(对外返回)';
|
||||
COMMENT ON COLUMN asynch_task.biz_name IS '业务名称(调用方模块/系统)';
|
||||
COMMENT ON COLUMN asynch_task.callback_url IS '回调地址(可选,用于后续业务通知)';
|
||||
COMMENT ON COLUMN asynch_task.model_key IS '动态请求头(用于覆盖/补充模型配置 head_msg),如 X-API-Key:xxx';
|
||||
COMMENT ON COLUMN asynch_task.task_id IS '任务ID(对外返回)';
|
||||
COMMENT ON COLUMN asynch_task.biz_name IS '业务名称(调用方模块/系统)';
|
||||
COMMENT ON COLUMN asynch_task.callback_url IS '回调地址(可选,用于后续业务通知)';
|
||||
COMMENT ON COLUMN asynch_task.model_key IS '动态请求头(用于覆盖/补充模型配置 head_msg),如 X-API-Key:xxx';
|
||||
COMMENT ON COLUMN asynch_task.state IS '0排队中/1执行中/2成功/3失败/4已下载';
|
||||
COMMENT ON COLUMN asynch_task.oss_file IS '结果文件OSS地址';
|
||||
COMMENT ON COLUMN asynch_task.file_type IS '文件类型(mp3/mp4/png/...)';
|
||||
COMMENT ON COLUMN asynch_task.file_size IS '文件大小(字节)';
|
||||
COMMENT ON COLUMN asynch_task.file_type IS '文件类型(mp3/mp4/png/...)';
|
||||
COMMENT ON COLUMN asynch_task.file_size IS '文件大小(字节)';
|
||||
COMMENT ON COLUMN asynch_task.error_msg IS '错误信息';
|
||||
COMMENT ON COLUMN asynch_task.started_at IS '开始执行时间';
|
||||
COMMENT ON COLUMN asynch_task.finished_at IS '执行结束时间';
|
||||
COMMENT ON COLUMN asynch_task.duration_seconds IS '耗时(秒):从创建到完成(成功/失败)整体耗时';
|
||||
COMMENT ON COLUMN asynch_task.duration_seconds IS '耗时(秒):从创建到完成(成功/失败)整体耗时';
|
||||
COMMENT ON COLUMN asynch_task.expire_at IS 'state=4 后写入,用于清理';
|
||||
COMMENT ON COLUMN asynch_task.retry_count IS '已重试次数(不含首次)';
|
||||
COMMENT ON COLUMN asynch_task.enqueue_at IS '入队时间(用于排队顺序)';
|
||||
COMMENT ON COLUMN asynch_task.phase IS '执行阶段:0模型阶段/1OSS阶段(模型已成功,等待上传OSS)';
|
||||
COMMENT ON COLUMN asynch_task.tmp_file IS '临时结果文件路径(phase=1 时仅重试 OSS 上传)';
|
||||
COMMENT ON COLUMN asynch_task.input_ref IS '输入引用(如OSS/业务资源ID等)';
|
||||
COMMENT ON COLUMN asynch_task.request_payload IS '请求参数(可选,JSON)';
|
||||
COMMENT ON COLUMN asynch_task.retry_count IS '已重试次数(不含首次)';
|
||||
COMMENT ON COLUMN asynch_task.enqueue_at IS '入队时间(用于排队顺序)';
|
||||
COMMENT ON COLUMN asynch_task.phase IS '执行阶段 模型阶段/1OSS阶段(模型已成功,等待上传OSS)';
|
||||
COMMENT ON COLUMN asynch_task.tmp_file IS '临时结果文件路径(phase=1 时仅重试 OSS 上传)';
|
||||
COMMENT ON COLUMN asynch_task.input_ref IS '输入引用(如OSS/业务资源ID等)';
|
||||
COMMENT ON COLUMN asynch_task.request_payload IS '请求参数(可选,JSON)';
|
||||
COMMENT ON COLUMN asynch_task.text_result IS '文本类结果(可选,支持直接回调)';
|
||||
COMMENT ON COLUMN asynch_task.epicycle_id IS '轮次ID(用于标识同一轮次的任务)';
|
||||
COMMENT ON COLUMN asynch_task.expend_tokens IS '消耗 token 数';
|
||||
|
||||
|
||||
|
||||
-- =========================
|
||||
-- 3) asynch_op_log
|
||||
-- 3) logs_model_op
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS asynch_op_log (
|
||||
-- 基础字段(与现有表保持一致)
|
||||
CREATE TABLE IF NOT EXISTS logs_model_op (
|
||||
-- 基础字段
|
||||
id BIGINT PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL DEFAULT 0,
|
||||
creator VARCHAR(64) NOT NULL,
|
||||
@@ -211,64 +183,60 @@ CREATE TABLE IF NOT EXISTS asynch_op_log (
|
||||
updater VARCHAR(64) NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP(6),
|
||||
|
||||
-- 基础审计信息
|
||||
ip VARCHAR(64) DEFAULT '',
|
||||
user_agent VARCHAR(256) DEFAULT '',
|
||||
api_path VARCHAR(256) DEFAULT '',
|
||||
http_method VARCHAR(16) DEFAULT '',
|
||||
|
||||
-- 业务信息
|
||||
biz_name VARCHAR(128) NOT NULL DEFAULT '', -- 调用方业务模块/系统
|
||||
model_name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
task_id VARCHAR(64) NOT NULL DEFAULT '',
|
||||
|
||||
-- 统计字段
|
||||
op_type VARCHAR(64) NOT NULL DEFAULT 'createTask', -- 操作类型(默认创建任务)
|
||||
op_type VARCHAR(64) NOT NULL DEFAULT 'createTask', -- 操作类型(默认创建任务)
|
||||
success SMALLINT NOT NULL DEFAULT 1, -- 1成功/0失败
|
||||
error_msg TEXT DEFAULT '',
|
||||
cost_ms BIGINT NOT NULL DEFAULT 0, -- 耗时(毫秒)
|
||||
|
||||
-- 请求/响应 JSON(用于后期统计分析)
|
||||
cost_ms BIGINT NOT NULL DEFAULT 0, -- 耗时(毫秒)
|
||||
-- 请求/响应 JSON(用于后期统计分析)
|
||||
request_payload JSONB,
|
||||
response_payload JSONB
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_op_log_tenant_time ON asynch_op_log(tenant_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_op_log_model_name ON asynch_op_log(model_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_op_log_biz_name ON asynch_op_log(biz_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_op_log_task_id ON asynch_op_log(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_op_log_op_type ON asynch_op_log(op_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_op_log_deleted_at ON asynch_op_log(deleted_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_model_op_tenant_time ON logs_model_op(tenant_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_model_op_model_name ON logs_model_op(model_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_model_op_biz_name ON logs_model_op(biz_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_model_op_task_id ON logs_model_op(task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_model_op_op_type ON logs_model_op(op_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_model_op_deleted_at ON logs_model_op(deleted_at);
|
||||
|
||||
COMMENT ON TABLE asynch_op_log IS '操作记录日志表(创建任务等,用于统计)';
|
||||
COMMENT ON COLUMN asynch_op_log.id IS '主键ID(非自增)';
|
||||
COMMENT ON COLUMN asynch_op_log.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN asynch_op_log.creator IS '创建人';
|
||||
COMMENT ON COLUMN asynch_op_log.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN asynch_op_log.updater IS '更新人';
|
||||
COMMENT ON COLUMN asynch_op_log.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN asynch_op_log.deleted_at IS '删除时间(软删)';
|
||||
COMMENT ON COLUMN asynch_op_log.ip IS '客户端IP';
|
||||
COMMENT ON COLUMN asynch_op_log.user_agent IS 'User-Agent';
|
||||
COMMENT ON COLUMN asynch_op_log.api_path IS '接口路径';
|
||||
COMMENT ON COLUMN asynch_op_log.http_method IS 'HTTP方法';
|
||||
COMMENT ON COLUMN asynch_op_log.biz_name IS '业务名称(调用方模块/系统)';
|
||||
COMMENT ON COLUMN asynch_op_log.model_name IS '模型名称';
|
||||
COMMENT ON COLUMN asynch_op_log.task_id IS '任务ID';
|
||||
COMMENT ON COLUMN asynch_op_log.op_type IS '操作类型(如 createTask/getTaskResult/getTaskBatch 等)';
|
||||
COMMENT ON COLUMN asynch_op_log.success IS '是否成功:1成功/0失败';
|
||||
COMMENT ON COLUMN asynch_op_log.error_msg IS '错误信息(失败时)';
|
||||
COMMENT ON COLUMN asynch_op_log.cost_ms IS '耗时(毫秒)';
|
||||
COMMENT ON COLUMN asynch_op_log.request_payload IS '请求 JSON';
|
||||
COMMENT ON COLUMN asynch_op_log.response_payload IS '响应 JSON';
|
||||
COMMENT ON TABLE logs_model_op IS '操作记录日志表(创建任务等,用于统计)';
|
||||
COMMENT ON COLUMN logs_model_op.id IS '主键ID(非自增)';
|
||||
COMMENT ON COLUMN logs_model_op.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN logs_model_op.creator IS '创建人';
|
||||
COMMENT ON COLUMN logs_model_op.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN logs_model_op.updater IS '更新人';
|
||||
COMMENT ON COLUMN logs_model_op.updated_at IS '更新时间';
|
||||
COMMENT ON COLUMN logs_model_op.deleted_at IS '删除时间(软删)';
|
||||
COMMENT ON COLUMN logs_model_op.ip IS '客户端IP';
|
||||
COMMENT ON COLUMN logs_model_op.user_agent IS 'User-Agent';
|
||||
COMMENT ON COLUMN logs_model_op.api_path IS '接口路径';
|
||||
COMMENT ON COLUMN logs_model_op.http_method IS 'HTTP方法';
|
||||
COMMENT ON COLUMN logs_model_op.biz_name IS '业务名称(调用方模块/系统)';
|
||||
COMMENT ON COLUMN logs_model_op.model_name IS '模型名称';
|
||||
COMMENT ON COLUMN logs_model_op.task_id IS '任务ID';
|
||||
COMMENT ON COLUMN logs_model_op.op_type IS '操作类型(如 createTask/getTaskResult/getTaskBatch 等)';
|
||||
COMMENT ON COLUMN logs_model_op.success IS '是否成功:1成功/0失败';
|
||||
COMMENT ON COLUMN logs_model_op.error_msg IS '错误信息(失败时)';
|
||||
COMMENT ON COLUMN logs_model_op.cost_ms IS '耗时(毫秒)';
|
||||
COMMENT ON COLUMN logs_model_op.request_payload IS '请求 JSON';
|
||||
COMMENT ON COLUMN logs_model_op.response_payload IS '响应 JSON';
|
||||
|
||||
|
||||
-- =========================
|
||||
-- 4) asynch_model_stat
|
||||
-- 4) logs_model_stat
|
||||
-- =========================
|
||||
CREATE TABLE IF NOT EXISTS asynch_model_stat (
|
||||
day DATE NOT NULL, -- 天(YYYY-MM-DD)
|
||||
CREATE TABLE IF NOT EXISTS logs_model_stat (
|
||||
day DATE NOT NULL, -- 天(YYYY-MM-DD)
|
||||
tenant_id BIGINT NOT NULL DEFAULT 0, -- 租户ID
|
||||
creator VARCHAR(64) NOT NULL DEFAULT '', -- 创建人
|
||||
model_name VARCHAR(128) NOT NULL DEFAULT '', -- 模型名称
|
||||
@@ -279,16 +247,16 @@ CREATE TABLE IF NOT EXISTS asynch_model_stat (
|
||||
);
|
||||
|
||||
-- 便于时间段/租户/人/模型过滤
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_model_stat_tenant_day ON asynch_model_stat(tenant_id, day);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_model_stat_day ON asynch_model_stat(day);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_model_stat_model_name ON asynch_model_stat(model_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_asynch_model_stat_creator ON asynch_model_stat(creator);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_model_stat_tenant_day ON logs_model_stat(tenant_id, day);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_model_stat_day ON logs_model_stat(day);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_model_stat_model_name ON logs_model_stat(model_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_model_stat_creator ON logs_model_stat(creator);
|
||||
|
||||
COMMENT ON TABLE asynch_model_stat IS '按天模型请求统计(用于限流/监控)';
|
||||
COMMENT ON COLUMN asynch_model_stat.day IS '天(YYYY-MM-DD)';
|
||||
COMMENT ON COLUMN asynch_model_stat.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN asynch_model_stat.creator IS '创建人';
|
||||
COMMENT ON COLUMN asynch_model_stat.model_name IS '模型名称';
|
||||
COMMENT ON COLUMN asynch_model_stat.request_count IS '请求次数';
|
||||
COMMENT ON COLUMN asynch_model_stat.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN asynch_model_stat.updated_at IS '更新时间';
|
||||
COMMENT ON TABLE logs_model_stat IS '按天模型请求统计(用于限流/监控)';
|
||||
COMMENT ON COLUMN logs_model_stat.day IS '天(YYYY-MM-DD)';
|
||||
COMMENT ON COLUMN logs_model_stat.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN logs_model_stat.creator IS '创建人';
|
||||
COMMENT ON COLUMN logs_model_stat.model_name IS '模型名称';
|
||||
COMMENT ON COLUMN logs_model_stat.request_count IS '请求次数';
|
||||
COMMENT ON COLUMN logs_model_stat.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN logs_model_stat.updated_at IS '更新时间';
|
||||
|
||||
Reference in New Issue
Block a user