chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"data-terminal-cloud/internal/device"
|
||||
"data-terminal-cloud/internal/deviceauth"
|
||||
"data-terminal-cloud/internal/saas"
|
||||
)
|
||||
|
||||
type DeviceCommandService interface {
|
||||
PullCommands(context.Context, deviceauth.Identity, int) ([]device.Command, error)
|
||||
AcknowledgeCommand(context.Context, deviceauth.Identity, string, device.CommandAcknowledgement) (device.Command, error)
|
||||
}
|
||||
|
||||
func PullDeviceCommandsHandler(service DeviceCommandService) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := deviceauth.FromContext(r.Context())
|
||||
if !ok {
|
||||
WriteError(w, r, http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED", "设备身份验证失败")
|
||||
return
|
||||
}
|
||||
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||
items, err := service.PullCommands(r.Context(), id, limit)
|
||||
if err != nil {
|
||||
WriteAPIError(w, r, http.StatusServiceUnavailable, Error{Code: "DEVICE_COMMANDS_UNAVAILABLE", Message: "暂时无法读取设备命令", Retryable: true})
|
||||
return
|
||||
}
|
||||
WriteJSON(w, r, http.StatusOK, map[string]any{"items": items})
|
||||
})
|
||||
}
|
||||
|
||||
func AcknowledgeDeviceCommandHandler(service DeviceCommandService) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := deviceauth.FromContext(r.Context())
|
||||
if !ok {
|
||||
WriteError(w, r, http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED", "设备身份验证失败")
|
||||
return
|
||||
}
|
||||
var input struct {
|
||||
Status string `json:"status"`
|
||||
ResultSummary map[string]any `json:"result_summary"`
|
||||
}
|
||||
if !decodeWebJSON(w, r, &input, "设备命令回执不正确") {
|
||||
return
|
||||
}
|
||||
input.Status = strings.ToUpper(strings.TrimSpace(input.Status))
|
||||
if input.Status != "ACKNOWLEDGED" && input.Status != "SUCCEEDED" && input.Status != "FAILED" {
|
||||
writeValidation(w, r, "设备命令回执不正确", FieldError{Field: "status", Message: "必须为ACKNOWLEDGED、SUCCEEDED或FAILED"})
|
||||
return
|
||||
}
|
||||
if input.ResultSummary == nil {
|
||||
input.ResultSummary = map[string]any{}
|
||||
}
|
||||
item, err := service.AcknowledgeCommand(r.Context(), id, r.PathValue("id"), device.CommandAcknowledgement{Status: input.Status, ResultSummary: input.ResultSummary})
|
||||
if err != nil {
|
||||
WriteError(w, r, http.StatusConflict, "DEVICE_COMMAND_STATE_CONFLICT", "命令不存在、已过期或状态不允许回执")
|
||||
return
|
||||
}
|
||||
WriteJSON(w, r, http.StatusOK, item)
|
||||
})
|
||||
}
|
||||
|
||||
func ListDeviceCommandsHandler(service *saas.Service) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
p, ok := resourcePrincipal(w, r, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
items, err := service.ListDeviceCommands(r.Context(), p, r.PathValue("id"))
|
||||
if err != nil {
|
||||
writeSaaSError(w, r, err)
|
||||
return
|
||||
}
|
||||
WriteJSON(w, r, http.StatusOK, map[string]any{"items": items})
|
||||
})
|
||||
}
|
||||
|
||||
func CreateDeviceCommandHandler(service *saas.Service) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
p, ok := resourcePrincipal(w, r, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
key := strings.TrimSpace(r.Header.Get("Idempotency-Key"))
|
||||
if len(key) < 16 || len(key) > 128 {
|
||||
writeValidation(w, r, "缺少有效的重复请求识别码", FieldError{Field: "Idempotency-Key", Message: "长度必须为16至128字符"})
|
||||
return
|
||||
}
|
||||
var input struct {
|
||||
Type string `json:"type"`
|
||||
Payload map[string]any `json:"payload"`
|
||||
ExpiresInSec int `json:"expires_in_sec"`
|
||||
}
|
||||
if !decodeWebJSON(w, r, &input, "设备命令参数不正确") {
|
||||
return
|
||||
}
|
||||
input.Type = strings.TrimSpace(input.Type)
|
||||
if input.Type != "apply_config" && input.Type != "reboot" && input.Type != "diagnostics" && input.Type != "firmware_update" {
|
||||
writeValidation(w, r, "设备命令参数不正确", FieldError{Field: "type", Message: "命令类型不受支持"})
|
||||
return
|
||||
}
|
||||
if input.ExpiresInSec < 30 || input.ExpiresInSec > 86400 {
|
||||
writeValidation(w, r, "设备命令参数不正确", FieldError{Field: "expires_in_sec", Message: "必须为30至86400秒"})
|
||||
return
|
||||
}
|
||||
if input.Payload == nil {
|
||||
input.Payload = map[string]any{}
|
||||
}
|
||||
item, created, err := service.CreateDeviceCommand(r.Context(), p, r.PathValue("id"), key, RequestID(r.Context()), input.Type, input.Payload, input.ExpiresInSec)
|
||||
if err != nil {
|
||||
writeSaaSError(w, r, err)
|
||||
return
|
||||
}
|
||||
status := http.StatusAccepted
|
||||
if !created {
|
||||
status = http.StatusOK
|
||||
w.Header().Set("Idempotency-Replayed", "true")
|
||||
}
|
||||
WriteJSON(w, r, status, item)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user