191 lines
6.6 KiB
Go
191 lines
6.6 KiB
Go
package platform
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"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/vehicle-service", h.handleVehicleDetail)
|
|
h.mux.HandleFunc("GET /api/vehicle-service/overview", h.handleVehicleServiceOverview)
|
|
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("GET /api/quality/summary", h.handleQualitySummary)
|
|
h.mux.HandleFunc("GET /api/quality/issues", h.handleQualityIssues)
|
|
h.mux.HandleFunc("GET /api/ops/health", h.handleOpsHealth)
|
|
}
|
|
|
|
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) 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) 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) 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) 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) handleOpsHealth(w http.ResponseWriter, r *http.Request) {
|
|
data, err := h.service.OpsHealth(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 {
|
|
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 nil
|
|
}
|
|
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
|
|
}
|