219 lines
8.4 KiB
Go
219 lines
8.4 KiB
Go
package realtime
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"strings"
|
||
)
|
||
|
||
func NewOpenAPIHandler() http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodGet {
|
||
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||
return
|
||
}
|
||
if strings.Trim(r.URL.Path, "/") != "openapi.json" {
|
||
writeError(w, http.StatusNotFound, "route not found")
|
||
return
|
||
}
|
||
w.Header().Set("Content-Type", "application/json")
|
||
_ = json.NewEncoder(w).Encode(realtimeOpenAPISpec())
|
||
})
|
||
}
|
||
|
||
func NewSwaggerUIHandler() http.Handler {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodGet {
|
||
writeError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||
return
|
||
}
|
||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||
_, _ = w.Write([]byte(swaggerHTML))
|
||
})
|
||
}
|
||
|
||
func realtimeOpenAPISpec() map[string]any {
|
||
return map[string]any{
|
||
"openapi": "3.0.3",
|
||
"info": map[string]any{
|
||
"title": "Lingniu Vehicle Realtime API",
|
||
"version": "1.0.0",
|
||
"description": "MySQL realtime query APIs for vehicle_realtime_snapshot, vehicle_realtime_location, and vehicle_realtime_kv.",
|
||
},
|
||
"paths": map[string]any{
|
||
"/api/realtime/snapshots": map[string]any{
|
||
"get": map[string]any{
|
||
"summary": "查询车辆实时快照",
|
||
"description": "从 vehicle_realtime_snapshot 查询各协议最新在线快照,支持协议、VIN、车牌过滤和分页。默认不执行 COUNT(*),total 默认表示本页返回条数;需要精确总数时传 includeTotal=true。",
|
||
"tags": []string{"Realtime Snapshot API"},
|
||
"x-table": "vehicle_realtime_snapshot",
|
||
"parameters": commonRealtimeParameters(),
|
||
"responses": map[string]any{
|
||
"200": map[string]any{
|
||
"description": "Snapshot page",
|
||
"content": map[string]any{
|
||
"application/json": map[string]any{
|
||
"schema": map[string]any{"$ref": "#/components/schemas/SnapshotPage"},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
"/api/realtime/locations": map[string]any{
|
||
"get": map[string]any{
|
||
"summary": "查询车辆实时位置",
|
||
"description": "从 vehicle_realtime_location 查询各协议最新位置,支持协议、VIN、车牌过滤和分页。默认不执行 COUNT(*),total 默认表示本页返回条数;需要精确总数时传 includeTotal=true。",
|
||
"tags": []string{"Realtime Location API"},
|
||
"x-table": "vehicle_realtime_location",
|
||
"parameters": commonRealtimeParameters(),
|
||
"responses": map[string]any{
|
||
"200": map[string]any{
|
||
"description": "Location page",
|
||
"content": map[string]any{
|
||
"application/json": map[string]any{
|
||
"schema": map[string]any{"$ref": "#/components/schemas/LocationPage"},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
"/api/realtime/kv": map[string]any{
|
||
"get": map[string]any{
|
||
"summary": "查询车辆实时 KV 字段",
|
||
"description": "从 vehicle_realtime_kv 查询实时字段投影,支持协议、VIN、domain、field 过滤和分页。适合 UI 局部刷新、快速字段查询和统计输入。",
|
||
"tags": []string{"Realtime KV API"},
|
||
"x-table": "vehicle_realtime_kv",
|
||
"parameters": kvRealtimeParameters(),
|
||
"responses": map[string]any{
|
||
"200": map[string]any{
|
||
"description": "KV page",
|
||
"content": map[string]any{
|
||
"application/json": map[string]any{
|
||
"schema": map[string]any{"$ref": "#/components/schemas/KVPage"},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
"components": map[string]any{
|
||
"schemas": map[string]any{
|
||
"SnapshotPage": pageSchema("#/components/schemas/SnapshotRow"),
|
||
"LocationPage": pageSchema("#/components/schemas/LocationRow"),
|
||
"KVPage": pageSchema("#/components/schemas/KVRow"),
|
||
"SnapshotRow": map[string]any{
|
||
"type": "object",
|
||
"properties": map[string]any{
|
||
"protocol": stringSchema("GB32960"),
|
||
"vin": stringSchema("LB9A32A21R0LS1707"),
|
||
"plate": stringSchema("浙A12345"),
|
||
"event_time": stringSchema("2026-07-02 16:01:02.123"),
|
||
"received_at": stringSchema("2026-07-02 16:01:03.456"),
|
||
"event_id": stringSchema("event id"),
|
||
"updated_at": stringSchema("2026-07-02 16:01:04"),
|
||
},
|
||
},
|
||
"LocationRow": map[string]any{
|
||
"type": "object",
|
||
"properties": map[string]any{
|
||
"protocol": stringSchema("JT808"),
|
||
"vin": stringSchema("LKLG7C4E3NA774736"),
|
||
"plate": stringSchema("粤B98765"),
|
||
"event_time": stringSchema("2026-07-02 16:11:02.000"),
|
||
"latitude": numberSchema(30.123456),
|
||
"longitude": numberSchema(120.654321),
|
||
"speed_kmh": numberSchema(54.3),
|
||
"total_mileage_km": numberSchema(48798.9),
|
||
"soc_percent": numberSchema(81.5),
|
||
"altitude_m": numberSchema(19.0),
|
||
"direction_deg": numberSchema(88.0),
|
||
"alarm_flag": integerSchema(0),
|
||
"status_flag": integerSchema(3),
|
||
"received_at": stringSchema("2026-07-02 16:11:03.000"),
|
||
"event_id": stringSchema("event id"),
|
||
"updated_at": stringSchema("2026-07-02 16:11:04"),
|
||
},
|
||
},
|
||
"KVRow": map[string]any{
|
||
"type": "object",
|
||
"properties": map[string]any{
|
||
"protocol": stringSchema("GB32960"),
|
||
"vin": stringSchema("LNXNEGRR0SR321372"),
|
||
"domain": stringSchema("gd_fc_stack"),
|
||
"field": stringSchema("stack_water_outlet_temp_c"),
|
||
"value": stringSchema("63"),
|
||
"value_type": stringSchema("number"),
|
||
"event_time": stringSchema("2026-07-03 11:18:45.000"),
|
||
"received_at": stringSchema("2026-07-03 11:18:45.123"),
|
||
"event_id": stringSchema("event id"),
|
||
"updated_at": stringSchema("2026-07-03 11:18:46"),
|
||
},
|
||
},
|
||
},
|
||
},
|
||
}
|
||
}
|
||
|
||
func kvRealtimeParameters() []map[string]any {
|
||
params := commonRealtimeParameters()
|
||
params = append(params,
|
||
map[string]any{"name": "domain", "in": "query", "schema": map[string]any{"type": "string", "example": "gd_fc_stack"}, "required": false},
|
||
map[string]any{"name": "field", "in": "query", "schema": map[string]any{"type": "string", "example": "stack_water_outlet_temp_c"}, "required": false},
|
||
)
|
||
return params
|
||
}
|
||
|
||
func commonRealtimeParameters() []map[string]any {
|
||
return []map[string]any{
|
||
{"name": "protocol", "in": "query", "schema": map[string]any{"type": "string", "enum": []string{"GB32960", "JT808", "YUTONG_MQTT"}}, "required": false},
|
||
{"name": "vin", "in": "query", "schema": map[string]any{"type": "string"}, "required": false},
|
||
{"name": "plate", "in": "query", "schema": map[string]any{"type": "string"}, "required": false},
|
||
{"name": "includeTotal", "in": "query", "schema": map[string]any{"type": "boolean", "default": false}, "required": false, "description": "默认 false,不执行 COUNT(*);true 时返回精确总数。"},
|
||
{"name": "limit", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 1, "maximum": 1000, "default": 50}, "required": false},
|
||
{"name": "offset", "in": "query", "schema": map[string]any{"type": "integer", "minimum": 0, "maximum": 1000000, "default": 0}, "required": false},
|
||
}
|
||
}
|
||
|
||
func pageSchema(itemRef string) map[string]any {
|
||
return map[string]any{
|
||
"type": "object",
|
||
"properties": map[string]any{
|
||
"items": map[string]any{"type": "array", "items": map[string]any{"$ref": itemRef}},
|
||
"total": map[string]any{"type": "integer", "example": 1, "description": "默认表示本页返回条数;includeTotal=true 时表示精确总数。"},
|
||
"limit": integerSchema(50),
|
||
"offset": integerSchema(0),
|
||
},
|
||
}
|
||
}
|
||
|
||
func stringSchema(example string) map[string]any {
|
||
return map[string]any{"type": "string", "example": example}
|
||
}
|
||
|
||
func numberSchema(example float64) map[string]any {
|
||
return map[string]any{"type": "number", "example": example}
|
||
}
|
||
|
||
func integerSchema(example int64) map[string]any {
|
||
return map[string]any{"type": "integer", "example": example}
|
||
}
|
||
|
||
const swaggerHTML = `<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>Lingniu Vehicle Realtime API</title>
|
||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
||
</head>
|
||
<body>
|
||
<div id="swagger-ui"></div>
|
||
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
||
<script>
|
||
window.ui = SwaggerUIBundle({ url: "/openapi.json", dom_id: "#swagger-ui" });
|
||
</script>
|
||
</body>
|
||
</html>`
|