package handler import ( "errors" "log" "net/http" "github.com/gin-gonic/gin" "wx_service/internal/common/auth/service" "wx_service/internal/middleware" "wx_service/internal/model" ) type AuthHandler struct { // handler 层通常只做“协议转换”: // - 把 HTTP 请求(JSON/header 等)解析成结构体 // - 调用 service 完成业务逻辑 // - 把结果/错误转换成统一的 JSON 响应 authService *service.AuthService } func NewAuthHandler(authService *service.AuthService) *AuthHandler { return &AuthHandler{ authService: authService, } } type weChatLoginRequest struct { // binding:"required" 是 Gin 的校验标签:字段缺失或为空会导致 ShouldBindJSON 返回错误 MiniProgramID uint `json:"mini_program_id" binding:"required"` Code string `json:"code" binding:"required"` NickName string `json:"nickname"` AvatarURL string `json:"avatar_url"` // 使用 *int 可以区分: // - nil:前端没传 gender // - 非 nil:前端传了具体值(即使是 0) Gender *int `json:"gender"` Phone string `json:"phone"` } func (h *AuthHandler) LoginWithWeChat(c *gin.Context) { // gin.Context 是每个请求的上下文对象: // - c.Request.Context() 是标准库 context,用于把超时/取消信号传递到 DB/HTTP 调用 // - c.JSON(...) 用于写 JSON 响应 var req weChatLoginRequest // ShouldBindJSON 会从请求体 JSON 反序列化到结构体,并根据 binding 标签做基础校验 if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "invalid request payload")) return } // 业务逻辑下沉到 service:这样 handler 更薄、更容易测试 result, err := h.authService.LoginWithCode(c.Request.Context(), service.LoginRequest{ MiniProgramID: req.MiniProgramID, Code: req.Code, NickName: req.NickName, AvatarURL: req.AvatarURL, Gender: req.Gender, Phone: req.Phone, }) if err != nil { switch { case errors.Is(err, service.ErrCodeRequired): c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "code is required")) case errors.Is(err, service.ErrMiniProgramRequired): c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "mini_program_id is required")) case errors.Is(err, service.ErrMiniProgramNotFound): c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "mini program not found")) default: var apiErr *service.WeChatError if errors.As(err, &apiErr) { c.JSON(http.StatusBadGateway, model.Error(http.StatusBadGateway, apiErr.Error())) return } c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "login failed")) } return } userPayload := gin.H{ "id": result.User.ID, "mini_program_id": result.User.MiniProgramID, "open_id": result.User.OpenID, "nickname": result.User.NickName, "avatar_url": result.User.AvatarURL, "gender": result.User.Gender, "phone": result.User.Phone, } if result.User.UnionID != "" { userPayload["union_id"] = result.User.UnionID } if result.Mode != "" { userPayload["mode"] = result.Mode } miniProgramPayload := gin.H{ "id": result.MiniProgram.ID, "name": result.MiniProgram.Name, "app_id": result.MiniProgram.AppID, } if result.MiniProgram.Description != "" { miniProgramPayload["description"] = result.MiniProgram.Description } c.JSON(http.StatusOK, model.Success(gin.H{ "user": userPayload, "session_key": result.SessionKey, "mini_program": miniProgramPayload, })) } type devLoginRequest struct { MiniProgramID uint `json:"mini_program_id"` } // DevLogin 仅在非 release 模式下可用,用于 H5 开发调试。 func (h *AuthHandler) DevLogin(c *gin.Context) { if gin.Mode() == gin.ReleaseMode { c.JSON(http.StatusNotFound, model.Error(http.StatusNotFound, "not found")) return } var req devLoginRequest _ = c.ShouldBindJSON(&req) if req.MiniProgramID == 0 { req.MiniProgramID = 3 } result, err := h.authService.DevLogin(c.Request.Context(), req.MiniProgramID) if err != nil { log.Printf("[dev_login] error: %v", err) c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "dev login failed: "+err.Error())) return } c.JSON(http.StatusOK, model.Success(gin.H{ "user": gin.H{ "id": result.User.ID, "mini_program_id": result.User.MiniProgramID, "open_id": result.User.OpenID, "nickname": result.User.NickName, "avatar_url": result.User.AvatarURL, }, "session_key": result.SessionKey, "mini_program": gin.H{ "id": result.MiniProgram.ID, "name": result.MiniProgram.Name, }, })) } type updateProfileRequest struct { Nickname string `json:"nickname"` AvatarURL string `json:"avatar_url"` } func (h *AuthHandler) UpdateProfile(c *gin.Context) { user := middleware.MustCurrentUser(c) var req updateProfileRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "参数错误")) return } if req.Nickname == "" && req.AvatarURL == "" { c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请提供昵称或头像")) return } updated, err := h.authService.UpdateProfile(c.Request.Context(), user.ID, req.Nickname, req.AvatarURL) if err != nil { log.Printf("[update_profile] error: %v", err) c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "更新失败")) return } c.JSON(http.StatusOK, model.Success(gin.H{ "id": updated.ID, "nickname": updated.NickName, "avatar_url": updated.AvatarURL, })) }