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
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
|
|
}
|
|
}
|
|
|