优化小程序

This commit is contained in:
hero920103 committed 2026-10-31 01:21:55 +08:00
1 parent 2e2b837a20
commit 727319a6eb
24 files changed
+1509 -200

No files matched your search

+112 -2
View File
@@ -6,9 +6,15 @@ import (
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"regexp"
"strings"
"sync"
"time"
"github.com/beego/beego/v2/core/logs"
)
// =============================================================
@@ -48,6 +54,106 @@ func (e *APIError) Explain() string {
return ""
}
// ============================ 出口 IP 探测(40164 IP 白名单排查用) ============================
var (
outboundIPMu sync.Mutex
outboundIPCache string
outboundIPAt time.Time
)
// outboundIPTTL 出口 IP 缓存时长(探测失败也按此负缓存,避免每次请求都外网探测)
const outboundIPTTL = 5 * time.Minute
// ipDetectServices 出口 IP 探测服务,按顺序尝试,任一返回 IPv4 即采用
var ipDetectServices = []string{"https://ifconfig.me/ip", "https://api.ipify.org", "https://myip.ipip.net"}
var ipv4Re = regexp.MustCompile(`\d{1,3}(?:\.\d{1,3}){3}`)
// OutboundIP 返回本服务器访问公网时的出口 IP(微信后台「IP白名单」需填入该 IP)。
// 结果内存缓存 5 分钟;服务器无法访问探测服务时返回空串,不影响主流程。
func OutboundIP() string {
outboundIPMu.Lock()
if time.Since(outboundIPAt) < outboundIPTTL {
ip := outboundIPCache
outboundIPMu.Unlock()
return ip
}
outboundIPMu.Unlock()
ip := detectOutboundIP()
outboundIPMu.Lock()
outboundIPCache = ip
outboundIPAt = time.Now()
outboundIPMu.Unlock()
return ip
}
func detectOutboundIP() string {
client := &http.Client{Timeout: 3 * time.Second}
for _, svc := range ipDetectServices {
resp, err := client.Get(svc)
if err != nil {
continue
}
body, rerr := io.ReadAll(io.LimitReader(resp.Body, 256))
resp.Body.Close()
if rerr != nil {
continue
}
if ip := firstIPv4(string(body)); ip != "" {
return ip
}
}
return ""
}
// firstIPv4 取文本中第一个合法 IPv4(形如 1.2.3.4),无则返回空串。
func firstIPv4(s string) string {
m := ipv4Re.FindString(s)
if m == "" || net.ParseIP(m) == nil {
return ""
}
return m
}
// ErrMsgIPs 从微信 errmsg 中提取 IP(如 "invalid ip 1.2.3.4 ipv6 ::ffff:1.2.3.4")。
func ErrMsgIPs(errmsg string) []string {
var out []string
seen := make(map[string]bool)
for _, m := range ipv4Re.FindAllString(errmsg, -1) {
if net.ParseIP(m) == nil || seen[m] {
continue
}
seen[m] = true
out = append(out, m)
}
return out
}
// logAPIError 记录微信接口错误详情:
// errcode / errmsg / 微信提示的 IP / 本服务器出口 IP / 排查建议只落服务端日志。
// 注册页、登录页等匿名可访问页面不应回显这些信息(暴露服务器出口 IP 与内部错误)。
func logAPIError(scene string, err error) {
if err == nil {
return
}
detail := err.Error()
var ae *APIError
if errors.As(err, &ae) && ae != nil {
if hint := ae.Explain(); hint != "" {
detail += ";" + hint
}
if ips := ErrMsgIPs(ae.Msg); len(ips) > 0 {
detail += ";微信提示的 IP:" + strings.Join(ips, ", ")
}
}
if ip := OutboundIP(); ip != "" {
detail += ";本服务器出口 IP:" + ip
}
logs.Warning("[wechat-mp] %s 失败:%s", scene, detail)
}
// ExplainError 提取微信接口错误的中文排查建议;无可用建议时返回 ("", false)。
func ExplainError(err error) (string, bool) {
var ae *APIError
@@ -84,7 +190,9 @@ func GetAccessToken(cfg *Config) (string, error) {
return "", err
}
if resp.AccessToken == "" {
return "", &APIError{API: "获取 access_token", Code: resp.ErrCode, Msg: resp.ErrMsg}
apiErr := &APIError{API: "获取 access_token", Code: resp.ErrCode, Msg: resp.ErrMsg}
logAPIError("获取 access_token", apiErr)
return "", apiErr
}
ttl := time.Duration(resp.ExpiresIn) * time.Second
if ttl <= tokenMinTTL {
@@ -125,7 +233,9 @@ func CreateQRCode(cfg *Config, scene string, expireSeconds int) (string, error)
return "", err
}
if resp.Ticket == "" {
return "", &APIError{API: "创建带参二维码", Code: resp.ErrCode, Msg: resp.ErrMsg}
apiErr := &APIError{API: "创建带参二维码", Code: resp.ErrCode, Msg: resp.ErrMsg}
logAPIError("创建带参二维码", apiErr)
return "", apiErr
}
return "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + url.QueryEscape(resp.Ticket), nil
}