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