package httpapi import ( "bytes" "context" "encoding/json" "errors" "io" "net/http" "strings" "data-terminal-cloud/internal/deviceauth" "data-terminal-cloud/internal/devicebinding" "data-terminal-cloud/internal/devicereset" "data-terminal-cloud/internal/idempotency" ) type DeviceFactoryResetService interface { Reset(context.Context, deviceauth.Identity, string, string, []byte, devicereset.Input) (devicereset.Response, bool, error) } func DeviceFactoryResetHandler(service DeviceFactoryResetService) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Cache-Control", "no-store") writer.Header().Set("Pragma", "no-cache") identity, ok := deviceauth.FromContext(request.Context()) if !ok { WriteError(writer, request, http.StatusUnauthorized, "DEVICE_AUTHENTICATION_FAILED", "设备身份验证失败") return } key := strings.TrimSpace(request.Header.Get("Idempotency-Key")) if len(key) < 16 || len(key) > 128 { WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "缺少有效的重复请求识别码") return } rawBody, err := io.ReadAll(request.Body) if err != nil { WriteError(writer, request, http.StatusBadRequest, "INVALID_REQUEST_BODY", "请求正文无法读取") return } decoder := json.NewDecoder(bytes.NewReader(rawBody)) decoder.DisallowUnknownFields() var input devicereset.Input if err := decoder.Decode(&input); err != nil || ensureJSONEOF(decoder) != nil || devicereset.Validate(input) != nil { WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "设备恢复出厂参数不正确") return } response, replayed, err := service.Reset(request.Context(), identity, key, RequestID(request.Context()), rawBody, input) if replayed { writer.Header().Set("Idempotency-Replayed", "true") } if err != nil { switch { case errors.Is(err, devicebinding.ErrConfirmationRequired): WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_AUTHORIZATION_REQUIRED", "请先由当前家庭授权解绑,随后再执行设备重置") case errors.Is(err, devicereset.ErrPendingTranscripts): WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_PENDING_TRANSCRIPTS", "设备仍有未上传的聊天记录") case errors.Is(err, devicereset.ErrCallInProgress): WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_CALL_IN_PROGRESS", "设备正在通话,请结束后再恢复出厂") case errors.Is(err, devicereset.ErrFirmwareInProgress): WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_FIRMWARE_IN_PROGRESS", "设备正在升级固件,暂时不能恢复出厂") case errors.Is(err, devicereset.ErrResetCounterConflict): WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_COUNTER_CONFLICT", "设备重置计数器不是递增值") case errors.Is(err, devicereset.ErrStateConflict): WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_STATE_CONFLICT", "当前设备状态不允许恢复出厂") case errors.Is(err, idempotency.ErrKeyConflict): WriteError(writer, request, http.StatusConflict, "IDEMPOTENCY_KEY_CONFLICT", "重复请求识别码已用于不同的请求") case errors.Is(err, idempotency.ErrInProgress): WriteAPIError(writer, request, http.StatusConflict, Error{Code: "IDEMPOTENCY_IN_PROGRESS", Message: "相同恢复出厂请求仍在处理中", Retryable: true}) case errors.Is(err, devicereset.ErrInvalid): WriteError(writer, request, http.StatusBadRequest, "VALIDATION_FAILED", "设备恢复出厂参数不正确") default: WriteAPIError(writer, request, http.StatusServiceUnavailable, Error{Code: "DEVICE_RESET_FAILED", Message: "设备恢复出厂暂时失败", Retryable: true}) } return } WriteJSON(writer, request, http.StatusOK, response) }) }