// Package user /v1/me 聚合、设备登记、应用更新检查。 package user import ( "net/http" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" "gorm.io/gorm/clause" "dudu/server/internal/auth" "dudu/server/internal/quota" "dudu/server/internal/store" "dudu/server/pkg/protocol" ) type Handlers struct { DB *gorm.DB Quota *quota.Manager // AppVersions 启动时由 config 解析好的平台→版本信息(17G);nil/空表示未配置。 AppVersions map[string]protocol.AppLatestResponse } // Me GET /v1/me(需登录):余额/试用/账户态聚合 + 设备心跳登记。 func (h *Handlers) Me(c *gin.Context) { uid := auth.UserID(c) var u store.User if err := h.DB.First(&u, "id = ?", uid).Error; err != nil { c.JSON(http.StatusUnauthorized, protocol.NewAPIError(protocol.ErrUnauthorized)) return } snap, err := h.Quota.Get(c, uid) if err != nil { c.JSON(http.StatusInternalServerError, protocol.NewAPIError(protocol.ErrInternal)) return } h.touchDevice(c, uid) state := protocol.AccountOK switch { case snap.BalanceSeconds > 0: state = protocol.AccountOK case snap.TrialUsedToday < protocol.TrialDailySeconds: state = protocol.AccountTrial default: state = protocol.AccountQuota } c.JSON(http.StatusOK, protocol.MeResponse{ UserID: uid, NicknameMasked: auth.MaskNickname(u.Nickname), BalanceSeconds: snap.BalanceSeconds, TrialDailyLimit: protocol.TrialDailySeconds, TrialUsedToday: snap.TrialUsedToday, AccountState: state, }) } // touchDevice 设备登记/心跳(X-Device-ID / X-Platform / X-App-Version 头)。 func (h *Handlers) touchDevice(c *gin.Context, uid string) { did := c.GetHeader("X-Device-ID") if did == "" { return } dev := store.Device{ ID: did, UserID: uid, Platform: c.GetHeader("X-Platform"), AppVersion: c.GetHeader("X-App-Version"), LastSeen: time.Now(), } _ = h.DB.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "id"}}, DoUpdates: clause.AssignmentColumns([]string{"user_id", "platform", "app_version", "last_seen"}), }).Create(&dev).Error } // AppLatest GET /v1/app/latest?platform=mac|win|android|ios 🔓 // 版本信息来自启动时 config 解析好的 APP_LATEST_JSON(17G,{"mac":{"version":...,"url":...},...}), // MVP 不建表;未配置/未知平台返回 204。 func (h *Handlers) AppLatest(c *gin.Context) { if v, ok := h.AppVersions[c.Query("platform")]; ok { c.JSON(http.StatusOK, v) return } c.Status(http.StatusNoContent) }