Integrate WeChat Official Account support and update configuration

- Added WeChat Official Account configuration options to .env.example and config.go for OAuth2 integration.
- Updated main.go to initialize WeChat OAuth handler and register routes for handling OAuth requests.
- Enhanced documentation to include references for WeChat Official Account functionality.
- Updated route registration to accommodate the new OAuth handler for improved API structure.
This commit is contained in:
nepiedg
2025-12-31 03:55:30 +00:00
parent bba6dc6b4f
commit 1c48fbdeaf
10 changed files with 349 additions and 1 deletions
@@ -0,0 +1,80 @@
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))
}