477 lines
13 KiB
Go
477 lines
13 KiB
Go
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
|
||
}
|