mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat: 增加系统用户管理
This commit is contained in:
		@@ -27,6 +27,8 @@ require github.com/xxl-job/xxl-job-executor-go v1.2.0
 | 
			
		||||
 | 
			
		||||
require github.com/bg5t/mydiscordgo v0.28.1
 | 
			
		||||
 | 
			
		||||
require github.com/shopspring/decimal v1.3.1 // indirect
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/andybalholm/brotli v1.0.4 // indirect
 | 
			
		||||
	github.com/bytedance/sonic v1.9.1 // indirect
 | 
			
		||||
 
 | 
			
		||||
@@ -166,6 +166,8 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA
 | 
			
		||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 | 
			
		||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
 | 
			
		||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 | 
			
		||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
 | 
			
		||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 | 
			
		||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 | 
			
		||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 | 
			
		||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										150
									
								
								api/handler/admin/admin_user_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								api/handler/admin/admin_user_handler.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,150 @@
 | 
			
		||||
package admin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"chatplus/core"
 | 
			
		||||
	"chatplus/core/types"
 | 
			
		||||
	"chatplus/handler"
 | 
			
		||||
	"chatplus/store/model"
 | 
			
		||||
	"chatplus/store/vo"
 | 
			
		||||
	"chatplus/utils"
 | 
			
		||||
	"chatplus/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SysUserHandler struct {
 | 
			
		||||
	handler.BaseHandler
 | 
			
		||||
	db *gorm.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewSysUserHandler(app *core.AppServer, db *gorm.DB) *SysUserHandler {
 | 
			
		||||
	h := SysUserHandler{db: db}
 | 
			
		||||
	h.App = app
 | 
			
		||||
	return &h
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List 用户列表
 | 
			
		||||
func (h *SysUserHandler) List(c *gin.Context) {
 | 
			
		||||
	page := h.GetInt(c, "page", 1)
 | 
			
		||||
	pageSize := h.GetInt(c, "page_size", 20)
 | 
			
		||||
	username := h.GetTrim(c, "username")
 | 
			
		||||
 | 
			
		||||
	offset := (page - 1) * pageSize
 | 
			
		||||
	var items []model.AdminUser
 | 
			
		||||
	var users = make([]vo.AdminUser, 0)
 | 
			
		||||
	var total int64
 | 
			
		||||
 | 
			
		||||
	session := h.db.Session(&gorm.Session{})
 | 
			
		||||
	if username != "" {
 | 
			
		||||
		session = session.Where("username LIKE ?", "%"+username+"%")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 查询total
 | 
			
		||||
	session.Model(&model.AdminUser{}).Count(&total)
 | 
			
		||||
	res := session.Offset(offset).Limit(pageSize).Find(&items)
 | 
			
		||||
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		for _, item := range items {
 | 
			
		||||
			var userVo vo.AdminUser
 | 
			
		||||
			err := utils.CopyObject(item, &userVo)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				userVo.Id = item.Id
 | 
			
		||||
				userVo.CreatedAt = item.CreatedAt.Unix()
 | 
			
		||||
				userVo.UpdatedAt = item.UpdatedAt.Unix()
 | 
			
		||||
				users = append(users, userVo)
 | 
			
		||||
			} else {
 | 
			
		||||
				logger.Error(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	pageVo := vo.NewPage(total, page, pageSize, users)
 | 
			
		||||
	resp.SUCCESS(c, pageVo)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Save 更新或者新增
 | 
			
		||||
func (h *SysUserHandler) Save(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id          uint   `json:"id"`
 | 
			
		||||
		Password    string `json:"password"`
 | 
			
		||||
		Username    string `json:"username"`
 | 
			
		||||
		ExpiredTime string `json:"expired_time"`
 | 
			
		||||
		Status      bool   `json:"status"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var user = model.AdminUser{}
 | 
			
		||||
	var res *gorm.DB
 | 
			
		||||
	var userVo vo.AdminUser
 | 
			
		||||
	if data.Id > 0 { // 更新
 | 
			
		||||
		user.Id = data.Id
 | 
			
		||||
		// 此处需要用 map 更新,用结构体无法更新 0 值
 | 
			
		||||
		res = h.db.Model(&user).Updates(map[string]interface{}{
 | 
			
		||||
			"username":     data.Username,
 | 
			
		||||
			"status":       data.Status,
 | 
			
		||||
			"expired_time": utils.Str2stamp(data.ExpiredTime),
 | 
			
		||||
		})
 | 
			
		||||
	} else {
 | 
			
		||||
		salt := utils.RandString(8)
 | 
			
		||||
		u := model.AdminUser{
 | 
			
		||||
			Username:    data.Username,
 | 
			
		||||
			Password:    utils.GenPassword(data.Password, salt),
 | 
			
		||||
			Salt:        salt,
 | 
			
		||||
			Status:      true,
 | 
			
		||||
			ExpiredTime: utils.Str2stamp(data.ExpiredTime),
 | 
			
		||||
		}
 | 
			
		||||
		res = h.db.Create(&u)
 | 
			
		||||
		_ = utils.CopyObject(u, &userVo)
 | 
			
		||||
		userVo.Id = u.Id
 | 
			
		||||
		userVo.CreatedAt = u.CreatedAt.Unix()
 | 
			
		||||
		userVo.UpdatedAt = u.UpdatedAt.Unix()
 | 
			
		||||
	}
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "更新数据库失败")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, userVo)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ResetPass 重置密码
 | 
			
		||||
func (h *SysUserHandler) ResetPass(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id       uint
 | 
			
		||||
		Password string
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var user model.AdminUser
 | 
			
		||||
	res := h.db.First(&user, data.Id)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "No user found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	password := utils.GenPassword(data.Password, user.Salt)
 | 
			
		||||
	user.Password = password
 | 
			
		||||
	res = h.db.Updates(&user)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c)
 | 
			
		||||
	} else {
 | 
			
		||||
		resp.SUCCESS(c)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Remove 删除
 | 
			
		||||
func (h *SysUserHandler) Remove(c *gin.Context) {
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	if id > 0 {
 | 
			
		||||
		res := h.db.Where("id = ?", id).Delete(&model.AdminUser{})
 | 
			
		||||
		if res.Error != nil {
 | 
			
		||||
			resp.ERROR(c, "删除失败")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
@@ -7,6 +7,7 @@ import (
 | 
			
		||||
	"chatplus/store/model"
 | 
			
		||||
	"chatplus/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/shopspring/decimal"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
@@ -23,10 +24,11 @@ func NewDashboardHandler(app *core.AppServer, db *gorm.DB) *DashboardHandler {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type statsVo struct {
 | 
			
		||||
	Users  int64   `json:"users"`
 | 
			
		||||
	Chats  int64   `json:"chats"`
 | 
			
		||||
	Tokens int     `json:"tokens"`
 | 
			
		||||
	Income float64 `json:"income"`
 | 
			
		||||
	Users  int64                         `json:"users"`
 | 
			
		||||
	Chats  int64                         `json:"chats"`
 | 
			
		||||
	Tokens int                           `json:"tokens"`
 | 
			
		||||
	Income float64                       `json:"income"`
 | 
			
		||||
	Chart  map[string]map[string]float64 `json:"chart"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *DashboardHandler) Stats(c *gin.Context) {
 | 
			
		||||
@@ -67,5 +69,52 @@ func (h *DashboardHandler) Stats(c *gin.Context) {
 | 
			
		||||
	for _, item := range orders {
 | 
			
		||||
		stats.Income += item.Amount
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 统计7天的订单的图表
 | 
			
		||||
	startDate := now.Add(-7 * 24 * time.Hour).Format("2006-01-02")
 | 
			
		||||
	var statsChart = make(map[string]map[string]float64)
 | 
			
		||||
	//// 初始化
 | 
			
		||||
	var userStatistic, historyMessagesStatistic, incomeStatistic = make(map[string]float64), make(map[string]float64), make(map[string]float64)
 | 
			
		||||
	for i := 0; i < 7; i++ {
 | 
			
		||||
		var initTime = time.Date(now.Year(), now.Month(), now.Day()-i, 0, 0, 0, 0, now.Location()).Format("2006-01-02")
 | 
			
		||||
		userStatistic[initTime] = float64(0)
 | 
			
		||||
		historyMessagesStatistic[initTime] = float64(0)
 | 
			
		||||
		incomeStatistic[initTime] = float64(0)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 统计用户7天增加的曲线
 | 
			
		||||
	var users []model.User
 | 
			
		||||
	res = h.db.Model(&model.User{}).Where("created_at > ?", startDate).Find(&users)
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		for _, item := range users {
 | 
			
		||||
			userStatistic[item.CreatedAt.Format("2006-01-02")] += 1
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 统计7天Token 消耗
 | 
			
		||||
	res = h.db.Where("created_at > ?", startDate).Find(&historyMessages)
 | 
			
		||||
	for _, item := range historyMessages {
 | 
			
		||||
		historyMessagesStatistic[item.CreatedAt.Format("2006-01-02")] += float64(item.Tokens)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 浮点数相加?
 | 
			
		||||
	// 统计最近7天的众筹
 | 
			
		||||
	res = h.db.Where("created_at > ?", startDate).Find(&rewards)
 | 
			
		||||
	for _, item := range rewards {
 | 
			
		||||
		incomeStatistic[item.CreatedAt.Format("2006-01-02")], _ = decimal.NewFromFloat(incomeStatistic[item.CreatedAt.Format("2006-01-02")]).Add(decimal.NewFromFloat(item.Amount)).Float64()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 统计最近7天的订单
 | 
			
		||||
	res = h.db.Where("status = ?", types.OrderPaidSuccess).Where("created_at > ?", startDate).Find(&orders)
 | 
			
		||||
	for _, item := range orders {
 | 
			
		||||
		incomeStatistic[item.CreatedAt.Format("2006-01-02")], _ = decimal.NewFromFloat(incomeStatistic[item.CreatedAt.Format("2006-01-02")]).Add(decimal.NewFromFloat(item.Amount)).Float64()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	statsChart["users"] = userStatistic
 | 
			
		||||
	statsChart["historyMessage"] = historyMessagesStatistic
 | 
			
		||||
	statsChart["orders"] = incomeStatistic
 | 
			
		||||
 | 
			
		||||
	stats.Chart = statsChart
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, stats)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								api/main.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								api/main.go
									
									
									
									
									
								
							@@ -369,6 +369,16 @@ func main() {
 | 
			
		||||
			group.GET("token", h.GenToken)
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		// 系统管理员
 | 
			
		||||
		fx.Provide(admin.NewSysUserHandler),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *admin.SysUserHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/admin/sysUser/")
 | 
			
		||||
			group.POST("save", h.Save)
 | 
			
		||||
			group.GET("list", h.List)
 | 
			
		||||
			group.POST("remove", h.Remove)
 | 
			
		||||
			group.POST("resetPass", h.ResetPass)
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		fx.Provide(handler.NewFunctionHandler),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.FunctionHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/function/")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								api/store/model/admin_user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								api/store/model/admin_user.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
type AdminUser struct {
 | 
			
		||||
	BaseModel
 | 
			
		||||
	Username    string
 | 
			
		||||
	Password    string
 | 
			
		||||
	Salt        string // 密码盐
 | 
			
		||||
	ExpiredTime int64  // 账户到期时间
 | 
			
		||||
	Status      bool   `gorm:"default:true"` // 当前状态
 | 
			
		||||
	LastLoginAt int64  // 最后登录时间
 | 
			
		||||
	LastLoginIp string // 最后登录 IP
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								api/store/vo/admin_user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								api/store/vo/admin_user.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
package vo
 | 
			
		||||
 | 
			
		||||
type AdminUser struct {
 | 
			
		||||
	BaseVo
 | 
			
		||||
	Username    string `json:"username"`
 | 
			
		||||
	Salt        string `json:"salt"`          // 密码盐
 | 
			
		||||
	ExpiredTime int64  `json:"expired_time"`  // 账户到期时间
 | 
			
		||||
	Status      bool   `json:"status"`        // 当前状态
 | 
			
		||||
	LastLoginAt int64  `json:"last_login_at"` // 最后登录时间
 | 
			
		||||
	LastLoginIp string `json:"last_login_ip"` // 最后登录 IP
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
VUE_APP_API_HOST=http://localhost:5678
 | 
			
		||||
VUE_APP_WS_HOST=ws://localhost:5678
 | 
			
		||||
VUE_APP_API_HOST=http://172.22.11.2:5678
 | 
			
		||||
VUE_APP_WS_HOST=ws://172.22.11.2:5678
 | 
			
		||||
VUE_APP_USER=18575670125
 | 
			
		||||
VUE_APP_PASS=12345678
 | 
			
		||||
VUE_APP_ADMIN_USER=admin
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7627
									
								
								web/pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7627
									
								
								web/pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user