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;"
>