diff --git a/web/core/scopes.go b/web/core/scopes.go index c9dc896..36e807c 100644 --- a/web/core/scopes.go +++ b/web/core/scopes.go @@ -22,6 +22,9 @@ const ( ScopeResourceRead = string("resource:read") ScopeResourceWrite = string("resource:write") + ScopeUserRead = string("user:read") + ScopeUserWrite = string("user:write") + ScopeCouponRead = string("coupon:read") ScopeCouponWrite = string("coupon:write") ) diff --git a/web/error.go b/web/error.go index 27b02cb..f58469a 100644 --- a/web/error.go +++ b/web/error.go @@ -74,6 +74,7 @@ func ErrorHandler(c *fiber.Ctx, err error) error { slog.Warn("未处理的异常", slog.String("type", t.String()), slog.String("error", err.Error())) } + slog.Warn(message) c.Set(fiber.HeaderContentType, fiber.MIMETextPlainCharsetUTF8) return c.Status(code).SendString(message) } diff --git a/web/handlers/admin.go b/web/handlers/admin.go index 7ce516f..d99486a 100644 --- a/web/handlers/admin.go +++ b/web/handlers/admin.go @@ -37,6 +37,20 @@ type PageAdminsReq struct { core.PageReq } +func ListAdminsByAdmin(c *fiber.Ctx) error { + _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeAdminRead) + if err != nil { + return err + } + + list, err := s.Admin.All() + if err != nil { + return err + } + + return c.JSON(list) +} + func CreateAdmin(c *fiber.Ctx) error { _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeAdminWrite) if err != nil { diff --git a/web/handlers/user.go b/web/handlers/user.go index 2c8e053..71a5e27 100644 --- a/web/handlers/user.go +++ b/web/handlers/user.go @@ -12,10 +12,67 @@ import ( "golang.org/x/crypto/bcrypt" ) +// 管理员创建用户 +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 PageUserByAdmin(c *fiber.Ctx) error { // 检查权限 - _, err := auth.GetAuthCtx(c).PermitAdmin() + _, err := auth.GetAuthCtx(c).PermitAdmin(core.ScopeUserRead) if err != nil { return err } @@ -67,6 +124,7 @@ func PageUserByAdmin(c *fiber.Ctx) error { Preload(q.User.Admin). Omit(q.User.Password). Where(do). + Order(q.User.CreatedAt). FindByPage(req.GetOffset(), req.GetLimit()) if err != nil { return err diff --git a/web/routes.go b/web/routes.go index 03c543f..ca2e339 100644 --- a/web/routes.go +++ b/web/routes.go @@ -130,17 +130,21 @@ func adminRouter(api fiber.Router) { adminRole.Post("/update", handlers.UpdateAdminRole) adminRole.Post("/remove", handlers.RemoveAdminRole) - // admins 管理员账户 - var admins = api.Group("/admin") - admins.Post("/page", handlers.PageAdminsByAdmin) - admins.Post("/create", handlers.CreateAdmin) - admins.Post("/update", handlers.UpdateAdmin) - admins.Post("/remove", handlers.RemoveAdmin) + // admin 管理员账户 + var admin = api.Group("/admin") + admin.Post("/page", handlers.PageAdminsByAdmin) + admin.Post("/all", handlers.ListAdminsByAdmin) + admin.Post("/create", handlers.CreateAdmin) + admin.Post("/update", handlers.UpdateAdmin) + admin.Post("/remove", handlers.RemoveAdmin) // user 用户 var user = api.Group("/user") user.Post("/page", handlers.PageUserByAdmin) user.Post("/bind", handlers.BindAdmin) + user.Post("/create", handlers.CreateUserByAdmin) + user.Post("/update", handlers.UpdateUserByAdmin) + user.Post("/remove", handlers.RemoveUserByAdmin) // resource 套餐 var resource = api.Group("/resource") diff --git a/web/services/admin.go b/web/services/admin.go index bf8775e..6f9cad7 100644 --- a/web/services/admin.go +++ b/web/services/admin.go @@ -23,6 +23,13 @@ func (s *adminService) PageAdmins(req core.PageReq) (result []*m.Admin, count in FindByPage(req.GetOffset(), req.GetLimit()) } +func (s *adminService) All() (result []*m.Admin, err error) { + return q.Admin. + Omit(q.Admin.Password). + Order(q.Admin.CreatedAt.Desc()). + Find() +} + type CreateAdmin struct { Username string `json:"username" validate:"required,min=3,max=50"` Password string `json:"password" validate:"required,min=6,max=50"` diff --git a/web/services/user.go b/web/services/user.go index e17d5d1..eb128a1 100644 --- a/web/services/user.go +++ b/web/services/user.go @@ -1,12 +1,18 @@ package services import ( + "errors" "fmt" + "platform/pkg/u" "platform/web/core" m "platform/web/models" q "platform/web/queries" + "time" "github.com/shopspring/decimal" + "golang.org/x/crypto/bcrypt" + "gorm.io/gen/field" + "gorm.io/gorm" ) var User = &userService{} @@ -62,3 +68,139 @@ func (data *UpdateBalanceData) TradeDetail(user *m.User) (*TradeDetail, error) { nil, nil, }, nil } + +// CreateByAdmin 管理员创建用户的数据 +func (s *userService) CreateByAdmin(data CreateUserByAdminData) error { + var hashedPwd *string + if data.Password != nil { + hash, err := bcrypt.GenerateFromPassword([]byte(*data.Password), bcrypt.DefaultCost) + if err != nil { + return core.NewServErr("密码加密失败", err) + } + hashedPwd = u.P(string(hash)) + } + + source := m.UserSourceAdd + err := q.User.Create(&m.User{ + Phone: data.Phone, + AdminID: data.AdminID, + DiscountID: data.DiscountID, + Username: data.Username, + Email: data.Email, + Password: hashedPwd, + Source: &source, + Name: data.Name, + Avatar: data.Avatar, + Status: u.Else(data.Status, m.UserStatusEnabled), + ContactQQ: data.ContactQQ, + ContactWechat: data.ContactWechat, + }) + if errors.Is(err, gorm.ErrDuplicatedKey) { + return core.NewBizErr("账号已存在,请检查手机号/用户名/邮箱是否重复") + } + if err != nil { + return err + } + + return nil +} + +type CreateUserByAdminData struct { + Phone string `json:"phone" validate:"required"` + AdminID *int32 `json:"admin_id"` + DiscountID *int32 `json:"discount_id"` + Username *string `json:"username"` + Email *string `json:"email"` + Password *string `json:"password"` + Name *string `json:"name"` + Avatar *string `json:"avatar"` + Status *m.UserStatus `json:"status"` + ContactQQ *string `json:"contact_qq"` + ContactWechat *string `json:"contact_wechat"` +} + +// UpdateByAdmin 管理员更新用户的数据 +func (s *userService) UpdateByAdmin(data UpdateUserByAdminData) error { + do := make([]field.AssignExpr, 0) + + if data.Phone != nil { + do = append(do, q.User.Phone.Value(*data.Phone)) + } + if data.AdminID != nil { + do = append(do, q.User.AdminID.Value(*data.AdminID)) + } + if data.DiscountID != nil { + do = append(do, q.User.DiscountID.Value(*data.DiscountID)) + } + if data.Username != nil { + do = append(do, q.User.Username.Value(*data.Username)) + } + if data.Email != nil { + do = append(do, q.User.Email.Value(*data.Email)) + } + if data.Password != nil { + hash, err := bcrypt.GenerateFromPassword([]byte(*data.Password), bcrypt.DefaultCost) + if err != nil { + return core.NewServErr("密码加密失败", err) + } + do = append(do, q.User.Password.Value(string(hash))) + } + if data.Name != nil { + do = append(do, q.User.Name.Value(*data.Name)) + } + if data.Avatar != nil { + do = append(do, q.User.Avatar.Value(*data.Avatar)) + } + if data.Status != nil { + do = append(do, q.User.Status.Value(int(*data.Status))) + } + if data.IDType != nil { + do = append(do, q.User.IDType.Value(int(*data.IDType))) + } + if data.IDNo != nil { + do = append(do, q.User.IDNo.Value(*data.IDNo)) + } + if data.IDToken != nil { + do = append(do, q.User.IDToken.Value(*data.IDToken)) + } + if data.ContactQQ != nil { + do = append(do, q.User.ContactQQ.Value(*data.ContactQQ)) + } + if data.ContactWechat != nil { + do = append(do, q.User.ContactWechat.Value(*data.ContactWechat)) + } + + if len(do) == 0 { + return nil + } + + _, err := q.User.Where(q.User.ID.Eq(data.ID)).UpdateSimple(do...) + if errors.Is(err, gorm.ErrDuplicatedKey) { + return core.NewBizErr("账号已存在,请检查手机号/用户名/邮箱是否重复") + } + + return err +} + +type UpdateUserByAdminData struct { + ID int32 `json:"id" validate:"required"` + Phone *string `json:"phone"` + AdminID *int32 `json:"admin_id"` + DiscountID *int32 `json:"discount_id"` + Username *string `json:"username"` + Email *string `json:"email"` + Password *string `json:"password"` + Name *string `json:"name"` + Avatar *string `json:"avatar"` + Status *m.UserStatus `json:"status"` + IDType *m.UserIDType `json:"id_type"` + IDNo *string `json:"id_no"` + IDToken *string `json:"id_token"` + ContactQQ *string `json:"contact_qq"` + ContactWechat *string `json:"contact_wechat"` +} + +func (s *userService) RemoveByAdmin(id int32) error { + _, err := q.User.Where(q.User.ID.Eq(id)).UpdateColumn(q.User.DeletedAt, time.Now()) + return err +}