990 lines
38 KiB
Go
990 lines
38 KiB
Go
package platform
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"lingniu/vehicle-data-platform/apps/api/internal/httpx"
|
|
)
|
|
|
|
type Handler struct {
|
|
service *Service
|
|
mux *http.ServeMux
|
|
}
|
|
|
|
func NewHandler(service *Service) *Handler {
|
|
h := &Handler{service: service, mux: http.NewServeMux()}
|
|
h.routes()
|
|
return h
|
|
}
|
|
|
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
h.mux.ServeHTTP(w, r)
|
|
}
|
|
|
|
func (h *Handler) routes() {
|
|
h.mux.HandleFunc("GET /api/dashboard/summary", h.handleDashboardSummary)
|
|
h.mux.HandleFunc("GET /api/vehicles", h.handleVehicles)
|
|
h.mux.HandleFunc("GET /api/vehicles/resolve", h.handleVehicleResolve)
|
|
h.mux.HandleFunc("GET /api/vehicles/coverage", h.handleVehicleCoverage)
|
|
h.mux.HandleFunc("GET /api/vehicles/coverage/summary", h.handleVehicleCoverageSummary)
|
|
h.mux.HandleFunc("GET /api/vehicles/business-filters", h.handleVehicleBusinessFilters)
|
|
h.mux.HandleFunc("GET /api/vehicle-service", h.handleVehicleDetail)
|
|
h.mux.HandleFunc("GET /api/vehicle-service/summary", h.handleVehicleServiceSummary)
|
|
h.mux.HandleFunc("GET /api/vehicle-service/overview", h.handleVehicleServiceOverview)
|
|
h.mux.HandleFunc("POST /api/vehicle-service/overviews", h.handleVehicleServiceOverviews)
|
|
h.mux.HandleFunc("GET /api/vehicles/detail", h.handleVehicleDetail)
|
|
h.mux.HandleFunc("GET /api/realtime/vehicles", h.handleVehicleRealtime)
|
|
h.mux.HandleFunc("GET /api/realtime/locations", h.handleRealtimeLocations)
|
|
h.mux.HandleFunc("GET /api/history/locations", h.handleHistoryLocations)
|
|
h.mux.HandleFunc("GET /api/history/raw-frames", h.handleRawFramesGet)
|
|
h.mux.HandleFunc("POST /api/history/raw-frames/query", h.handleRawFramesPost)
|
|
h.mux.HandleFunc("GET /api/mileage/summary", h.handleMileageSummary)
|
|
h.mux.HandleFunc("GET /api/mileage/daily", h.handleDailyMileage)
|
|
h.mux.HandleFunc("POST /api/mileage/daily", h.handleDailyMileagePost)
|
|
h.mux.HandleFunc("GET /api/v2/statistics/mileage", h.handleMileageStatistics)
|
|
h.mux.HandleFunc("POST /api/v2/statistics/mileage", h.handleMileageStatisticsPost)
|
|
h.mux.HandleFunc("GET /api/statistics/online-summary", h.handleOnlineStatisticsSummary)
|
|
h.mux.HandleFunc("GET /api/statistics/online-vehicles", h.handleOnlineVehicleStatuses)
|
|
h.mux.HandleFunc("GET /api/quality/summary", h.handleQualitySummary)
|
|
h.mux.HandleFunc("GET /api/quality/issues", h.handleQualityIssues)
|
|
h.mux.HandleFunc("GET /api/quality/notification-plan", h.handleQualityNotificationPlan)
|
|
h.mux.HandleFunc("GET /api/alert-events/summary", h.handleQualitySummary)
|
|
h.mux.HandleFunc("GET /api/alert-events", h.handleQualityIssues)
|
|
h.mux.HandleFunc("GET /api/alert-events/notification-plan", h.handleQualityNotificationPlan)
|
|
h.mux.HandleFunc("GET /api/ops/health", h.handleOpsHealth)
|
|
h.mux.HandleFunc("GET /api/ops/source-readiness", h.handleSourceReadiness)
|
|
h.mux.HandleFunc("GET /api/v2/monitor/summary", h.handleMonitorSummary)
|
|
h.mux.HandleFunc("GET /api/v2/monitor/map", h.handleMonitorMap)
|
|
h.mux.HandleFunc("GET /api/v2/monitor/workspace", h.handleMonitorWorkspace)
|
|
h.mux.HandleFunc("GET /api/v2/vehicles/{vin}/profile", h.handleVehicleProfile)
|
|
h.mux.HandleFunc("PUT /api/v2/vehicles/{vin}/profile", h.handleSaveVehicleProfile)
|
|
h.mux.HandleFunc("GET /api/v2/vehicles/{vin}/telemetry/latest", h.handleLatestTelemetry)
|
|
h.mux.HandleFunc("GET /api/v2/vehicles/{vin}/source-evidence", h.handleVehicleSourceEvidence)
|
|
h.mux.HandleFunc("GET /api/v2/operations/vehicles/{vin}/sources", h.handleVehicleSourceDiagnostic)
|
|
h.mux.HandleFunc("PUT /api/v2/operations/vehicles/{vin}/sources/{sourceRef}", h.handleUpdateVehicleSourcePolicy)
|
|
h.mux.HandleFunc("GET /api/v2/reconciliation/summary", h.handleReconciliationSummary)
|
|
h.mux.HandleFunc("GET /api/v2/reconciliation/assignees", h.handleReconciliationAssignees)
|
|
h.mux.HandleFunc("POST /api/v2/reconciliation/issues", h.handleReconciliationIssues)
|
|
h.mux.HandleFunc("POST /api/v2/reconciliation/issues/export", h.handleReconciliationIssuesExport)
|
|
h.mux.HandleFunc("POST /api/v2/reconciliation/issues/batch-actions", h.handleReconciliationBatchAction)
|
|
h.mux.HandleFunc("POST /api/v2/reconciliation/issues/batch-assignments", h.handleReconciliationBatchAssignment)
|
|
h.mux.HandleFunc("POST /api/v2/reconciliation/issues/batch-archive", h.handleReconciliationBatchArchive)
|
|
h.mux.HandleFunc("POST /api/v2/reconciliation/issues/batch-restore", h.handleReconciliationBatchRestore)
|
|
h.mux.HandleFunc("GET /api/v2/reconciliation/issues/{id}", h.handleReconciliationIssue)
|
|
h.mux.HandleFunc("POST /api/v2/reconciliation/issues/{id}/actions", h.handleReconciliationAction)
|
|
h.mux.HandleFunc("POST /api/v2/reconciliation/issues/{id}/assignment", h.handleReconciliationAssignment)
|
|
h.mux.HandleFunc("POST /api/v2/reconciliation/issues/{id}/archive", h.handleReconciliationArchive)
|
|
h.mux.HandleFunc("POST /api/v2/reconciliation/issues/{id}/restore", h.handleReconciliationRestore)
|
|
h.mux.HandleFunc("POST /api/v2/vehicle-profiles/sync", h.handleSyncVehicleProfiles)
|
|
h.mux.HandleFunc("GET /api/v2/tracks", h.handleTrackPlayback)
|
|
h.mux.HandleFunc("GET /api/v2/metrics", h.handleMetricCatalog)
|
|
h.mux.HandleFunc("GET /api/v2/history/metrics", h.handleHistoryMetricCatalog)
|
|
h.mux.HandleFunc("GET /api/v2/history/query", h.handleHistoryData)
|
|
h.mux.HandleFunc("GET /api/v2/history/series", h.handleHistorySeries)
|
|
h.mux.HandleFunc("GET /api/v2/history/preferences", h.handleHistoryPreferences)
|
|
h.mux.HandleFunc("PUT /api/v2/history/preferences", h.handleUpdateHistoryPreferences)
|
|
h.mux.HandleFunc("POST /api/v2/exports", h.handleCreateHistoryExport)
|
|
h.mux.HandleFunc("GET /api/v2/exports", h.handleListHistoryExports)
|
|
h.mux.HandleFunc("GET /api/v2/exports/page", h.handleListHistoryExportsPage)
|
|
h.mux.HandleFunc("GET /api/v2/exports/cleanup/preview", h.handlePreviewHistoryExportCleanup)
|
|
h.mux.HandleFunc("GET /api/v2/exports/cleanup/audit", h.handleHistoryExportCleanupAudit)
|
|
h.mux.HandleFunc("POST /api/v2/exports/cleanup", h.handleCleanupHistoryExports)
|
|
h.mux.HandleFunc("GET /api/v2/exports/cleanup/automation", h.handleHistoryExportCleanupAutomation)
|
|
h.mux.HandleFunc("PUT /api/v2/exports/cleanup/automation", h.handleUpdateHistoryExportCleanupAutomation)
|
|
h.mux.HandleFunc("POST /api/v2/exports/cleanup/automation/{id}/approve", h.handleApproveHistoryExportCleanupAutomation)
|
|
h.mux.HandleFunc("POST /api/v2/exports/cleanup/automation/{id}/reject", h.handleRejectHistoryExportCleanupAutomation)
|
|
h.mux.HandleFunc("POST /api/v2/exports/cleanup/automation/{id}/retry", h.handleRetryHistoryExportCleanupAutomation)
|
|
h.mux.HandleFunc("POST /api/v2/exports/batch", h.handleBatchHistoryExports)
|
|
h.mux.HandleFunc("POST /api/v2/exports/{id}/cancel", h.handleCancelHistoryExport)
|
|
h.mux.HandleFunc("POST /api/v2/exports/{id}/rebuild", h.handleRebuildHistoryExport)
|
|
h.mux.HandleFunc("POST /api/v2/exports/{id}/archive", h.handleArchiveHistoryExport)
|
|
h.mux.HandleFunc("POST /api/v2/exports/{id}/restore", h.handleRestoreHistoryExport)
|
|
h.mux.HandleFunc("PUT /api/v2/exports/{id}/cleanup-protection", h.handleHistoryExportCleanupProtection)
|
|
h.mux.HandleFunc("GET /api/v2/exports/{id}/download", h.handleDownloadHistoryExport)
|
|
h.mux.HandleFunc("POST /api/v2/access/summary", h.handleAccessSummary)
|
|
h.mux.HandleFunc("POST /api/v2/access/vehicles", h.handleAccessVehicles)
|
|
h.mux.HandleFunc("POST /api/v2/access/unresolved-identities", h.handleAccessUnresolvedIdentities)
|
|
h.mux.HandleFunc("POST /api/v2/access/unresolved-identities/{id}/claim", h.handleClaimAccessIdentity)
|
|
h.mux.HandleFunc("GET /api/v2/access/thresholds", h.handleAccessThresholds)
|
|
h.mux.HandleFunc("PUT /api/v2/access/thresholds", h.handleUpdateAccessThresholds)
|
|
h.mux.HandleFunc("POST /api/v2/alerts/summary", h.handleAlertSummary)
|
|
h.mux.HandleFunc("POST /api/v2/alerts/events", h.handleAlertEvents)
|
|
h.mux.HandleFunc("GET /api/v2/alerts/events/{id}", h.handleAlertEvent)
|
|
h.mux.HandleFunc("POST /api/v2/alerts/events/{id}/actions", h.handleAlertAction)
|
|
h.mux.HandleFunc("GET /api/v2/alerts/rules", h.handleAlertRules)
|
|
h.mux.HandleFunc("GET /api/v2/alerts/rules/library", h.handleAlertRuleLibrary)
|
|
h.mux.HandleFunc("POST /api/v2/alerts/rules", h.handleSaveAlertRule)
|
|
h.mux.HandleFunc("PUT /api/v2/alerts/rules/{id}", h.handleSaveAlertRule)
|
|
h.mux.HandleFunc("PUT /api/v2/alerts/rules/{id}/enabled", h.handleAlertRuleEnabled)
|
|
h.mux.HandleFunc("GET /api/v2/alerts/rules/{id}/revisions", h.handleAlertRuleRevisions)
|
|
h.mux.HandleFunc("POST /api/v2/alerts/rules/{id}/rollback", h.handleAlertRuleRollback)
|
|
h.mux.HandleFunc("POST /api/v2/alerts/rules/{id}/archive", h.handleArchiveAlertRule)
|
|
h.mux.HandleFunc("POST /api/v2/alerts/rules/{id}/restore", h.handleRestoreAlertRule)
|
|
h.mux.HandleFunc("GET /api/v2/alerts/notification-config", h.handleAlertNotificationConfig)
|
|
h.mux.HandleFunc("GET /api/v2/alerts/notifications/health", h.handleAlertNotificationDeliveryHealth)
|
|
h.mux.HandleFunc("GET /api/v2/alerts/notifications", h.handleAlertNotifications)
|
|
h.mux.HandleFunc("POST /api/v2/alerts/notifications/read", h.handleAlertNotificationsRead)
|
|
h.mux.HandleFunc("POST /api/v2/alerts/notifications/{id}/retry", h.handleAlertNotificationRetry)
|
|
h.mux.HandleFunc("GET /api/v2/alerts/notifications/{id}/retry-audit", h.handleAlertNotificationRetryAudit)
|
|
}
|
|
|
|
func (h *Handler) handleAlertNotificationConfig(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.AlertNotificationConfig(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertNotificationDeliveryHealth(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.AlertNotificationDeliveryHealth(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationSummary(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.ReconciliationSummary(r.Context(), parsePositive(r.URL.Query().Get("days"), 30))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationIssues(w http.ResponseWriter, r *http.Request) {
|
|
var query ReconciliationQuery
|
|
if !decodeJSONBody(w, r, &query) {
|
|
return
|
|
}
|
|
data, err := h.service.ReconciliationIssues(r.Context(), query)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationAssignees(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.ReconciliationAssignees(r.Context(), r.URL.Query().Get("search"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationIssuesExport(w http.ResponseWriter, r *http.Request) {
|
|
var query ReconciliationQuery
|
|
if !decodeJSONBody(w, r, &query) {
|
|
return
|
|
}
|
|
file, err := h.service.ExportReconciliationIssues(r.Context(), query)
|
|
if err != nil {
|
|
h.write(w, r, nil, err)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
|
|
w.Header().Set("Content-Disposition", `attachment; filename="quality-issues.csv"; filename*=UTF-8''`+url.PathEscape(file.Name))
|
|
w.Header().Set("X-Export-Name", url.PathEscape(file.Name))
|
|
w.Header().Set("X-Export-Count", strconv.Itoa(file.RowCount))
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write(file.Content)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationIssue(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.ReconciliationIssue(r.Context(), r.PathValue("id"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationAction(w http.ResponseWriter, r *http.Request) {
|
|
var request ReconciliationActionRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.UpdateReconciliationIssue(r.Context(), r.PathValue("id"), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationAssignment(w http.ResponseWriter, r *http.Request) {
|
|
var request ReconciliationAssignmentRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.AssignReconciliationIssue(r.Context(), r.PathValue("id"), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationBatchAssignment(w http.ResponseWriter, r *http.Request) {
|
|
var request ReconciliationBatchAssignmentRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.BatchAssignReconciliationIssues(r.Context(), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationBatchAction(w http.ResponseWriter, r *http.Request) {
|
|
var request ReconciliationBatchActionRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.BatchUpdateReconciliationIssues(r.Context(), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationArchive(w http.ResponseWriter, r *http.Request) {
|
|
h.handleReconciliationLifecycle(w, r, true)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationRestore(w http.ResponseWriter, r *http.Request) {
|
|
h.handleReconciliationLifecycle(w, r, false)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationLifecycle(w http.ResponseWriter, r *http.Request, archived bool) {
|
|
var request ReconciliationLifecycleRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.SetReconciliationIssueArchived(r.Context(), r.PathValue("id"), archived, request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationBatchArchive(w http.ResponseWriter, r *http.Request) {
|
|
h.handleReconciliationBatchLifecycle(w, r, true)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationBatchRestore(w http.ResponseWriter, r *http.Request) {
|
|
h.handleReconciliationBatchLifecycle(w, r, false)
|
|
}
|
|
|
|
func (h *Handler) handleReconciliationBatchLifecycle(w http.ResponseWriter, r *http.Request, archived bool) {
|
|
var request ReconciliationBatchLifecycleRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.BatchSetReconciliationIssuesArchived(r.Context(), archived, request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAccessUnresolvedIdentities(w http.ResponseWriter, r *http.Request) {
|
|
var query AccessUnresolvedIdentityQuery
|
|
if !decodeJSONBody(w, r, &query) {
|
|
return
|
|
}
|
|
data, err := h.service.AccessUnresolvedIdentities(r.Context(), query)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleClaimAccessIdentity(w http.ResponseWriter, r *http.Request) {
|
|
var input AccessIdentityClaimInput
|
|
if !decodeJSONBody(w, r, &input) {
|
|
return
|
|
}
|
|
input.Actor = ActorFromContext(r.Context())
|
|
data, err := h.service.ClaimAccessIdentity(r.Context(), r.PathValue("id"), input)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleProfile(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.VehicleProfile(r.Context(), r.PathValue("vin"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleLatestTelemetry(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.LatestTelemetry(r.Context(), r.PathValue("vin"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleSourceEvidence(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.VehicleSourceEvidence(r.Context(), r.PathValue("vin"), r.URL.Query().Get("date"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleSourceDiagnostic(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.VehicleSourceDiagnostic(r.Context(), r.PathValue("vin"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleUpdateVehicleSourcePolicy(w http.ResponseWriter, r *http.Request) {
|
|
var update VehicleSourcePolicyUpdate
|
|
if !decodeJSONBody(w, r, &update) {
|
|
return
|
|
}
|
|
update.SourceRef = r.PathValue("sourceRef")
|
|
update.Actor = ActorFromContext(r.Context())
|
|
data, err := h.service.UpdateVehicleSourcePolicy(r.Context(), r.PathValue("vin"), update)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleSaveVehicleProfile(w http.ResponseWriter, r *http.Request) {
|
|
var input VehicleProfileInput
|
|
if !decodeJSONBody(w, r, &input) {
|
|
return
|
|
}
|
|
input.Actor = ActorFromContext(r.Context())
|
|
data, err := h.service.SaveVehicleProfile(r.Context(), r.PathValue("vin"), input)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleSyncVehicleProfiles(w http.ResponseWriter, r *http.Request) {
|
|
var request VehicleProfileSyncRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
request.Actor = ActorFromContext(r.Context())
|
|
data, err := h.service.SyncVehicleProfiles(r.Context(), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleMetricCatalog(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.MetricCatalog(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertSummary(w http.ResponseWriter, r *http.Request) {
|
|
var query AlertQuery
|
|
if !decodeJSONBody(w, r, &query) {
|
|
return
|
|
}
|
|
data, err := h.service.AlertSummary(r.Context(), query)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertEvents(w http.ResponseWriter, r *http.Request) {
|
|
var query AlertQuery
|
|
if !decodeJSONBody(w, r, &query) {
|
|
return
|
|
}
|
|
data, err := h.service.AlertEvents(r.Context(), query)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertEvent(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.AlertEvent(r.Context(), r.PathValue("id"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertAction(w http.ResponseWriter, r *http.Request) {
|
|
var request AlertActionRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
request.Actor = ActorFromContext(r.Context())
|
|
data, err := h.service.ActOnAlert(r.Context(), r.PathValue("id"), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertRules(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.AlertRules(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertRuleLibrary(w http.ResponseWriter, r *http.Request) {
|
|
query := AlertRuleQuery{
|
|
Keyword: r.URL.Query().Get("keyword"), Status: r.URL.Query().Get("status"),
|
|
Protocol: r.URL.Query().Get("protocol"), Lifecycle: r.URL.Query().Get("lifecycle"),
|
|
Limit: parsePositive(r.URL.Query().Get("limit"), 10), Offset: parsePositive(r.URL.Query().Get("offset"), 0),
|
|
}
|
|
data, err := h.service.AlertRulePage(r.Context(), query)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleSaveAlertRule(w http.ResponseWriter, r *http.Request) {
|
|
var input AlertRuleInput
|
|
if !decodeJSONBody(w, r, &input) {
|
|
return
|
|
}
|
|
if pathID := r.PathValue("id"); pathID != "" {
|
|
input.ID = pathID
|
|
}
|
|
input.Actor = ActorFromContext(r.Context())
|
|
data, err := h.service.SaveAlertRule(r.Context(), input)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertRuleEnabled(w http.ResponseWriter, r *http.Request) {
|
|
var update AlertRuleEnabledUpdate
|
|
if !decodeJSONBody(w, r, &update) {
|
|
return
|
|
}
|
|
update.Actor = ActorFromContext(r.Context())
|
|
data, err := h.service.SetAlertRuleEnabled(r.Context(), r.PathValue("id"), update)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertRuleRevisions(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.AlertRuleRevisions(r.Context(), r.PathValue("id"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertRuleRollback(w http.ResponseWriter, r *http.Request) {
|
|
var request AlertRuleRollbackRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
request.Actor = ActorFromContext(r.Context())
|
|
data, err := h.service.RollbackAlertRule(r.Context(), r.PathValue("id"), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleArchiveAlertRule(w http.ResponseWriter, r *http.Request) {
|
|
var request AlertRuleLifecycleRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
request.Actor = ActorFromContext(r.Context())
|
|
data, err := h.service.SetAlertRuleArchived(r.Context(), r.PathValue("id"), true, request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleRestoreAlertRule(w http.ResponseWriter, r *http.Request) {
|
|
var request AlertRuleLifecycleRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
request.Actor = ActorFromContext(r.Context())
|
|
data, err := h.service.SetAlertRuleArchived(r.Context(), r.PathValue("id"), false, request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertNotifications(w http.ResponseWriter, r *http.Request) {
|
|
limit := parsePositive(r.URL.Query().Get("limit"), 20)
|
|
offset := parsePositive(r.URL.Query().Get("offset"), 0)
|
|
unread := strings.EqualFold(r.URL.Query().Get("unreadOnly"), "true") || r.URL.Query().Get("unreadOnly") == "1"
|
|
data, err := h.service.AlertNotifications(r.Context(), AlertNotificationQuery{
|
|
UnreadOnly: unread,
|
|
Search: strings.TrimSpace(r.URL.Query().Get("search")),
|
|
DeliveryStatus: strings.TrimSpace(r.URL.Query().Get("deliveryStatus")),
|
|
Limit: limit,
|
|
Offset: offset,
|
|
})
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertNotificationsRead(w http.ResponseWriter, r *http.Request) {
|
|
var request AlertNotificationReadRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
request.Actor = ActorFromContext(r.Context())
|
|
count, err := h.service.MarkAlertNotificationsRead(r.Context(), request)
|
|
h.write(w, r, map[string]int{"updated": count}, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertNotificationRetry(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseInt(strings.TrimSpace(r.PathValue("id")), 10, 64)
|
|
if err != nil || id <= 0 {
|
|
h.write(w, r, nil, clientError{Code: "ALERT_NOTIFICATION_ID_INVALID", Message: "通知记录 ID 无效"})
|
|
return
|
|
}
|
|
var request AlertNotificationRetryRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.RetryAlertNotification(r.Context(), id, request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAlertNotificationRetryAudit(w http.ResponseWriter, r *http.Request) {
|
|
id, err := strconv.ParseInt(strings.TrimSpace(r.PathValue("id")), 10, 64)
|
|
if err != nil || id <= 0 {
|
|
h.write(w, r, nil, clientError{Code: "ALERT_NOTIFICATION_ID_INVALID", Message: "通知记录 ID 无效"})
|
|
return
|
|
}
|
|
data, err := h.service.AlertNotificationRetryAudits(r.Context(), id)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAccessSummary(w http.ResponseWriter, r *http.Request) {
|
|
var query AccessQuery
|
|
if !decodeJSONBody(w, r, &query) {
|
|
return
|
|
}
|
|
data, err := h.service.AccessSummary(r.Context(), query)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAccessVehicles(w http.ResponseWriter, r *http.Request) {
|
|
var query AccessQuery
|
|
if !decodeJSONBody(w, r, &query) {
|
|
return
|
|
}
|
|
data, err := h.service.AccessVehicles(r.Context(), query)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleAccessThresholds(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.AccessThresholds(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleUpdateAccessThresholds(w http.ResponseWriter, r *http.Request) {
|
|
var update AccessThresholdUpdate
|
|
if !decodeJSONBody(w, r, &update) {
|
|
return
|
|
}
|
|
update.Actor = ActorFromContext(r.Context())
|
|
data, err := h.service.UpdateAccessThresholds(r.Context(), update)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func decodeJSONBody(w http.ResponseWriter, r *http.Request, target any) bool {
|
|
defer r.Body.Close()
|
|
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20))
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(target); err != nil {
|
|
httpx.WriteError(w, http.StatusBadRequest, "BAD_JSON", "请求 JSON 解析失败", err.Error(), traceID(r))
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (h *Handler) handleCreateHistoryExport(w http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close()
|
|
var request HistoryExportRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
|
httpx.WriteError(w, http.StatusBadRequest, "BAD_JSON", "请求 JSON 解析失败", err.Error(), traceID(r))
|
|
return
|
|
}
|
|
data, err := h.service.CreateHistoryExport(r.Context(), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleListHistoryExports(w http.ResponseWriter, r *http.Request) {
|
|
h.write(w, r, h.service.ListHistoryExports(r.Context()), nil)
|
|
}
|
|
|
|
func (h *Handler) handleListHistoryExportsPage(w http.ResponseWriter, r *http.Request) {
|
|
limit := parsePositive(r.URL.Query().Get("limit"), 10)
|
|
offset := parsePositive(r.URL.Query().Get("offset"), 0)
|
|
h.write(w, r, h.service.ListHistoryExportsPage(r.Context(), HistoryExportQuery{
|
|
Search: r.URL.Query().Get("search"), Status: r.URL.Query().Get("status"), Scope: r.URL.Query().Get("scope"), OwnerScope: r.URL.Query().Get("ownerScope"), Sort: r.URL.Query().Get("sort"), Limit: limit, Offset: offset,
|
|
}), nil)
|
|
}
|
|
|
|
func (h *Handler) handlePreviewHistoryExportCleanup(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.PreviewHistoryExportCleanup(r.Context(), HistoryExportCleanupQuery{
|
|
OlderThanDays: parsePositive(r.URL.Query().Get("olderThanDays"), 180),
|
|
OwnerScope: r.URL.Query().Get("ownerScope"),
|
|
})
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleHistoryExportCleanupAudit(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.HistoryExportCleanupAudit(r.Context(), parsePositive(r.URL.Query().Get("limit"), 20))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleCleanupHistoryExports(w http.ResponseWriter, r *http.Request) {
|
|
var request HistoryExportCleanupRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.CleanupHistoryExports(r.Context(), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleHistoryExportCleanupAutomation(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.HistoryExportCleanupAutomation(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleUpdateHistoryExportCleanupAutomation(w http.ResponseWriter, r *http.Request) {
|
|
var request HistoryExportCleanupAutomationPolicyRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.UpdateHistoryExportCleanupAutomation(r.Context(), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleApproveHistoryExportCleanupAutomation(w http.ResponseWriter, r *http.Request) {
|
|
var request HistoryExportCleanupAutomationActionRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.ApproveHistoryExportCleanupAutomation(r.Context(), r.PathValue("id"), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleRejectHistoryExportCleanupAutomation(w http.ResponseWriter, r *http.Request) {
|
|
var request HistoryExportCleanupAutomationActionRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.RejectHistoryExportCleanupAutomation(r.Context(), r.PathValue("id"), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleRetryHistoryExportCleanupAutomation(w http.ResponseWriter, r *http.Request) {
|
|
var request HistoryExportCleanupAutomationActionRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.RetryHistoryExportCleanupAutomation(r.Context(), r.PathValue("id"), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleHistoryPreferences(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.HistoryPreferences(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleUpdateHistoryPreferences(w http.ResponseWriter, r *http.Request) {
|
|
var input HistoryPreferences
|
|
if !decodeJSONBody(w, r, &input) {
|
|
return
|
|
}
|
|
data, err := h.service.UpdateHistoryPreferences(r.Context(), input)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleCancelHistoryExport(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.CancelHistoryExport(r.Context(), r.PathValue("id"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleRebuildHistoryExport(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.RebuildHistoryExport(r.Context(), r.PathValue("id"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleArchiveHistoryExport(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.SetHistoryExportArchived(r.Context(), r.PathValue("id"), true)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleRestoreHistoryExport(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.SetHistoryExportArchived(r.Context(), r.PathValue("id"), false)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleHistoryExportCleanupProtection(w http.ResponseWriter, r *http.Request) {
|
|
var request HistoryExportCleanupProtectionRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.SetHistoryExportCleanupProtection(r.Context(), r.PathValue("id"), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleBatchHistoryExports(w http.ResponseWriter, r *http.Request) {
|
|
var request HistoryExportBatchRequest
|
|
if !decodeJSONBody(w, r, &request) {
|
|
return
|
|
}
|
|
data, err := h.service.BatchHistoryExports(r.Context(), request)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleDownloadHistoryExport(w http.ResponseWriter, r *http.Request) {
|
|
path, name, err := h.service.HistoryExportFile(r.Context(), r.PathValue("id"))
|
|
if err != nil {
|
|
h.write(w, r, nil, err)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
|
|
w.Header().Set("Content-Disposition", `attachment; filename="history-export.csv"; filename*=UTF-8''`+url.PathEscape(name))
|
|
w.Header().Set("X-Export-Name", url.PathEscape(name))
|
|
http.ServeFile(w, r, path)
|
|
}
|
|
|
|
func (h *Handler) handleHistoryMetricCatalog(w http.ResponseWriter, r *http.Request) {
|
|
h.write(w, r, h.service.HistoryMetricCatalog(), nil)
|
|
}
|
|
|
|
func (h *Handler) handleHistoryData(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.HistoryData(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleHistorySeries(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.HistorySeries(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleTrackPlayback(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.TrackPlayback(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleMonitorSummary(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.MonitorSummary(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleMonitorMap(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.MonitorMap(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleMonitorWorkspace(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.MonitorWorkspace(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleDashboardSummary(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.DashboardSummary(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicles(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.Vehicles(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleResolve(w http.ResponseWriter, r *http.Request) {
|
|
keyword := firstNonEmpty(r.URL.Query().Get("keyword"), r.URL.Query().Get("vin"))
|
|
keyword = strings.TrimSpace(keyword)
|
|
if keyword == "" {
|
|
httpx.WriteError(w, http.StatusBadRequest, "VEHICLE_KEY_REQUIRED", "车辆关键词不能为空", "", traceID(r))
|
|
return
|
|
}
|
|
data, err := h.service.ResolveVehicleIdentity(r.Context(), keyword, r.URL.Query().Get("protocol"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleCoverage(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.VehicleCoverage(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleCoverageSummary(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.VehicleCoverageSummary(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleBusinessFilters(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.VehicleBusinessFilters(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleServiceSummary(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.VehicleServiceSummary(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleDetail(w http.ResponseWriter, r *http.Request) {
|
|
keyword := firstNonEmpty(r.URL.Query().Get("keyword"), r.URL.Query().Get("vin"))
|
|
keyword = strings.TrimSpace(keyword)
|
|
if keyword == "" {
|
|
httpx.WriteError(w, http.StatusBadRequest, "VEHICLE_KEY_REQUIRED", "车辆关键词不能为空", "", traceID(r))
|
|
return
|
|
}
|
|
data, err := h.service.VehicleDetail(r.Context(), keyword, r.URL.Query().Get("protocol"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleServiceOverview(w http.ResponseWriter, r *http.Request) {
|
|
keyword := firstNonEmpty(r.URL.Query().Get("keyword"), r.URL.Query().Get("vin"))
|
|
keyword = strings.TrimSpace(keyword)
|
|
if keyword == "" {
|
|
httpx.WriteError(w, http.StatusBadRequest, "VEHICLE_KEY_REQUIRED", "车辆关键词不能为空", "", traceID(r))
|
|
return
|
|
}
|
|
data, err := h.service.VehicleServiceOverview(r.Context(), keyword, r.URL.Query().Get("protocol"))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleServiceOverviews(w http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close()
|
|
var query VehicleOverviewBatchQuery
|
|
if err := json.NewDecoder(r.Body).Decode(&query); err != nil {
|
|
httpx.WriteError(w, http.StatusBadRequest, "BAD_JSON", "请求 JSON 解析失败", err.Error(), traceID(r))
|
|
return
|
|
}
|
|
data, err := h.service.VehicleServiceOverviews(r.Context(), query)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleVehicleRealtime(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.VehicleRealtime(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleRealtimeLocations(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.RealtimeLocations(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleHistoryLocations(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.HistoryLocations(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleRawFramesGet(w http.ResponseWriter, r *http.Request) {
|
|
q := r.URL.Query()
|
|
query := RawFrameQuery{
|
|
Protocol: q.Get("protocol"),
|
|
VIN: firstNonEmpty(q.Get("vin"), q.Get("keyword")),
|
|
Keyword: q.Get("keyword"),
|
|
DateFrom: q.Get("dateFrom"),
|
|
DateTo: q.Get("dateTo"),
|
|
Fields: splitCSV(q.Get("fields")),
|
|
IncludeFields: q.Get("includeFields") == "true",
|
|
Limit: parsePositive(q.Get("limit"), 100),
|
|
Offset: parsePositive(q.Get("offset"), 0),
|
|
}
|
|
data, err := h.service.RawFrames(r.Context(), query)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleRawFramesPost(w http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close()
|
|
var query RawFrameQuery
|
|
if err := json.NewDecoder(r.Body).Decode(&query); err != nil {
|
|
httpx.WriteError(w, http.StatusBadRequest, "BAD_JSON", "请求 JSON 解析失败", err.Error(), traceID(r))
|
|
return
|
|
}
|
|
data, err := h.service.RawFrames(r.Context(), query)
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleDailyMileage(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.DailyMileage(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleDailyMileagePost(w http.ResponseWriter, r *http.Request) {
|
|
query, ok := decodeMileageQuery(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
data, err := h.service.DailyMileage(r.Context(), mileageQueryValues(query))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleMileageSummary(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.MileageSummary(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleMileageStatistics(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.MileageStatistics(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleMileageStatisticsPost(w http.ResponseWriter, r *http.Request) {
|
|
query, ok := decodeMileageQuery(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
data, err := h.service.MileageStatistics(r.Context(), mileageQueryValues(query))
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func decodeMileageQuery(w http.ResponseWriter, r *http.Request) (MileageQuery, bool) {
|
|
defer r.Body.Close()
|
|
var query MileageQuery
|
|
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<20))
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(&query); err != nil {
|
|
httpx.WriteError(w, http.StatusBadRequest, "BAD_JSON", "里程查询 JSON 解析失败", err.Error(), traceID(r))
|
|
return MileageQuery{}, false
|
|
}
|
|
return query, true
|
|
}
|
|
|
|
func mileageQueryValues(query MileageQuery) url.Values {
|
|
values := url.Values{}
|
|
if query.DateFrom != "" {
|
|
values.Set("dateFrom", query.DateFrom)
|
|
}
|
|
if query.DateTo != "" {
|
|
values.Set("dateTo", query.DateTo)
|
|
}
|
|
if query.Keyword != "" {
|
|
values.Set("keyword", query.Keyword)
|
|
}
|
|
if len(query.VINs) > 0 {
|
|
values.Set("vins", strings.Join(query.VINs, ","))
|
|
}
|
|
if query.VehicleScope != "" {
|
|
values.Set("vehicleScope", query.VehicleScope)
|
|
}
|
|
if query.Protocol != "" {
|
|
values.Set("protocol", query.Protocol)
|
|
}
|
|
if len(query.Protocols) > 0 {
|
|
values.Set("protocols", strings.Join(query.Protocols, ","))
|
|
}
|
|
if query.Deduplicate {
|
|
values.Set("deduplicate", "1")
|
|
}
|
|
if query.Limit > 0 {
|
|
values.Set("limit", strconv.Itoa(query.Limit))
|
|
}
|
|
if query.Offset > 0 {
|
|
values.Set("offset", strconv.Itoa(query.Offset))
|
|
}
|
|
return values
|
|
}
|
|
|
|
func (h *Handler) handleOnlineStatisticsSummary(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.OnlineStatisticsSummary(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleOnlineVehicleStatuses(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.OnlineVehicleStatuses(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleQualityIssues(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.QualityIssues(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleQualitySummary(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.QualitySummary(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleQualityNotificationPlan(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.QualityNotificationPlan(r.Context(), r.URL.Query())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleOpsHealth(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.OpsHealth(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) handleSourceReadiness(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.SourceReadiness(r.Context())
|
|
h.write(w, r, data, err)
|
|
}
|
|
|
|
func (h *Handler) write(w http.ResponseWriter, r *http.Request, data any, err error) {
|
|
if err != nil {
|
|
if clientErr, ok := asClientError(err); ok {
|
|
status := http.StatusBadRequest
|
|
switch {
|
|
case clientErr.Code == "VEHICLE_PERMISSION_DENIED":
|
|
status = http.StatusForbidden
|
|
case strings.HasPrefix(clientErr.Code, "HISTORY_SCOPE_"), clientErr.Code == "HISTORY_BEFORE_AUTHORIZATION":
|
|
status = http.StatusForbidden
|
|
case strings.HasSuffix(clientErr.Code, "_NOT_FOUND"):
|
|
status = http.StatusNotFound
|
|
case strings.HasSuffix(clientErr.Code, "_CONFLICT"), clientErr.Code == "ALERT_ACTION_NOT_ALLOWED":
|
|
status = http.StatusConflict
|
|
}
|
|
httpx.WriteError(w, status, clientErr.Code, clientErr.Message, "", traceID(r))
|
|
return
|
|
}
|
|
httpx.WriteError(w, http.StatusInternalServerError, "INTERNAL", "服务处理失败", err.Error(), traceID(r))
|
|
return
|
|
}
|
|
httpx.WriteOK(w, traceID(r), data)
|
|
}
|
|
|
|
func traceID(r *http.Request) string {
|
|
if value := r.Header.Get("X-Trace-Id"); value != "" {
|
|
return value
|
|
}
|
|
return "trace-" + time.Now().Format("20060102150405.000000")
|
|
}
|
|
|
|
func splitCSV(value string) []string {
|
|
if strings.TrimSpace(value) == "" {
|
|
return []string{}
|
|
}
|
|
parts := strings.Split(value, ",")
|
|
out := make([]string, 0, len(parts))
|
|
for _, part := range parts {
|
|
if trimmed := strings.TrimSpace(part); trimmed != "" {
|
|
out = append(out, trimmed)
|
|
}
|
|
}
|
|
return out
|
|
}
|