453 lines
13 KiB
Go
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", "联调诊断处理失败")
|
|
}
|
|
}
|