package httpapi import ( "errors" "net/http" "strings" "data-terminal-cloud/internal/concurrency" "data-terminal-cloud/internal/diagnostics" "data-terminal-cloud/internal/idempotency" ) func ListOpsIntegrationIssuesHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, ok := opsTenantHeader(w, r) if !ok { return } items, err := service.ListIntegrationIssues(r.Context(), principal, tenantID, r.URL.Query().Get("status")) if err != nil { writeDiagnosticError(w, r, err) return } summary, err := service.SummarizeIntegrationIssues(r.Context(), principal, tenantID) if err != nil { writeDiagnosticError(w, r, err) return } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, map[string]any{"items": items, "summary": summary}) }) } func CreateOpsIntegrationIssueHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, key, ok := opsCreateHeaders(w, r) if !ok { return } var input diagnostics.CreateIntegrationIssueInput if !decodeWebJSON(w, r, &input, "联调问题参数不正确") { return } item, created, err := service.CreateIntegrationIssue(r.Context(), principal, tenantID, key, RequestID(r.Context()), input) if err != nil { writeDiagnosticError(w, r, err) return } status := http.StatusCreated if !created { status = http.StatusOK w.Header().Set("Idempotency-Replayed", "true") } w.Header().Set("ETag", concurrency.FormatETag(item.Version)) w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, status, item) }) } func UpdateOpsIntegrationIssueHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, ok := opsTenantHeader(w, r) if !ok { return } expectedVersion, err := concurrency.ParseIfMatch(r) if err != nil { writeETagError(w, r, err) return } var input diagnostics.UpdateIntegrationIssueInput if !decodeWebJSON(w, r, &input, "联调问题更新参数不正确") { return } item, err := service.UpdateIntegrationIssue(r.Context(), principal, tenantID, r.PathValue("id"), RequestID(r.Context()), expectedVersion, input) if err != nil { if errors.Is(err, concurrency.ErrVersionConflict) { writeETagError(w, r, err) return } writeDiagnosticError(w, r, err) return } w.Header().Set("ETag", concurrency.FormatETag(item.Version)) w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, item) }) } func SearchOpsDiagnosticsHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, ok := opsTenantHeader(w, r) if !ok { return } items, err := service.Search(r.Context(), principal, tenantID, strings.TrimSpace(r.URL.Query().Get("field")), strings.TrimSpace(r.URL.Query().Get("value"))) if err != nil { writeDiagnosticError(w, r, err) return } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, map[string]any{"items": items}) }) } func ListOpsExternalDependenciesHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, ok := opsTenantHeader(w, r) if !ok { return } items, err := service.ListExternalDependencies(r.Context(), principal, tenantID) if err != nil { writeDiagnosticError(w, r, err) return } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, map[string]any{"items": items}) }) } func StopOpsDiagnosticConversationHandler(service *diagnostics.Service) http.Handler { return diagnosticConversationActionHandler(service, "stop") } func ReconcileOpsDiagnosticConversationHandler(service *diagnostics.Service) http.Handler { return diagnosticConversationActionHandler(service, "reconcile") } func diagnosticConversationActionHandler(service *diagnostics.Service, action string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, key, ok := opsCreateHeaders(w, r) if !ok { return } var body struct { Reason string `json:"reason"` } if !decodeWebJSON(w, r, &body, "联调修复参数不正确") { return } var result diagnostics.ActionResult var created bool var err error if action == "stop" { result, created, err = service.StopConversation(r.Context(), principal, tenantID, r.PathValue("id"), key, RequestID(r.Context()), body.Reason) } else { result, created, err = service.ReconcileConversation(r.Context(), principal, tenantID, r.PathValue("id"), key, RequestID(r.Context()), body.Reason) } if err != nil { writeDiagnosticError(w, r, err) return } if !created { w.Header().Set("Idempotency-Replayed", "true") } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, result) }) } func RefreshOpsDiagnosticDeviceConfigHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, key, ok := opsCreateHeaders(w, r) if !ok { return } var body struct { Reason string `json:"reason"` } if !decodeWebJSON(w, r, &body, "配置刷新参数不正确") { return } result, created, err := service.RefreshDeviceConfig(r.Context(), principal, tenantID, r.PathValue("id"), key, RequestID(r.Context()), body.Reason) if err != nil { writeDiagnosticError(w, r, err) return } status := http.StatusCreated if !created { status = http.StatusOK w.Header().Set("Idempotency-Replayed", "true") } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, status, result) }) } func ListOpsDiagnosticJobsHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, ok := opsTenantHeader(w, r) if !ok { return } items, err := service.ListDeadJobs(r.Context(), principal, tenantID) if err != nil { writeDiagnosticError(w, r, err) return } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, map[string]any{"items": items}) }) } func RetryOpsDiagnosticJobHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, key, ok := opsCreateHeaders(w, r) if !ok { return } var body struct { Reason string `json:"reason"` } if !decodeWebJSON(w, r, &body, "任务重试参数不正确") { return } result, created, err := service.RetryDeadJob(r.Context(), principal, tenantID, r.PathValue("id"), key, RequestID(r.Context()), body.Reason) if err != nil { writeDiagnosticError(w, r, err) return } if !created { w.Header().Set("Idempotency-Replayed", "true") } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, result) }) } func RevokeOpsDiagnosticDeviceHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, key, ok := opsCreateHeaders(w, r) if !ok { return } var body struct { Reason string `json:"reason"` } if !decodeWebJSON(w, r, &body, "设备吊销参数不正确") { return } result, created, err := service.RevokeDevice(r.Context(), principal, tenantID, r.PathValue("id"), key, RequestID(r.Context()), body.Reason) if err != nil { writeDiagnosticError(w, r, err) return } if !created { w.Header().Set("Idempotency-Replayed", "true") } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, http.StatusOK, result) }) } func ExportOpsDiagnosticBundleHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, key, ok := opsCreateHeaders(w, r) if !ok { return } var body struct { Field string `json:"field"` Value string `json:"value"` Reason string `json:"reason"` } if !decodeWebJSON(w, r, &body, "诊断包参数不正确") { return } bundle, created, err := service.ExportBundle(r.Context(), principal, tenantID, body.Field, body.Value, key, RequestID(r.Context()), body.Reason) if err != nil { writeDiagnosticError(w, r, err) return } status := http.StatusCreated if !created { status = http.StatusOK w.Header().Set("Idempotency-Replayed", "true") } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, status, bundle) }) } func ExportOpsAcceptanceSnapshotHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, key, ok := opsCreateHeaders(w, r) if !ok { return } var input diagnostics.AcceptanceSnapshotInput if !decodeWebJSON(w, r, &input, "发布候选验收快照参数不正确") { return } snapshot, created, err := service.ExportAcceptanceSnapshot(r.Context(), principal, tenantID, key, RequestID(r.Context()), input) if err != nil { writeDiagnosticError(w, r, err) return } status := http.StatusCreated if !created { status = http.StatusOK w.Header().Set("Idempotency-Replayed", "true") } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, status, snapshot) }) } func ExportOpsPilotDailySnapshotHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, key, ok := opsCreateHeaders(w, r) if !ok { return } var input diagnostics.PilotDailySnapshotInput if !decodeWebJSON(w, r, &input, "试点运行日报参数不正确") { return } snapshot, created, err := service.ExportPilotDailySnapshot(r.Context(), principal, tenantID, key, RequestID(r.Context()), input) if err != nil { writeDiagnosticError(w, r, err) return } status := http.StatusCreated if !created { status = http.StatusOK w.Header().Set("Idempotency-Replayed", "true") } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, status, snapshot) }) } func ExportOpsPilotWeeklySnapshotHandler(service *diagnostics.Service) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { principal, ok := webPrincipal(w, r) if !ok { return } tenantID, key, ok := opsCreateHeaders(w, r) if !ok { return } var input diagnostics.PilotWeeklySnapshotInput if !decodeWebJSON(w, r, &input, "试点运行周报参数不正确") { return } snapshot, created, err := service.ExportPilotWeeklySnapshot(r.Context(), principal, tenantID, key, RequestID(r.Context()), input) if err != nil { writeDiagnosticError(w, r, err) return } status := http.StatusCreated if !created { status = http.StatusOK w.Header().Set("Idempotency-Replayed", "true") } w.Header().Set("Cache-Control", "no-store") WriteJSON(w, r, status, snapshot) }) } func writeDiagnosticError(w http.ResponseWriter, r *http.Request, err error) { switch { case errors.Is(err, diagnostics.ErrInvalid): writeValidation(w, r, "联调检索或修复参数不正确", FieldError{Field: "diagnostics", Message: "检索字段、编号或操作原因不符合要求"}) case errors.Is(err, diagnostics.ErrAccessDenied): WriteError(w, r, http.StatusForbidden, "OPS_ACCESS_DENIED", "当前账号没有所需的内部运营权限") case errors.Is(err, diagnostics.ErrNotFound): WriteError(w, r, http.StatusNotFound, "RESOURCE_NOT_FOUND", "指定设备或通话不存在") case errors.Is(err, diagnostics.ErrStateConflict): WriteError(w, r, http.StatusConflict, "DIAGNOSTIC_STATE_CONFLICT", "当前资源状态不允许执行该修复动作") case errors.Is(err, idempotency.ErrKeyConflict): WriteError(w, r, http.StatusConflict, "IDEMPOTENCY_KEY_CONFLICT", "重复请求识别码已被不同请求使用") case errors.Is(err, idempotency.ErrInProgress): WriteError(w, r, http.StatusConflict, "IDEMPOTENCY_IN_PROGRESS", "相同请求正在处理中") default: WriteError(w, r, http.StatusInternalServerError, "INTERNAL_ERROR", "联调诊断处理失败") } }