567 lines
14 KiB
Go
567 lines
14 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/loveuer/nf"
|
|
"github.com/loveuer/nf/nft/resp"
|
|
"github.com/samber/lo"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
"time"
|
|
"ultone/internal/controller"
|
|
"ultone/internal/database/cache"
|
|
"ultone/internal/database/db"
|
|
"ultone/internal/middleware/oplog"
|
|
"ultone/internal/model"
|
|
"ultone/internal/opt"
|
|
"ultone/internal/sqlType"
|
|
"ultone/internal/tool"
|
|
)
|
|
|
|
func AuthLogin(c *nf.Ctx) error {
|
|
type Req struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
var (
|
|
err error
|
|
req = new(Req)
|
|
target = new(model.User)
|
|
token string
|
|
now = time.Now()
|
|
)
|
|
|
|
if err = c.BodyParser(req); err != nil {
|
|
return resp.Resp400(c, err.Error())
|
|
}
|
|
|
|
if err = db.New(tool.Timeout(3)).
|
|
Model(&model.User{}).
|
|
Where("username = ?", req.Username).
|
|
Where("deleted_at = 0").
|
|
Take(target).
|
|
Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return resp.Resp400(c, err.Error(), "用户名或密码错误")
|
|
}
|
|
|
|
return resp.Resp500(c, err.Error())
|
|
}
|
|
|
|
if !tool.ComparePassword(req.Password, target.Password) {
|
|
return resp.Resp400(c, nil, "用户名或密码错误")
|
|
}
|
|
|
|
if err = target.IsValid(true); err != nil {
|
|
return resp.Resp401(c, nil, err.Error())
|
|
}
|
|
|
|
if err = controller.UserController.CacheUser(c.Context(), target); err != nil {
|
|
return resp.RespError(c, err)
|
|
}
|
|
|
|
if token, err = target.JwtEncode(); err != nil {
|
|
return resp.Resp500(c, err.Error())
|
|
}
|
|
|
|
if err = controller.UserController.CacheToken(c.Context(), token, target); err != nil {
|
|
return resp.RespError(c, err)
|
|
}
|
|
|
|
if !opt.MultiLogin {
|
|
var (
|
|
last = fmt.Sprintf("%s:user:last_token:%d", opt.CachePrefix, target.Id)
|
|
bs []byte
|
|
)
|
|
|
|
// 获取之前的 token
|
|
if bs, err = cache.Client.Get(tool.Timeout(3), last); err == nil {
|
|
key := fmt.Sprintf("%s:user:token:%s", opt.CachePrefix, string(bs))
|
|
_ = cache.Client.Del(tool.Timeout(3), key)
|
|
}
|
|
|
|
// 删掉之前的 token
|
|
if len(bs) > 0 {
|
|
_ = controller.UserController.RmToken(c.Context(), string(bs))
|
|
}
|
|
|
|
// 将当前的 token 存入 last_token
|
|
if err = cache.Client.Set(tool.Timeout(3), last, token); err != nil {
|
|
return resp.Resp500(c, err.Error())
|
|
}
|
|
}
|
|
|
|
c.Set("Set-Cookie", fmt.Sprintf("%s=%s; Path=/", opt.CookieName, token))
|
|
c.Locals("user", target)
|
|
c.Locals(opt.OpLogLocalKey, &oplog.OpLog{Type: model.OpLogTypeLogin, Content: map[string]any{
|
|
"time": now.UnixMilli(),
|
|
"ip": c.IP(true),
|
|
}})
|
|
|
|
return resp.Resp200(c, nf.Map{"token": token, "user": target})
|
|
}
|
|
|
|
func AuthVerify(c *nf.Ctx) error {
|
|
op, ok := c.Locals("user").(*model.User)
|
|
if !ok {
|
|
return resp.Resp401(c, nil)
|
|
}
|
|
|
|
token, ok := c.Locals("token").(string)
|
|
if !ok {
|
|
return resp.Resp401(c, nil)
|
|
}
|
|
|
|
return resp.Resp200(c, nf.Map{"token": token, "user": op})
|
|
}
|
|
|
|
func AuthLogout(c *nf.Ctx) error {
|
|
op, ok := c.Locals("user").(*model.User)
|
|
if !ok {
|
|
return resp.Resp401(c, nil)
|
|
}
|
|
|
|
_ = controller.UserController.RmUserCache(c.Context(), op.Id)
|
|
|
|
c.Locals(opt.OpLogLocalKey, &oplog.OpLog{
|
|
Type: model.OpLogTypeLogout,
|
|
Content: map[string]any{
|
|
"time": time.Now().UnixMilli(),
|
|
"ip": c.IP(),
|
|
},
|
|
})
|
|
|
|
return resp.Resp200(c, nil)
|
|
}
|
|
|
|
func UserUpdate(c *nf.Ctx) error {
|
|
type Req struct {
|
|
OldPassword string `json:"old_password"`
|
|
NewPassword string `json:"new_password"`
|
|
}
|
|
|
|
type Model struct {
|
|
Password string `gorm:"column:password"`
|
|
}
|
|
|
|
var (
|
|
ok bool
|
|
err error
|
|
req = new(Req)
|
|
user *model.User
|
|
m = new(Model)
|
|
)
|
|
|
|
if user, ok = c.Locals("user").(*model.User); !ok {
|
|
return resp.Resp401(c, nil)
|
|
}
|
|
|
|
if err = c.BodyParser(req); err != nil {
|
|
return resp.Resp400(c, err)
|
|
}
|
|
|
|
if req.OldPassword == "" || req.NewPassword == "" {
|
|
return resp.Resp400(c, req)
|
|
}
|
|
|
|
if err = tool.CheckPassword(req.NewPassword); err != nil {
|
|
return resp.Resp400(c, req, err.Error())
|
|
}
|
|
|
|
if err = db.New(tool.Timeout(3)).
|
|
Select("password").
|
|
Model(&model.User{}).
|
|
Where("username = ?", user.Username).
|
|
Where("deleted_at = 0").
|
|
Take(m).
|
|
Error; err != nil {
|
|
return resp.Resp500(c, err.Error())
|
|
}
|
|
|
|
if !tool.ComparePassword(req.OldPassword, m.Password) {
|
|
return resp.Resp400(c, nil, "原密码错误")
|
|
}
|
|
|
|
if err = db.New(tool.Timeout(5)).
|
|
Model(&model.User{}).
|
|
Where("id = ?", user.Id).
|
|
Update("password", tool.NewPassword(req.NewPassword)).
|
|
Error; err != nil {
|
|
return resp.Resp500(c, err.Error())
|
|
}
|
|
|
|
_ = controller.UserController.RmUserCache(c.Context(), user.Id)
|
|
// todo delete token
|
|
|
|
c.SetHeader("Set-Cookie", fmt.Sprintf("%s=;Path=/", opt.CookieName))
|
|
|
|
return resp.Resp200(c, nil, "修改成功, 请重新登录")
|
|
}
|
|
|
|
func ManageUserList(c *nf.Ctx) error {
|
|
type Req struct {
|
|
Page int `query:"page"`
|
|
Size int `query:"size"`
|
|
Keyword string `query:"keyword"`
|
|
}
|
|
|
|
var (
|
|
err error
|
|
ok bool
|
|
op *model.User
|
|
req = new(Req)
|
|
list = make([]*model.User, 0)
|
|
total = 0
|
|
)
|
|
|
|
if op, ok = c.Locals("user").(*model.User); !ok {
|
|
return resp.Resp401(c, nil)
|
|
}
|
|
|
|
if err = c.QueryParser(req); err != nil {
|
|
return resp.Resp400(c, err.Error())
|
|
}
|
|
|
|
if req.Size == 0 {
|
|
req.Size = opt.DefaultSize
|
|
}
|
|
|
|
if req.Size > opt.MaxSize {
|
|
return resp.Resp400(c, nf.Map{"msg": "size over max", "max": opt.MaxSize})
|
|
}
|
|
|
|
txList := op.Role.Where(db.New(tool.Timeout(10)).
|
|
Model(&model.User{}).
|
|
Where("deleted_at = 0"))
|
|
txCount := op.Role.Where(db.New(tool.Timeout(5)).
|
|
Model(&model.User{}).
|
|
Select("COUNT(id)").
|
|
Where("deleted_at = 0"))
|
|
|
|
if req.Keyword != "" {
|
|
keyword := fmt.Sprintf("%%%s%%", req.Keyword)
|
|
txList = txList.Where("username LIKE ?", keyword)
|
|
txCount = txCount.Where("username LIKE ?", keyword)
|
|
}
|
|
|
|
if err = txList.
|
|
Order("updated_at DESC").
|
|
Offset(req.Page * req.Size).
|
|
Limit(req.Size).
|
|
Find(&list).
|
|
Error; err != nil {
|
|
return resp.Resp500(c, err.Error())
|
|
}
|
|
|
|
if err = txCount.
|
|
Find(&total).
|
|
Error; err != nil {
|
|
return resp.Resp500(c, err.Error())
|
|
}
|
|
|
|
return resp.Resp200(c, nf.Map{"list": list, "total": total})
|
|
}
|
|
|
|
func ManageUserCreate(c *nf.Ctx) error {
|
|
type Req struct {
|
|
Username string `json:"username"`
|
|
Nickname string `json:"nickname"`
|
|
Password string `json:"password"`
|
|
Status model.Status `json:"status"`
|
|
Role model.Role `json:"role"`
|
|
Privileges sqlType.NumSlice[model.Privilege] `json:"privileges"`
|
|
Comment string `json:"comment"`
|
|
ActiveAt int64 `json:"active_at"`
|
|
Deadline int64 `json:"deadline"`
|
|
}
|
|
|
|
var (
|
|
err error
|
|
ok bool
|
|
op *model.User
|
|
req = new(Req)
|
|
now = time.Now()
|
|
)
|
|
|
|
if op, ok = c.Locals("user").(*model.User); !ok {
|
|
return resp.Resp401(c, nil)
|
|
}
|
|
|
|
if err = c.BodyParser(req); err != nil {
|
|
return resp.Resp400(c, err.Error())
|
|
}
|
|
|
|
if req.Username == "" || req.Password == "" {
|
|
return resp.Resp400(c, req)
|
|
}
|
|
|
|
if err = tool.CheckPassword(req.Password); err != nil {
|
|
return resp.Resp400(c, req, err.Error())
|
|
}
|
|
|
|
if req.Nickname == "" {
|
|
req.Nickname = req.Username
|
|
}
|
|
|
|
if req.Status.Code() == "unknown" {
|
|
return resp.Resp400(c, req, "用户状态不正常")
|
|
}
|
|
|
|
if req.Role == 0 {
|
|
req.Role = model.RoleUser
|
|
}
|
|
|
|
if req.ActiveAt == 0 {
|
|
req.ActiveAt = now.UnixMilli()
|
|
}
|
|
|
|
if req.Deadline == 0 {
|
|
req.Deadline = now.AddDate(99, 0, 0).UnixMilli()
|
|
}
|
|
|
|
newUser := &model.User{
|
|
CreatedAt: now.UnixMilli(),
|
|
UpdatedAt: now.UnixMilli(),
|
|
Username: req.Username,
|
|
Password: tool.NewPassword(req.Password),
|
|
Status: req.Status,
|
|
Nickname: req.Nickname,
|
|
Comment: req.Comment,
|
|
Role: req.Role,
|
|
Privileges: req.Privileges,
|
|
CreatedById: op.Id,
|
|
CreatedByName: op.CreatedByName,
|
|
ActiveAt: op.ActiveAt,
|
|
Deadline: op.Deadline,
|
|
}
|
|
|
|
if err = newUser.IsValid(false); err != nil {
|
|
return resp.Resp400(c, newUser, err.Error())
|
|
}
|
|
|
|
if !newUser.Role.CanOP(op) {
|
|
return resp.Resp403(c, newUser, "角色不符合")
|
|
}
|
|
|
|
if err = db.New(tool.Timeout(5)).
|
|
Create(newUser).
|
|
Error; err != nil {
|
|
return resp.Resp500(c, err.Error())
|
|
}
|
|
|
|
c.Locals(opt.OpLogLocalKey, &oplog.OpLog{Type: model.OpLogTypeCreateUser, Content: map[string]any{
|
|
"target_id": newUser.Id,
|
|
"target_username": newUser.Username,
|
|
"target_nickname": newUser.Nickname,
|
|
"target_status": newUser.Status.Label(),
|
|
"target_role": newUser.Role.Label(),
|
|
"target_privileges": lo.Map(newUser.Privileges, func(item model.Privilege, index int) string {
|
|
return item.Label()
|
|
}),
|
|
"target_active_at": op.ActiveAt,
|
|
"target_deadline": op.Deadline,
|
|
}})
|
|
|
|
return resp.Resp200(c, newUser)
|
|
}
|
|
|
|
func ManageUserUpdate(c *nf.Ctx) error {
|
|
type Req struct {
|
|
Id uint64 `json:"id"`
|
|
Nickname string `json:"nickname"`
|
|
Password string `json:"password"`
|
|
Status model.Status `json:"status"`
|
|
Comment string `json:"comment"`
|
|
Role model.Role `json:"role"`
|
|
Privileges sqlType.NumSlice[model.Privilege] `json:"privileges"`
|
|
ActiveAt int64 `json:"active_at"`
|
|
Deadline int64 `json:"deadline"`
|
|
}
|
|
|
|
type Change struct {
|
|
Old any `json:"old"`
|
|
New any `json:"new"`
|
|
}
|
|
|
|
var (
|
|
ok bool
|
|
op *model.User
|
|
target *model.User
|
|
err error
|
|
req = new(Req)
|
|
rm = make(map[string]any)
|
|
updates = make(map[string]any)
|
|
changes = make(map[string]Change)
|
|
)
|
|
|
|
if op, ok = c.Locals("user").(*model.User); !ok {
|
|
return resp.Resp401(c, nil)
|
|
}
|
|
|
|
if err = c.BodyParser(req); err != nil {
|
|
return resp.Resp400(c, err.Error())
|
|
}
|
|
|
|
if err = c.BodyParser(&rm); err != nil {
|
|
return resp.Resp400(c, err.Error())
|
|
}
|
|
|
|
if req.Id == 0 {
|
|
return resp.Resp400(c, "未指定目标用户")
|
|
}
|
|
|
|
if target, err = controller.UserController.GetUser(c.Context(), req.Id); err != nil {
|
|
return resp.RespError(c, err)
|
|
}
|
|
|
|
if op.Role < target.Role || ((op.Role == target.Role) && opt.RoleMustLess) {
|
|
return resp.Resp403(c, req)
|
|
}
|
|
|
|
if op.Id == req.Id {
|
|
return resp.Resp403(c, req, "无法更新自己")
|
|
}
|
|
|
|
if _, ok = rm["nickname"]; ok {
|
|
if req.Nickname == "" {
|
|
return resp.Resp400(c, req)
|
|
}
|
|
|
|
updates["nickname"] = req.Nickname
|
|
changes["昵称"] = Change{Old: target.Nickname, New: req.Nickname}
|
|
}
|
|
|
|
if _, ok = rm["password"]; ok {
|
|
if err = tool.CheckPassword(req.Password); err != nil {
|
|
return resp.Resp400(c, err.Error())
|
|
}
|
|
|
|
updates["password"] = tool.NewPassword(req.Password)
|
|
changes["密码"] = Change{Old: "******", New: "******"}
|
|
}
|
|
|
|
if _, ok = rm["status"]; ok {
|
|
if req.Status.Code() == "unknown" {
|
|
return resp.Resp400(c, req, "用户状态不符合")
|
|
}
|
|
|
|
updates["status"] = req.Status
|
|
changes["状态"] = Change{Old: target.Status.Label(), New: req.Status.Label()}
|
|
}
|
|
|
|
if _, ok = rm["comment"]; ok {
|
|
updates["comment"] = req.Comment
|
|
changes["备注"] = Change{Old: target.Comment, New: req.Comment}
|
|
}
|
|
|
|
if _, ok = rm["role"]; ok {
|
|
if op.Role < req.Role || ((op.Role == req.Role) && opt.RoleMustLess) {
|
|
return resp.Resp400(c, req, "用户角色不符合")
|
|
}
|
|
|
|
updates["role"] = req.Role
|
|
changes["角色"] = Change{Old: target.Role.Label(), New: req.Role.Label()}
|
|
}
|
|
|
|
if _, ok = rm["privileges"]; ok {
|
|
for _, val := range req.Privileges {
|
|
if lo.IndexOf(op.Privileges, val) < 0 {
|
|
return resp.Resp400(c, req, fmt.Sprintf("权限: %s 不符合", val.Label()))
|
|
}
|
|
}
|
|
|
|
changes["权限"] = Change{
|
|
Old: lo.Map(target.Privileges, func(item model.Privilege, index int) string {
|
|
return item.Label()
|
|
}),
|
|
New: lo.Map(req.Privileges, func(item model.Privilege, index int) string {
|
|
return item.Label()
|
|
}),
|
|
}
|
|
updates["privileges"] = req.Privileges
|
|
}
|
|
|
|
if _, ok = rm["active_at"]; ok {
|
|
updates["active_at"] = time.UnixMilli(req.ActiveAt).UnixMilli()
|
|
changes["激活时间"] = Change{Old: target.ActiveAt, New: req.ActiveAt}
|
|
}
|
|
|
|
if _, ok = rm["deadline"]; ok {
|
|
updates["deadline"] = time.UnixMilli(req.Deadline).UnixMilli()
|
|
changes["到期时间"] = Change{Old: target.Deadline, New: req.Deadline}
|
|
}
|
|
|
|
updated := new(model.User)
|
|
if err = db.New(tool.Timeout(5)).
|
|
Model(updated).
|
|
Clauses(clause.Returning{}).
|
|
Where("id = ?", req.Id).
|
|
Updates(updates).
|
|
Error; err != nil {
|
|
return resp.Resp500(c, err.Error())
|
|
}
|
|
|
|
if err = controller.UserController.RmUserCache(c.Context(), req.Id); err != nil {
|
|
return resp.RespError(c, err)
|
|
}
|
|
|
|
c.Locals(opt.OpLogLocalKey, &oplog.OpLog{Type: model.OpLogTypeUpdateUser, Content: map[string]any{
|
|
"target_id": target.Id,
|
|
"target_username": target.Username,
|
|
"changes": changes,
|
|
}})
|
|
|
|
return resp.Resp200(c, updated)
|
|
}
|
|
|
|
func ManageUserDelete(c *nf.Ctx) error {
|
|
type Req struct {
|
|
Id uint64 `json:"id"`
|
|
}
|
|
|
|
var (
|
|
ok bool
|
|
op *model.User
|
|
target *model.User
|
|
err error
|
|
req = new(Req)
|
|
)
|
|
|
|
if err = c.BodyParser(req); err != nil {
|
|
return resp.Resp400(c, err.Error())
|
|
}
|
|
|
|
if req.Id == 0 {
|
|
return resp.Resp400(c, req)
|
|
}
|
|
|
|
if op, ok = c.Locals("user").(*model.User); !ok {
|
|
return resp.Resp401(c, nil)
|
|
}
|
|
|
|
if req.Id == op.Id {
|
|
return resp.Resp400(c, nil, "无法删除自己")
|
|
}
|
|
|
|
if target, err = controller.UserController.GetUser(c.Context(), req.Id); err != nil {
|
|
return resp.RespError(c, err)
|
|
}
|
|
|
|
if op.Role < target.Role || (op.Role == target.Role && opt.RoleMustLess) {
|
|
return resp.Resp403(c, nil)
|
|
}
|
|
|
|
if err = controller.UserController.DeleteUser(c.Context(), target.Id); err != nil {
|
|
return resp.RespError(c, err)
|
|
}
|
|
|
|
c.Locals(opt.OpLogLocalKey, &oplog.OpLog{Type: model.OpLogTypeDeleteUser, Content: map[string]any{
|
|
"target_id": target.Id,
|
|
"target_username": target.Username,
|
|
}})
|
|
|
|
return resp.Resp200(c, nil, "删除成功")
|
|
}
|