Files
2026-10-25 20:27:59 +08:00

76 lines
2.1 KiB
Go

// 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
}