219 lines
6.3 KiB
Go
219 lines
6.3 KiB
Go
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
|
||
}
|