Files
story/internal/httpapi/diagnostic_handlers.go
T

453 lines
13 KiB
Go

package httpapi
import (
"errors"
"net/http"
"strings"
"data-terminal-cloud/internal/concurrency"
"data-terminal-cloud/internal/diagnostics"
"data-terminal-cloud/internal/idempotency"
)
func GetOpsPlatformHealthHandler(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
}
item, err := service.GetLatestPlatformHealth(r.Context(), principal, tenantID)
if err != nil {
writeDiagnosticError(w, r, err)
return
}
w.Header().Set("Cache-Control", "no-store")
WriteJSON(w, r, http.StatusOK, item)
})
}
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", "联调诊断处理失败")
}
}