From ec6186596ded7a341759a2c5574fe97f15d332e4 Mon Sep 17 00:00:00 2001 From: RockYang Date: Thu, 21 Mar 2024 15:24:28 +0800 Subject: [PATCH] feat: add manager list page in console page --- api/core/app_server.go | 3 + api/handler/admin/admin_handler.go | 165 ++++++++++++++++++- api/handler/admin/product_handler.go | 2 +- api/main.go | 5 + web/src/components/admin/AdminSidebar.vue | 5 + web/src/router.js | 6 + web/src/views/admin/Dashboard.vue | 2 + web/src/views/admin/Manager.vue | 188 ++++++++++++++++++++++ web/src/views/admin/Product.vue | 1 - 9 files changed, 367 insertions(+), 10 deletions(-) create mode 100644 web/src/views/admin/Manager.vue diff --git a/api/core/app_server.go b/api/core/app_server.go index 2371ce86..d633fc18 100644 --- a/api/core/app_server.go +++ b/api/core/app_server.go @@ -186,6 +186,9 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc { } key := fmt.Sprintf("users/%v", claims["user_id"]) + if isAdminApi { + key = fmt.Sprintf("admin/%v", claims["user_id"]) + } if _, err := client.Get(context.Background(), key).Result(); err != nil && needLogin(c) { resp.NotAuth(c, "Token is not found in redis") c.Abort() diff --git a/api/handler/admin/admin_handler.go b/api/handler/admin/admin_handler.go index 0eb1f63b..d9d3e988 100644 --- a/api/handler/admin/admin_handler.go +++ b/api/handler/admin/admin_handler.go @@ -6,9 +6,11 @@ import ( "chatplus/handler" logger2 "chatplus/logger" "chatplus/store/model" + "chatplus/store/vo" "chatplus/utils" "chatplus/utils/resp" "context" + "fmt" "github.com/go-redis/redis/v8" "github.com/golang-jwt/jwt/v5" "github.com/mojocn/base64Captcha" @@ -28,6 +30,8 @@ type Manager struct { CaptchaId string `json:"captcha_id"` // 验证码id } +const SuperManagerID = 1 + type ManagerHandler struct { handler.BaseHandler redis *redis.Client @@ -64,14 +68,14 @@ func (h *ManagerHandler) Login(c *gin.Context) { } // 超级管理员默认是ID:1 - if manager.Id != 1 && manager.Status == false { + if manager.Id != SuperManagerID && manager.Status == false { resp.ERROR(c, "该用户已被禁止登录,请联系超级管理员") return } // 创建 token token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "user_id": manager.Username, + "user_id": manager.Id, "expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)).Unix(), }) tokenString, err := token.SignedString([]byte(h.App.Config.AdminSession.SecretKey)) @@ -80,7 +84,7 @@ func (h *ManagerHandler) Login(c *gin.Context) { return } // 保存到 redis - key := "users/" + manager.Username + key := fmt.Sprintf("admin/%d", manager.Id) if _, err := h.redis.Set(context.Background(), key, tokenString, 0).Result(); err != nil { resp.ERROR(c, "error with save token: "+err.Error()) return @@ -89,7 +93,7 @@ func (h *ManagerHandler) Login(c *gin.Context) { // 更新最后登录时间和IP manager.LastLoginIp = c.ClientIP() manager.LastLoginAt = time.Now().Unix() - h.DB.Model(&manager).Updates(manager) + h.DB.Updates(&manager) var result = struct { IsSuperAdmin bool `json:"is_super_admin"` @@ -114,10 +118,155 @@ func (h *ManagerHandler) Logout(c *gin.Context) { // Session 会话检测 func (h *ManagerHandler) Session(c *gin.Context) { - token := c.GetHeader(types.AdminAuthHeader) - if token == "" { + id := h.GetLoginUserId(c) + key := fmt.Sprintf("admin/%d", id) + if _, err := h.redis.Get(context.Background(), key).Result(); err != nil { resp.NotAuth(c) - } else { - resp.SUCCESS(c) + return } + var manager model.AdminUser + res := h.DB.Where("id", id).First(&manager) + if res.Error != nil { + resp.NotAuth(c) + return + } + + resp.SUCCESS(c, manager) +} + +// List 数据列表 +func (h *ManagerHandler) List(c *gin.Context) { + var items []model.AdminUser + res := h.DB.Find(&items) + if res.Error != nil { + resp.ERROR(c, res.Error.Error()) + return + } + + users := make([]vo.AdminUser, 0) + for _, item := range items { + var u vo.AdminUser + err := utils.CopyObject(item, &u) + if err != nil { + continue + } + u.Id = item.Id + u.CreatedAt = item.CreatedAt.Unix() + users = append(users, u) + } + + resp.SUCCESS(c, users) + +} + +func (h *ManagerHandler) Save(c *gin.Context) { + var data struct { + Username string `json:"username"` + Password string `json:"password"` + Status bool `json:"status"` + } + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + var user model.AdminUser + res := h.DB.Where("username", data.Username).First(&user) + if res.Error == nil { + resp.ERROR(c, "用户名已存在") + return + } + + // 生成密码 + salt := utils.RandString(8) + password := utils.GenPassword(data.Password, salt) + res = h.DB.Save(&model.AdminUser{ + Username: data.Username, + Password: password, + Salt: salt, + Status: data.Status, + }) + if res.Error != nil { + resp.ERROR(c, "failed with update database") + return + } + + resp.SUCCESS(c) +} + +// Remove 删除管理员 +func (h *ManagerHandler) Remove(c *gin.Context) { + id := h.GetInt(c, "id", 0) + if id <= 0 { + resp.ERROR(c, types.InvalidArgs) + return + } + + if id == SuperManagerID { + resp.ERROR(c, "超级管理员不能删除") + return + } + + res := h.DB.Where("id", id).Delete(&model.AdminUser{}) + if res.Error != nil { + resp.ERROR(c, res.Error.Error()) + return + } + + resp.SUCCESS(c) +} + +// Enable 启用/禁用 +func (h *ManagerHandler) Enable(c *gin.Context) { + var data struct { + Id uint `json:"id"` + Enabled bool `json:"enabled"` + } + + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + res := h.DB.Model(&model.AdminUser{}).Where("id", data.Id).UpdateColumn("status", data.Enabled) + if res.Error != nil { + resp.ERROR(c, res.Error.Error()) + return + } + resp.SUCCESS(c) +} + +// ResetPass 重置密码 +func (h *ManagerHandler) ResetPass(c *gin.Context) { + id := h.GetLoginUserId(c) + if id != SuperManagerID { + resp.ERROR(c, "只有超级管理员能够进行该操作") + return + } + + var data struct { + Id int `json:"id"` + Password string `json:"password"` + } + if err := c.ShouldBindJSON(&data); err != nil { + resp.ERROR(c, types.InvalidArgs) + return + } + + var user model.AdminUser + res := h.DB.Where("id", data.Id).First(&user) + if res.Error != nil { + resp.ERROR(c, res.Error.Error()) + return + } + + password := utils.GenPassword(data.Password, user.Salt) + user.Password = password + res = h.DB.Updates(&user) + if res.Error != nil { + resp.ERROR(c, res.Error.Error()) + return + } + + resp.SUCCESS(c) } diff --git a/api/handler/admin/product_handler.go b/api/handler/admin/product_handler.go index b626b02e..8e960c24 100644 --- a/api/handler/admin/product_handler.go +++ b/api/handler/admin/product_handler.go @@ -108,7 +108,7 @@ func (h *ProductHandler) Enable(c *gin.Context) { return } - res := h.DB.Model(&model.Product{}).Where("id = ?", data.Id).Update("enabled", data.Enabled) + res := h.DB.Model(&model.Product{}).Where("id", data.Id).UpdateColumn("enabled", data.Enabled) if res.Error != nil { resp.ERROR(c, "更新数据库失败!") return diff --git a/api/main.go b/api/main.go index ab8b3eb4..d1235e2b 100644 --- a/api/main.go +++ b/api/main.go @@ -274,6 +274,11 @@ func main() { group.POST("login", h.Login) group.GET("logout", h.Logout) group.GET("session", h.Session) + group.GET("list", h.List) + group.POST("save", h.Save) + group.POST("enable", h.Enable) + group.GET("remove", h.Remove) + group.POST("resetPass", h.ResetPass) }), fx.Invoke(func(s *core.AppServer, h *admin.ApiKeyHandler) { group := s.Engine.Group("/api/admin/apikey/") diff --git a/web/src/components/admin/AdminSidebar.vue b/web/src/components/admin/AdminSidebar.vue index 3a94c671..0393c984 100644 --- a/web/src/components/admin/AdminSidebar.vue +++ b/web/src/components/admin/AdminSidebar.vue @@ -121,6 +121,11 @@ const items = [ index: '/admin/chats', title: '对话管理', }, + { + icon: 'role', + index: '/admin/manger', + title: '管理员', + }, { icon: 'config', index: '/admin/system', diff --git a/web/src/router.js b/web/src/router.js index d510af6f..f4c81e61 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -168,6 +168,12 @@ const routes = [ meta: {title: '算力日志'}, component: () => import('@/views/admin/PowerLog.vue'), }, + { + path: '/admin/manger', + name: 'admin-manger', + meta: {title: '管理员'}, + component: () => import('@/views/admin/Manager.vue'), + }, ] }, diff --git a/web/src/views/admin/Dashboard.vue b/web/src/views/admin/Dashboard.vue index b97e2e52..2338c7e2 100644 --- a/web/src/views/admin/Dashboard.vue +++ b/web/src/views/admin/Dashboard.vue @@ -80,6 +80,8 @@ httpGet('/api/admin/dashboard/stats').then((res) => { \ No newline at end of file diff --git a/web/src/views/admin/Product.vue b/web/src/views/admin/Product.vue index 9939e35e..3f4bd8a5 100644 --- a/web/src/views/admin/Product.vue +++ b/web/src/views/admin/Product.vue @@ -51,7 +51,6 @@ v-model="showDialog" :title="title" :close-on-click-modal="false" - style="width: 90%; max-width: 600px;" >