diff --git a/config.yml b/config.yml index dc9f898..8491f74 100644 --- a/config.yml +++ b/config.yml @@ -1,5 +1,5 @@ server: - address: ":8001" + address: ":3004" name: "model-gateway" workerId: 1 # 雪花算法worker ID(用于 common/db/gfdb) @@ -29,14 +29,14 @@ database: redis: default: - address: 116.204.74.41:6379 + address: 192.168.3.30:6379 db: 0 consul: - address: 116.204.74.41:8500 + address: 192.168.3.30:8500 jaeger: - addr: 116.204.74.41:4318 + addr: 192.168.3.30:4318 # 本地调试用:可选自动执行 worker/cleaner(默认关闭) asynch: diff --git a/consts/public/table_name.go b/consts/public/table_name.go index 4f59f4e..8ee0bdf 100644 --- a/consts/public/table_name.go +++ b/consts/public/table_name.go @@ -1,9 +1,8 @@ package public const ( - TableNameModel = "asynch_models" // 模型表 - TableNameModelType = "asynch_models_type" // 模型类型表 - TableNameTask = "asynch_task" // 任务表 - TableNameOpLog = "logs_model_op" // 操作日志表 - TableNameStat = "logs_model_stat" // 按天统计表(请求次数) + TableNameModel = "asynch_models" // 模型表 + TableNameTask = "asynch_task" // 任务表 + TableNameOpLog = "logs_model_op" // 操作日志表 + TableNameStat = "logs_model_stat" // 按天统计表(请求次数) ) diff --git a/controller/model_controller.go b/controller/model_controller.go index acd9191..204e737 100644 --- a/controller/model_controller.go +++ b/controller/model_controller.go @@ -3,9 +3,9 @@ package controller import ( "context" - "model-asynch/model/dto" - "model-asynch/model/entity" - "model-asynch/service" + "model-gateway/model/dto" + "model-gateway/model/entity" + "model-gateway/service" "gitea.com/red-future/common/beans" ) @@ -38,21 +38,15 @@ func (c *model) GetModel(ctx context.Context, req *dto.GetModelReq) (res *dto.Ge if err != nil { return nil, err } + if model == nil { + return nil, nil + } return &dto.GetModelRes{Model: model}, nil } // ListModel 配置列表 func (c *model) ListModel(ctx context.Context, req *dto.ListModelReq) (res *dto.ListModelRes, err error) { - pageNum, pageSize := 1, 10 //默认分页参数 - if req != nil { - if req.PageNum > 0 { - pageNum = req.PageNum - } - if req.PageSize > 0 { - pageSize = req.PageSize - } - } - list, total, err := service.Model.List(ctx, pageNum, pageSize, req) + list, total, err := service.Model.List(ctx, req) if err != nil { return nil, err } diff --git a/controller/stat_controller.go b/controller/stat_controller.go index fd77cb5..dba21ad 100644 --- a/controller/stat_controller.go +++ b/controller/stat_controller.go @@ -3,8 +3,8 @@ package controller import ( "context" - "model-asynch/model/dto" - "model-asynch/service" + "model-gateway/model/dto" + "model-gateway/service" ) type stat struct{} diff --git a/controller/task_controller.go b/controller/task_controller.go index 64e0801..f305ae8 100644 --- a/controller/task_controller.go +++ b/controller/task_controller.go @@ -3,8 +3,8 @@ package controller import ( "context" - "model-asynch/model/dto" - "model-asynch/service" + "model-gateway/model/dto" + "model-gateway/service" ) type task struct{} diff --git a/dao/model_dao.go b/dao/model_dao.go index f97f263..7e3bf9f 100644 --- a/dao/model_dao.go +++ b/dao/model_dao.go @@ -4,11 +4,12 @@ import ( "context" "fmt" - "model-asynch/consts/public" - "model-asynch/model/dto" - "model-asynch/model/entity" + "model-gateway/consts/public" + "model-gateway/model/dto" + "model-gateway/model/entity" "gitea.com/red-future/common/db/gfdb" + "gitea.com/red-future/common/utils" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" ) @@ -17,8 +18,13 @@ var Model = &modelDao{} type modelDao struct{} -func (d *modelDao) Insert(ctx context.Context, m *entity.AsynchModel) (id int64, err error) { - r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).Data(m).Insert() +func (d *modelDao) Insert(ctx context.Context, req *dto.CreateModelReq) (id int64, err error) { + asyncModel := new(entity.AsynchModel) + err = gconv.Struct(req, &asyncModel) + if err != nil { + return + } + r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel).Data(asyncModel).Insert() if err != nil { return 0, err } @@ -38,20 +44,6 @@ func (d *modelDao) Update(ctx context.Context, m *dto.UpdateModelReq) (rows int6 return r.RowsAffected() } -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). @@ -78,6 +70,7 @@ func (d *modelDao) GetByModelName(ctx context.Context, modelName string) (m *ent func (d *modelDao) Get(ctx context.Context, id int64) (m *entity.AsynchModel, err error) { r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel). + NoTenantId(ctx). Where(entity.AsynchModelCol.Id, id). One() if err != nil { @@ -90,6 +83,13 @@ func (d *modelDao) Get(ctx context.Context, id int64) (m *entity.AsynchModel, er return } +func (d *modelDao) Count(ctx context.Context, req *dto.GetModelReq) (count int, err error) { + count, err = gfdb.DB(ctx).Model(ctx, public.TableNameModel).OmitEmpty(). + Where(entity.AsynchModelCol.Creator, req.Creator). + Where(entity.AsynchModelCol.Id, req.ID).Count() + return +} + func (d *modelDao) List(ctx context.Context, pageNum, pageSize int, modelNameLike string, modelType int, isPrivate int) (list []*entity.AsynchModel, total int64, err error) { model := gfdb.DB(ctx).Model(ctx, public.TableNameModel). OrderDesc(entity.AsynchModelCol.CreatedAt) @@ -97,7 +97,7 @@ func (d *modelDao) List(ctx context.Context, pageNum, pageSize int, modelNameLik model = model.WhereLike(entity.AsynchModelCol.ModelName, "%"+modelNameLike+"%") } if modelType != 0 { - model = model.Where(entity.AsynchModelCol.ModelsType, modelType) + model = model.Where(entity.AsynchModelCol.ModelType, modelType) } if isPrivate != 0 { model = model.Where(entity.AsynchModelCol.IsPrivate, isPrivate) @@ -150,39 +150,64 @@ func (d *modelDao) ListByCreatorAndPlatform(ctx context.Context, creator string, err = r.Structs(&list) return } - -func (d *modelDao) GetByCreatorAndPlatform(ctx context.Context, creator string, modelNameLike string, modelType int, isPrivate 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+"%") +func (d *modelDao) GetByCreatorAndPlatform(ctx context.Context, req *dto.ListModelReq) (list []*entity.AsynchModel, total int, err error) { + // 基础 SQL + sql := ` +SELECT DISTINCT ON (model_name) * +FROM asynch_models +WHERE deleted_at IS NULL + AND (? = '' OR model_name LIKE ?) + AND (? = 0 OR model_type = ?) +` + args := []any{ + req.ModelName, "%" + req.ModelName + "%", + req.ModelType, req.ModelType, } - if modelType != 0 { - whereSQL += " AND models_type = ?" - args = append(args, modelType) + if !g.IsEmpty(req.IsPrivate) { + sql += ` AND is_private = ? ` + args = append(args, req.IsPrivate) } - if isPrivate != 0 { - whereSQL += " AND is_private = ?" - args = append(args, isPrivate) + if req.IsOwner != nil && *req.IsOwner == 0 { + sql += ` AND creator = ? AND is_owner = ? ` + args = append(args, req.Creator) + args = append(args, req.IsOwner) + } else if req.IsOwner != nil && *req.IsOwner == 1 { + if req.Enabled != nil && *req.Enabled == 1 { + sql += ` AND ((creator = ? AND is_owner = ? AND enabled=1) OR (is_owner = 0 AND enabled=1)) ` + } else if req.Enabled != nil && *req.Enabled == 0 { + sql += ` AND ((creator = ? AND is_owner = ? AND enabled=0) OR (is_owner = 0 AND enabled=1)) ` + } else { + sql += ` AND ((creator = ? AND is_owner = ?) OR (is_owner = 0 AND enabled=1)) ` + } + args = append(args, req.Creator) + args = append(args, req.IsOwner) } - querySQL := fmt.Sprintf("SELECT * FROM %s WHERE %s ORDER BY created_at DESC", public.TableNameModel, whereSQL) + // 最后拼接排序 + sql += ` ORDER BY model_name, is_owner DESC, created_at DESC` - r, err := gfdb.DB(ctx).GetAll(ctx, querySQL, args...) + r, err := gfdb.DB(ctx).GetAll(ctx, sql, args...) if err != nil { - return nil, err + return nil, 0, err } err = r.Structs(&list) + if err != nil { + return nil, 0, err + } + + total = len(list) return } -func (d *modelDao) GetByIsChatModel(ctx context.Context, userName string) (m *entity.AsynchModel, err error) { +func (d *modelDao) GetByIsChatModel(ctx context.Context) (m *entity.AsynchModel, err error) { + userInfo, err := utils.GetUserInfo(ctx) + if err != nil { + return nil, err + } r, err := gfdb.DB(ctx).Model(ctx, public.TableNameModel). Where(entity.AsynchModelCol.IsChatModel, 1). - Where(entity.AsynchModelCol.Creator, userName). + Where(entity.AsynchModelCol.Creator, userInfo.UserName). One() if err != nil { return nil, err diff --git a/dao/model_dao_bg.go b/dao/model_dao_bg.go index 34715eb..beddd2f 100644 --- a/dao/model_dao_bg.go +++ b/dao/model_dao_bg.go @@ -3,8 +3,8 @@ package dao import ( "context" - "model-asynch/consts/public" - "model-asynch/model/entity" + "model-gateway/consts/public" + "model-gateway/model/entity" "gitea.com/red-future/common/db/gfdb" ) diff --git a/dao/op_log_dao.go b/dao/op_log_dao.go index 2d009de..6888827 100644 --- a/dao/op_log_dao.go +++ b/dao/op_log_dao.go @@ -3,8 +3,8 @@ package dao import ( "context" - "model-asynch/consts/public" - "model-asynch/model/entity" + "model-gateway/consts/public" + "model-gateway/model/entity" "gitea.com/red-future/common/db/gfdb" ) diff --git a/dao/stat_dao.go b/dao/stat_dao.go index e6e89e2..e6cdfdc 100644 --- a/dao/stat_dao.go +++ b/dao/stat_dao.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "model-asynch/consts/public" - "model-asynch/model/entity" + "model-gateway/consts/public" + "model-gateway/model/entity" "gitea.com/red-future/common/db/gfdb" "github.com/gogf/gf/v2/os/gtime" diff --git a/dao/task_dao.go b/dao/task_dao.go index 5f4a7d0..fcaaffd 100644 --- a/dao/task_dao.go +++ b/dao/task_dao.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "model-asynch/consts/public" - "model-asynch/model/entity" + "model-gateway/consts/public" + "model-gateway/model/entity" "gitea.com/red-future/common/db/gfdb" "github.com/gogf/gf/v2/database/gdb" diff --git a/dao/task_dao_bg.go b/dao/task_dao_bg.go index 3bbc9f5..500c69f 100644 --- a/dao/task_dao_bg.go +++ b/dao/task_dao_bg.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "model-asynch/consts/public" - "model-asynch/model/entity" + "model-gateway/consts/public" + "model-gateway/model/entity" "gitea.com/red-future/common/db/gfdb" "github.com/gogf/gf/v2/database/gdb" diff --git a/go.mod b/go.mod index ff0a09c..3732215 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ -module model-asynch +module model-gateway go 1.26.0 require ( - gitea.com/red-future/common v0.0.19 // indirect + gitea.com/red-future/common v0.0.19 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 diff --git a/go.sum b/go.sum index daabfa3..3966516 100644 --- a/go.sum +++ b/go.sum @@ -603,6 +603,7 @@ 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.7/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= diff --git a/main.go b/main.go index d8873b3..28b7e77 100644 --- a/main.go +++ b/main.go @@ -7,8 +7,8 @@ import ( "syscall" "time" - "model-asynch/controller" - "model-asynch/service" + "model-gateway/controller" + "model-gateway/service" "gitea.com/red-future/common/http" "gitea.com/red-future/common/jaeger" diff --git a/model/dto/model_dto.go b/model/dto/model_dto.go index c8ed514..d261cd6 100644 --- a/model/dto/model_dto.go +++ b/model/dto/model_dto.go @@ -1,6 +1,7 @@ package dto import ( + "gitea.com/red-future/common/beans" "github.com/gogf/gf/v2/frame/g" ) @@ -8,14 +9,15 @@ import ( type CreateModelReq struct { g.Meta `path:"/createModel" method:"post" tags:"模型管理" summary:"创建模型配置" dc:"添加新的模型配置"` ModelName string `p:"modelName" json:"modelName" v:"required#modelName不能为空" dc:"模型名称(唯一标识)"` - ModelsType int `p:"modelsType" json:"modelsType" v:"required#modelsType不能为空" dc:"模型类型:1-文本生成 2-图像生成 3-语音 4-视频 5-多模态"` + ModelType int `p:"modelType" json:"modelType" v:"required#modelType不能为空" dc:"模型类型:1-文本生成 2-图像生成 3-语音 4-视频 5-多模态"` BaseURL string `p:"baseUrl" json:"baseUrl" v:"required#baseUrl不能为空" dc:"模型服务基础地址(如 http(s)://host:port)"` HttpMethod string `p:"httpMethod" json:"httpMethod" dc:"请求方式:GET/POST(默认POST)"` 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:"调用凭证/密钥,用于模型认证"` + 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)"` + IsOwner *int `p:"isOwner" json:"isOwner" v:"in:0,1#是否为所有者参数只能为0或1" dc:"是否为所有者:0-否,1-是(默认0)"` + ApiKey string `p:"apiKey" json:"apiKey" dc:"调用凭证/密钥,用于模型认证"` Form any `p:"form" json:"form" dc:"动态表单配置(JSON),用于前端渲染配置项"` RequestMapping any `p:"requestMapping" json:"requestMapping" dc:"请求映射"` ResponseMapping any `p:"responseMapping" json:"responseMapping" dc:"返回映射"` @@ -38,17 +40,21 @@ type CreateModelRes struct { type UpdateModelReq struct { g.Meta `path:"/updateModel" method:"put" tags:"模型管理" summary:"更新模型配置" dc:"更新指定ID的模型配置"` ID int64 `p:"id" json:"id" v:"required#id不能为空" dc:"配置ID"` - ModelsType string `p:"modelsType" json:"modelsType" dc:"模型类型ID列表(逗号分隔)(可选更新)"` + ModelName string `p:"modelName" json:"modelName" dc:"模型名称(唯一标识)"` + ModelType int `p:"modelType" json:"modelType" 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:"请求头绑定(可选更新)"` + ApiKey string `p:"apiKey" json:"apiKey" 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)"` + Enabled *int `p:"enabled" json:"enabled" dc:"是否启用:0-禁用,1-启用(可选更新)"` + IsPrivate *int `p:"isPrivate" json:"isPrivate" v:"in:0,1#私有化参数只能为0或1" dc:"是否私有化:0-私有(默认) 1-公共"` + IsChatModel *int `p:"isChatModel" json:"isChatModel" v:"in:0,1#对话模型参数只能为0或1" dc:"是否为对话模型:0-否,1-是(默认0)"` + IsOwner *int `p:"isOwner" json:"isOwner" 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:"请求超时时间(秒)(可选更新)"` @@ -67,8 +73,9 @@ type DeleteModelReq struct { // GetModelReq 获取模型配置详情 type GetModelReq struct { - g.Meta `path:"/getModel" method:"get" tags:"模型管理" summary:"获取模型配置" dc:"根据模型ID获取配置详情"` - ID int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"配置ID"` + g.Meta `path:"/getModel" method:"get" tags:"模型管理" summary:"获取模型配置" dc:"根据模型ID获取配置详情"` + ID int64 `p:"id" json:"id,string" v:"required#id不能为空" dc:"配置ID"` + Creator string `p:"creator" json:"creator" dc:"创建人"` } type GetModelRes struct { @@ -78,16 +85,18 @@ type GetModelRes struct { // ListModelReq 配置列表 type ListModelReq struct { 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:"模型类型"` - IsPrivate int `p:"isPrivate" json:"isPrivate" dc:"是否私有化 0-私有 1-公共"` + Page *beans.Page `json:"page"` + ModelName string `p:"modelName" json:"modelName" dc:"模型名称(模糊查询,可选)"` + ModelType int `p:"modelType" json:"modelType" dc:"模型类型"` + Enabled *int `p:"enabled" json:"enabled" dc:"是否启用:0-禁用,1-启用"` + IsPrivate *int `p:"isPrivate" json:"isPrivate" dc:"是否私有化 0-私有 1-公共"` + IsOwner *int `p:"isOwner" json:"isOwner" dc:"是否为所有者 0-否 1-是"` + Creator string `p:"creator" json:"creator" dc:"创建人"` } type ListModelRes struct { - List any `json:"list" dc:"列表数据"` - Total int64 `json:"total" dc:"总数"` + List any `json:"list" dc:"列表数据"` + Total int `json:"total" dc:"总数"` } // AutoTuneReq 动态调参(由上层定时任务每小时触发一次) diff --git a/model/entity/asynch_model.go b/model/entity/asynch_model.go index fdca743..6d713ae 100644 --- a/model/entity/asynch_model.go +++ b/model/entity/asynch_model.go @@ -5,7 +5,7 @@ import "gitea.com/red-future/common/beans" type asynchModelCol struct { beans.SQLBaseCol ModelName string - ModelsType string + ModelType string BaseURL string HttpMethod string HeadMsg string @@ -27,12 +27,13 @@ type asynchModelCol struct { RetryQueueMaxSecs string AutoCleanSeconds string Remark string + IsOwner string } var AsynchModelCol = asynchModelCol{ SQLBaseCol: beans.DefSQLBaseCol, ModelName: "model_name", - ModelsType: "models_type", + ModelType: "model_type", BaseURL: "base_url", HttpMethod: "http_method", HeadMsg: "head_msg", @@ -54,13 +55,14 @@ var AsynchModelCol = asynchModelCol{ RetryQueueMaxSecs: "retry_queue_max_seconds", AutoCleanSeconds: "auto_clean_seconds", Remark: "remark", + IsOwner: "is_owner", } // AsynchModel 异步模型配置 type AsynchModel struct { beans.SQLBaseDO `orm:",inline"` ModelName string `orm:"model_name" json:"modelName"` - ModelsType int `orm:"models_type" json:"modelsType"` + ModelType int `orm:"model_type" json:"modelType"` BaseURL string `orm:"base_url" json:"baseUrl"` HttpMethod string `orm:"http_method" json:"httpMethod"` HeadMsg string `orm:"head_msg" json:"headMsg"` @@ -70,10 +72,10 @@ type AsynchModel struct { 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"` + 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"` + 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"` @@ -82,4 +84,5 @@ type AsynchModel struct { RetryQueueMaxSeconds int `orm:"retry_queue_max_seconds" json:"retryQueueMaxSeconds"` AutoCleanSeconds int `orm:"auto_clean_seconds" json:"autoCleanSeconds"` Remark string `orm:"remark" json:"remark"` + IsOwner *int `json:"isOwner" orm:"is_owner"` // 1=当前用户创建的,0=超级管理员的 } diff --git a/service/auto_tune.go b/service/auto_tune.go index 552357c..c36321c 100644 --- a/service/auto_tune.go +++ b/service/auto_tune.go @@ -5,8 +5,8 @@ import ( "fmt" "math" - "model-asynch/consts/public" - "model-asynch/model/entity" + "model-gateway/consts/public" + "model-gateway/model/entity" "gitea.com/red-future/common/db/gfdb" "github.com/gogf/gf/v2/frame/g" @@ -14,9 +14,9 @@ import ( // AutoTuneResult 单次调参结果(按 model_name) type AutoTuneResult struct { - ModelName string `json:"modelName"` // 模型名称(asynch_models.model_name) - Samples int `json:"samples"` // 统计样本数(窗口内 state=2/3 且 started_at/finished_at 非空的任务数量) - P90Exec float64 `json:"p90ExecSeconds"` // 执行耗时 P90(秒),口径:finished_at - started_at + ModelName string `json:"modelName"` // 模型名称(asynch_models.model_name) + Samples int `json:"samples"` // 统计样本数(窗口内 state=2/3 且 started_at/finished_at 非空的任务数量) + P90Exec float64 `json:"p90ExecSeconds"` // 执行耗时 P90(秒),口径:finished_at - started_at CapMaxConcurrency int `json:"capMaxConcurrency"` // 配置上限:asynch_models.max_concurrency(cap,不会被动态调参覆盖) OldMaxConcurrency int `json:"oldMaxConcurrency"` // 调参前运行时值(Redis),若无则等于 cap diff --git a/service/callback.go b/service/callback.go index 0d73838..bb7bee8 100644 --- a/service/callback.go +++ b/service/callback.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "model-asynch/model/entity" + "model-gateway/model/entity" "gitea.com/red-future/common/http" "github.com/gogf/gf/v2/frame/g" @@ -65,23 +65,3 @@ func triggerPromptsCallback(ctx context.Context, t *entity.AsynchTask, epicycleI } 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 -} diff --git a/service/cleaner.go b/service/cleaner.go index 07db871..0aea6eb 100644 --- a/service/cleaner.go +++ b/service/cleaner.go @@ -4,7 +4,7 @@ import ( "context" "time" - "model-asynch/dao" + "model-gateway/dao" "github.com/gogf/gf/v2/frame/g" ) diff --git a/service/model_invoker.go b/service/model_invoker.go index eb62e94..6b1d7d9 100644 --- a/service/model_invoker.go +++ b/service/model_invoker.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "model-asynch/model/entity" + "model-gateway/model/entity" "github.com/gogf/gf/v2/container/gvar" "github.com/gogf/gf/v2/frame/g" diff --git a/service/model_service.go b/service/model_service.go index 42f6efc..7eaa562 100644 --- a/service/model_service.go +++ b/service/model_service.go @@ -3,13 +3,15 @@ package service import ( "context" "errors" - "sort" - - "model-asynch/dao" - "model-asynch/model/dto" - "model-asynch/model/entity" + "model-gateway/dao" + "model-gateway/model/dto" + "model-gateway/model/entity" + "gitea.com/red-future/common/beans" + "gitea.com/red-future/common/db/gfdb" + "gitea.com/red-future/common/http" "gitea.com/red-future/common/utils" + "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" ) @@ -18,32 +20,45 @@ var Model = &modelService{} 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: 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, +// IsSuperAdmin 调用admin-go服务检查是否是超级管理员 +func (s *modelService) 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 } - id, err := dao.Model.Insert(ctx, m) + return r["isSuperAdmin"], err +} + +func (s *modelService) Create(ctx context.Context, req *dto.CreateModelReq) (res *dto.CreateModelRes, err error) { + // 获取当前会话模型 + if !g.IsEmpty(req.IsChatModel) && *req.IsChatModel == 1 { + var model *entity.AsynchModel + model, err = dao.Model.GetByIsChatModel(ctx) + if err != nil { + return nil, err + } + // 如果有会话模型,那就改变为 0 + if model != nil { + _, err = dao.Model.Update(ctx, &dto.UpdateModelReq{ + ID: model.Id, + IsChatModel: gconv.PtrInt(0), + }) + if err != nil { + return nil, err + } + } + } + + req.IsOwner = gconv.PtrInt(1) + admin, err := s.IsSuperAdmin(ctx) + if err != nil { + return + } + if admin { + req.IsOwner = gconv.PtrInt(0) + } + id, err := dao.Model.Insert(ctx, req) if err != nil { return nil, err } @@ -52,23 +67,55 @@ func (s *modelService) Create(ctx context.Context, req *dto.CreateModelReq) (res func (s *modelService) Update(ctx context.Context, req *dto.UpdateModelReq) error { //根据当前 isChatModel 来判断是否更新模型 - if req.IsChatModel == 1 { - user, err := utils.GetUserInfo(ctx) - if err != nil { - return err - } + if req.IsChatModel == gconv.PtrInt(1) { //判断当前用户是否有会话模型 - model, err := dao.Model.GetByIsChatModel(ctx, user.UserName) + model, err := dao.Model.GetByIsChatModel(ctx) if err != nil { return err } if model != nil { - return errors.New("用户已存在会话模型,不能创建新的会话模型") + return errors.New("用户已存在会话模型,不能创建") } - _, err = dao.Model.Update(ctx, req) + } + + req.IsOwner = gconv.PtrInt(1) + admin, err := s.IsSuperAdmin(ctx) + if err != nil { return err } - _, err := dao.Model.Update(ctx, req) + if admin { + req.IsOwner = gconv.PtrInt(0) + _, err = dao.Model.Update(ctx, req) + if err != nil { + return err + } + return nil + } + + var user *beans.User + user, err = utils.GetUserInfo(ctx) + if err != nil { + return err + } + // 判断当前传过来的模型id的模型是否是超级管理员的。如果是超管的进行创建,否则更新 + var count int + count, err = dao.Model.Count(ctx, &dto.GetModelReq{ + ID: req.ID, + Creator: user.UserName, + }) + if err != nil { + return err + } + if count == 0 { + insertDto := new(dto.CreateModelReq) + err = gconv.Struct(req, insertDto) + if err != nil { + return err + } + _, err = dao.Model.Insert(ctx, insertDto) + return err + } + _, err = dao.Model.Update(ctx, req) return err } @@ -89,27 +136,29 @@ func (s *modelService) Get(ctx context.Context, id int64) (*entity.AsynchModel, return model, nil } -func (s *modelService) List(ctx context.Context, pageNum, pageSize int, req *dto.ListModelReq) (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 - } - +func (s *modelService) List(ctx context.Context, req *dto.ListModelReq) (list []*entity.AsynchModel, total int, err error) { var models []*entity.AsynchModel - var count int64 - if isSuperAdmin { - models, count, err = dao.Model.List(ctx, pageNum, pageSize, req.ModelName, req.ModelType, req.IsPrivate) - } else { - models, count, err = s.getModelsWithDedup(ctx, user.UserName, pageNum, pageSize, req.ModelName, req.ModelType, req.IsPrivate) + req.IsOwner = gconv.PtrInt(1) + admin, err := s.IsSuperAdmin(ctx) + if err != nil { + return } + if admin { + req.IsOwner = gconv.PtrInt(0) + } + + var user *beans.User + user, err = utils.GetUserInfo(ctx) if err != nil { return nil, 0, err } + req.Creator = user.UserName + + models, total, err = dao.Model.GetByCreatorAndPlatform(ctx, req) + if err != nil { + return + } // 处理列表中每条记录的 JSONB 字段 for _, m := range models { @@ -118,61 +167,7 @@ func (s *modelService) List(ctx context.Context, pageNum, pageSize int, req *dto 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, isPrivate int) (list []*entity.AsynchModel, total int64, err error) { - // 1. 查全量数据(不分页,便于去重) - allModels, err := dao.Model.GetByCreatorAndPlatform(ctx, creator, modelNameLike, modelType, isPrivate) - 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 + return models, total, nil } // GetModelTypesFromConfig 从配置文件读取模型类型 @@ -202,11 +197,6 @@ func GetModelTypesFromConfig(ctx context.Context) map[int]string { } 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 { @@ -217,48 +207,40 @@ func (s *modelService) UpdateChatModel(ctx context.Context, req *dto.UpdateChatM } // 获取当前用户会话模型 - currentModel, err := dao.Model.GetByIsChatModel(ctx, user.UserName) + currentModel, err := dao.Model.GetByIsChatModel(ctx) if err != nil { return err } - if currentModel.ModelsType != 1 { - return errors.New("当前模型为非推理模型,不能设置为会话模型") - } + err = gfdb.DB(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + if !g.IsEmpty(currentModel) { + if currentModel.ModelType != 1 { + return errors.New("当前模型为非推理模型,不能设置为会话模型") + } - // 如果点击的就是当前会话模型(已经是1),取消它(设为0) - if currentModel != nil && currentModel.Id == req.Id { - _, err = dao.Model.UpdateByID(ctx, &dto.UpdateModelReq{ + // 如果点击的就是当前会话模型(已经是1),取消它(设为0) + if currentModel.Id != req.Id { + _, err = dao.Model.Update(ctx, &dto.UpdateModelReq{ + ID: currentModel.Id, + IsChatModel: gconv.PtrInt(0), + }) + if err != nil { + return err + } + } + } + + // 设置当前为会话模型(设为1) + _, err = dao.Model.Update(ctx, &dto.UpdateModelReq{ ID: req.Id, - IsChatModel: 0, + IsChatModel: gconv.PtrInt(1), }) 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) + model, err := dao.Model.GetByIsChatModel(ctx) if err != nil { return nil, err } diff --git a/service/stat_service.go b/service/stat_service.go index c068e99..9aaf7c0 100644 --- a/service/stat_service.go +++ b/service/stat_service.go @@ -3,8 +3,8 @@ package service import ( "context" - "model-asynch/dao" - "model-asynch/model/dto" + "model-gateway/dao" + "model-gateway/model/dto" ) type statService struct{} diff --git a/service/storage.go b/service/storage.go index 920071f..759767a 100644 --- a/service/storage.go +++ b/service/storage.go @@ -4,7 +4,7 @@ import ( "context" "errors" - "model-asynch/model/entity" + "model-gateway/model/entity" ) // StorageService 结果存储(OSS/MinIO)抽象 diff --git a/service/storage_oss.go b/service/storage_oss.go index a5295c6..a938d4a 100644 --- a/service/storage_oss.go +++ b/service/storage_oss.go @@ -7,7 +7,7 @@ import ( "mime/multipart" "time" - "model-asynch/model/entity" + "model-gateway/model/entity" commonHttp "gitea.com/red-future/common/http" "github.com/gogf/gf/v2/frame/g" diff --git a/service/task_service.go b/service/task_service.go index e28cf32..91941f7 100644 --- a/service/task_service.go +++ b/service/task_service.go @@ -3,11 +3,12 @@ package service import ( "context" "errors" + "fmt" "time" - "model-asynch/dao" - "model-asynch/model/dto" - "model-asynch/model/entity" + "model-gateway/dao" + "model-gateway/model/dto" + "model-gateway/model/entity" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" @@ -20,6 +21,7 @@ var Task = &taskService{} type taskService struct{} func (s *taskService) Create(ctx context.Context, req *dto.CreateTaskReq) (res *dto.CreateTaskRes, err error) { + fmt.Printf("打印请求:%+v", req) startAt := time.Now() // 固化 token/user 等信息 ctx = asyncCtx(ctx) @@ -29,7 +31,7 @@ func (s *taskService) Create(ctx context.Context, req *dto.CreateTaskReq) (res * if err != nil { return nil, err } - if m == nil || m.Enabled != 1 { + if m == nil || (m.Enabled != nil && *m.Enabled != 1) { return nil, errors.New("模型不存在或未启用") } diff --git a/service/worker.go b/service/worker.go index 7dbed6a..cff9aab 100644 --- a/service/worker.go +++ b/service/worker.go @@ -7,8 +7,8 @@ import ( "time" "unicode/utf8" - "model-asynch/dao" - "model-asynch/model/entity" + "model-gateway/dao" + "model-gateway/model/entity" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/grpool" @@ -95,7 +95,7 @@ func (w *asyncWorker) handleOne(ctx context.Context, t *entity.AsynchTask, epicy // ================================ return } - if m == nil || m.Enabled != 1 { + if m == nil || (m.Enabled != nil && *m.Enabled != 1) { errMsg := "模型不存在或未启用" _ = dao.Task.UpdateFailedGlobal(ctx, t.Id, errMsg) ReleaseQueueSlot(ctx, t.ModelName, t.TaskID) @@ -172,9 +172,6 @@ func (w *asyncWorker) handleOne(ctx context.Context, t *entity.AsynchTask, epicy contentType, ext = DetectFileType(data) 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 != "" { diff --git a/update.sql b/update.sql index 9f740ab..4dc2578 100644 --- a/update.sql +++ b/update.sql @@ -18,13 +18,14 @@ CREATE TABLE IF NOT EXISTS asynch_models ( deleted_at TIMESTAMP(6), -- 删除时间(软删) -- 业务字段 model_name VARCHAR(128) NOT NULL, -- 模型名称 - models_type SMALLINT NOT NULL DEFAULT 0, -- 模型类型 + model_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-是 + is_owner SMALLINT NOT NULL DEFAULT 99, -- 1=当前用户创建的,0=超级管理员的 api_key VARCHAR(256) NOT NULL DEFAULT '', -- 调用凭证,密钥 prompt TEXT NOT NULL DEFAULT '', -- 提示词内容(文本) form_json JSONB NOT NULL DEFAULT '{}'::jsonb, -- 表单结构(用于前端渲染) @@ -46,7 +47,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS uk_asynch_models_tenant_creator_chat ON asynch 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_model_type ON asynch_models(model_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); @@ -60,13 +61,14 @@ 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 '模型类型'; +COMMENT ON COLUMN asynch_models.model_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.is_owner IS '1=当前用户创建的,0=超级管理员的'; COMMENT ON COLUMN asynch_models.api_key IS '调用凭证,密钥'; COMMENT ON COLUMN asynch_models.prompt IS '提示词内容(文本)'; COMMENT ON COLUMN asynch_models.form_json IS '表单结构(用于前端渲染,也用于后端校验)'; @@ -97,7 +99,7 @@ CREATE TABLE IF NOT EXISTS asynch_task ( 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(对外返回)