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. 返回统一响应结构。
|
// 5. 返回统一响应结构。
|
||||||
c.JSON(http.StatusOK, respond.RespWithData(respond.Ok, resp))
|
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
|
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 批量执行“任务紧急性平移”。
|
// PromoteTaskUrgencyByIDs 批量执行“任务紧急性平移”。
|
||||||
//
|
//
|
||||||
// 职责边界:
|
// 职责边界:
|
||||||
|
|||||||
@@ -81,6 +81,27 @@ type UserCompleteTaskResponse struct {
|
|||||||
Status string `json:"status"`
|
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 {
|
type GetUserTaskResp struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
UserID int `json:"user_id"`
|
UserID int `json:"user_id"`
|
||||||
|
|||||||
@@ -323,4 +323,9 @@ var ( //请求相关的响应
|
|||||||
Status: "40051",
|
Status: "40051",
|
||||||
Info: "token usage exceeds limit",
|
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.Use(middleware.JWTTokenAuth(cache), middleware.RateLimitMiddleware(limiter, 20, 1))
|
||||||
taskGroup.POST("/create", middleware.IdempotencyMiddleware(cache), handlers.TaskHandler.AddTask)
|
taskGroup.POST("/create", middleware.IdempotencyMiddleware(cache), handlers.TaskHandler.AddTask)
|
||||||
taskGroup.PUT("/complete", middleware.IdempotencyMiddleware(cache), handlers.TaskHandler.CompleteTask)
|
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)
|
taskGroup.GET("/get", handlers.TaskHandler.GetUserTasks)
|
||||||
}
|
}
|
||||||
courseGroup := apiGroup.Group("/course")
|
courseGroup := apiGroup.Group("/course")
|
||||||
|
|||||||
@@ -113,6 +113,47 @@ func (ts *TaskService) CompleteTask(ctx context.Context, req *model.UserComplete
|
|||||||
return resp, nil
|
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 获取用户任务列表(含“读时紧急性派生”与“异步平移触发”)。
|
// GetUserTasks 获取用户任务列表(含“读时紧急性派生”与“异步平移触发”)。
|
||||||
//
|
//
|
||||||
// 核心流程(步骤化):
|
// 核心流程(步骤化):
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
openapi: 3.0.1
|
|
||||||
info:
|
|
||||||
title: ''
|
|
||||||
version: 1.0.0
|
|
||||||
paths:
|
|
||||||
/agent/chat:
|
|
||||||
post:
|
|
||||||
summary: AI Agent&聊天
|
|
||||||
deprecated: false
|
|
||||||
description: >-
|
|
||||||
本接口既支持带着消息新建对话,也支持通过旧对话继续聊天。
|
|
||||||
在 JSON 中传入 conversation_id,后端查库:存在则延续,不存在则创建新对话后再聊天。
|
|
||||||
|
|
||||||
流式响应采用 OpenAI/DeepSeek 兼容格式:
|
|
||||||
- 思考流:choices[0].delta.reasoning_content
|
|
||||||
- 正文流:choices[0].delta.content
|
|
||||||
- 结束标记:data: [DONE]
|
|
||||||
tags:
|
|
||||||
- Agent模块
|
|
||||||
parameters:
|
|
||||||
- name: Authorization
|
|
||||||
in: header
|
|
||||||
description: token
|
|
||||||
required: false
|
|
||||||
example: ''
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
- name: Content-Type
|
|
||||||
in: header
|
|
||||||
description: ''
|
|
||||||
required: false
|
|
||||||
example:
|
|
||||||
- application/json
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
conversation_id:
|
|
||||||
type: string
|
|
||||||
description: 可选。不传时后端自动生成,并通过 X-Conversation-ID 响应头返回。
|
|
||||||
x-apifox-mock: '{{$string.uuid}}'
|
|
||||||
message:
|
|
||||||
type: string
|
|
||||||
description: 用户输入内容
|
|
||||||
model:
|
|
||||||
type: string
|
|
||||||
description: 可选,worker 或 strategist(默认 worker)
|
|
||||||
thinking:
|
|
||||||
type: boolean
|
|
||||||
description: 是否开启深度思考
|
|
||||||
required:
|
|
||||||
- message
|
|
||||||
- thinking
|
|
||||||
x-apifox-orders:
|
|
||||||
- conversation_id
|
|
||||||
- message
|
|
||||||
- model
|
|
||||||
- thinking
|
|
||||||
example:
|
|
||||||
conversation_id: 0b6eac35-ccaa-46d1-aa58-d33bc2cd48aa
|
|
||||||
message: 提醒我有空的时候给自己挑一件新衣服
|
|
||||||
model: worker
|
|
||||||
thinking: true
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: ''
|
|
||||||
content:
|
|
||||||
text/event-stream:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
description: >-
|
|
||||||
每条 SSE 事件都是 `data: {JSON}`,最终以 `data: [DONE]` 结束。
|
|
||||||
Apifox 可按 OpenAI 兼容格式自动合并,并区分 reasoning_content 与 content。
|
|
||||||
example: |-
|
|
||||||
data: {"id":"chatcmpl-3f3ee5d6-8c4f-4b5b-a2a8-7f5b9bde8b9d","object":"chat.completion.chunk","created":1740637581,"model":"worker","choices":[{"index":0,"delta":{"role":"assistant","reasoning_content":"先分析一下你的需求。"},"finish_reason":null}]}
|
|
||||||
|
|
||||||
data: {"id":"chatcmpl-3f3ee5d6-8c4f-4b5b-a2a8-7f5b9bde8b9d","object":"chat.completion.chunk","created":1740637581,"model":"worker","choices":[{"index":0,"delta":{"reasoning_content":"你提到的是空闲时提醒。"},"finish_reason":null}]}
|
|
||||||
|
|
||||||
data: {"id":"chatcmpl-3f3ee5d6-8c4f-4b5b-a2a8-7f5b9bde8b9d","object":"chat.completion.chunk","created":1740637581,"model":"worker","choices":[{"index":0,"delta":{"content":"可以,我会在你有空时提醒你。"},"finish_reason":null}]}
|
|
||||||
|
|
||||||
data: {"id":"chatcmpl-3f3ee5d6-8c4f-4b5b-a2a8-7f5b9bde8b9d","object":"chat.completion.chunk","created":1740637581,"model":"worker","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
|
|
||||||
|
|
||||||
data: [DONE]
|
|
||||||
headers:
|
|
||||||
X-Conversation-ID:
|
|
||||||
example: 0b6eac35-ccaa-46d1-aa58-d33bc2cd48aa
|
|
||||||
required: false
|
|
||||||
description: 生效的会话 ID,用于后续续聊
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
x-apifox-name: 成功
|
|
||||||
x-apifox-ordering: 0
|
|
||||||
security: []
|
|
||||||
x-apifox-folder: Agent模块
|
|
||||||
x-apifox-status: developing
|
|
||||||
components:
|
|
||||||
schemas: {}
|
|
||||||
responses: {}
|
|
||||||
securitySchemes: {}
|
|
||||||
servers:
|
|
||||||
- url: http://127.0.0.1:8080/api/v1
|
|
||||||
description: 开发环境
|
|
||||||
security: []
|
|
||||||
Reference in New Issue
Block a user