Files
ServerStatus/server/openapi.go
T
2026-07-10 15:35:10 +08:00

224 lines
9.5 KiB
Go

package main
import "strings"
func openAPISpec() map[string]any {
paths := map[string]any{
"/api/health": map[string]any{
"get": publicOperation("health", "服务健康状态", objectResponse("Go 服务、Agent TCP 与配置状态")),
},
"/api/schema": map[string]any{
"get": publicOperation("schema", "ServerStatus API 简表", objectResponse("端点和集合描述")),
},
"/api/openapi.json": map[string]any{
"get": publicOperation("openapi", "OpenAPI 3.1 文档", objectResponse("OpenAPI document")),
},
"/json/stats.json": map[string]any{
"get": publicOperation("stats", "实时状态快照", objectResponse("WebUI 状态数据")),
},
"/api/config": map[string]any{
"get": protectedOperation("getConfig", "读取完整配置", nil, configResponse("200", "配置文档")),
"put": protectedOperation("replaceConfig", "整体校验并替换配置", requestBody("Config"), configResponse("200", "已保存并热重载的配置")),
},
"/api/reload": map[string]any{
"post": protectedOperation("reloadConfig", "从磁盘热重载配置", nil, objectResponse("重载结果")),
},
"/api/restart": map[string]any{
"post": protectedOperation("restartRuntime", "在进程内重启采集运行时", nil, statusResponse("202", "重启结果", map[string]any{
"operation": map[string]any{"type": "string", "const": "restart"},
"mode": map[string]any{"type": "string", "const": "in-process"},
})),
},
"/api/servers/{username}/reset-traffic": map[string]any{
"post": withParameters(protectedOperation("resetServerTraffic", "重置节点本月流量基线", nil, objectResponse("流量重置结果")), pathParameter("username", "节点用户名")),
},
}
for _, key := range []string{"servers", "monitors", "sslcerts", "watchdog"} {
spec := collectionSpecs[key]
schemaName := map[string]string{"servers": "Server", "monitors": "Monitor", "sslcerts": "SSLCert", "watchdog": "Watchdog"}[key]
operationBase := strings.TrimSuffix(key, "s")
if key == "sslcerts" {
operationBase = "sslcert"
}
paths["/api/"+key] = map[string]any{
"get": protectedOperation("list"+titleWord(key), "查询 "+key, nil, collectionResponse(key, schemaName, "配置集合")),
"post": protectedOperation("create"+titleWord(operationBase), "新增 "+spec.itemName, requestBody(schemaName), itemResponse("201", spec.itemName, schemaName, "创建结果")),
}
parameterName := "id"
parameterDescription := "数字下标或唯一的 " + spec.idField
if key == "servers" {
parameterName = "username"
parameterDescription = "节点用户名"
}
paths["/api/"+key+"/{"+parameterName+"}"] = map[string]any{
"put": withParameters(protectedOperation("update"+titleWord(operationBase), "修改 "+spec.itemName, requestBody(schemaName), itemResponse("200", spec.itemName, schemaName, "修改结果")), pathParameter(parameterName, parameterDescription)),
"delete": withParameters(protectedOperation("delete"+titleWord(operationBase), "删除 "+spec.itemName, nil, itemResponse("200", "removed", schemaName, "删除结果")), pathParameter(parameterName, parameterDescription)),
}
}
return map[string]any{
"openapi": "3.1.0",
"info": map[string]any{
"title": "ServerStatus HTTP API",
"version": version,
"description": "单进程 Go ServerStatus 的配置、运行状态和采集控制 API。",
},
"servers": []any{map[string]any{"url": "/", "description": "当前 ServerStatus 实例"}},
"paths": paths,
"components": map[string]any{
"securitySchemes": map[string]any{
"bearerAuth": map[string]any{"type": "http", "scheme": "bearer", "bearerFormat": "ADMIN_TOKEN"},
},
"schemas": openAPISchemas(),
},
}
}
func openAPISchemas() map[string]any {
stringProperty := func(description string) map[string]any {
return map[string]any{"type": "string", "description": description}
}
integerProperty := func(description string, minimum, maximum int) map[string]any {
property := map[string]any{"type": "integer", "description": description, "minimum": minimum}
if maximum > 0 {
property["maximum"] = maximum
}
return property
}
server := map[string]any{
"type": "object", "required": collectionSpecs["servers"].required,
"properties": map[string]any{
"username": stringProperty("唯一客户端用户名"), "name": stringProperty("节点显示名称"),
"type": stringProperty("虚拟化或节点类型"), "host": stringProperty("主机标识"),
"location": stringProperty("位置"), "password": stringProperty("客户端密码"),
"monthstart": integerProperty("月流量重置日", 1, 28), "disabled": map[string]any{"type": "boolean", "default": false},
},
}
monitor := map[string]any{
"type": "object", "required": collectionSpecs["monitors"].required,
"properties": map[string]any{
"name": stringProperty("监测名称"), "host": stringProperty("HTTP(S) URL 或 TCP 地址"),
"type": stringProperty("https、http 或 tcp"), "interval": integerProperty("客户端探测间隔秒数", 1, 0),
},
}
sslcert := map[string]any{
"type": "object", "required": collectionSpecs["sslcerts"].required,
"properties": map[string]any{
"name": stringProperty("证书名称"), "domain": stringProperty("域名或 URL"),
"port": integerProperty("TLS 端口", 1, 65535), "interval": integerProperty("检查间隔秒数", 1, 0),
"callback": stringProperty("告警回调 URL 前缀"),
},
}
watchdog := map[string]any{
"type": "object", "required": collectionSpecs["watchdog"].required,
"properties": map[string]any{
"name": stringProperty("告警名称"), "rule": stringProperty("兼容 Exprtk 的状态表达式"),
"interval": integerProperty("通知冷却秒数", 1, 0), "callback": stringProperty("告警回调 URL 前缀"),
},
}
return map[string]any{
"Server": server, "Monitor": monitor, "SSLCert": sslcert, "Watchdog": watchdog,
"Config": map[string]any{
"type": "object", "required": []string{"servers", "monitors", "sslcerts", "watchdog"},
"properties": map[string]any{
"servers": arraySchema("Server"), "monitors": arraySchema("Monitor"),
"sslcerts": arraySchema("SSLCert"), "watchdog": arraySchema("Watchdog"),
},
},
"Error": map[string]any{
"type": "object", "required": []string{"ok", "error"},
"properties": map[string]any{"ok": map[string]any{"type": "boolean", "const": false}, "error": map[string]any{"type": "string"}, "details": map[string]any{}},
},
}
}
func titleWord(value string) string {
if value == "" {
return ""
}
return strings.ToUpper(value[:1]) + value[1:]
}
func schemaRef(name string) map[string]any {
return map[string]any{"$ref": "#/components/schemas/" + name}
}
func arraySchema(name string) map[string]any {
return map[string]any{"type": "array", "items": schemaRef(name)}
}
func requestBody(schemaName string) map[string]any {
return map[string]any{
"required": true,
"content": map[string]any{"application/json": map[string]any{"schema": schemaRef(schemaName)}},
}
}
func objectResponse(description string) map[string]any {
return map[string]any{"200": jsonResponse(description, map[string]any{"type": "object"}), "4XX": jsonResponse("请求错误", schemaRef("Error"))}
}
func configResponse(status, description string) map[string]any {
return statusResponse(status, description, map[string]any{"config": schemaRef("Config")})
}
func collectionResponse(key, schemaName, description string) map[string]any {
return statusResponse("200", description, map[string]any{key: arraySchema(schemaName)})
}
func itemResponse(status, key, schemaName, description string) map[string]any {
return statusResponse(status, description, map[string]any{
key: schemaRef(schemaName),
"reloaded": map[string]any{"type": "boolean", "const": true},
"pid": map[string]any{"type": "integer", "minimum": 1},
"config": schemaRef("Config"),
})
}
func statusResponse(status, description string, properties map[string]any) map[string]any {
required := []string{"ok"}
allProperties := map[string]any{"ok": map[string]any{"type": "boolean", "const": true}}
for key, property := range properties {
allProperties[key] = property
required = append(required, key)
}
return map[string]any{
status: jsonResponse(description, map[string]any{
"type": "object", "required": required, "properties": allProperties,
}),
"4XX": jsonResponse("请求错误", schemaRef("Error")),
}
}
func jsonResponse(description string, schema map[string]any) map[string]any {
return map[string]any{"description": description, "content": map[string]any{"application/json": map[string]any{"schema": schema}}}
}
func publicOperation(operationID, summary string, responses map[string]any) map[string]any {
return map[string]any{"operationId": operationID, "summary": summary, "security": []any{}, "responses": responses}
}
func protectedOperation(operationID, summary string, body map[string]any, responses map[string]any) map[string]any {
operation := map[string]any{
"operationId": operationID, "summary": summary,
"security": []any{map[string]any{"bearerAuth": []any{}}}, "responses": responses,
}
if body != nil {
operation["requestBody"] = body
}
return operation
}
func withParameters(operation map[string]any, parameters ...map[string]any) map[string]any {
items := make([]any, 0, len(parameters))
for _, parameter := range parameters {
items = append(items, parameter)
}
operation["parameters"] = items
return operation
}
func pathParameter(name, description string) map[string]any {
return map[string]any{"name": name, "in": "path", "required": true, "description": description, "schema": map[string]any{"type": "string"}}
}