内网服务访问外部第三方 API 的统一网关
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

203 lines
5.8 KiB

package client
import (
"encoding/json"
"errors"
"fmt"
"jhttp"
"jredis"
"log"
"strconv"
"sync"
"third-party-gateway/api/constant"
"time"
)
type MiniProgramAuthReq struct {
Code string `json:"code" binding:"required"`
SceneId string `json:"sceneId"`
}
type MiniProgramLoginResponse struct {
OpenID string `json:"openid"`
SessionKey string `json:"session_key"`
UnionID string `json:"unionid,omitempty"`
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
type MiniProgramTokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
var (
_app_id = ""
_app_secret = ""
)
// InitMiniProgramClient 初始化小程序客户端配置
func InitMiniProgramClient(appid, appsecret string) {
_app_id = appid
_app_secret = appsecret
}
// GetMiniProgramQrCode 获取小程序码
func GetMiniProgramQrCode(envVersion string) ([]byte, int64, error) {
if envVersion == "" {
envVersion = "release"
}
// 生成唯一的场景值
sceneId := generateUniqueID()
// 从缓存获取access_token
key := fmt.Sprintf(constant.MiniProgramAccessToken, _app_id)
accessToken, err := jredis.GetString(key)
if err != nil || accessToken == "" {
// 重新获取access_token
accessToken, err = refreshAccessToken()
if err != nil {
return nil, 0, fmt.Errorf("获取access_token失败: %w", err)
}
}
// 调用微信接口获取小程序码
url := fmt.Sprintf("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s", accessToken)
params := map[string]any{
"scene": sceneId,
"width": 280,
"env_version": envVersion,
}
qrcode, err := jhttp.Post(url, nil, params)
if err != nil {
return nil, 0, err
}
// 尝试解析错误响应
var errResp MiniProgramTokenResponse
if err := json.Unmarshal(qrcode, &errResp); err == nil && errResp.ErrCode != 0 {
return nil, 0, fmt.Errorf("获取小程序码失败:%s", errResp.ErrMsg)
}
// 将sceneId存入Redis,设置过期时间
//err = common.SetValueWithExpired(strconv.FormatInt(sceneId, 10), "waiting", 300) // 5分钟过期
//if err != nil {
// log.ERROR("存储sceneId状态失败", err)
// return nil, 0, err
//}
return qrcode, sceneId, nil
}
var (
mu sync.Mutex
seq int64
lastTs int64
)
// GenerateUniqueID 生成小于14位的唯一ID(最多13位)
func generateUniqueID() int64 {
mu.Lock()
defer mu.Unlock()
// 当前时间戳:秒级(10位)或毫秒级(13位)可调
ts := time.Now().Unix() // 秒级时间戳(10位)
// 如果在同一秒内生成,则递增序列
if ts == lastTs {
seq++
} else {
lastTs = ts
seq = 0
}
// 拼接时间戳和序列(限制在13位内)
idStr := fmt.Sprintf("%d%02d", ts%1e10, seq%100) // 10位时间戳 + 2位序列(最多12位)
id, _ := strconv.ParseInt(idStr, 10, 64)
return id
}
// GetMiniProgramQrCode 获取小程序码
func GetPrintMiniProgramQrCode(scene_Id, envVersion string) ([]byte, error) {
if envVersion == "" {
envVersion = "release"
}
// 生成唯一的场景值
sceneId := fmt.Sprintf("scene_Id:%v", scene_Id)
// 从缓存获取access_token
key := fmt.Sprintf(constant.MiniProgramAccessToken, _app_id)
accessToken, err := jredis.GetString(key)
if err != nil || accessToken == "" {
// 重新获取access_token
accessToken, err = refreshAccessToken()
if err != nil {
return nil, fmt.Errorf("获取access_token失败: %w", err)
}
}
// 调用微信接口获取小程序码
url := fmt.Sprintf("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s", accessToken)
params := map[string]any{
"scene": sceneId,
"page": "pages/index/index",
"width": 280,
"env_version": envVersion,
}
qrcode, err := jhttp.Post(url, nil, params)
if err != nil {
return nil, err
}
// 尝试解析错误响应
var errResp MiniProgramTokenResponse
if err := json.Unmarshal(qrcode, &errResp); err == nil && errResp.ErrCode != 0 {
log.Printf("获取小程序码失败:%s", errResp.ErrMsg)
return nil, fmt.Errorf("获取小程序码失败:%s", errResp.ErrMsg)
}
// 将sceneId存入Redis,设置过期时间
//err = common.SetValueWithExpired(strconv.FormatInt(sceneId, 10), "waiting", 300) // 5分钟过期
//if err != nil {
// log.ERROR("存储sceneId状态失败", err)
// return nil, 0, err
//}
return qrcode, nil
}
// GetMiniProgramOpenId 获取小程序用户openId
func GetMiniProgramOpenId(code string) (string, error) {
if code == "" {
return "", errors.New("code不能为空")
}
// 构建请求URL
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
_app_id, _app_secret, code)
// 发送请求并解析响应
if result, err := jhttp.GetJson[MiniProgramLoginResponse](url, nil); err != nil {
log.Println("获取openId失败", err)
return "", err
} else if result.ErrCode != 0 {
log.Println(fmt.Errorf("获取openId失败:%s", result.ErrMsg))
return "", fmt.Errorf("获取openId失败:%s", result.ErrMsg)
} else {
return result.OpenID, nil
}
}
// refreshAccessToken 刷新access_token
func refreshAccessToken() (string, error) {
// 获取新的access_token
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
_app_id, _app_secret)
// 发送请求并解析响应
if result, err := jhttp.GetJson[MiniProgramTokenResponse](url, nil); err != nil {
return "", err
} else if result.ErrCode != 0 {
return "", fmt.Errorf("获取access_token失败:%s", result.ErrMsg)
} else {
// 将access_token存入Redis,设置过期时间为7000秒(微信默认7200秒)
key := fmt.Sprintf(constant.MiniProgramAccessToken, _app_id)
err := jredis.SetValueWithExpired(key, result.AccessToken, 7000)
if err != nil {
return "", err
}
return result.AccessToken, nil
}
}