Files
qitongxue-api/internal/logic/admin.go
2026-09-17 14:42:44 +08:00

477 lines
13 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"
"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()],
Status: r["status"].Int(),
CreatedAt: r["created_at"].String(),
}
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(),
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) {
data := g.Map{
"module_key": req.ModuleKey,
"name": req.Name,
"icon": req.Icon,
"description": req.Description,
"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
}