Add backend foundation and config-driven workbench
This commit is contained in:
120
backend/internal/platform/wechatmini/client.go
Normal file
120
backend/internal/platform/wechatmini/client.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package wechatmini
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
appID string
|
||||
appSecret string
|
||||
devPrefix string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
AppID string
|
||||
OpenID string
|
||||
UnionID string
|
||||
SessionKey string
|
||||
}
|
||||
|
||||
type code2SessionResponse struct {
|
||||
OpenID string `json:"openid"`
|
||||
SessionKey string `json:"session_key"`
|
||||
UnionID string `json:"unionid"`
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
func NewClient(appID, appSecret, devPrefix string) *Client {
|
||||
return &Client{
|
||||
appID: appID,
|
||||
appSecret: appSecret,
|
||||
devPrefix: devPrefix,
|
||||
httpClient: &http.Client{Timeout: 8 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) ExchangeCode(ctx context.Context, code string) (*Session, error) {
|
||||
code = strings.TrimSpace(code)
|
||||
if code == "" {
|
||||
return nil, fmt.Errorf("wechat code is required")
|
||||
}
|
||||
|
||||
if c.devPrefix != "" && strings.HasPrefix(code, c.devPrefix) {
|
||||
suffix := strings.TrimPrefix(code, c.devPrefix)
|
||||
if suffix == "" {
|
||||
suffix = "default"
|
||||
}
|
||||
return &Session{
|
||||
AppID: fallbackString(c.appID, "dev-mini-app"),
|
||||
OpenID: "dev_openid_" + normalizeDevID(suffix),
|
||||
UnionID: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
if c.appID == "" || c.appSecret == "" {
|
||||
return nil, fmt.Errorf("wechat mini app credentials are not configured")
|
||||
}
|
||||
|
||||
values := url.Values{}
|
||||
values.Set("appid", c.appID)
|
||||
values.Set("secret", c.appSecret)
|
||||
values.Set("js_code", code)
|
||||
values.Set("grant_type", "authorization_code")
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.weixin.qq.com/sns/jscode2session?"+values.Encode(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var parsed code2SessionResponse
|
||||
if err := json.Unmarshal(body, &parsed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parsed.ErrCode != 0 {
|
||||
return nil, fmt.Errorf("wechat code2session failed: %d %s", parsed.ErrCode, parsed.ErrMsg)
|
||||
}
|
||||
if parsed.OpenID == "" {
|
||||
return nil, fmt.Errorf("wechat code2session returned empty openid")
|
||||
}
|
||||
|
||||
return &Session{
|
||||
AppID: c.appID,
|
||||
OpenID: parsed.OpenID,
|
||||
UnionID: parsed.UnionID,
|
||||
SessionKey: parsed.SessionKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func normalizeDevID(value string) string {
|
||||
sum := sha1.Sum([]byte(value))
|
||||
return hex.EncodeToString(sum[:])[:16]
|
||||
}
|
||||
|
||||
func fallbackString(value, fallback string) string {
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
Reference in New Issue
Block a user