package handler import ( "errors" "net/http" "github.com/gin-gonic/gin" "wx_service/internal/common/wechat_official/service" "wx_service/internal/model" ) type OAuthHandler struct { oaService *service.WeChatOAService } func NewOAuthHandler(oaService *service.WeChatOAService) *OAuthHandler { return &OAuthHandler{oaService: oaService} } type codeToUserRequest struct { Code string `json:"code" binding:"required"` WithUserInfo *bool `json:"with_userinfo"` } // CodeToUser 使用微信公众号网页授权 code 换取 openid,并可选拉取用户信息(需要 snsapi_userinfo 授权)。 func (h *OAuthHandler) CodeToUser(c *gin.Context) { var req codeToUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请求参数错误")) return } withUserInfo := true if req.WithUserInfo != nil { withUserInfo = *req.WithUserInfo } token, err := h.oaService.ExchangeCode(c.Request.Context(), req.Code) if err != nil { switch { case errors.Is(err, service.ErrWeChatOANotConfigured): c.JSON(http.StatusServiceUnavailable, model.Error(http.StatusServiceUnavailable, "未配置公众号服务,请联系管理员")) case errors.Is(err, service.ErrWeChatOACodeRequired): c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "code 不能为空")) default: var apiErr *service.WeChatOAError if errors.As(err, &apiErr) { c.JSON(http.StatusBadGateway, model.Error(http.StatusBadGateway, "微信接口异常,请稍后重试")) return } c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "获取 openid 失败,请稍后重试")) } return } payload := gin.H{ "openid": token.OpenID, "unionid": token.UnionID, "scope": token.Scope, "expires_in": token.ExpiresIn, } if withUserInfo { info, err := h.oaService.FetchUserInfo(c.Request.Context(), token.AccessToken, token.OpenID) if err != nil { // 可能是 scope 不够(snsapi_base),此时仍返回 openid,并给出提示。 payload["userinfo_error"] = "未获取到用户信息(可能未授权 snsapi_userinfo)" } else { // 统一从 userinfo 里回填 unionid(如果有) if info.UnionID != "" { payload["unionid"] = info.UnionID } payload["userinfo"] = info } } // 为安全起见,这里不向前端返回 access_token/refresh_token。 c.JSON(http.StatusOK, model.Success(payload)) }