Files
wx_service/internal/common/wechat_official/service/oauth_service.go
T
nepiedg 1c48fbdeaf 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.
2025-12-31 03:55:30 +00:00

156 lines
3.9 KiB
Go

package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"time"
"wx_service/config"
)
var (
ErrWeChatOANotConfigured = errors.New("wechat official account is not configured")
ErrWeChatOACodeRequired = errors.New("code is required")
)
type WeChatOAService struct {
cfg config.WeChatOfficialConfig
client *http.Client
}
func NewWeChatOAService(cfg config.WeChatOfficialConfig) *WeChatOAService {
timeout := cfg.RequestTimeout
if timeout <= 0 {
timeout = 5 * time.Second
}
return &WeChatOAService{
cfg: cfg,
client: &http.Client{
Timeout: timeout,
},
}
}
type OAuthToken struct {
OpenID string `json:"openid"`
UnionID string `json:"unionid,omitempty"`
Scope string `json:"scope,omitempty"`
ExpiresIn int `json:"expires_in,omitempty"`
AccessToken string `json:"access_token,omitempty"`
}
type UserInfo struct {
OpenID string `json:"openid"`
NickName string `json:"nickname,omitempty"`
Sex int `json:"sex,omitempty"`
Province string `json:"province,omitempty"`
City string `json:"city,omitempty"`
Country string `json:"country,omitempty"`
HeadImgURL string `json:"headimgurl,omitempty"`
Privilege []string `json:"privilege,omitempty"`
UnionID string `json:"unionid,omitempty"`
}
type wechatError struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
type WeChatOAError struct {
Code int
Msg string
}
func (e *WeChatOAError) Error() string {
return fmt.Sprintf("wechat oa error: errcode=%d errmsg=%s", e.Code, e.Msg)
}
func (s *WeChatOAService) ExchangeCode(ctx context.Context, code string) (OAuthToken, error) {
if s.cfg.AppID == "" || s.cfg.AppSecret == "" {
return OAuthToken{}, ErrWeChatOANotConfigured
}
if code == "" {
return OAuthToken{}, ErrWeChatOACodeRequired
}
params := url.Values{}
params.Set("appid", s.cfg.AppID)
params.Set("secret", s.cfg.AppSecret)
params.Set("code", code)
params.Set("grant_type", "authorization_code")
endpoint := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?%s", params.Encode())
body, err := s.get(ctx, endpoint)
if err != nil {
return OAuthToken{}, err
}
var apiErr wechatError
_ = json.Unmarshal(body, &apiErr)
if apiErr.ErrCode != 0 {
return OAuthToken{}, &WeChatOAError{Code: apiErr.ErrCode, Msg: apiErr.ErrMsg}
}
var token OAuthToken
if err := json.Unmarshal(body, &token); err != nil {
return OAuthToken{}, fmt.Errorf("decode oauth token: %w", err)
}
return token, nil
}
func (s *WeChatOAService) FetchUserInfo(ctx context.Context, accessToken string, openid string) (UserInfo, error) {
if accessToken == "" || openid == "" {
return UserInfo{}, fmt.Errorf("missing access_token or openid")
}
params := url.Values{}
params.Set("access_token", accessToken)
params.Set("openid", openid)
params.Set("lang", "zh_CN")
endpoint := fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?%s", params.Encode())
body, err := s.get(ctx, endpoint)
if err != nil {
return UserInfo{}, err
}
var apiErr wechatError
_ = json.Unmarshal(body, &apiErr)
if apiErr.ErrCode != 0 {
return UserInfo{}, &WeChatOAError{Code: apiErr.ErrCode, Msg: apiErr.ErrMsg}
}
var info UserInfo
if err := json.Unmarshal(body, &info); err != nil {
return UserInfo{}, fmt.Errorf("decode user info: %w", err)
}
return info, nil
}
func (s *WeChatOAService) get(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("build request: %w", err)
}
resp, err := s.client.Do(req)
if err != nil {
return nil, fmt.Errorf("call wechat api: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("wechat api status=%d body=%s", resp.StatusCode, string(body))
}
return body, nil
}