package logic import ( "context" "encoding/json" "time" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "golang.org/x/crypto/bcrypt" v1 "tool-api/api/admin/v1" "tool-api/internal/consts" ) // ===== 登录 ===== func AdminLogin(ctx context.Context, account, password string) (*v1.LoginRes, error) { record, err := g.Model(consts.TableAdminUsers).Where("account", account).One() if err != nil { return nil, err } if record.IsEmpty() { return nil, gerror.New("账号或密码错误") } if err = bcrypt.CompareHashAndPassword([]byte(record["password"].String()), []byte(password)); err != nil { return nil, gerror.New("账号或密码错误") } nickName := record["nick_name"].String() roleName := record["role_name"].String() roleValue := record["role_value"].String() token, err := IssueToken(ctx, consts.AudAdmin, record["id"].Int64(), map[string]string{ "nickName": nickName, "roleName": roleName, "roleValue": roleValue, }) if err != nil { return nil, err } return &v1.LoginRes{Token: token, NickName: nickName, RoleName: roleName, RoleValue: roleValue}, nil } func AdminMyInfo(ctx context.Context) (*v1.MyInfoRes, error) { record, err := g.Model(consts.TableAdminUsers).Where("id", CtxAdminId(ctx)).One() if err != nil { return nil, err } if record.IsEmpty() { return nil, gerror.NewCode(gcodeUnauthorized(), "管理员不存在") } return &v1.MyInfoRes{ Id: record["id"].Int64(), Account: record["account"].String(), NickName: record["nick_name"].String(), RoleName: record["role_name"].String(), RoleValue: record["role_value"].String(), }, nil } // ===== 用户管理 ===== func AdminUserList(ctx context.Context, req *v1.UserListReq) (*v1.UserListRes, error) { if req.Page <= 0 { req.Page = 1 } if req.PageSize <= 0 { req.PageSize = 20 } m := g.Model(consts.TableUsers) if req.Keyword != "" { kw := "%" + req.Keyword + "%" m = m.Where("nickname LIKE ? OR openid LIKE ?", kw, kw) } total, err := m.Count() if err != nil { return nil, err } records, err := m.Page(req.Page, req.PageSize).Order("id desc").All() if err != nil { return nil, err } levelNames, err := levelNameMap(ctx) if err != nil { return nil, err } list := make([]v1.AdminUserItem, 0, len(records)) for _, r := range records { item := v1.AdminUserItem{ Id: r["id"].Int64(), Nickname: r["nickname"].String(), AvatarUrl: r["avatar_url"].String(), Openid: r["openid"].String(), LevelKey: r["level_key"].String(), LevelName: levelNames[r["level_key"].String()], QuotaPeriodDays: r["quota_period_days"].Int(), Status: r["status"].Int(), CreatedAt: r["created_at"].String(), } if expire := r["level_expire_at"].GTime(); expire != nil && !expire.IsZero() { item.LevelExpireAt = expire.Format("Y-m-d") } list = append(list, item) } // 附加使用统计 if len(list) > 0 { ids := make([]int64, 0, len(list)) for _, u := range list { ids = append(ids, u.Id) } stats, err := g.Model(consts.TableUsageLogs). Fields("user_id, COUNT(*) AS c, MAX(used_at) AS last_used"). WhereIn("user_id", ids).Group("user_id").All() if err == nil { statMap := map[int64]gdb.Record{} for _, s := range stats { statMap[s["user_id"].Int64()] = s } for i := range list { if s, ok := statMap[list[i].Id]; ok { list[i].UsageCount = s["c"].Int64() list[i].LastUsedAt = s["last_used"].String() } } } } return &v1.UserListRes{List: list, Total: total}, nil } func levelNameMap(ctx context.Context) (map[string]string, error) { records, err := g.Model(consts.TableLevels).All() if err != nil { return nil, err } m := map[string]string{} for _, r := range records { m[r["level_key"].String()] = r["name"].String() } return m, nil } func AdminUserSetLevel(ctx context.Context, userId int64, levelKey string) (*v1.UserSetLevelRes, error) { level, err := g.Model(consts.TableLevels).Where("level_key", levelKey).One() if err != nil { return nil, err } if level.IsEmpty() { return nil, gerror.New("等级不存在") } if _, err = g.Model(consts.TableUsers).Where("id", userId). Data(g.Map{"level_key": levelKey}).Update(); err != nil { return nil, err } return &v1.UserSetLevelRes{}, nil } func AdminUserStatus(ctx context.Context, userId int64, status int) (*v1.UserStatusRes, error) { if _, err := g.Model(consts.TableUsers).Where("id", userId). Data(g.Map{"status": status}).Update(); err != nil { return nil, err } return &v1.UserStatusRes{}, nil } // ===== 等级管理 ===== func AdminLevelList(ctx context.Context) (*v1.LevelListRes, error) { records, err := g.Model(consts.TableLevels).Order("sort asc").All() if err != nil { return nil, err } list := make([]v1.LevelItem, 0, len(records)) for _, r := range records { modules := []string{} _ = json.Unmarshal([]byte(r["modules"].String()), &modules) list = append(list, v1.LevelItem{ Id: r["id"].Int64(), LevelKey: r["level_key"].String(), Name: r["name"].String(), Modules: modules, Sort: r["sort"].Int(), IsEnabled: r["is_enabled"].Int(), Remark: r["remark"].String(), }) } return &v1.LevelListRes{List: list}, nil } func AdminLevelSave(ctx context.Context, req *v1.LevelSaveReq) (*v1.LevelSaveRes, error) { modulesJson, _ := json.Marshal(req.Modules) data := g.Map{ "name": req.Name, "modules": string(modulesJson), "sort": req.Sort, "is_enabled": req.IsEnabled, "remark": req.Remark, } if req.Id > 0 { // 编辑:level_key 只读,不修改 if _, err := g.Model(consts.TableLevels).Where("id", req.Id).Data(data).Update(); err != nil { return nil, err } } else { count, err := g.Model(consts.TableLevels).Where("level_key", req.LevelKey).Count() if err != nil { return nil, err } if count > 0 { return nil, gerror.New("等级标识已存在") } data["level_key"] = req.LevelKey if _, err := g.Model(consts.TableLevels).Data(data).Insert(); err != nil { return nil, err } } return &v1.LevelSaveRes{}, nil } // ===== 模块管理 ===== func AdminModuleList(ctx context.Context) (*v1.ModuleListRes, error) { records, err := g.Model(consts.TableModules).Order("sort asc").All() if err != nil { return nil, err } counts, err := g.Model(consts.TableTools).Fields("module_key, COUNT(*) AS c").Group("module_key").All() if err != nil { return nil, err } countMap := map[string]int{} for _, c := range counts { countMap[c["module_key"].String()] = c["c"].Int() } list := make([]v1.ModuleItem, 0, len(records)) for _, r := range records { key := r["module_key"].String() list = append(list, v1.ModuleItem{ Id: r["id"].Int64(), ModuleKey: key, Name: r["name"].String(), Icon: r["icon"].String(), Description: r["description"].String(), Sort: r["sort"].Int(), IsEnabled: r["is_enabled"].Int(), ToolCount: countMap[key], }) } return &v1.ModuleListRes{List: list}, nil } func AdminModuleSave(ctx context.Context, req *v1.ModuleSaveReq) (*v1.ModuleSaveRes, error) { data := g.Map{ "name": req.Name, "icon": req.Icon, "description": req.Description, "sort": req.Sort, "is_enabled": req.IsEnabled, } if req.Id > 0 { if _, err := g.Model(consts.TableModules).Where("id", req.Id).Data(data).Update(); err != nil { return nil, err } } else { count, err := g.Model(consts.TableModules).Where("module_key", req.ModuleKey).Count() if err != nil { return nil, err } if count > 0 { return nil, gerror.New("模块标识已存在") } data["module_key"] = req.ModuleKey if _, err := g.Model(consts.TableModules).Data(data).Insert(); err != nil { return nil, err } } return &v1.ModuleSaveRes{}, nil } // ===== 工具管理 ===== func AdminToolsList(ctx context.Context, req *v1.ToolsListReq) (*v1.ToolsListRes, error) { if req.Page <= 0 { req.Page = 1 } if req.PageSize <= 0 { req.PageSize = 50 } m := g.Model(consts.TableTools) if req.ModuleKey != "" { m = m.Where("module_key", req.ModuleKey) } if req.Keyword != "" { kw := "%" + req.Keyword + "%" m = m.Where("name LIKE ? OR tool_key LIKE ?", kw, kw) } if !req.IncludeDisabled { m = m.Where("is_enabled", 1) } total, err := m.Count() if err != nil { return nil, err } records, err := m.Page(req.Page, req.PageSize).Order("sort asc").All() if err != nil { return nil, err } moduleNames, err := moduleNameMap(ctx) if err != nil { return nil, err } list := make([]v1.AdminToolItem, 0, len(records)) for _, r := range records { mk := r["module_key"].String() list = append(list, v1.AdminToolItem{ Id: r["id"].Int64(), ToolKey: r["tool_key"].String(), ModuleKey: mk, ModuleName: moduleNames[mk], Name: r["name"].String(), Icon: r["icon"].String(), Description: r["description"].String(), FreeQuota: r["free_quota"].Int(), MemberQuota: r["member_quota"].Int(), PayCost: r["pay_cost"].Int(), Sort: r["sort"].Int(), IsEnabled: r["is_enabled"].Int(), IsHot: r["is_hot"].Int(), UsageCount: r["usage_count"].Int64(), }) } return &v1.ToolsListRes{List: list, Total: total}, nil } func moduleNameMap(ctx context.Context) (map[string]string, error) { records, err := g.Model(consts.TableModules).All() if err != nil { return nil, err } m := map[string]string{} for _, r := range records { m[r["module_key"].String()] = r["name"].String() } return m, nil } func AdminToolsSave(ctx context.Context, req *v1.ToolsSaveReq) (*v1.ToolsSaveRes, error) { payCost := req.PayCost if payCost <= 0 { payCost = 1 } data := g.Map{ "module_key": req.ModuleKey, "name": req.Name, "icon": req.Icon, "description": req.Description, "free_quota": req.FreeQuota, "member_quota": req.MemberQuota, "pay_cost": payCost, "sort": req.Sort, "is_enabled": req.IsEnabled, "is_hot": req.IsHot, } if req.Id > 0 { // 编辑:tool_key 只读,不修改 if _, err := g.Model(consts.TableTools).Where("id", req.Id).Data(data).Update(); err != nil { return nil, err } } else { count, err := g.Model(consts.TableTools).Where("tool_key", req.ToolKey).Count() if err != nil { return nil, err } if count > 0 { return nil, gerror.New("工具标识已存在") } data["tool_key"] = req.ToolKey if _, err := g.Model(consts.TableTools).Data(data).Insert(); err != nil { return nil, err } } return &v1.ToolsSaveRes{}, nil } func AdminToolsToggle(ctx context.Context, id int64, field string, value int) (*v1.ToolsToggleRes, error) { if field != "is_enabled" && field != "is_hot" { return nil, gerror.New("不支持的字段") } if _, err := g.Model(consts.TableTools).Where("id", id).Data(g.Map{field: value}).Update(); err != nil { return nil, err } return &v1.ToolsToggleRes{}, nil } // ===== 数据看板 ===== func AdminDashboardStats(ctx context.Context) (*v1.DashboardStatsRes, error) { res := &v1.DashboardStatsRes{WeekTrend: []v1.TrendItem{}, ToolsTop: []v1.TopItem{}} if c, err := g.Model(consts.TableUsers).Count(); err == nil { res.UserCount = int64(c) } if c, err := g.Model(consts.TableUsageLogs).Count(); err == nil { res.TotalUsage = int64(c) } // 今日活跃:今天有使用记录或登录过的用户数 record, err := g.DB().GetOne(ctx, "SELECT COUNT(*) AS c FROM users u WHERE DATE(u.last_login_at) = CURDATE() "+ "OR EXISTS (SELECT 1 FROM tool_usage_logs l WHERE l.user_id = u.id AND DATE(l.used_at) = CURDATE())") if err == nil && !record.IsEmpty() { res.TodayActive = record["c"].Int64() } // 7 日使用趋势(缺失日期补 0) trend, err := g.DB().GetAll(ctx, "SELECT DATE(used_at) AS d, COUNT(*) AS c FROM tool_usage_logs "+ "WHERE used_at >= DATE_SUB(CURDATE(), INTERVAL 6 DAY) GROUP BY d") if err == nil { countByDate := map[string]int64{} for _, r := range trend { countByDate[r["d"].String()] = r["c"].Int64() } for i := 6; i >= 0; i-- { day := time.Now().AddDate(0, 0, -i).Format("2006-01-02") res.WeekTrend = append(res.WeekTrend, v1.TrendItem{Date: day, Count: countByDate[day]}) } } // 工具使用 TOP10 top, err := g.DB().GetAll(ctx, "SELECT tool_key, COUNT(*) AS c FROM tool_usage_logs GROUP BY tool_key ORDER BY c DESC LIMIT 10") if err == nil { names, _ := toolNameMap(ctx) for _, r := range top { key := r["tool_key"].String() res.ToolsTop = append(res.ToolsTop, v1.TopItem{ToolKey: key, Name: names[key], Count: r["c"].Int64()}) } } return res, nil } func toolNameMap(ctx context.Context) (map[string]string, error) { records, err := g.Model(consts.TableTools).All() if err != nil { return nil, err } m := map[string]string{} for _, r := range records { m[r["tool_key"].String()] = r["name"].String() } return m, nil } // ===== 反馈管理 ===== func AdminFeedbackList(ctx context.Context, req *v1.FeedbackListReq) (*v1.FeedbackListRes, error) { if req.Page <= 0 { req.Page = 1 } if req.PageSize <= 0 { req.PageSize = 20 } m := g.Model(consts.TableFeedbacks) if req.Status != nil { m = m.Where("status", *req.Status) } total, err := m.Count() if err != nil { return nil, err } records, err := m.Page(req.Page, req.PageSize).Order("id desc").All() if err != nil { return nil, err } list := make([]v1.FeedbackItem, 0, len(records)) for _, r := range records { list = append(list, v1.FeedbackItem{ Id: r["id"].Int64(), UserId: r["user_id"].Int64(), Content: r["content"].String(), Contact: r["contact"].String(), Status: r["status"].Int(), CreatedAt: r["created_at"].String(), }) } return &v1.FeedbackListRes{List: list, Total: total}, nil } func AdminFeedbackHandle(ctx context.Context, id int64) (*v1.FeedbackHandleRes, error) { if _, err := g.Model(consts.TableFeedbacks).Where("id", id).Data(g.Map{"status": 1}).Update(); err != nil { return nil, err } return &v1.FeedbackHandleRes{}, nil }