package api import ( "encoding/json" "errors" "net/http" "strings" "git.gozeros.com/wsk/eightbf-analysis-go/internal/service" ) // Handler HTTP 入口,路径与 Java AnalysisJobController 对齐。 type Handler struct { Jobs *service.JobService } func (h *Handler) Routes(mux *http.ServeMux) { mux.HandleFunc("GET /api/v1/analysis/health", h.health) mux.HandleFunc("POST /api/v1/analysis/jobs", h.create) mux.HandleFunc("GET /api/v1/analysis/jobs/{id}", h.get) mux.HandleFunc("GET /api/v1/analysis/jobs", h.getByTask) mux.HandleFunc("POST /api/v1/analysis/jobs/{id}/cancel", h.cancel) mux.HandleFunc("POST /api/v1/analysis/jobs/{id}/resume", h.resume) mux.HandleFunc("POST /api/v1/analysis/jobs/{id}/plan", h.patchPlan) mux.HandleFunc("POST /api/v1/analysis/jobs/{id}/inject", h.inject) mux.HandleFunc("GET /api/v1/analysis/jobs/{id}/result", h.result) } func (h *Handler) caller(r *http.Request) service.Caller { return service.NewCaller(r.Header.Get("X-Account-Id"), r.Header.Get("X-Analysis-Role")) } func (h *Handler) health(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, map[string]interface{}{ "ok": true, "service": "eightbf-analysis-go", }) } func (h *Handler) create(w http.ResponseWriter, r *http.Request) { var body service.CreateJobRequest _ = json.NewDecoder(r.Body).Decode(&body) job, err := h.Jobs.Create(&body, h.caller(r)) if err != nil { h.mapErr(w, err) return } Created(w, job) } func (h *Handler) get(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") job, err := h.Jobs.Get(id, h.caller(r)) if err != nil { h.mapErr(w, err) return } if job == nil { Fail(w, http.StatusNotFound, 404, "job not found") return } OK(w, job) } func (h *Handler) getByTask(w http.ResponseWriter, r *http.Request) { taskID := strings.TrimSpace(r.URL.Query().Get("taskId")) if taskID == "" { Fail(w, http.StatusBadRequest, 400, "taskId required") return } latest := r.URL.Query().Get("latest") == "true" job, err := h.Jobs.GetByTaskID(taskID, latest, h.caller(r)) if err != nil { h.mapErr(w, err) return } if job == nil { Fail(w, http.StatusNotFound, 404, "job not found for taskId") return } OK(w, job) } func (h *Handler) cancel(w http.ResponseWriter, r *http.Request) { job, err := h.Jobs.Cancel(r.PathValue("id"), h.caller(r)) if err != nil { h.mapErr(w, err) return } if job == nil { Fail(w, http.StatusNotFound, 404, "job not found") return } OK(w, job) } func (h *Handler) resume(w http.ResponseWriter, r *http.Request) { var body service.ResumeJobRequest _ = json.NewDecoder(r.Body).Decode(&body) job, err := h.Jobs.Resume(r.PathValue("id"), &body, h.caller(r)) if err != nil { h.mapErr(w, err) return } if job == nil { Fail(w, http.StatusNotFound, 404, "job not found") return } OK(w, job) } func (h *Handler) patchPlan(w http.ResponseWriter, r *http.Request) { var body service.PatchJobPlanRequest _ = json.NewDecoder(r.Body).Decode(&body) job, err := h.Jobs.PatchPlan(r.PathValue("id"), &body, h.caller(r)) if err != nil { h.mapErr(w, err) return } if job == nil { Fail(w, http.StatusNotFound, 404, "job not found") return } OK(w, job) } func (h *Handler) inject(w http.ResponseWriter, r *http.Request) { var body service.InjectJobRequest _ = json.NewDecoder(r.Body).Decode(&body) job, err := h.Jobs.Inject(r.PathValue("id"), &body, h.caller(r)) if err != nil { h.mapErr(w, err) return } if job == nil { Fail(w, http.StatusNotFound, 404, "job not found") return } OK(w, job) } func (h *Handler) result(w http.ResponseWriter, r *http.Request) { job, err := h.Jobs.Get(r.PathValue("id"), h.caller(r)) if err != nil { h.mapErr(w, err) return } if job == nil { Fail(w, http.StatusNotFound, 404, "job not found") return } OK(w, map[string]interface{}{ "id": job.ID, "status": job.Status, "result": job.Result, "error": job.Error, "dialogues": job.Dialogues, }) } func (h *Handler) mapErr(w http.ResponseWriter, err error) { var be *service.BizError if errors.As(err, &be) { switch { case errors.Is(be.Kind, service.ErrForbidden): Fail(w, http.StatusForbidden, 403, be.Msg) case errors.Is(be.Kind, service.ErrBadState), errors.Is(be.Kind, service.ErrBadArg): Fail(w, http.StatusBadRequest, 400, be.Msg) default: Fail(w, http.StatusInternalServerError, 500, be.Msg) } return } Fail(w, http.StatusInternalServerError, 500, err.Error()) } // WithCORS 本机联调放开跨域(经反代同源时无影响)。 func WithCORS(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "*") if r.Method == http.MethodOptions { w.WriteHeader(http.StatusNoContent) return } next.ServeHTTP(w, r) }) }