package controller import ( "encoding/json" "net/http" "strconv" "stock-analysis/internal/cache" "stock-analysis/internal/job" ) // 任务接口错误(包级哨兵错误,便于测试断言)。 var ( errBadTaskType = errString("无效的任务类型,支持 full / incremental") errMissingTaskID = errString("缺少任务 ID") errTaskNotFound = errString("任务不存在") errTaskNotRunning = errString("任务不存在或已结束,无法取消") ) // errString 将字符串转为 error,避免重复 fmt.Errorf 调用。 type errString string func (e errString) Error() string { return string(e) } // TaskController 提供数据入库任务管理的 HTTP 接口。 // 任务由 job.Manager 异步执行,状态实时写入 Redis,持久化到 SQLite。 type TaskController struct { mgr *job.Manager } // NewTaskController 创建任务控制器。 func NewTaskController(mgr *job.Manager) *TaskController { return &TaskController{mgr: mgr} } // taskRequest 创建任务请求参数。 type taskRequest struct { Type string `json:"type"` // full | incremental Since string `json:"since"` // 增量起点(YYYY-MM-DD),可选,留空自动取最后K线日期 Limit int `json:"limit"` // 限制处理数量,0 表示全部 } // CreateTask 处理 POST /api/tasks,创建数据入库任务(全量/增量)。 // 任务异步执行,立即返回 pending 状态的任务快照,前端通过轮询 GetTask 获取进度。 func (c *TaskController) CreateTask(w http.ResponseWriter, r *http.Request) { var req taskRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, err) return } var typ cache.TaskType switch req.Type { case "full", "": typ = cache.TaskTypeFull case "incremental": typ = cache.TaskTypeIncremental default: writeError(w, http.StatusBadRequest, errBadTaskType) return } task, err := c.mgr.Start(r.Context(), typ, req.Since, req.Limit) if err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusOK, task) } // ListTasks 处理 GET /api/tasks,返回任务列表(按开始时间降序)。 // 默认返回最近 50 条,可通过 ?limit=N 调整。 // 运行中任务会合并 Redis 实时进度。 func (c *TaskController) ListTasks(w http.ResponseWriter, r *http.Request) { limit := 50 if q := r.URL.Query().Get("limit"); q != "" { if n, err := strconv.Atoi(q); err == nil && n > 0 { limit = n } } tasks, err := c.mgr.List(r.Context(), limit) if err != nil { writeError(w, http.StatusInternalServerError, err) return } writeJSON(w, http.StatusOK, tasks) } // GetTask 处理 GET /api/tasks/{id},返回单个任务状态。 // 优先从 Redis 读取实时状态,回退 SQLite 持久化记录。 func (c *TaskController) GetTask(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if id == "" { writeError(w, http.StatusBadRequest, errMissingTaskID) return } task, err := c.mgr.Get(r.Context(), id) if err != nil { writeError(w, http.StatusNotFound, err) return } if task == nil { writeError(w, http.StatusNotFound, errTaskNotFound) return } writeJSON(w, http.StatusOK, task) } // CancelTask 处理 POST /api/tasks/{id}/cancel,取消运行中任务。 // 仅运行中任务可取消,已完成任务忽略。 func (c *TaskController) CancelTask(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if id == "" { writeError(w, http.StatusBadRequest, errMissingTaskID) return } if ok := c.mgr.Cancel(id); !ok { writeError(w, http.StatusNotFound, errTaskNotRunning) return } writeJSON(w, http.StatusOK, map[string]string{"message": "任务已取消"}) }