package logic import ( "context" "encoding/json" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" v1 "tool-api/api/user/v1" "tool-api/internal/consts" "tool-api/internal/model/entity" ) func gcodeUnauthorized() gcode.Code { return gcode.New(401, "", nil) } func gcodeLocked() gcode.Code { return gcode.New(consts.CodeLocked, "", nil) } // ToolsList 工具目录(含模块与工具的锁定态) func ToolsList(ctx context.Context) (*v1.ToolsListRes, error) { user, err := getUserById(ctx, CtxUserId(ctx)) if err != nil { return nil, err } level, err := EffectiveLevelInfo(ctx, user) if err != nil { return nil, err } toolRecords, err := g.Model(consts.TableTools).Where("is_enabled", 1).Order("sort asc").All() if err != nil { return nil, err } modRecords, err := g.Model(consts.TableModules).Where("is_enabled", 1).Order("sort asc").All() if err != nil { return nil, err } // 额度快照:一次算完所有工具,避免前端再为每个工具单独请求 toolEntities := make([]*entity.Tools, 0, len(toolRecords)) for _, r := range toolRecords { tool := &entity.Tools{} if err = r.Struct(tool); err == nil { toolEntities = append(toolEntities, tool) } } quotaSnapshot, err := QuotaSnapshotForTools(ctx, user, toolEntities) if err != nil { // 额度查询失败不该拖垮工具目录:降级为不返回额度 g.Log().Warningf(ctx, "[quota] 工具额度快照失败: %v", err) quotaSnapshot = map[string]*v1.QuotaToolOut{} } res := &v1.ToolsListRes{Modules: []v1.ModuleOut{}, List: []v1.ToolOut{}} countByModule := map[string]int{} for _, t := range toolRecords { moduleKey := t["module_key"].String() countByModule[moduleKey]++ toolKey := t["tool_key"].String() res.List = append(res.List, v1.ToolOut{ ToolKey: toolKey, ModuleKey: moduleKey, Name: t["name"].String(), Icon: t["icon"].String(), Description: t["description"].String(), IsHot: t["is_hot"].Int() == 1, Locked: !slicesContain(level.Modules, moduleKey), Quota: quotaSnapshot[toolKey], }) } for _, m := range modRecords { moduleKey := m["module_key"].String() res.Modules = append(res.Modules, v1.ModuleOut{ ModuleKey: moduleKey, Name: m["name"].String(), Icon: m["icon"].String(), Locked: !slicesContain(level.Modules, moduleKey), EnabledToolsCount: countByModule[moduleKey], }) } return res, nil } func slicesContain(list []string, s string) bool { for _, v := range list { if v == s { return true } } return false } // ToolsUsed 工具使用上报(打开工具页时调用,锁定/下架时拒绝)。 // // ⚠️ 这里只记统计,**不扣额度**。额度扣减走 POST /quota/consume, // 时机是「产出结果」(保存/导出成功)而不是打开页面 —— 用户看了看没产出就不该扣。 func ToolsUsed(ctx context.Context, toolKey string) (*v1.ToolsUsedRes, error) { user, err := getUserById(ctx, CtxUserId(ctx)) if err != nil { return nil, err } level, err := EffectiveLevelInfo(ctx, user) if err != nil { return nil, err } tool, err := g.Model(consts.TableTools).Where("tool_key", toolKey).Where("is_enabled", 1).One() if err != nil { return nil, err } if tool.IsEmpty() { return nil, gerror.New("工具不存在或已下架") } if !slicesContain(level.Modules, tool["module_key"].String()) { return nil, gerror.NewCode(gcodeLocked(), "该功能需要升级等级后使用") } if _, err = g.Model(consts.TableUsageLogs).Data(g.Map{ "user_id": user.Id, "tool_key": toolKey, "used_at": gtime.Now(), }).Insert(); err != nil { return nil, err } if _, err = g.Model(consts.TableTools).Where("tool_key", toolKey). Increment("usage_count", 1); err != nil { return nil, err } return &v1.ToolsUsedRes{}, nil } // WorkbenchGet 我的工作台;未配置时返回热推工具作为默认组合 func WorkbenchGet(ctx context.Context) (*v1.WorkbenchGetRes, error) { user, err := getUserById(ctx, CtxUserId(ctx)) if err != nil { return nil, err } record, err := g.Model(consts.TableWorkbench).Where("user_id", user.Id).One() if err != nil { return nil, err } if !record.IsEmpty() { keys := []string{} _ = json.Unmarshal([]byte(record["tool_keys"].String()), &keys) return &v1.WorkbenchGetRes{ToolKeys: keys}, nil } // 首次进入:预置已授权模块下的热推工具 level, err := EffectiveLevelInfo(ctx, user) if err != nil { return nil, err } hot, err := g.Model(consts.TableTools).Where("is_enabled", 1).Where("is_hot", 1).Order("sort asc").All() if err != nil { return nil, err } keys := make([]string, 0, 4) for _, t := range hot { if slicesContain(level.Modules, t["module_key"].String()) { keys = append(keys, t["tool_key"].String()) } } return &v1.WorkbenchGetRes{ToolKeys: keys}, nil } // WorkbenchSave 保存工作台(校验启用 + 已授权) func WorkbenchSave(ctx context.Context, toolKeys []string) (*v1.WorkbenchSaveRes, error) { user, err := getUserById(ctx, CtxUserId(ctx)) if err != nil { return nil, err } level, err := EffectiveLevelInfo(ctx, user) if err != nil { return nil, err } if len(toolKeys) == 0 { return nil, gerror.New("工作台至少保留一个工具") } for _, key := range toolKeys { tool, err := g.Model(consts.TableTools).Where("tool_key", key).Where("is_enabled", 1).One() if err != nil { return nil, err } if tool.IsEmpty() { return nil, gerror.Newf("工具 %s 不存在或已下架", key) } if !slicesContain(level.Modules, tool["module_key"].String()) { return nil, gerror.NewCode(gcodeLocked(), "包含未解锁的工具,请升级等级") } } jsonStr, err := json.Marshal(toolKeys) if err != nil { return nil, err } if _, err = g.Model(consts.TableWorkbench).Data(g.Map{ "user_id": user.Id, "tool_keys": string(jsonStr), "updated_at": gtime.Now(), }).Save(); err != nil { return nil, err } return &v1.WorkbenchSaveRes{}, nil } // FeedbackSubmit 提交反馈 func FeedbackSubmit(ctx context.Context, content, contact string) (*v1.FeedbackSubmitRes, error) { if _, err := g.Model(consts.TableFeedbacks).Data(g.Map{ "user_id": CtxUserId(ctx), "content": content, "contact": contact, }).Insert(); err != nil { return nil, err } return &v1.FeedbackSubmitRes{}, nil }