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) }) }