From eb521a4c358c9c12d9539791ebc87484e7757e86 Mon Sep 17 00:00:00 2001 From: LoveLosita <2810873701@qq.com> Date: Wed, 4 Feb 2026 20:17:52 +0800 Subject: [PATCH] =?UTF-8?q?Version:0.0.6.dev.260204=20feat:=20=F0=9F=8F=97?= =?UTF-8?q?=EF=B8=8F=20=E5=9C=A8=20sv/dao=20=E5=B1=82=E5=BC=95=E5=85=A5=20?= =?UTF-8?q?ctx=20=E6=94=AF=E6=8C=81=E9=93=BE=E8=B7=AF=E8=BF=BD=E8=B8=AA?= =?UTF-8?q?=E4=B8=8E=E8=B6=85=E6=97=B6=E6=8E=A7=E5=88=B6=EF=BC=88=E9=A2=84?= =?UTF-8?q?=E7=95=99=E6=89=A9=E5=B1=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 架构改造:在 sv 与 dao 层统一引入 ctx 🧩 - 为后续链路追踪、超时控制等能力提供支持 ⏱️(未来开发) perf: 🚀 为获取任务类列表接口引入 Redis 缓存并保证一致性 - 获取任务类列表接口新增 Redis 缓存提升访问速度 ⚡ - 通过新增任务类时主动删除缓存,确保主从一致性 ✅ - 防止缓存与数据库列表不一致问题 🛡️ --- backend/api/schedule.go | 7 ++++- backend/api/task-class.go | 12 +++++++-- backend/api/task.go | 12 +++++++-- backend/api/user.go | 23 +++++++++++++---- backend/cmd/start.go | 2 +- backend/dao/cache.go | 34 +++++++++++++++++++++++++ backend/service/schedule.go | 3 ++- backend/service/task-class.go | 48 ++++++++++++++++++++++++++++------- backend/service/task.go | 6 +++-- backend/service/user.go | 32 +++++------------------ 10 files changed, 130 insertions(+), 49 deletions(-) diff --git a/backend/api/schedule.go b/backend/api/schedule.go index 50fd628..bad3d7c 100644 --- a/backend/api/schedule.go +++ b/backend/api/schedule.go @@ -1,8 +1,10 @@ package api import ( + "context" "errors" "net/http" + "time" "github.com/LoveLosita/smartflow/backend/model" "github.com/LoveLosita/smartflow/backend/respond" @@ -51,7 +53,10 @@ func (sa *ScheduleHandler) AddUserCourses(c *gin.Context) { //2.从上下文获取用户ID userIDInterface := c.GetInt("user_id") //3.调用 service 层的 AddUserCourses 方法添加课程 - err = sa.service.AddUserCourses(req, userIDInterface) + // 创建一个带 1 秒超时的上下文 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + err = sa.service.AddUserCourses(ctx, req, userIDInterface) if err != nil { switch { case errors.Is(err, respond.WrongParamType), errors.Is(err, respond.WrongCourseInfo): diff --git a/backend/api/task-class.go b/backend/api/task-class.go index e273068..65bd264 100644 --- a/backend/api/task-class.go +++ b/backend/api/task-class.go @@ -1,8 +1,10 @@ package api import ( + "context" "errors" "net/http" + "time" "github.com/LoveLosita/smartflow/backend/model" "github.com/LoveLosita/smartflow/backend/respond" @@ -29,7 +31,10 @@ func (api *TaskClassHandler) UserAddTaskClass(c *gin.Context) { return } userIDInterface := c.GetInt("user_id") - err = api.svc.AddTaskClass(&req, userIDInterface) + // 创建一个带 1 秒超时的上下文 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + err = api.svc.AddTaskClass(ctx, &req, userIDInterface) if err != nil { if errors.Is(err, respond.WrongParamType) { c.JSON(http.StatusBadRequest, respond.WrongParamType) @@ -42,7 +47,10 @@ func (api *TaskClassHandler) UserAddTaskClass(c *gin.Context) { func (api *TaskClassHandler) UserGetTaskClassInfos(c *gin.Context) { userIDInterface := c.GetInt("user_id") - resp, err := api.svc.GetUserTaskClassInfos(userIDInterface) + // 创建一个带 1 秒超时的上下文 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + resp, err := api.svc.GetUserTaskClassInfos(ctx, userIDInterface) if err != nil { c.JSON(http.StatusInternalServerError, respond.InternalError(err)) return diff --git a/backend/api/task.go b/backend/api/task.go index 4cf1c17..717cc0d 100644 --- a/backend/api/task.go +++ b/backend/api/task.go @@ -1,9 +1,11 @@ package api import ( + "context" "errors" "fmt" "net/http" + "time" "github.com/LoveLosita/smartflow/backend/model" "github.com/LoveLosita/smartflow/backend/respond" @@ -34,7 +36,10 @@ func (th *TaskHandler) AddTask(c *gin.Context) { // 用户ID从上下文中获取 userID := c.GetInt("user_id") //2. 调用 Service 层处理业务逻辑 - resp, err := th.svc.AddTask(&req, userID) + // 创建一个带 1 秒超时的上下文 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + resp, err := th.svc.AddTask(ctx, &req, userID) if err != nil { switch { case errors.Is(err, respond.InvalidPriority): //如果是无效刷新令牌或者无效claims或者无效签名方法 @@ -52,7 +57,10 @@ func (th *TaskHandler) GetUserTasks(c *gin.Context) { // 用户ID从上下文中获取 userID := c.GetInt("user_id") //2. 调用 Service 层处理业务逻辑 - resp, err := th.svc.GetUserTasks(userID) + // 创建一个带 1 秒超时的上下文 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + resp, err := th.svc.GetUserTasks(ctx, userID) if err != nil { switch { case errors.Is(err, respond.UserTasksEmpty): //如果任务列表为空 diff --git a/backend/api/user.go b/backend/api/user.go index b236e78..24cad80 100644 --- a/backend/api/user.go +++ b/backend/api/user.go @@ -3,8 +3,10 @@ package api import ( + "context" "errors" "net/http" + "time" "github.com/LoveLosita/smartflow/backend/model" "github.com/LoveLosita/smartflow/backend/respond" @@ -33,8 +35,10 @@ func (api *UserHandler) UserRegister(c *gin.Context) { c.JSON(http.StatusBadRequest, respond.WrongParamType) return } - - retUser, err := api.svc.UserRegister(user) + // 创建一个带 1 秒超时的上下文 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + retUser, err := api.svc.UserRegister(ctx, user) if err != nil { switch { case errors.Is(err, respond.InvalidName), errors.Is(err, respond.MissingParam), @@ -57,7 +61,10 @@ func (api *UserHandler) UserLogin(c *gin.Context) { c.JSON(http.StatusOK, respond.WrongParamType) return } - tokens, err := api.svc.UserLogin(&req) + // 创建一个带 1 秒超时的上下文 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + tokens, err := api.svc.UserLogin(ctx, &req) if err != nil { switch { case errors.Is(err, respond.WrongName), errors.Is(err, respond.WrongPwd): //如果是无效ID或者缺少参数的错误 @@ -82,7 +89,10 @@ func (api *UserHandler) RefreshTokenHandler(c *gin.Context) { if requestBody.RefreshToken == "" { c.JSON(http.StatusBadRequest, respond.MissingParam) } - tokens, err := api.svc.RefreshTokenHandler(requestBody.RefreshToken) + // 创建一个带 1 秒超时的上下文 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + tokens, err := api.svc.RefreshTokenHandler(ctx, requestBody.RefreshToken) if err != nil { switch { case errors.Is(err, respond.InvalidRefreshToken), errors.Is(err, respond.InvalidClaims), @@ -101,7 +111,10 @@ func (api *UserHandler) UserLogout(c *gin.Context) { claims, _ := c.Get("claims") cl := claims.(*model.MyCustomClaims) //2.调用 Service 层的 UserLogout 方法 - err := api.svc.UserLogout(cl.Jti, cl.ExpiresAt.Time) + // 创建一个带 1 秒超时的上下文 + ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second) + defer cancel() // 记得释放资源 + err := api.svc.UserLogout(ctx, cl.Jti, cl.ExpiresAt.Time) if err != nil { c.JSON(http.StatusInternalServerError, respond.InternalError(err)) return diff --git a/backend/cmd/start.go b/backend/cmd/start.go index dca74e5..d205dc7 100644 --- a/backend/cmd/start.go +++ b/backend/cmd/start.go @@ -49,7 +49,7 @@ func Start() { userService := service.NewUserService(userRepo, cacheRepo) taskSv := service.NewTaskService(taskRepo) scheduleService := service.NewScheduleService(scheduleRepo) - taskClassService := service.NewTaskClassService(taskClassRepo) + taskClassService := service.NewTaskClassService(taskClassRepo, cacheRepo) //api 层 userApi := api.NewUserHandler(userService) taskApi := api.NewTaskHandler(taskSv) diff --git a/backend/dao/cache.go b/backend/dao/cache.go index 6ce0f27..6632942 100644 --- a/backend/dao/cache.go +++ b/backend/dao/cache.go @@ -2,9 +2,12 @@ package dao import ( "context" + "encoding/json" "errors" + "fmt" "time" + "github.com/LoveLosita/smartflow/backend/model" "github.com/go-redis/redis/v8" ) @@ -31,3 +34,34 @@ func (dao *CacheDAO) IsBlacklisted(jti string) (bool, error) { } return result == "1", nil // 在黑名单 } + +func (dao *CacheDAO) AddTaskClassList(ctx context.Context, userID int, list *model.UserGetTaskClassesResponse) error { + // 1. 定义 Key,使用 userID 隔离不同用户的数据 + key := fmt.Sprintf("smartflow:task_classes:%d", userID) + // 2. 序列化:将结构体转为 []byte + data, err := json.Marshal(list) + if err != nil { + return err + } + // 3. 存储:设置 30 分钟过期(根据业务灵活调整) + return dao.client.Set(ctx, key, data, 30*time.Minute).Err() +} + +func (dao *CacheDAO) GetTaskClassList(ctx context.Context, userID int) (model.UserGetTaskClassesResponse, error) { + key := fmt.Sprintf("smartflow:task_classes:%d", userID) + var resp model.UserGetTaskClassesResponse + // 1. 从 Redis 获取字符串 + val, err := dao.client.Get(ctx, key).Result() + if err != nil { + // 注意:如果是 redis.Nil,交给 Service 层处理查库逻辑 + return resp, err + } + // 2. 反序列化:将 JSON 还原回结构体 + err = json.Unmarshal([]byte(val), &resp) + return resp, err +} + +func (dao *CacheDAO) DeleteTaskClassList(ctx context.Context, userID int) error { + key := fmt.Sprintf("smartflow:task_classes:%d", userID) + return dao.client.Del(ctx, key).Err() +} diff --git a/backend/service/schedule.go b/backend/service/schedule.go index fb13c77..4de6fa1 100644 --- a/backend/service/schedule.go +++ b/backend/service/schedule.go @@ -1,6 +1,7 @@ package service import ( + "context" "fmt" "github.com/LoveLosita/smartflow/backend/dao" @@ -33,7 +34,7 @@ func CheckSingleCourse(req model.UserCheckCourseRequest) bool { } // AddUserCourses 添加用户课程表 -func (ss *ScheduleService) AddUserCourses(req model.UserImportCoursesRequest, userID int) error { +func (ss *ScheduleService) AddUserCourses(ctx context.Context, req model.UserImportCoursesRequest, userID int) error { //1.先校验参数是否正确 for _, course := range req.Courses { result := CheckSingleCourse(course) diff --git a/backend/service/task-class.go b/backend/service/task-class.go index e7da6b7..c67b855 100644 --- a/backend/service/task-class.go +++ b/backend/service/task-class.go @@ -1,51 +1,81 @@ package service import ( + "context" + "errors" + "log" + "github.com/LoveLosita/smartflow/backend/conv" "github.com/LoveLosita/smartflow/backend/dao" "github.com/LoveLosita/smartflow/backend/model" + "github.com/go-redis/redis/v8" ) type TaskClassService struct { // 这里可以添加数据库连接或其他依赖 taskClassRepo *dao.TaskClassDAO + cacheRepo *dao.CacheDAO } -func NewTaskClassService(taskClassRepo *dao.TaskClassDAO) *TaskClassService { +func NewTaskClassService(taskClassRepo *dao.TaskClassDAO, cacheRepo *dao.CacheDAO) *TaskClassService { return &TaskClassService{ taskClassRepo: taskClassRepo, + cacheRepo: cacheRepo, } } // AddTaskClass 为指定用户添加任务类 -func (sv *TaskClassService) AddTaskClass(req *model.UserAddTaskClassRequest, userID int) error { - return sv.taskClassRepo.Transaction(func(txDAO *dao.TaskClassDAO) error { - //1.先转换 +func (sv *TaskClassService) AddTaskClass(ctx context.Context, req *model.UserAddTaskClassRequest, userID int) error { + // 1) 先写数据库(事务内) + if err := sv.taskClassRepo.Transaction(func(txDAO *dao.TaskClassDAO) error { taskClass, items, err := conv.ProcessUserAddTaskClassRequest(req, userID) if err != nil { return err } - //2.插入 task class 并获取 ID + taskClassID, err := txDAO.AddTaskClass(taskClass) if err != nil { return err } - //3.插入 items + for i := range items { - items[i].CategoryID = &taskClassID // 关联 task class ID + items[i].CategoryID = &taskClassID } if err := txDAO.AddTaskClassItems(items); err != nil { return err } return nil - }) + }); err != nil { + return err + } + + // 2) 事务提交成功后删除该用户缓存(如果有) + err := sv.cacheRepo.DeleteTaskClassList(ctx, userID) + if err != nil { + log.Printf("redis删除任务分类列表缓存失败: userID=%d err=%v", userID, err) + } + return nil } -func (sv *TaskClassService) GetUserTaskClassInfos(userID int) (*model.UserGetTaskClassesResponse, error) { +func (sv *TaskClassService) GetUserTaskClassInfos(ctx context.Context, userID int) (*model.UserGetTaskClassesResponse, error) { + //1.先查询redis + list, err := sv.cacheRepo.GetTaskClassList(ctx, userID) + if err == nil { + //命中缓存 + return &list, nil + } else if !errors.Is(err, redis.Nil) { //不是缓存未命中错误,说明redis可能炸了,照常放行 + log.Println("redis获取任务分类列表失败:", err) + } + //2.缓存未命中,查询数据库 taskClasses, err := sv.taskClassRepo.GetUserTaskClasses(userID) if err != nil { return nil, err } resp := conv.TaskClassModelToResponse(taskClasses) + //3.写入缓存 + err = sv.cacheRepo.AddTaskClassList(ctx, userID, resp) + if err != nil { + return nil, err + } return resp, nil } diff --git a/backend/service/task.go b/backend/service/task.go index 7512e80..5767c82 100644 --- a/backend/service/task.go +++ b/backend/service/task.go @@ -1,6 +1,8 @@ package service import ( + "context" + "github.com/LoveLosita/smartflow/backend/conv" "github.com/LoveLosita/smartflow/backend/dao" "github.com/LoveLosita/smartflow/backend/model" @@ -19,7 +21,7 @@ func NewTaskService(dao *dao.TaskDAO) *TaskService { } } -func (ts *TaskService) AddTask(req *model.UserAddTaskRequest, userID int) (*model.UserAddTaskResponse, error) { +func (ts *TaskService) AddTask(ctx context.Context, req *model.UserAddTaskRequest, userID int) (*model.UserAddTaskResponse, error) { //1. 调用 conv 层进行转换 taskModel := conv.UserAddTaskRequestToModel(req, userID) //2.检查优先级是否合法 @@ -36,7 +38,7 @@ func (ts *TaskService) AddTask(req *model.UserAddTaskRequest, userID int) (*mode return response, nil } -func (ts *TaskService) GetUserTasks(userID int) ([]model.GetUserTaskResp, error) { +func (ts *TaskService) GetUserTasks(ctx context.Context, userID int) ([]model.GetUserTaskResp, error) { //1. 调用 dao 层获取数据 tasks, err := ts.dao.GetTasksByUserID(userID) if err != nil { diff --git a/backend/service/user.go b/backend/service/user.go index 858e4c4..f1a9fd1 100644 --- a/backend/service/user.go +++ b/backend/service/user.go @@ -6,6 +6,8 @@ import ( "errors" "time" + "context" + "github.com/LoveLosita/smartflow/backend/auth" "github.com/LoveLosita/smartflow/backend/dao" "github.com/LoveLosita/smartflow/backend/model" @@ -26,7 +28,7 @@ func NewUserService(userRepo *dao.UserDAO, cacheRepo *dao.CacheDAO) *UserService } } -func (sv *UserService) UserRegister(user model.UserRegisterRequest) (*model.UserRegisterResponse, error) { +func (sv *UserService) UserRegister(ctx context.Context, user model.UserRegisterRequest) (*model.UserRegisterResponse, error) { //检查是否有空字段 if user.Username == "" || user.Password == "" || user.PhoneNumber == "" { @@ -57,7 +59,7 @@ func (sv *UserService) UserRegister(user model.UserRegisterRequest) (*model.User return &model.UserRegisterResponse{ID: newUser.ID}, nil } -func (sv *UserService) UserLogin(req *model.UserLoginRequest) (*model.Tokens, error) { +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 { @@ -86,29 +88,7 @@ func (sv *UserService) UserLogin(req *model.UserLoginRequest) (*model.Tokens, er return &tokens, nil } -/*func (sv *UserService) RefreshTokenHandler(refreshToken string) (*model.Tokens, error) { - // 验证刷新令牌 - token, err := auth.ValidateRefreshToken(refreshToken, sv.cacheRepo) - if err != nil || !token.Valid { // 刷新令牌无效 - return nil, respond.InvalidRefreshToken - } - - // 生成新的访问令牌和刷新令牌 - if claims, ok := token.Claims.(jwt.MapClaims); ok { - userID := int(claims["user_id"].(float64)) - newAccessToken, newRefreshToken, err := auth.GenerateTokens(userID) - if err != nil { - return nil, err - } - - // 返回新的访问令牌和刷新令牌 - return &model.Tokens{AccessToken: newAccessToken, RefreshToken: newRefreshToken}, nil - } else { - return nil, respond.InvalidClaims - } -}*/ - -func (sv *UserService) RefreshTokenHandler(refreshToken string) (*model.Tokens, error) { +func (sv *UserService) RefreshTokenHandler(ctx context.Context, refreshToken string) (*model.Tokens, error) { // 1. 验证刷新令牌 (这里已经包含了 Redis 黑名单检查) token, err := auth.ValidateRefreshToken(refreshToken, sv.cacheRepo) if err != nil { @@ -132,7 +112,7 @@ func (sv *UserService) RefreshTokenHandler(refreshToken string) (*model.Tokens, return nil, respond.InvalidClaims } -func (sv *UserService) UserLogout(jti string, expireTime time.Time) error { +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)