package handler import ( "github.com/gin-gonic/gin" "github.com/sundynix/pets-be/internal/middleware" "github.com/sundynix/pets-be/internal/model" appjwt "github.com/sundynix/pets-be/pkg/jwt" "github.com/sundynix/pets-be/pkg/response" ) type loginReq struct { Nickname string `json:"nickname"` } // userTokens 签发小程序用户的「access + refresh」令牌对。 // access token 只有 2 小时,前端过期后拿 refresh_token 静默续期。 func (h *Handler) userTokens(user *model.User) (gin.H, error) { token, err := h.jwt.Generate(user.ID, appjwt.KindUser, user.Nickname, "") if err != nil { return nil, err } refresh, refreshExp, err := h.svc.IssueRefreshToken(string(appjwt.KindUser), user.ID) if err != nil { return nil, err } return gin.H{ "token": token, "refresh_token": refresh, "expires_in": h.jwt.ExpireSeconds(), "refresh_expires_at": refreshExp, "user": user, }, nil } // Login 开发态 Mock 登录 func (h *Handler) Login(c *gin.Context) { if !h.cfg.Auth.DevLogin { response.Fail(c, 40300, "开发登录未开启,请使用微信登录") return } var req loginReq _ = c.ShouldBindJSON(&req) user, err := h.svc.MockLogin(req.Nickname) if err != nil { response.FailErr(c, err) return } data, err := h.userTokens(user) if err != nil { response.FailErr(c, err) return } response.OK(c, data) } type wechatLoginReq struct { Code string `json:"code"` Nickname string `json:"nickname"` Avatar string `json:"avatar"` } // WechatLogin 微信小程序登录:前端 wx.login 拿 code 传入 func (h *Handler) WechatLogin(c *gin.Context) { var req wechatLoginReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } if req.Code == "" { response.FailParams(c, "缺少 code") return } user, err := h.svc.WechatLogin(req.Code) if err != nil { response.Fail(c, 40100, err.Error()) return } // 可选:首次登录写入用户授权的昵称/头像 if fields := map[string]any{}; true { if req.Nickname != "" && user.Nickname == "微信用户" { fields["nickname"] = req.Nickname } if req.Avatar != "" && user.Avatar == "" { fields["avatar"] = req.Avatar } if len(fields) > 0 { if u, e := h.svc.UpdateUser(user.ID, fields); e == nil { user = u } } } data, err := h.userTokens(user) if err != nil { response.FailErr(c, err) return } response.OK(c, data) } type refreshReq struct { RefreshToken string `json:"refresh_token"` } // RefreshToken POST /api/auth/refresh // 用刷新令牌换新的 access token;刷新令牌本身也会轮换,旧的立即失效。 func (h *Handler) RefreshToken(c *gin.Context) { var req refreshReq _ = c.ShouldBindJSON(&req) userID, err := h.svc.ConsumeRefreshToken(req.RefreshToken, string(appjwt.KindUser)) if err != nil { response.Fail(c, 40100, err.Error()) return } user, err := h.svc.GetUser(userID) if err != nil { response.Fail(c, 40100, "登录已失效,请重新登录") return } if user.Disabled { response.Fail(c, 40300, "账号已被禁用") return } data, err := h.userTokens(user) if err != nil { response.FailErr(c, err) return } response.OK(c, data) } // Logout POST /api/auth/logout 作废当前刷新令牌 func (h *Handler) Logout(c *gin.Context) { var req refreshReq _ = c.ShouldBindJSON(&req) h.svc.RevokeRefreshToken(req.RefreshToken, string(appjwt.KindUser)) response.OK(c, gin.H{"ok": true}) } // Profile 当前用户资料 func (h *Handler) Profile(c *gin.Context) { user, err := h.svc.GetUser(middleware.UserID(c)) if err != nil { response.FailErr(c, err) return } response.OK(c, user) } type updateProfileReq struct { Nickname *string `json:"nickname"` Avatar *string `json:"avatar"` AvatarFileID *string `json:"avatar_file_id"` Phone *string `json:"phone"` } // UpdateProfile 更新资料 func (h *Handler) UpdateProfile(c *gin.Context) { var req updateProfileReq if err := c.ShouldBindJSON(&req); err != nil { response.FailParams(c, err.Error()) return } fields := map[string]any{} if req.Nickname != nil { fields["nickname"] = *req.Nickname } if req.Avatar != nil { fields["avatar"] = *req.Avatar } if req.AvatarFileID != nil { fields["avatar_file_id"] = *req.AvatarFileID } if req.Phone != nil { fields["phone"] = *req.Phone } user, err := h.svc.UpdateUser(middleware.UserID(c), fields) if err != nil { response.FailErr(c, err) return } response.OK(c, user) }