feat: 🏗️ 在 sv/dao 层引入 ctx 支持链路追踪与超时控制(预留扩展) - 架构改造:在 sv 与 dao 层统一引入 ctx 🧩 - 为后续链路追踪、超时控制等能力提供支持 ⏱️(未来开发) perf: 🚀 为获取任务类列表接口引入 Redis 缓存并保证一致性 - 获取任务类列表接口新增 Redis 缓存提升访问速度 ⚡ - 通过新增任务类时主动删除缓存,确保主从一致性 ✅ - 防止缓存与数据库列表不一致问题 🛡️
124 lines
3.7 KiB
Go
124 lines
3.7 KiB
Go
// Package service 业务逻辑层
|
|
// 包含所有核心业务逻辑
|
|
package service
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"context"
|
|
|
|
"github.com/LoveLosita/smartflow/backend/auth"
|
|
"github.com/LoveLosita/smartflow/backend/dao"
|
|
"github.com/LoveLosita/smartflow/backend/model"
|
|
"github.com/LoveLosita/smartflow/backend/respond"
|
|
"github.com/LoveLosita/smartflow/backend/utils"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type UserService struct {
|
|
userRepo *dao.UserDAO
|
|
cacheRepo *dao.CacheDAO
|
|
}
|
|
|
|
func NewUserService(userRepo *dao.UserDAO, cacheRepo *dao.CacheDAO) *UserService {
|
|
return &UserService{
|
|
userRepo: userRepo, // 把传进来的 DAO 揣进口袋里
|
|
cacheRepo: cacheRepo,
|
|
}
|
|
}
|
|
|
|
func (sv *UserService) UserRegister(ctx context.Context, user model.UserRegisterRequest) (*model.UserRegisterResponse, error) {
|
|
//检查是否有空字段
|
|
if user.Username == "" || user.Password == "" ||
|
|
user.PhoneNumber == "" {
|
|
return nil, respond.MissingParam
|
|
}
|
|
// 检查字段长度是否超过90%
|
|
if len(user.Username) > 45 || len(user.Password) > 229 || len(user.PhoneNumber) > 18 {
|
|
return nil, respond.ParamTooLong
|
|
}
|
|
//检查用户名是否已存在
|
|
result, err := sv.userRepo.IfUsernameExists(user.Username)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if result {
|
|
return nil, respond.InvalidName
|
|
}
|
|
hashedPwd, err := utils.HashPassword(user.Password) //调用utils层的方法
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
user.Password = hashedPwd //将user的密码字段改为加密后的密码
|
|
newUser, err := sv.userRepo.Create(user.Username, user.PhoneNumber, user.Password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
//返回注册成功的用户ID
|
|
return &model.UserRegisterResponse{ID: newUser.ID}, nil
|
|
}
|
|
|
|
func (sv *UserService) UserLogin(ctx context.Context, req *model.UserLoginRequest) (*model.Tokens, error) {
|
|
var tokens model.Tokens
|
|
hashedPwd, err := sv.userRepo.GetUserHashedPasswordByName(req.Username) //调用dao层的方法
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, respond.WrongName
|
|
}
|
|
return nil, err
|
|
}
|
|
result, err := utils.CompareHashPwdAndPwd(hashedPwd, req.Password) //比较密码是否匹配
|
|
if err != nil { //其他错误
|
|
return &tokens, err
|
|
} else if !result { //密码不匹配
|
|
return nil, respond.WrongPwd
|
|
}
|
|
id, err := sv.userRepo.GetUserIDByName(req.Username)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, respond.WrongName
|
|
}
|
|
return nil, err
|
|
}
|
|
tokens.AccessToken, tokens.RefreshToken, err = auth.GenerateTokens(id) //生成jwt key
|
|
if err != nil { //其他错误
|
|
return nil, err
|
|
}
|
|
return &tokens, nil
|
|
}
|
|
|
|
func (sv *UserService) RefreshTokenHandler(ctx context.Context, refreshToken string) (*model.Tokens, error) {
|
|
// 1. 验证刷新令牌 (这里已经包含了 Redis 黑名单检查)
|
|
token, err := auth.ValidateRefreshToken(refreshToken, sv.cacheRepo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 2. 改动点:直接断言为你定义的结构体 model.MyCustomClaims
|
|
if claims, ok := token.Claims.(*model.MyCustomClaims); ok {
|
|
// 3. 这里的 userID 已经是 int 了,不再需要 (float64) 转换
|
|
newAccessToken, newRefreshToken, err := auth.GenerateTokens(claims.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// 返回新的双 Token
|
|
return &model.Tokens{
|
|
AccessToken: newAccessToken,
|
|
RefreshToken: newRefreshToken,
|
|
}, nil
|
|
}
|
|
|
|
return nil, respond.InvalidClaims
|
|
}
|
|
|
|
func (sv *UserService) UserLogout(ctx context.Context, jti string, expireTime time.Time) error {
|
|
//1.直接把 jti 扔进黑名单
|
|
expiration := time.Until(expireTime)
|
|
err := sv.cacheRepo.SetBlacklist(jti, expiration)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|