package wechat import ( "context" "encoding/json" "errors" "fmt" "net/http" "net/url" "strings" "time" ) // Session is the jscode2session result. type Session struct { OpenID string UnionID string SessionKey string } // Client exchanges a mini-program js_code for a session. type Client interface { Code2Session(ctx context.Context, code string) (Session, error) } // APIClient calls api.weixin.qq.com. type APIClient struct { AppID string Secret string HTTP *http.Client } type wxSessionResp struct { OpenID string `json:"openid"` SessionKey string `json:"session_key"` UnionID string `json:"unionid"` ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } // Enabled reports whether WeChat credentials are configured. func (c *APIClient) Enabled() bool { return c != nil && strings.TrimSpace(c.AppID) != "" && strings.TrimSpace(c.Secret) != "" } // Code2Session implements Client. func (c *APIClient) Code2Session(ctx context.Context, code string) (Session, error) { if !c.Enabled() { return Session{}, errors.New("微信登录未配置") } code = strings.TrimSpace(code) if code == "" { return Session{}, errors.New("缺少微信登录码") } q := url.Values{} q.Set("appid", c.AppID) q.Set("secret", c.Secret) q.Set("js_code", code) q.Set("grant_type", "authorization_code") req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.weixin.qq.com/sns/jscode2session?"+q.Encode(), nil) if err != nil { return Session{}, err } cli := c.HTTP if cli == nil { cli = &http.Client{Timeout: 10 * time.Second} } resp, err := cli.Do(req) if err != nil { return Session{}, errors.New("微信服务暂不可用") } defer resp.Body.Close() var body wxSessionResp if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { return Session{}, errors.New("微信服务暂不可用") } if body.ErrCode != 0 { return Session{}, fmt.Errorf("微信登录失败(%d)", body.ErrCode) } if body.OpenID == "" || body.SessionKey == "" { return Session{}, errors.New("微信登录失败") } return Session{OpenID: body.OpenID, UnionID: body.UnionID, SessionKey: body.SessionKey}, nil }