Version: 0.6.8.dev.260317
- 🧹 删除 `docs/apifox` 目录,接口契约统一迁移并维护于 Apifox 云端 - ✨ 新增“取消任务完成状态”接口
This commit is contained in:
@@ -95,3 +95,36 @@ func (th *TaskHandler) CompleteTask(c *gin.Context) {
|
||||
// 5. 返回统一响应结构。
|
||||
c.JSON(http.StatusOK, respond.RespWithData(respond.Ok, resp))
|
||||
}
|
||||
|
||||
// UndoCompleteTask 取消任务“已完成”勾选。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责解析请求与读取 user_id;
|
||||
// 2. 负责调用 Service 执行业务恢复;
|
||||
// 3. 不负责“任务是否已完成”的业务判断(由 Service/DAO 负责)。
|
||||
func (th *TaskHandler) UndoCompleteTask(c *gin.Context) {
|
||||
// 1. 绑定请求参数。
|
||||
var req model.UserUndoCompleteTaskRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, respond.WrongParamType)
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 从鉴权上下文读取 user_id,保证只操作当前用户任务。
|
||||
userID := c.GetInt("user_id")
|
||||
|
||||
// 3. 设置短超时,避免该写接口占用连接过久。
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// 4. 调用 Service 执行“取消已完成勾选”逻辑。
|
||||
resp, err := th.svc.UndoCompleteTask(ctx, &req, userID)
|
||||
if err != nil {
|
||||
respond.DealWithError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 5. 返回统一响应结构。
|
||||
c.JSON(http.StatusOK, respond.RespWithData(respond.Ok, resp))
|
||||
}
|
||||
|
||||
@@ -116,6 +116,74 @@ func (dao *TaskDAO) CompleteTaskByID(ctx context.Context, userID int, taskID int
|
||||
return &target, false, nil
|
||||
}
|
||||
|
||||
// UndoCompleteTaskByID 将指定任务从“已完成”恢复为“未完成”。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只负责当前用户(user_id)下指定 task_id 的状态恢复;
|
||||
// 2. 若任务本就未完成,按业务要求返回明确错误,不做幂等成功;
|
||||
// 3. 不负责响应文案拼装(由 Service 层处理)。
|
||||
//
|
||||
// 返回语义:
|
||||
// 1. *model.Task:恢复后的任务快照;
|
||||
// 2. error:
|
||||
// 2.1 gorm.ErrRecordNotFound:任务不存在或不属于当前用户;
|
||||
// 2.2 respond.TaskNotCompleted:任务当前不是“已完成”状态,不能执行取消勾选;
|
||||
// 2.3 其他 error:数据库异常。
|
||||
func (dao *TaskDAO) UndoCompleteTaskByID(ctx context.Context, userID int, taskID int) (*model.Task, error) {
|
||||
// 1. 参数兜底:非法 user/task 参数统一按“记录不存在”处理,避免误写。
|
||||
if userID <= 0 || taskID <= 0 {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
// 2. 先读取目标任务,明确区分“不存在”和“状态不允许恢复”。
|
||||
var target model.Task
|
||||
findErr := dao.db.WithContext(ctx).
|
||||
Where("id = ? AND user_id = ?", taskID, userID).
|
||||
First(&target).Error
|
||||
if findErr != nil {
|
||||
return nil, findErr
|
||||
}
|
||||
|
||||
// 3. 严格业务约束:若任务当前未完成,直接返回业务错误。
|
||||
// 3.1 这是本接口和“标记完成”接口的关键差异:这里不做幂等成功。
|
||||
if !target.IsCompleted {
|
||||
return nil, respond.TaskNotCompleted
|
||||
}
|
||||
|
||||
// 4. 执行状态恢复(is_completed=true -> false)。
|
||||
//
|
||||
// 4.1 使用 Model(&model.Task{UserID:userID}) 的目的是让 cache_deleter 拿到 user_id,
|
||||
// 从而在回调中正确删除该用户任务缓存。
|
||||
updateResult := dao.db.WithContext(ctx).
|
||||
Model(&model.Task{UserID: userID}).
|
||||
Where("id = ? AND user_id = ?", taskID, userID).
|
||||
Update("is_completed", false)
|
||||
if updateResult.Error != nil {
|
||||
return nil, updateResult.Error
|
||||
}
|
||||
|
||||
// 5. 并发兜底:
|
||||
// 5.1 若 RowsAffected=0,说明可能被并发请求先一步恢复;
|
||||
// 5.2 重新读取当前状态,若已是未完成则按业务规则返回“任务未完成”错误。
|
||||
if updateResult.RowsAffected == 0 {
|
||||
var check model.Task
|
||||
checkErr := dao.db.WithContext(ctx).
|
||||
Where("id = ? AND user_id = ?", taskID, userID).
|
||||
First(&check).Error
|
||||
if checkErr != nil {
|
||||
return nil, checkErr
|
||||
}
|
||||
if !check.IsCompleted {
|
||||
return nil, respond.TaskNotCompleted
|
||||
}
|
||||
return nil, errors.New("取消任务完成状态失败")
|
||||
}
|
||||
|
||||
// 6. 回填恢复后状态并返回。
|
||||
target.IsCompleted = false
|
||||
return &target, nil
|
||||
}
|
||||
|
||||
// PromoteTaskUrgencyByIDs 批量执行“任务紧急性平移”。
|
||||
//
|
||||
// 职责边界:
|
||||
|
||||
@@ -81,6 +81,27 @@ type UserCompleteTaskResponse struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// UserUndoCompleteTaskRequest 是“取消任务已完成勾选”接口请求体。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只承载目标 task_id;
|
||||
// 2. 不承载 user_id(user_id 始终由鉴权中间件注入,防止越权操作)。
|
||||
type UserUndoCompleteTaskRequest struct {
|
||||
TaskID int `json:"task_id"`
|
||||
}
|
||||
|
||||
// UserUndoCompleteTaskResponse 是“取消任务已完成勾选”接口响应体。
|
||||
//
|
||||
// 字段语义:
|
||||
// 1. TaskID:本次操作目标任务;
|
||||
// 2. IsCompleted:操作后完成状态(成功时恒为 false);
|
||||
// 3. Status:给前端的简短状态文案。
|
||||
type UserUndoCompleteTaskResponse struct {
|
||||
TaskID int `json:"task_id"`
|
||||
IsCompleted bool `json:"is_completed"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type GetUserTaskResp struct {
|
||||
ID int `json:"id"`
|
||||
UserID int `json:"user_id"`
|
||||
|
||||
@@ -323,4 +323,9 @@ var ( //请求相关的响应
|
||||
Status: "40051",
|
||||
Info: "token usage exceeds limit",
|
||||
}
|
||||
|
||||
TaskNotCompleted = Response{ //任务未完成,无法取消勾选
|
||||
Status: "40052",
|
||||
Info: "task is not completed",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -54,6 +54,7 @@ func RegisterRouters(handlers *api.ApiHandlers, cache *dao.CacheDAO, userRepo *d
|
||||
taskGroup.Use(middleware.JWTTokenAuth(cache), middleware.RateLimitMiddleware(limiter, 20, 1))
|
||||
taskGroup.POST("/create", middleware.IdempotencyMiddleware(cache), handlers.TaskHandler.AddTask)
|
||||
taskGroup.PUT("/complete", middleware.IdempotencyMiddleware(cache), handlers.TaskHandler.CompleteTask)
|
||||
taskGroup.PUT("/undo-complete", middleware.IdempotencyMiddleware(cache), handlers.TaskHandler.UndoCompleteTask)
|
||||
taskGroup.GET("/get", handlers.TaskHandler.GetUserTasks)
|
||||
}
|
||||
courseGroup := apiGroup.Group("/course")
|
||||
|
||||
@@ -113,6 +113,47 @@ func (ts *TaskService) CompleteTask(ctx context.Context, req *model.UserComplete
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UndoCompleteTask 取消用户任务的“已完成勾选”。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责入参校验与业务错误映射;
|
||||
// 2. 负责调用 DAO 执行状态恢复;
|
||||
// 3. 不负责幂等缓存(本接口按需求要求:任务未完成时必须报错);
|
||||
// 4. 不负责缓存删除细节(由 GORM cache_deleter 回调自动处理)。
|
||||
func (ts *TaskService) UndoCompleteTask(ctx context.Context, req *model.UserUndoCompleteTaskRequest, userID int) (*model.UserUndoCompleteTaskResponse, error) {
|
||||
// 1. 参数兜底:请求体为空、非法 user 或非法 task_id 直接返回业务错误。
|
||||
if req == nil || userID <= 0 || req.TaskID <= 0 {
|
||||
return nil, respond.WrongTaskID
|
||||
}
|
||||
|
||||
// 2. 调用 DAO 执行“恢复未完成”逻辑。
|
||||
updatedTask, err := ts.dao.UndoCompleteTaskByID(ctx, userID, req.TaskID)
|
||||
if err != nil {
|
||||
// 2.1 任务不存在或不属于当前用户,统一映射为 WrongTaskID。
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, respond.WrongTaskID
|
||||
}
|
||||
// 2.2 任务本来就未完成:按需求返回明确业务错误。
|
||||
if errors.Is(err, respond.TaskNotCompleted) {
|
||||
return nil, respond.TaskNotCompleted
|
||||
}
|
||||
// 2.3 其余数据库异常继续向上透传。
|
||||
return nil, err
|
||||
}
|
||||
if updatedTask == nil {
|
||||
// 3. 极端防御:DAO 成功但返回 nil,视为内部异常。
|
||||
return nil, errors.New("undo complete task succeeded but task is nil")
|
||||
}
|
||||
|
||||
// 4. 组装响应:恢复成功后 is_completed 恒为 false。
|
||||
resp := &model.UserUndoCompleteTaskResponse{
|
||||
TaskID: updatedTask.ID,
|
||||
IsCompleted: false,
|
||||
Status: "uncompleted",
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetUserTasks 获取用户任务列表(含“读时紧急性派生”与“异步平移触发”)。
|
||||
//
|
||||
// 核心流程(步骤化):
|
||||
|
||||
Reference in New Issue
Block a user