Files
qitongxue-api/internal/logic/tools.go
2026-09-17 17:38:45 +08:00

219 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}