// Package wechat 封装微信小程序相关调用(此处为 code2session 实现)。 package wechat import ( "context" "encoding/json" "fmt" "io" "log/slog" "net/http" "net/url" "portal-mini-go/internal/pkg/errors" ) const code2SessionURL = "https://api.weixin.qq.com/sns/jscode2session" // SessionInfo 是 code2session 返回的会话信息。 type SessionInfo struct { OpenID string UnionID string SessionKey string } // Client 微信调用客户端。凭证(appid/secret)由调用方按请求传入, // 不再依赖静态配置,方便后台动态纳管多个小程序。 type Client struct { client *http.Client } func NewClient() *Client { return &Client{client: &http.Client{}} } // Code2Session 用 appID + secret + 登录 code 换取 openid / session_key。 func (c *Client) Code2Session(ctx context.Context, appID, secret, code string) (*SessionInfo, error) { q := url.Values{} q.Set("appid", appID) q.Set("secret", secret) q.Set("js_code", code) q.Set("grant_type", "authorization_code") req, err := http.NewRequestWithContext(ctx, http.MethodGet, code2SessionURL+"?"+q.Encode(), nil) if err != nil { return nil, errors.Wrap(50000, "build wechat request", err) } resp, err := c.client.Do(req) if err != nil { return nil, errors.Wrap(50000, "call wechat code2session", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.Wrap(50000, "read wechat response", err) } var r struct { OpenID string `json:"openid"` UnionID string `json:"unionid"` SessionKey string `json:"session_key"` ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } if err := json.Unmarshal(body, &r); err != nil { return nil, errors.Wrap(50000, "decode wechat response", err) } if r.ErrCode != 0 { slog.Warn("wechat code2session error", slog.Int("errcode", r.ErrCode), slog.String("errmsg", r.ErrMsg)) return nil, errors.Wrap(50000, fmt.Sprintf("wechat code2session: %s", r.ErrMsg), nil) } return &SessionInfo{OpenID: r.OpenID, UnionID: r.UnionID, SessionKey: r.SessionKey}, nil }