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