package device import ( stdcontext "context" "encoding/json" "errors" "io" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" "github.com/go-admin-team/go-admin-core/sdk/pkg" "gorm.io/gorm" ) type Handler struct { DB *gorm.DB } func (handler Handler) List(context *gin.Context) { request, err := parseListRequest(context) if err != nil { writeError(context, err) return } db, err := handler.database(context) if err != nil { writeError(context, internalError(err)) return } response, err := NewService(db).List(context.Request.Context(), request) if err != nil { writeError(context, err) return } context.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "data": response}) } func (handler Handler) Register(context *gin.Context) { request, err := decodeRegisterRequest(context) if err != nil { writeError(context, invalidRequest("请求 JSON 无效")) return } db, err := handler.database(context) if err != nil { writeError(context, internalError(err)) return } response, err := NewService(db).Register(context.Request.Context(), request, bearerToken(context.GetHeader("Authorization"))) if err != nil { writeError(context, err) return } context.Header("Cache-Control", "no-store") context.JSON(http.StatusOK, gin.H{"data": response}) } func (handler Handler) Heartbeat(context *gin.Context) { var request HeartbeatRequest if err := decodeJSON(context, &request); err != nil { writeError(context, invalidRequest("请求 JSON 无效")) return } db, err := handler.database(context) if err != nil { writeError(context, internalError(err)) return } response, err := NewService(db).Heartbeat(context.Request.Context(), request, bearerToken(context.GetHeader("Authorization"))) if err != nil { writeError(context, err) return } context.JSON(http.StatusOK, gin.H{"data": response}) } func (handler Handler) Disable(context *gin.Context) { handler.adminAction(context, (*Service).Disable) } func (handler Handler) RevokeToken(context *gin.Context) { handler.adminAction(context, (*Service).RevokeToken) } func (handler Handler) adminAction(context *gin.Context, action func(*Service, stdcontext.Context, uint64) error) { deviceID, err := strconv.ParseUint(context.Param("deviceId"), 10, 64) if err != nil || deviceID == 0 { writeError(context, invalidRequest("deviceId 无效")) return } db, err := handler.database(context) if err != nil { writeError(context, internalError(err)) return } if err := action(NewService(db), context.Request.Context(), deviceID); err != nil { writeError(context, err) return } context.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "data": gin.H{"deviceId": deviceID}}) } func (handler Handler) database(context *gin.Context) (*gorm.DB, error) { if handler.DB != nil { return handler.DB, nil } return pkg.GetOrm(context) } func decodeRegisterRequest(context *gin.Context) (RegisterRequest, error) { var request RegisterRequest if err := decodeJSON(context, &request); err != nil { return RegisterRequest{}, err } return request, nil } func decodeJSON(context *gin.Context, destination any) error { context.Request.Body = http.MaxBytesReader(context.Writer, context.Request.Body, 64<<10) decoder := json.NewDecoder(context.Request.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(destination); err != nil { return err } if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) { return errors.New("request body must contain one JSON object") } return nil } func bearerToken(header string) string { parts := strings.Fields(header) if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { return "" } return parts[1] } func writeError(context *gin.Context, err error) { var serviceError *ServiceError if !errors.As(err, &serviceError) { serviceError = internalError(err).(*ServiceError) } status := http.StatusInternalServerError switch serviceError.Code { case CodeInvalidRequest: status = http.StatusUnprocessableEntity case CodeInstallIDConflict: status = http.StatusConflict case CodeTokenInvalid: status = http.StatusUnauthorized case CodeDeviceDisabled: status = http.StatusForbidden case CodeDeviceTaskMismatch: status = http.StatusConflict case CodeDeviceNotFound: status = http.StatusNotFound } context.JSON(status, gin.H{ "code": serviceError.Code, "message": serviceError.Message, "retryable": serviceError.Retryable, }) }