2026-09-06 17:04:11 +08:00
|
|
|
package httpapi
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"data-terminal-cloud/internal/deviceauth"
|
2026-09-12 08:57:29 +08:00
|
|
|
"data-terminal-cloud/internal/devicebinding"
|
2026-09-06 17:04:11 +08:00
|
|
|
"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 {
|
2026-09-12 08:57:29 +08:00
|
|
|
case errors.Is(err, devicebinding.ErrConfirmationRequired):
|
|
|
|
|
WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_AUTHORIZATION_REQUIRED", "请先由当前家庭授权解绑,随后再执行设备重置")
|
2026-09-06 17:04:11 +08:00
|
|
|
case errors.Is(err, devicereset.ErrPendingTranscripts):
|
|
|
|
|
WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_PENDING_TRANSCRIPTS", "设备仍有未上传的聊天记录")
|
2026-10-08 10:06:44 +08:00
|
|
|
case errors.Is(err, devicereset.ErrCallInProgress):
|
|
|
|
|
WriteError(writer, request, http.StatusConflict, "DEVICE_RESET_CALL_IN_PROGRESS", "设备正在通话,请结束后再恢复出厂")
|
2026-09-06 17:04:11 +08:00
|
|
|
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)
|
|
|
|
|
})
|
|
|
|
|
}
|