123 lines
3.6 KiB
Go
123 lines
3.6 KiB
Go
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": "任务已取消"})
|
||
}
|