package wechat import ( "context" "encoding/json" "net/http" "net/url" "time" "github.com/break/junhong_cmp_fiber/pkg/errors" "go.uber.org/zap" ) const miniAppCode2SessionURL = "https://api.weixin.qq.com/sns/jscode2session" // MiniAppServiceInterface 微信小程序服务接口 type MiniAppServiceInterface interface { Code2Session(ctx context.Context, code string) (openID, unionID, sessionKey string, err error) } // MiniAppService 微信小程序服务实现 type MiniAppService struct { appID string appSecret string client *http.Client logger *zap.Logger } // NewMiniAppService 创建微信小程序服务 func NewMiniAppService(appID, appSecret string, logger *zap.Logger) *MiniAppService { return &MiniAppService{ appID: appID, appSecret: appSecret, client: &http.Client{ Timeout: 10 * time.Second, }, logger: logger, } } type code2SessionResponse struct { OpenID string `json:"openid"` UnionID string `json:"unionid"` SessionKey string `json:"session_key"` ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } // Code2Session 通过小程序 code 换取 openid/session_key func (s *MiniAppService) Code2Session(ctx context.Context, code string) (openID, unionID, sessionKey string, err error) { if code == "" { return "", "", "", errors.New(errors.CodeInvalidParam, "授权码不能为空") } if s.appID == "" || s.appSecret == "" { return "", "", "", errors.New(errors.CodeWechatConfigUnavailable, "小程序配置不完整") } params := url.Values{} params.Set("appid", s.appID) params.Set("secret", s.appSecret) params.Set("js_code", code) params.Set("grant_type", "authorization_code") req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, miniAppCode2SessionURL+"?"+params.Encode(), nil) if reqErr != nil { s.logger.Error("构建小程序 Code2Session 请求失败", zap.Error(reqErr)) return "", "", "", errors.Wrap(errors.CodeWechatOAuthFailed, reqErr, "构建微信请求失败") } resp, doErr := s.client.Do(req) if doErr != nil { s.logger.Error("调用小程序 Code2Session 失败", zap.Error(doErr)) return "", "", "", errors.Wrap(errors.CodeWechatOAuthFailed, doErr, "调用微信接口失败") } defer resp.Body.Close() var result code2SessionResponse if decodeErr := json.NewDecoder(resp.Body).Decode(&result); decodeErr != nil { s.logger.Error("解析小程序 Code2Session 响应失败", zap.Error(decodeErr)) return "", "", "", errors.Wrap(errors.CodeWechatOAuthFailed, decodeErr, "解析微信响应失败") } if result.ErrCode != 0 { s.logger.Error("小程序 Code2Session 返回错误", zap.Int("errcode", result.ErrCode), zap.String("errmsg", result.ErrMsg), ) return "", "", "", errors.New(errors.CodeWechatOAuthFailed) } if result.OpenID == "" || result.SessionKey == "" { s.logger.Error("小程序 Code2Session 响应缺少关键字段", zap.String("open_id", result.OpenID)) return "", "", "", errors.New(errors.CodeWechatOAuthFailed, "微信返回数据不完整") } return result.OpenID, result.UnionID, result.SessionKey, nil }