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.Default.Session(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.Default.Session(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.Default.Session(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.Default.Session(tool.Timeout(10)). Model(&model.User{}). Where("deleted_at = 0")) txCount := op.Role.Where(db.Default.Session(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.Default.Session(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.Default.Session(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, "删除成功") }