Files
devstar-create-from-template/services/auth/wechat_qr.go
2025-08-25 15:46:12 +08:00

177 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package auth
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"code.gitea.io/gitea/services/context"
wechat_model "code.gitea.io/gitea/models/wechat"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
wechat_service "code.gitea.io/gitea/services/wechat"
)
// Define a Wechat Error type message
type WechatError struct {
message string
}
// Implement the Error() method for the `WechatError` type
func (e *WechatError) Error() string {
return e.message
}
type ResponseData struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data wechat_service.WechatTempQRData `json:"data"`
}
/**
* GetWechatQRTicket 生成微信官方账户临时二维码
*
* @return string 生成的微信二维码的 ticket
* @return string 生成的微信二维码图片URL
* @return error 如果生成二维码过程中出现错误,则返回相应的错误信息
*/
func GetWechatQRTicket(ctx *context.Context) (wechatQrTicket string, QRImageURL string, errorGenerateQr error) {
sceneStr := setting.Domain
qrExpireSeconds := setting.Wechat.TempQrExpireSeconds
// 构建请求的 URL
url := fmt.Sprintf("https://%s/api/wechat/login/qr/generate?qrExpireSeconds=%d&sceneStr=%s", setting.Wechat.DefaultDomainName, qrExpireSeconds, sceneStr)
// 发送 GET 请求
resp, err := http.Get(url)
if err != nil {
return "", "", fmt.Errorf("failed to send GET request: %v", err)
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return "", "", fmt.Errorf("failed to read response body: %v", err)
}
// 检查响应状态码
if resp.StatusCode != http.StatusOK {
return "", "", fmt.Errorf("received non-200 response status: %s", resp.Status)
}
// 解析 JSON 响应
var data ResponseData
if err := json.Unmarshal(bodyBytes, &data); err != nil {
return "", "", fmt.Errorf("failed to unmarshal JSON response: %v", err)
}
quit := make(chan bool) // 用来通知goroutine停止
// 启动一个新的goroutine来进行轮询
go func() {
// 创建一个超时定时器通道
timeout := time.After(time.Duration(qrExpireSeconds) * time.Second)
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
checkWechatQrTicketStatus(ctx, wechatQrTicket, quit)
case <-quit:
log.Info("Stopping polling...")
return
case <-timeout:
log.Info("Polling timed out after", qrExpireSeconds, "seconds.")
quit <- true // 发送停止信号给轮询goroutine
}
}
}()
return data.Data.Ticket, data.Data.QrImageSrcUrl, nil
}
// Response represents the top-level JSON structure
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data wechat_service.WechatTempQRStatus `json:"data"`
}
// 假设这是用于检查二维码状态的函数
func checkWechatQrTicketStatus(ctx *context.Context, qrTicket string, quit chan bool) {
url := fmt.Sprintf("https://%s/api/wechat/login/qr/check-status?ticket=%s&_=%d",
setting.Wechat.DefaultDomainName, qrTicket, time.Now().UnixMilli())
resp, err := http.Get(url)
if err != nil {
log.Error("There was a problem with the fetch operation:", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Error("Network response was not ok")
return
}
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
log.Error("Error reading response body:", err)
return
}
// 解析 JSON 响应
var data Response
if err := json.Unmarshal(bodyBytes, &data); err != nil {
log.Error("failed to unmarshal JSON response: %v", err)
return
}
if data.Code == 0 && data.Data.IsScanned {
log.Info("Caching WeChat QR Scanned Info: %s\n", bodyBytes)
// {"code":0,"msg":"操作成功","data":{
// "is_scanned":true,
// "scene_str":"2d521f80047c42aba27ee9beade35985@p2Z6hfheDxg=",
// "openid":"oQowJ6cD9WSuoxYaCc7mryfn-lVo",
// "is_binded":true}}
// 准备扫码状态VO对象
qrStatus := wechat_service.WechatTempQRStatus{
SceneStr: data.Data.SceneStr,
IsScanned: data.Data.IsScanned,
OpenId: data.Data.OpenId,
}
// 从微信服务器消息推送中解析扫码人的 OpenId
user, err := wechat_model.QueryUserByOpenid(ctx, qrStatus.OpenId)
if user == nil {
// 未找到 OpenId 对应的 DevStar 用户信息,提示前端导向注册页
qrStatus.IsBinded = false
qrStatusString, err := qrStatus.Marshal2JSONString()
if err == nil {
// 将扫码人的微信公众号 OpenId 标记为等待注册等待时间用户注册完成默认24小时
// key: qrScanResponseDigest.Ticket
// value: JSON 字符串
// TTL: setting.Wechat.TempQrExpireSeconds
wechat_service.SetWechatQrTicketWithTTL(
qrTicket,
qrStatusString,
setting.Wechat.RegisterationExpireSeconds)
}
quit <- true
return
}
qrStatus.IsBinded = true
qrVOJsonString, err := qrStatus.Marshal2JSONString()
if err == nil {
wechat_service.SetWechatQrTicketWithTTL(
qrTicket,
qrVOJsonString,
setting.Wechat.TempQrExpireSeconds)
}
quit <- true
return
}
}