优化小程序
This commit is contained in:
1 parent
2e2b837a20
commit
727319a6eb
24 files changed
+1509
-200
No files matched your search
@@ -2,11 +2,14 @@ package wechatmini
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -138,6 +141,65 @@ func GetPhoneNumber(cfg *Config, phoneCode string) (*PhoneNumber, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ============================ 小程序码 ============================
|
||||
|
||||
// CreateUnlimitedQRCode 生成小程序码(wxa/getwxacodeunlimited,数量无限制),
|
||||
// 返回图片 base64 data URL,可直接用于 <img src>。
|
||||
//
|
||||
// scene 会话标识,扫码后作为 options.scene 传给目标页面,1-32 个字符
|
||||
// page 小程序页面路径(如 pages/register/confirm),必须已发布,不带前导 /
|
||||
// envVersion release(正式版,默认)/ trial(体验版)/ develop(开发版)
|
||||
func CreateUnlimitedQRCode(cfg *Config, scene, page, envVersion string) (string, error) {
|
||||
if cfg == nil || cfg.AppID == "" || cfg.AppSecret == "" {
|
||||
return "", ErrNotConfigured
|
||||
}
|
||||
page = strings.TrimPrefix(strings.TrimSpace(page), "/")
|
||||
if page == "" {
|
||||
return "", errors.New("未配置小程序码页面路径")
|
||||
}
|
||||
scene = strings.TrimSpace(scene)
|
||||
if scene == "" || len(scene) > 32 {
|
||||
return "", errors.New("小程序码 scene 无效(需 1-32 个字符)")
|
||||
}
|
||||
envVersion = strings.TrimSpace(envVersion)
|
||||
if envVersion == "" {
|
||||
envVersion = "release"
|
||||
}
|
||||
token, err := getAccessToken(cfg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
payload := map[string]interface{}{
|
||||
"scene": scene,
|
||||
"page": page,
|
||||
"env_version": envVersion,
|
||||
"width": 280,
|
||||
}
|
||||
// 体验版 / 开发版下该页面尚未发布到正式版,必须关闭路径校验,
|
||||
// 否则微信返回 40066 invalid url(正式版可保留默认校验)
|
||||
if envVersion != "release" {
|
||||
payload["check_path"] = false
|
||||
}
|
||||
api := fmt.Sprintf("%s/wxa/getwxacodeunlimited?access_token=%s", apiBase, url.QueryEscape(token))
|
||||
body, contentType, err := httpPostBinary(api, payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 失败时微信仍返回 200,但响应体是 JSON 错误而非图片
|
||||
if strings.HasPrefix(strings.TrimSpace(string(body)), "{") {
|
||||
var resp struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
_ = json.Unmarshal(body, &resp)
|
||||
return "", &APIError{API: "生成小程序码", Code: resp.ErrCode, Msg: resp.ErrMsg}
|
||||
}
|
||||
if contentType == "" {
|
||||
contentType = "image/jpeg"
|
||||
}
|
||||
return "data:" + contentType + ";base64," + base64.StdEncoding.EncodeToString(body), nil
|
||||
}
|
||||
|
||||
// ============================ access_token 缓存 ============================
|
||||
|
||||
type tokenEntry struct {
|
||||
@@ -226,6 +288,24 @@ func httpPostJSON(api string, payload interface{}, out interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// httpPostBinary POST JSON 并返回原始响应体与 Content-Type(小程序码接口返回图片流)
|
||||
func httpPostBinary(api string, payload interface{}) ([]byte, string, error) {
|
||||
bs, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
resp, err := httpClient.Post(api, "application/json; charset=utf-8", bytes.NewReader(bs))
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("请求微信接口失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("读取微信响应失败: %w", err)
|
||||
}
|
||||
return body, resp.Header.Get("Content-Type"), nil
|
||||
}
|
||||
|
||||
func truncate(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
|
||||
Reference in new issue
Block a user