Files
platform/web/handlers/user.go

533 lines
11 KiB
Go

package handlers
import (
"errors"
"platform/web/auth"
"platform/web/core"
g "platform/web/globals"
m "platform/web/models"
q "platform/web/queries"
s "platform/web/services"
"github.com/gofiber/fiber/v2"
"github.com/shopspring/decimal"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
// 分页获取用户
func PageUserByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserRead)
if err != nil {
return err
}
// 解析请求参数
req := new(PageUserByAdminReq)
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
// 构建查询条件
do := q.User.Where()
if req.Account != nil {
do = do.Where(q.User.Where(
q.User.Username.Like("%" + *req.Account + "%"),
).Or(
q.User.Phone.Like("%" + *req.Account + "%"),
).Or(
q.User.Email.Like("%" + *req.Account + "%"),
))
}
if req.Name != nil {
do = do.Where(q.User.Name.Eq(*req.Name))
}
if req.Identified != nil {
if *req.Identified {
do = do.Where(q.User.IDType.Gt(0))
} else {
do = do.Where(q.User.IDType.Eq(0))
}
}
if req.Enabled != nil {
if *req.Enabled {
do = do.Where(q.User.Status.Eq(1))
} else {
do = do.Where(q.User.Status.Eq(0))
}
}
if req.Assigned != nil {
if *req.Assigned {
do = do.Where(q.User.AdminID.IsNotNull())
} else {
do = do.Where(q.User.AdminID.IsNull())
}
}
// 查询用户列表
users, total, err := q.User.
Preload(q.User.Admin, q.User.Discount).
Omit(q.User.Password, q.Admin.Password).
Where(do).
Order(q.User.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return err
}
for _, user := range users {
if user.IDNo != nil && len(*user.IDNo) == 18 {
var str = *user.IDNo
*user.IDNo = str[:6] + "****" + str[len(str)-2:]
}
if user.Admin != nil {
user.Admin = &m.Admin{
Name: user.Admin.Name,
}
}
}
// 返回结果
return c.JSON(core.PageResp{
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
List: users,
})
}
type PageUserByAdminReq struct {
core.PageReq
Account *string `json:"account,omitempty"`
Name *string `json:"name,omitempty"`
Identified *bool `json:"identified,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
Assigned *bool `json:"assigned,omitempty"`
}
// 管理员获取单个用户
func GetUserByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserReadOne)
if err != nil {
return err
}
// 解析请求参数
var req GetUserByAdminReq
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
// 构建查询条件
do := q.User.Where()
if req.Account != nil {
do = do.Where(q.User.Where(
q.User.Username.Like("%" + *req.Account + "%"),
).Or(
q.User.Phone.Like("%" + *req.Account + "%"),
).Or(
q.User.Email.Like("%" + *req.Account + "%"),
))
}
if req.Name != nil {
do = do.Where(q.User.Name.Eq(*req.Name))
}
// 查询用户
user, err := q.User.
Preload(q.User.Admin, q.User.Discount).
Omit(q.User.Password, q.Admin.Password).
Where(do).
Order(q.User.CreatedAt.Desc()).
First()
if err == gorm.ErrRecordNotFound {
return core.NewBizErr("找不到用户")
}
if err != nil {
return err
}
// 仅保留管理员名称
if user.Admin != nil {
user.Admin = &m.Admin{
Name: user.Admin.Name,
}
}
// 返回结果
return c.JSON(user)
}
type GetUserByAdminReq struct {
Account *string `json:"account,omitempty"`
Name *string `json:"name,omitempty"`
}
// 管理员创建用户
func CreateUserByAdmin(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWrite)
if err != nil {
return err
}
var req s.CreateUserByAdminData
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
if err := s.User.CreateByAdmin(req); err != nil {
return err
}
return c.JSON(nil)
}
// 管理员更新用户
func UpdateUserByAdmin(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWrite)
if err != nil {
return err
}
var req s.UpdateUserByAdminData
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
if err := s.User.UpdateByAdmin(req); err != nil {
return err
}
return c.JSON(nil)
}
// 管理员删除用户
func RemoveUserByAdmin(c *fiber.Ctx) error {
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWrite)
if err != nil {
return err
}
var req core.IdReq
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
if err := s.User.RemoveByAdmin(req.Id); err != nil {
return err
}
return c.JSON(nil)
}
// 管理员更新用户余额
func UpdateUserBalanceByAdmin(c *fiber.Ctx) error {
authCtx, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWriteBalance)
if err != nil {
return err
}
var req UpdateUserBalanceByAdminData
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
user, err := s.User.Get(q.Q, req.UserID)
if err != nil {
return err
}
balance, err := decimal.NewFromString(req.Balance)
if err != nil {
return err
}
if err := s.User.UpdateBalanceByAdmin(user, balance, &authCtx.Admin.ID); err != nil {
return err
}
return c.JSON(nil)
}
type UpdateUserBalanceByAdminData struct {
UserID int32 `json:"user_id" validate:"required"`
Balance string `json:"balance" validate:"required"`
}
// 绑定管理员
func BindAdmin(c *fiber.Ctx) error {
// 检查权限
authCtx, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWrite)
if err != nil {
return err
}
// 解析请求参数
req := new(struct {
UserID int `json:"user_id" validate:"required"`
})
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
// 更新用户信息
result, err := q.User.Where(
q.User.ID.Eq(int32(req.UserID)),
q.User.AdminID.IsNull(),
).UpdateColumnSimple(
q.User.AdminID.Value(authCtx.Admin.ID),
)
if err != nil {
return err
}
if result.RowsAffected == 0 {
return core.NewBizErr("用户已绑定管理员")
}
// 返回结果
return c.SendStatus(fiber.StatusNoContent)
}
// 更新用户
func UpdateUser(c *fiber.Ctx) error {
// 检查权限
authCtx, err := auth.GetAuthCtx(c).PermitUser()
if err != nil {
return err
}
// 解析请求参数
req := new(UpdateUserReq)
if err := c.BodyParser(req); err != nil {
return err
}
// 更新用户信息
_, err = q.User.
Where(q.User.ID.Eq(authCtx.User.ID)).
Updates(m.User{
Username: &req.Username,
Email: &req.Email,
ContactQQ: &req.ContactQQ,
ContactWechat: &req.ContactWechat,
})
if errors.Is(err, gorm.ErrDuplicatedKey) {
return core.NewBizErr("用户名或邮箱已被占用")
}
if err != nil {
return err
}
// 返回结果
return c.SendStatus(fiber.StatusNoContent)
}
type UpdateUserReq struct {
Username string `json:"username" validate:"omitempty,min=3,max=20"`
Email string `json:"email" validate:"omitempty,email"`
ContactQQ string `json:"contact_qq" validate:"omitempty,qq"`
ContactWechat string `json:"contact_wechat" validate:"omitempty,wechat"`
}
// 更新账号信息
func UpdateAccount(c *fiber.Ctx) error {
// 检查权限
authCtx, err := auth.GetAuthCtx(c).PermitUser()
if err != nil {
return err
}
// 解析请求参数
req := new(UpdateAccountReq)
if err := c.BodyParser(req); err != nil {
return err
}
// 更新用户信息
_, err = q.User.
Where(q.User.ID.Eq(authCtx.User.ID)).
Updates(m.User{
Username: &req.Username,
Password: &req.Password,
})
if err != nil {
return err
}
// 返回结果
return c.SendStatus(fiber.StatusNoContent)
}
type UpdateAccountReq struct {
Username string `json:"username" validate:"omitempty,min=3,max=20"`
Password string `json:"password" validate:"omitempty,min=6,max=20"`
}
// 更新账号密码
func UpdatePassword(c *fiber.Ctx) error {
// 检查权限
authCtx, err := auth.GetAuthCtx(c).PermitUser()
if err != nil {
return err
}
// 解析请求参数
req := new(UpdatePasswordReq)
if err := c.BodyParser(req); err != nil {
return err
}
// 验证手机号
if req.Phone != authCtx.User.Phone {
return fiber.NewError(fiber.StatusBadRequest, "手机号码不正确")
}
// 验证手机令牌
if req.Code == "" {
return fiber.NewError(fiber.StatusBadRequest, "手机号码和验证码不能为空")
}
err = s.Verifier.VerifySms(c.Context(), req.Phone, req.Code)
if err != nil {
return err
}
// 更新密码
newHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
_, err = q.User.
Where(q.User.ID.Eq(authCtx.User.ID)).
UpdateColumn(q.User.Password, newHash)
if err != nil {
return err
}
// 返回结果
return c.SendStatus(fiber.StatusNoContent)
}
type UpdatePasswordReq struct {
Phone string `json:"phone"`
Code string `json:"code"`
Password string `json:"password"`
}
// PageUserNotBindByAdmin 分页获取未绑定管理员的用户
func PageUserNotBindByAdmin(c *fiber.Ctx) error {
// 检查权限
_, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserReadNotBind)
if err != nil {
return err
}
// 解析请求参数
req := new(PageUserNotBindByAdminReq)
if err := g.Validator.ParseBody(c, req); err != nil {
return err
}
// 构建查询条件(强制过滤未绑定管理员的用户)
do := q.User.Where(q.User.AdminID.IsNull())
if req.Phone != nil {
do = do.Where(q.User.Phone.Eq(*req.Phone))
}
// 查询用户列表
users, total, err := q.User.
Omit(q.User.Password, q.User.IDNo).
Where(do).
Order(q.User.CreatedAt.Desc()).
FindByPage(req.GetOffset(), req.GetLimit())
if err != nil {
return err
}
// 返回结果
return c.JSON(core.PageResp{
Total: int(total),
Page: req.GetPage(),
Size: req.GetSize(),
List: users,
})
}
type PageUserNotBindByAdminReq struct {
core.PageReq
Phone *string `json:"phone,omitempty"`
}
// UpdateUserBalanceIncByAdmin 管理员增加用户余额
func UpdateUserBalanceIncByAdmin(c *fiber.Ctx) error {
authCtx, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWriteBalance)
if err != nil {
return err
}
var req UpdateUserBalanceChangeByAdminData
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
amount, err := decimal.NewFromString(req.Amount)
if err != nil {
return err
}
if !amount.IsPositive() {
return core.NewBizErr("金额必须为正数")
}
user, err := s.User.Get(q.Q, req.UserID)
if err != nil {
return err
}
newBalance := user.Balance.Add(amount)
if err := s.User.UpdateBalanceByAdmin(user, newBalance, &authCtx.Admin.ID); err != nil {
return err
}
return c.JSON(nil)
}
// UpdateUserBalanceDecByAdmin 管理员减少用户余额
func UpdateUserBalanceDecByAdmin(c *fiber.Ctx) error {
authCtx, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserWriteBalance)
if err != nil {
return err
}
var req UpdateUserBalanceChangeByAdminData
if err := g.Validator.ParseBody(c, &req); err != nil {
return err
}
amount, err := decimal.NewFromString(req.Amount)
if err != nil {
return err
}
if !amount.IsPositive() {
return core.NewBizErr("金额必须为正数")
}
user, err := s.User.Get(q.Q, req.UserID)
if err != nil {
return err
}
newBalance := user.Balance.Sub(amount)
if err := s.User.UpdateBalanceByAdmin(user, newBalance, &authCtx.Admin.ID); err != nil {
return err
}
return c.JSON(nil)
}
type UpdateUserBalanceChangeByAdminData struct {
UserID int32 `json:"user_id" validate:"required"`
Amount string `json:"amount" validate:"required"`
}