Files
platforms/backend/internal/http/router.go

108 lines
4.8 KiB
Go

// Package http 提供平台总后台的 HTTP 路由和统一响应。
package http
import (
"encoding/json"
"log/slog"
"net/http"
"time"
"git.apinb.com/heqiapp/platforms/backend/internal/platform"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
)
// NewRouter 注册首期平台总后台所需的只读列表、仪表盘与气站创建接口。
func NewRouter(pool *pgxpool.Pool, logger *slog.Logger) http.Handler {
repository := platform.NewRepository(pool)
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", func(writer http.ResponseWriter, request *http.Request) {
writeJSON(writer, http.StatusOK, map[string]any{"status": "ok"})
})
mux.HandleFunc("GET /api/v1/dashboard/overview", func(writer http.ResponseWriter, request *http.Request) {
respondRepository(writer, request, logger, func() (any, error) { return repository.DashboardOverview(request.Context()) })
})
mux.HandleFunc("GET /api/v1/org/gas-station", func(writer http.ResponseWriter, request *http.Request) {
respondRepository(writer, request, logger, func() (any, error) { return repository.ListOrgGasStation(request.Context()) })
})
mux.HandleFunc("POST /api/v1/org/gas-station", func(writer http.ResponseWriter, request *http.Request) {
var body struct {
StationCode string `json:"stationCode"`
Name string `json:"name"`
Principal string `json:"principal"`
ServiceArea string `json:"serviceArea"`
}
if err := json.NewDecoder(request.Body).Decode(&body); err != nil || body.StationCode == "" || body.Name == "" || body.Principal == "" || body.ServiceArea == "" {
writeError(writer, http.StatusBadRequest, "参数不完整:站点编码、名称、负责人和服务区域均为必填")
return
}
respondRepositoryWithStatus(writer, request, logger, http.StatusCreated, func() (any, error) {
return repository.CreateOrgGasStation(request.Context(), body.StationCode, body.Name, body.Principal, body.ServiceArea)
})
})
mux.HandleFunc("GET /api/v1/org/delivery-point", func(writer http.ResponseWriter, request *http.Request) {
respondRepository(writer, request, logger, func() (any, error) { return repository.ListOrgDeliveryPoint(request.Context()) })
})
mux.HandleFunc("GET /api/v1/org/service-person", func(writer http.ResponseWriter, request *http.Request) {
respondRepository(writer, request, logger, func() (any, error) { return repository.ListOrgServicePerson(request.Context()) })
})
mux.HandleFunc("GET /api/v1/idn/account", func(writer http.ResponseWriter, request *http.Request) {
respondRepository(writer, request, logger, func() (any, error) { return repository.ListIdnAccount(request.Context()) })
})
mux.HandleFunc("GET /api/v1/saf/event", func(writer http.ResponseWriter, request *http.Request) {
respondRepository(writer, request, logger, func() (any, error) { return repository.ListSafEvent(request.Context()) })
})
return requestLogger(cors(mux), logger)
}
func respondRepository(writer http.ResponseWriter, request *http.Request, logger *slog.Logger, query func() (any, error)) {
respondRepositoryWithStatus(writer, request, logger, http.StatusOK, query)
}
func respondRepositoryWithStatus(writer http.ResponseWriter, request *http.Request, logger *slog.Logger, status int, query func() (any, error)) {
result, err := query()
if err != nil {
logger.Error("处理平台 API 请求失败", "method", request.Method, "path", request.URL.Path, "error", err)
writeError(writer, http.StatusInternalServerError, "服务暂不可用,请稍后重试")
return
}
writeJSON(writer, status, map[string]any{"data": result})
}
func writeJSON(writer http.ResponseWriter, status int, data any) {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(status)
_ = json.NewEncoder(writer).Encode(data)
}
func writeError(writer http.ResponseWriter, status int, message string) {
writeJSON(writer, status, map[string]any{"error": message})
}
func cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
writer.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
writer.Header().Set("Access-Control-Allow-Headers", "Content-Type,Idempotency-Key")
if request.Method == http.MethodOptions {
writer.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(writer, request)
})
}
func requestLogger(next http.Handler, logger *slog.Logger) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
requestID, err := uuid.NewV7()
if err == nil {
writer.Header().Set("X-Request-Id", requestID.String())
}
startedAt := time.Now()
next.ServeHTTP(writer, request)
logger.Info("平台 API 请求完成", "method", request.Method, "path", request.URL.Path, "duration", time.Since(startedAt).String())
})
}