Files
wechat_ipad_pro/srv/wxcore/wxconnect.go
T

1207 lines
38 KiB
Go
Raw Normal View History

2026-02-17 13:06:23 +08:00
package wxcore
import (
"errors"
"fmt"
"math/rand"
"sync"
"sync/atomic"
"time"
"github.com/lunny/log"
"xiawan/wx/clientsdk"
"xiawan/wx/clientsdk/baseinfo"
"xiawan/wx/clientsdk/mmtls"
"xiawan/wx/db"
"xiawan/wx/srv"
"xiawan/wx/srv/websrv"
"xiawan/wx/srv/wxface"
)
// WXConnect 微信链接
type WXConnect struct {
wxServer wxface.IWXServer
// 微信链接ID
wxConnID uint32
// 微信账号信息
wxAccount *srv.WXAccount
// 发送请求缓存队列
longReqQueue chan wxface.IWXLongRequest
// 请求调用器
wxReqInvoker wxface.IWXReqInvoker
// 缓存器
wxCache wxface.IWXCache
// 任务管理器
wxTaskMgr wxface.IWXTaskMgr
// 同步请求管理器
wxSyncMgr wxface.IWXSyncMgr
// 文件助手消息管理器
wxFileHelperMgr wxface.IWXFileHelperMgr
//用户消息管理器
wxUserMsgMgr wxface.IWXUserMsgMgr
// web任务管理器
webTaskMgr *websrv.WebTaskMgr
// 心跳定时器
heartBeatTimer *time.Timer
// 二次登录定时器
autoAuthTimer *time.Timer
// 断开链接
ExitFlagChan chan bool
// 是否连接着 (原子操作)
connected int32
// 首次登录初始化只执行一次
// onceInit sync.Once
// 出错次数
// errCount int
// 开始的时间戳
StartDateTime int64
// 是否开启长连接监控
IsOpenShortLink bool
// 心跳时间
HeartBeatTime int64
// 扫码时间秒
ScanCodeTime int64
// 上次尝试重连长连接的时间
lastLongRetryTime int64
// 长连接重试计数器
longRetryCount int
// 互斥锁
mu sync.RWMutex
// 重启标志
restarting int32
}
// NewWXConnect 新的微信连接
func NewWXConnect(wxServer wxface.IWXServer, wxAccount *srv.WXAccount) wxface.IWXConnect {
// 从根上下文创建一个上下文
wxconn := &WXConnect{
wxServer: wxServer,
wxAccount: wxAccount,
longReqQueue: make(chan wxface.IWXLongRequest, 5000),
ExitFlagChan: make(chan bool, 1),
connected: 0,
HeartBeatTime: 0, // 默认 0 ,没有开始心跳
StartDateTime: time.Now().Unix(),
IsOpenShortLink: true,
ScanCodeTime: 0,
lastLongRetryTime: 0,
longRetryCount: 0,
restarting: 0,
}
wxconn.wxReqInvoker = NewWXReqInvoker(wxconn)
wxconn.wxTaskMgr = NewWXTaskMgr(wxconn)
wxconn.wxCache = NewWXCache(wxconn)
wxconn.wxSyncMgr = NewWXSyncMgr(wxconn)
//文件助手消息管理器
wxconn.wxFileHelperMgr = NewWXFileHelperMgr(wxconn)
//用户消息管理器
wxconn.wxUserMsgMgr = NewWXUSerMsgMgr(wxconn)
// 上报任务
wxconn.webTaskMgr = websrv.NewWebTaskMgr()
return wxconn
}
// startLongWriter 开启长链接发送数据
func (wxconn *WXConnect) startLongWriter() {
StartDateTime := wxconn.StartDateTime
for {
// 判断链接断开立马暂停
if !wxconn.IsConnected() {
return
}
// 判断是否重复启动 for 循环
if StartDateTime != wxconn.StartDateTime {
// 结束
return
}
select {
case longReq := <-wxconn.longReqQueue:
// 心跳和登录都会走这里
wxconn.ScanCodeTime = time.Now().Unix()
mmInfo := wxconn.wxAccount.GetUserInfo().MMInfo
err := mmtls.MMTCPSendReq(mmInfo, longReq.GetOpcode(), longReq.GetData())
if err != nil {
// 断开链接
fmt.Printf("[%s],[%s] 断开链接 - %s \n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName, err.Error())
// 判断是心跳出错 - 发送二次登录
if longReq.GetOpcode() == mmtls.MMLongOperationHeartBeat {
// 等 200 毫秒
time.Sleep(200 * time.Millisecond)
if StartDateTime == wxconn.StartDateTime {
wxconn.restartLong()
}
} else {
wxconn.Stop() // 断开链接
}
}
continue
case <-wxconn.heartBeatTimer.C:
// 发送心跳包
if wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateOnLine {
_ = wxconn.wxReqInvoker.SendHeartBeatRequest()
}
// 重置定时器,继续下一次心跳
wxconn.heartBeatTimer.Reset(time.Second * 175)
continue
case <-wxconn.autoAuthTimer.C:
// 进行二次登录
if wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateOnLine {
_ = wxconn.wxReqInvoker.SendAutoAuthRequest()
}
continue
case <-wxconn.ExitFlagChan:
return
}
}
}
// 重新链接
func (wxconn *WXConnect) restartLong() {
defer TryE("(wxconn *WXConnect) restartLong()")
// 防止重复重启
if !atomic.CompareAndSwapInt32(&wxconn.restarting, 0, 1) {
log.Printf("[%s] restartLong already in progress, skipping\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName())
return
}
defer atomic.StoreInt32(&wxconn.restarting, 0)
wxconn.mu.Lock()
// 断开链接
wxconn.setConnected(false)
// 安全发送退出信号
select {
case wxconn.ExitFlagChan <- true:
default:
// 通道已满或已关闭,忽略
}
// 关闭长链接
userInfo := wxconn.wxAccount.GetUserInfo()
if userInfo.MMInfo != nil && userInfo.MMInfo.Conn != nil {
userInfo.MMInfo.Conn.Close()
userInfo.MMInfo = nil
}
wxconn.mu.Unlock()
// 异步重启以避免阻塞
go func() {
defer TryE("restartLong async restart")
// 等待 30 秒
time.Sleep(30 * time.Second)
// 检查是否仍需要重启
if wxconn.IsConnected() {
log.Printf("[%s] Connection already restored, skipping restart\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName())
return
}
//重新开始
err := wxconn.startLongLink()
if err != nil {
log.Printf("[%s] restartLong - startLongLink error: %v\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), err)
return
}
fmt.Printf("[%s] 开始长连接状态!startLongLink\n", userInfo.UUID)
wxconn.SendHeartBeatWaitingSeconds(1)
// 等待30-60分钟
minutes := uint32(rand.Int31n(30)) + 30
// minutes := uint32(1)
wxconn.SendAutoAuthWaitingMinutes(minutes)
log.Printf("[%s] Long connection restarted successfully\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName())
}()
}
// 监听长连接失败,实际上没掉线
func (wxconn *WXConnect) StartShortReader() {
StartDateTime := wxconn.StartDateTime
// 延时 20 秒
time.Sleep(20 * time.Second)
// 20 秒之后大概率已经建立了长连接,如果建立长连接,则重新启动
// 判断是否关闭
if !wxconn.IsOpenShortLink {
return
}
forCount := 0
// 最大重试次数
const maxLongRetries = 5
// 最小重试间隔 (秒)
const minRetryInterval = 60
for {
// 判断是否重复启动 for 循环
if StartDateTime != wxconn.StartDateTime {
// 结束
return
}
// 已关闭
if !wxconn.IsOpenShortLink {
return
}
TimeSec := time.Now().Unix()
if TimeSec-wxconn.ScanCodeTime < 10 {
if wxconn.ScanCodeTime != 0 {
// 等待 3 秒
time.Sleep(3 * time.Second)
// 跳过
continue
}
}
// 判断是否在线
if wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateOnLine {
// 每10次短连接心跳前先尝试长连接
if forCount%10 == 0 && !wxconn.IsConnected() {
// 更新重试时间
wxconn.lastLongRetryTime = time.Now().Unix()
// 尝试发送长连接心跳,恢复长连接
log.Printf("[%s],[%s] 定期尝试恢复长连接心跳\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
wxconn.GetWXAccount().GetUserInfo().NickName)
// 使用defer+recover防止恢复过程中的panic影响短连接心跳
func() {
defer func() {
if r := recover(); r != nil {
log.Printf("[%s],[%s] 恢复长连接心跳时发生错误: %v\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName, r)
}
}()
wxconn.restartLong()
}()
// 给长连接一些时间建立
time.Sleep(2 * time.Second)
// 恢复后检查连接状态并记录日志
if wxconn.IsConnected() {
log.Printf("[%s],[%s] 长连接心跳恢复成功\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName)
wxconn.longRetryCount = 0 // 连接成功后重置计数器
// 长连接恢复后,立即触发一次消息同步
wxconn.wxSyncMgr.SendNewSyncRequest()
time.Sleep(500 * time.Millisecond) // 给同步请求一些处理时间
// 如果长连接成功,停止短连接心跳
return
} else {
log.Printf("[%s],[%s] 长连接心跳恢复失败,继续使用短连接同步\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName)
}
}
// 尝试恢复长连接心跳 - 代理可能已经恢复
if !wxconn.IsConnected() {
// 检查是否应该尝试恢复长连接
// 距离上次尝试时间超过最小间隔,并且重试次数在允许范围内
currentTime := time.Now().Unix()
shouldRetry := (currentTime-wxconn.lastLongRetryTime >= minRetryInterval) &&
(wxconn.longRetryCount < maxLongRetries)
// 如果重试计数器已达上限,但已经过去了更长时间(比如10分钟),重置计数器
if wxconn.longRetryCount >= maxLongRetries && (currentTime-wxconn.lastLongRetryTime >= minRetryInterval*10) {
wxconn.longRetryCount = 0
shouldRetry = true
}
if shouldRetry {
// 更新重试时间和计数器
wxconn.lastLongRetryTime = currentTime
wxconn.longRetryCount++
// 尝试发送长连接心跳,恢复长连接
log.Printf("[%s],[%s] 尝试恢复长连接心跳 (尝试 %d/%d)\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
wxconn.GetWXAccount().GetUserInfo().NickName,
wxconn.longRetryCount, maxLongRetries)
// 使用defer+recover防止恢复过程中的panic影响短连接心跳
func() {
defer func() {
if r := recover(); r != nil {
log.Printf("[%s],[%s] 恢复长连接心跳时发生错误: %v\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName, r)
}
}()
wxconn.restartLong()
}()
// 给长连接一些时间建立
time.Sleep(1 * time.Second)
// 恢复后检查连接状态并记录日志
if wxconn.IsConnected() {
log.Printf("[%s],[%s] 长连接心跳恢复成功,重置重试计数器\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName)
wxconn.longRetryCount = 0 // 连接成功后重置计数器
// 长连接恢复后,立即触发一次消息同步
wxconn.wxSyncMgr.SendNewSyncRequest()
time.Sleep(500 * time.Millisecond) // 给同步请求一些处理时间
// 同时触发收藏同步
currentTaskMgr := wxconn.GetWXTaskMgr()
taskMgr, _ := currentTaskMgr.(*WXTaskMgr)
currentSnsTransTask := taskMgr.GetSnsTransTask()
if currentSnsTransTask.IsSyncTrans() {
wxconn.wxSyncMgr.SendFavSyncRequest()
}
// 如果长连接成功,停止短连接心跳
return
} else {
log.Printf("[%s],[%s] 长连接心跳恢复失败,将继续使用短连接同步\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName)
}
} else if wxconn.longRetryCount >= maxLongRetries {
// 只有在第一次达到最大重试次数时记录日志
if wxconn.longRetryCount == maxLongRetries {
log.Printf("[%s],[%s] 已达到最大重试次数(%d),暂停长连接恢复尝试\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
wxconn.GetWXAccount().GetUserInfo().NickName,
maxLongRetries)
wxconn.longRetryCount++ // 增加一次以避免重复打印
}
}
}
// 发送同步消息
if forCount%20 == 0 {
isConn := "true"
if !wxconn.IsConnected() {
isConn = "false"
}
log.Printf("[%s],[%s] 短链接同步消息 StartShortReader: isConnected = %s \n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName, isConn)
// 同步收藏转发
// 判断是否开启收藏转发
if wxconn.IsConnected() {
currentTaskMgr := wxconn.GetWXTaskMgr()
taskMgr, _ := currentTaskMgr.(*WXTaskMgr)
currentSnsTransTask := taskMgr.GetSnsTransTask()
if currentSnsTransTask.IsSyncTrans() {
wxconn.wxSyncMgr.SendFavSyncRequest()
// 延时一秒
time.Sleep(1 * time.Second)
}
}
}
// 即使长连接没有恢复,也继续使用短链接同步消息
wxconn.wxSyncMgr.SendNewSyncRequest()
time.Sleep(3 * time.Second)
forCount++
if forCount >= 100 {
forCount = 0
}
continue
}
forCount++
if forCount >= 100 {
forCount = 0
}
time.Sleep(3 * time.Second)
continue
}
}
// 关闭重启任务
func (wxconn *WXConnect) StopShortReader() {
wxconn.IsOpenShortLink = false
// 保存状态变更到数据库
currentUserInfo := wxconn.GetWXAccount().GetUserInfo()
if currentUserInfo != nil {
db.SaveShortLinkStatusToDB(currentUserInfo.UUID, false)
}
}
// startLongReader 开启长连接接受数据
func (wxconn *WXConnect) startLongReader() {
StartDateTime := wxconn.StartDateTime
// 重试机制相关变量
retryCount := 0
maxRetries := 5
baseDelay := time.Second
for {
// 判断链接断开立马暂停
if !wxconn.IsConnected() {
break
}
// 判断是否重复启动 for 循环
if StartDateTime != wxconn.StartDateTime {
break
}
// 接收数据
mmInfo := wxconn.wxAccount.GetUserInfo().MMInfo
recvInfo, err := mmtls.MMTCPRecvData(mmInfo)
if err != nil {
errorMsg := err.Error()
log.Printf("[%s],[%s] 长连接出错 - %s \n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName, errorMsg)
if !wxconn.IsConnected() {
return
}
// 等待200毫秒
time.Sleep(200 * time.Millisecond)
// 判断是否重复启动 for 循环(StartDateTime 标识不对说明已经被重启了)
if StartDateTime != wxconn.StartDateTime {
// 结束
return
}
wxconn.restartLong()
return
}
// 系统推送-需要同步消息
if recvInfo.HeaderInfo.Operation < 1000000000 {
//fmt.Println(recvInfo.HeaderInfo.Operation)
loginState := wxconn.GetWXAccount().GetLoginState()
if loginState != baseinfo.MMLoginStateOnLine {
continue
}
// 同步收藏
if recvInfo.HeaderInfo.Operation == baseinfo.MMLongOperatorTypeFavSync {
wxconn.wxSyncMgr.SendFavSyncRequest()
continue
}
// TODO 发送同步请求
// syncKey := wxconn.wxAccount.GetUserInfo().SyncKey
// if syncKey == nil || len(syncKey) == 0 {
// wxconn.onceInit.Do(func() {
// wxconn.wxSyncMgr.SendSyncInitRequest()
// })
// } else {
wxconn.wxSyncMgr.SendNewSyncRequest()
// }
continue
}
// 解包响应数据
packHeader, err := clientsdk.DecodePackHeader(recvInfo.RespData, nil)
if err != nil {
// 判断是否重复启动 for 循环
if StartDateTime != wxconn.StartDateTime {
// 结束
return
}
// TODO 接受消息出错,断开链接-token登陆
log.Printf("[%s] startLongReader - DecodePackHeader error: %v, operation: %v\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), err, recvInfo.HeaderInfo.Operation)
/*
* 注意: 这里新号首次登录时(同省IP首次可能会多次掉线, 同市掉线少, 家里的内网穿透 socks5 代理IP基本不会掉线)
* 另外, 24小时内还可能会接收消息出错(导致掉线), 三天后基本稳定
*/
// 网络异常超时或解包失败 - 修复空指针问题
if packHeader == nil {
retryCount++
log.Printf("[%s] packHeader is nil (retry %d/%d), restarting connection\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), retryCount, maxRetries)
if retryCount >= maxRetries {
wxconn.restartLong()
return
}
// 指数退避
delay := baseDelay * time.Duration(1<<uint(retryCount-1))
time.Sleep(delay)
continue
}
// 重置重试计数器(成功解包)
retryCount = 0
// 检查会话超时
switch packHeader.RetCode {
case baseinfo.MMErrSessionTimeOut:
log.Printf("[%s] Session timeout, sending auto auth request\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName())
wxconn.HeartBeatTime = 0
wxconn.setConnected(false)
_ = wxconn.GetWXReqInvoker().SendAutoAuthRequest()
case baseinfo.MMErrDropped:
log.Printf("[%s] 账号被强制下线(MMErrDropped), 停止连接\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName())
wxconn.Stop()
return
default:
if !wxconn.IsConnected() {
return
}
log.Printf("[%s] Decode error with retcode: %d, restarting connection\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), packHeader.RetCode)
wxconn.restartLong()
return
}
return
}
wxResp := NewWXResponse(wxconn, packHeader)
wxconn.SendToWXMsgHandler(wxResp)
}
}
// startLong 开启长链接
func (wxconn *WXConnect) startLongLink() error {
defer TryE("(wxconn *WXConnect) startLongLink()")
wxconn.StartDateTime = int64(time.Now().Unix())
// 清理旧的定时器,防止资源泄漏
if wxconn.heartBeatTimer != nil {
wxconn.heartBeatTimer.Stop()
}
if wxconn.autoAuthTimer != nil {
wxconn.autoAuthTimer.Stop()
}
// 清空 ExitFlagChan 中可能残留的信号
select {
case <-wxconn.ExitFlagChan:
// 清空旧的退出信号
default:
// 通道为空,继续
}
// 在这里添加短连接状态的恢复
wxconn.IsOpenShortLink = true
// 从数据库恢复短连接状态
uuid := wxconn.wxAccount.GetUserInfo().UUID
shortLinkStatus := db.LoadShortLinkStatusFromDB(uuid)
if shortLinkStatus != nil {
wxconn.IsOpenShortLink = shortLinkStatus.IsEnabled
}
go wxconn.StartShortReader()
// 先进行长链接握手
userInfo := wxconn.wxAccount.GetUserInfo()
dialer := userInfo.GetDialer()
tmpMMInfo, err := mmtls.InitMMTLSInfoLong(dialer, userInfo.LongHost, userInfo.ShortHost, nil)
if err != nil {
return err
}
tmpMMInfo.Dialer = dialer
wxconn.setConnected(true)
userInfo.MMInfo = tmpMMInfo
// 启动长链接发送接收协程(创建新的定时器)
wxconn.heartBeatTimer = time.NewTimer(time.Second * 175)
wxconn.autoAuthTimer = time.NewTimer(time.Minute * 30)
// 收藏转发任务
wxconn.wxSyncMgr.Start()
go wxconn.startLongWriter()
go wxconn.startLongReader()
return nil
}
// Start 开启微信链接任务
func (wxconn *WXConnect) Start() error {
wxconn.mu.Lock()
// 如果是链接状态
if wxconn.IsConnected() {
wxconn.mu.Unlock()
return nil
}
// wxconn.errCount = 0
userInfo := wxconn.wxAccount.GetUserInfo()
// 判断微信信息是否为空
if userInfo == nil {
wxconn.mu.Unlock()
return errors.New("wxconn.Start() err: userInfo == nil")
}
isLock := true
defer func() {
if isLock {
isLock = false
wxconn.mu.Unlock()
}
}()
go func() {
// 等待 30 秒
time.Sleep(30 * time.Second)
// 关闭
if isLock {
isLock = false
log.Printf("Start - [%s],[%s] 长连接超时 30s - \n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName)
wxconn.mu.Unlock()
}
}()
// 开启长链接
err := wxconn.startLongLink()
if err != nil {
return err
}
// 开启任务管理器
// wxconn.wxTaskMgr.Start()
wxconn.webTaskMgr.Start()
wxconn.wxFileHelperMgr.Start()
wxconn.wxUserMsgMgr.Start()
wxconn.wxServer.GetWXConnectMgr().Add(wxconn)
// 初始化置顶群缓存
currentTaskMgr := wxconn.wxTaskMgr
uuid := userInfo.UUID
taskMgr, _ := currentTaskMgr.(*WXTaskMgr)
currentGrapHBMgr := taskMgr.GetGrabHBTask()
currentGrapHBMgr.InitTopGroup(uuid) // 初始化群置顶
// 初始化同步收藏朋友圈
currentSnsTransTask := taskMgr.GetSnsTransTask()
currentSnsTransTask.InitFriendTransMap()
fmt.Printf("[%s] 开始长连接状态!\n", userInfo.UUID)
// 设置下二次登录时间12
//userInfo.UpdateLastAuthTime()
wxconn.SendWebTask(websrv.TaskStateLoginSuccess, websrv.TaskTypeUploadStatus)
return nil
}
// Stop 停止链接
func (wxconn *WXConnect) Stop() {
wxconn.mu.Lock()
defer wxconn.mu.Unlock()
// 如果已经停止,则不需要再次停止
if !wxconn.IsConnected() {
return
}
// 在停止连接前,确保当前状态被持久化保存
currentUserInfo := wxconn.GetWXAccount().GetUserInfo()
if currentUserInfo != nil {
// 获取最后的状态缓存
statusCache := db.GetCheckStatusCache(currentUserInfo.UUID)
if statusCache != nil {
// 确保状态被持久化保存
db.SaveLastLoginStatus(currentUserInfo.UUID, statusCache)
}
// 保存短链接状态到数据库
db.SaveShortLinkStatusToDB(currentUserInfo.UUID, wxconn.IsOpenShortLink)
// 清理WebSocket连接
if taskMgr, ok := wxconn.wxTaskMgr.(*WXTaskMgr); ok {
wsTask := taskMgr.SocketMsgTask
userUUID := currentUserInfo.UUID
existingConn := wsTask.GetWebSocket(userUUID)
if existingConn != nil {
fmt.Println("Stop()时清理WebSocket连接:", userUUID)
existingConn.Close()
wsTask.DeleteWebSocket(userUUID)
}
// 禁用WebSocket功能
wsTask.SetWebSocketEnabled(false)
}
}
// 断开链接
wxconn.setConnected(false)
// 安全发送退出信号
select {
case wxconn.ExitFlagChan <- true:
default:
// 通道已满,忽略
}
// 停止定时器
if wxconn.heartBeatTimer != nil {
wxconn.heartBeatTimer.Stop()
}
if wxconn.autoAuthTimer != nil {
wxconn.autoAuthTimer.Stop()
}
// 关闭短链接同步消息
go wxconn.StopShortReader()
// 重置长连接开始时间
wxconn.StartDateTime = int64(time.Now().Unix())
fmt.Println("Stop() 关闭长链接: ", wxconn.StartDateTime)
// 关闭长链接
userInfo := wxconn.wxAccount.GetUserInfo()
if userInfo.MMInfo != nil && userInfo.MMInfo.Conn != nil {
userInfo.MMInfo.Conn.Close()
}
go func() {
// 设置成离线状态
if wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateOnLine {
wxconn.wxAccount.SetLoginState(baseinfo.MMLoginStateOffLine)
db.UpdateUserInfo(userInfo)
db.UpdateLoginStatus(userInfo.GetUserName(), int32(userInfo.GetLoginState()), "已离线")
}
}()
wxconn.HeartBeatTime = 0
wxconn.SendWebTask(websrv.TaskStateLogout, websrv.TaskTypeUploadStatus)
fmt.Printf("[%s],[%s],[%s] 退出!\n", userInfo.GetUserName(), userInfo.NickName, userInfo.UUID)
// 延迟关闭其他资源,确保状态保存完成
time.Sleep(time.Millisecond * 200)
go func() {
// 关闭任务管理器
wxconn.wxSyncMgr.Stop()
wxconn.wxFileHelperMgr.Stop()
wxconn.wxUserMsgMgr.Stop()
// 延迟从管理器中移除连接实例,给前端足够时间获取最后的状态
time.Sleep(time.Second * 5)
wxconn.wxServer.GetWXConnectMgr().Remove(wxconn)
}()
}
// SetWXConnID 设置微信链接ID
func (wxconn *WXConnect) SetWXConnID(wxConnID uint32) {
wxconn.wxConnID = wxConnID
}
// GetWXConnID 获取WX链接ID
func (wxconn *WXConnect) GetWXConnID() uint32 {
return wxconn.wxConnID
}
// GetWXAccount 获取微信帐号信息
func (wxconn *WXConnect) GetWXAccount() *srv.WXAccount {
return wxconn.wxAccount
}
// GetWXServer 获取微信服务器
func (wxconn *WXConnect) GetWXServer() wxface.IWXServer {
return wxconn.wxServer
}
// GetWXCache 获取缓存器
func (wxconn *WXConnect) GetWXCache() wxface.IWXCache {
return wxconn.wxCache
}
// GetWXReqInvoker 获取调用器
func (wxconn *WXConnect) GetWXReqInvoker() wxface.IWXReqInvoker {
return wxconn.wxReqInvoker
}
// GetWXTaskMgr 获取任务管理器
func (wxconn *WXConnect) GetWXTaskMgr() wxface.IWXTaskMgr {
return wxconn.wxTaskMgr
}
// GetWXSyncMgr 获取同步管理器
func (wxconn *WXConnect) GetWXSyncMgr() wxface.IWXSyncMgr {
return wxconn.wxSyncMgr
}
// GetWXFileHelperMgr 获取文件传输助手管理器
func (wxconn *WXConnect) GetWXFileHelperMgr() wxface.IWXFileHelperMgr {
return wxconn.wxFileHelperMgr
}
// GetWXFriendMsgMgr 好友消息管理器
func (wxconn *WXConnect) GetWXFriendMsgMgr() wxface.IWXUserMsgMgr {
return wxconn.wxUserMsgMgr
}
// 获取 HeartBeatTime
func (wxconn *WXConnect) GetHeartBeatTime() int64 {
return wxconn.HeartBeatTime
}
// 设置 HeartBeatTime
func (wxconn *WXConnect) SetHeartBeatTime(heartBeatTime int64) {
wxconn.HeartBeatTime = heartBeatTime
}
// IsConnected 判断是否长链接状态
func (wxconn *WXConnect) IsConnected() bool {
return atomic.LoadInt32(&wxconn.connected) == 1
}
// setConnected 安全地设置连接状态
func (wxconn *WXConnect) setConnected(connected bool) {
if connected {
atomic.StoreInt32(&wxconn.connected, 1)
} else {
atomic.StoreInt32(&wxconn.connected, 0)
}
}
// isRestarting 检查是否正在重启
func (wxconn *WXConnect) isRestarting() bool {
return atomic.LoadInt32(&wxconn.restarting) == 1
}
// setRestarting 设置重启状态
func (wxconn *WXConnect) setRestarting(restarting bool) {
if restarting {
atomic.StoreInt32(&wxconn.restarting, 1)
} else {
atomic.StoreInt32(&wxconn.restarting, 0)
}
}
// SendToWXMsgHandler 发送给消息队列去处理
func (wxconn *WXConnect) SendToWXMsgHandler(wxResp wxface.IWXResponse) {
wxconn.wxServer.GetWXMsgHandler().SendWXRespToTaskQueue(wxResp)
}
// SendToWXLongReqQueue 添加到长链接请求队列
func (wxconn *WXConnect) SendToWXLongReqQueue(wxLongReq wxface.IWXLongRequest) {
wxconn.longReqQueue <- wxLongReq
}
// SendHeartBeatWaitingSeconds 添加到微信心跳包队列
func (wxconn *WXConnect) SendHeartBeatWaitingSeconds(seconds uint32) {
wxconn.heartBeatTimer.Reset(time.Second * time.Duration(seconds))
}
// SendAutoAuthWaitingMinutes 添加到微信二次包包队列
func (wxconn *WXConnect) SendAutoAuthWaitingMinutes(minutes uint32) {
wxconn.autoAuthTimer.Reset(time.Minute * time.Duration(minutes))
}
// SendWebTask 发送web任务
func (wxconn *WXConnect) SendWebTask(status string, taskType uint32) {
currentAccount := wxconn.wxAccount
isServerRestart := currentAccount.GetUserInfo().GetIsServerRestart()
if isServerRestart {
return
}
connectInfo := wxconn.GetWXServer().GetWXConnectMgr().GetConnectInfo()
wxconn.webTaskMgr.AddWebTask(currentAccount.GetUserInfo(), currentAccount.GetTaskInfo(), status, taskType, connectInfo)
}
// AddGroupQrcodeData 添加群二维码
func (wxconn *WXConnect) AddGroupQrcodeData(fileData []byte, fileName string) {
wxconn.wxServer.GetWXFileMgr().AddGroupQrcodeData(fileData, fileName)
}
// DumpGroupQrcode 保存群二维码
func (wxconn *WXConnect) DumpGroupQrcode() {
taskMgr, _ := wxconn.wxTaskMgr.(*WXTaskMgr)
taskMgr.GetGroupTask().StartDownQrcode(10)
}
// CheckOnLineStatus 检查在线状态(获取二维码|唤醒)
func (wxconn *WXConnect) CheckOnLineStatusLogin() bool {
defer func() {
if r := recover(); r != nil {
fmt.Printf("CheckOnLineStatus recovered from panic: %v\n", r)
// 可以在这里记录日志或执行其他紧急恢复处理措施
}
}()
state := wxconn.GetWXAccount().GetLoginState()
uuid := wxconn.GetWXAccount().GetUserInfo().UUID
if state == baseinfo.MMLoginStateNoLogin || state == baseinfo.MMLoginStateLogout {
return false
} else if state == baseinfo.MMLoginStateOnLine && wxconn.IsConnected() {
// 已经是长连接状态,无需发送短连接心跳包
return true
} else if state == baseinfo.MMLoginStateOffLine || //离线状态
(state == baseinfo.MMLoginStateOnLine && !wxconn.IsConnected()) { //数据库状态为在线但是没有连接服务器
// 先尝试恢复长连接
log.Printf("[%s],[%s] 尝试恢复长连接\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
wxconn.GetWXAccount().GetUserInfo().NickName)
// 尝试启动长连接
if !wxconn.IsConnected() {
err := wxconn.Start()
if err == nil {
// 长连接成功建立
wxconn.SendHeartBeatWaitingSeconds(1)
// 取一个 30-60 之间的随机数, 错开发送二次登录包
minutes := uint32(rand.Int31n(30)) + 30
wxconn.SendAutoAuthWaitingMinutes(minutes)
// 等待长连接稳定
time.Sleep(2 * time.Second)
if wxconn.IsConnected() {
log.Printf("[%s],[%s] 长连接恢复成功\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
wxconn.GetWXAccount().GetUserInfo().NickName)
if state == baseinfo.MMLoginStateOffLine {
wxconn.GetWXAccount().SetLoginState(baseinfo.MMLoginStateOnLine)
db.UpdateLoginStatus(wxconn.GetWXAccount().GetUserInfo().GetUserName(), int32(wxconn.GetWXAccount().GetLoginState()), "重新上线成功!")
}
go func() {
// 判断重启服务器不执行下面代码
fmt.Println(wxconn.GetWXAccount().GetUserInfo().UUID, "【"+wxconn.GetWXAccount().GetUserInfo().NickName+"】", "重新上线成功!")
// 获取账号的wxProfile
_ = wxconn.GetWXReqInvoker().SendGetProfileRequest()
// 判断是否初始化完成
initContact := db.QueryInitContactStatus(uuid)
if initContact == 0 {
_ = wxconn.GetWXReqInvoker().SendInitContactRequest(0)
}
wxconn.GetWXCache().Clear()
wxconn.GetWXCache().SetInitContactFinished(true)
wxconn.GetWXCache().SetInitFavSyncFinished(true)
}()
return true
}
// 长连接似乎启动后断开了,继续走短连接
log.Println(wxconn.GetWXAccount().GetUserInfo().UUID, "启动长链接后连接丢失!")
} else {
log.Println(wxconn.GetWXAccount().GetUserInfo().UUID, "启动长链接失败!", err.Error())
}
}
// 长连接恢复失败,尝试短连接心跳
log.Printf("[%s],[%s] 尝试使用短连接心跳\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
wxconn.GetWXAccount().GetUserInfo().NickName)
// 先发送心跳 如果心跳发送成功则等待10分钟后再调用二次 ,心跳发送失败直接调用二次确认在线状态
_, _, err := wxconn.GetWXReqInvoker().SendHeartBeatShortRequest()
if err != nil {
wxconn.HeartBeatTime = 0
err := wxconn.GetWXReqInvoker().SendAutoAuthRequest()
if err != nil ||
wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateNoLogin ||
wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateLogout {
return false
}
}
if state == baseinfo.MMLoginStateOffLine {
wxconn.GetWXAccount().SetLoginState(baseinfo.MMLoginStateOnLine)
db.UpdateLoginStatus(wxconn.GetWXAccount().GetUserInfo().GetUserName(), int32(wxconn.GetWXAccount().GetLoginState()), "重新上线成功!")
}
// 如果此时已经建立了长连接,则重启长连接
if wxconn.IsConnected() {
wxconn.restartLong()
}
go func() {
// 判断重启服务器不执行下面代码
fmt.Println(wxconn.GetWXAccount().GetUserInfo().UUID, "【"+wxconn.GetWXAccount().GetUserInfo().NickName+"】", "重新上线成功!")
// 获取账号的wxProfile
_ = wxconn.GetWXReqInvoker().SendGetProfileRequest()
// 判断是否初始化完成
initContact := db.QueryInitContactStatus(uuid)
if initContact == 0 {
_ = wxconn.GetWXReqInvoker().SendInitContactRequest(0)
}
wxconn.GetWXCache().Clear()
wxconn.GetWXCache().SetInitContactFinished(true)
wxconn.GetWXCache().SetInitFavSyncFinished(true)
}()
return true
} else {
//真正掉线
return false
}
}
// CheckOnLineStatus 检查在线状态
func (wxconn *WXConnect) CheckOnLineStatus() bool {
state := wxconn.GetWXAccount().GetLoginState()
uuid := wxconn.GetWXAccount().GetUserInfo().UUID
if state == baseinfo.MMLoginStateNoLogin || state == baseinfo.MMLoginStateLogout {
return false
} else if state == baseinfo.MMLoginStateOnLine && wxconn.IsConnected() {
return true
} else if state == baseinfo.MMLoginStateOffLine || //离线状态
(state == baseinfo.MMLoginStateOnLine && !wxconn.IsConnected()) { //数据库状态为在线但是没有连接服务器
// 先尝试恢复长连接
log.Printf("[%s],[%s] 尝试恢复长连接\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
wxconn.GetWXAccount().GetUserInfo().NickName)
// 尝试启动长连接
if !wxconn.IsConnected() {
err := wxconn.Start()
if err == nil {
// 长连接成功建立
wxconn.SendHeartBeatWaitingSeconds(1)
// 取一个 30-60 之间的随机数, 错开发送二次登录包
minutes := uint32(rand.Int31n(30)) + 30
wxconn.SendAutoAuthWaitingMinutes(minutes)
// 等待长连接稳定
time.Sleep(2 * time.Second)
if wxconn.IsConnected() {
log.Printf("[%s],[%s] 长连接恢复成功\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
wxconn.GetWXAccount().GetUserInfo().NickName)
if state == baseinfo.MMLoginStateOffLine {
wxconn.GetWXAccount().SetLoginState(baseinfo.MMLoginStateOnLine)
db.UpdateLoginStatus(wxconn.GetWXAccount().GetUserInfo().GetUserName(), int32(wxconn.GetWXAccount().GetLoginState()), "重新上线成功!")
}
go func() {
// 获取账号的wxProfile
_ = wxconn.GetWXReqInvoker().SendGetProfileRequest()
wxconn.GetWXCache().Clear()
// 判断是否初始化完成
initContact := db.QueryInitContactStatus(uuid)
if initContact == 0 {
_ = wxconn.GetWXReqInvoker().SendInitContactRequest(0)
}
wxconn.GetWXCache().SetInitContactFinished(true)
wxconn.GetWXCache().SetInitFavSyncFinished(true)
}()
return true
}
// 长连接似乎启动后断开了,继续走短连接
log.Println(wxconn.GetWXAccount().GetUserInfo().UUID, "启动长链接后连接丢失!")
} else {
log.Println(wxconn.GetWXAccount().GetUserInfo().UUID, "启动长链接失败!", err.Error())
}
}
// 长连接恢复失败,尝试短连接心跳
log.Printf("[%s],[%s] 尝试使用短连接心跳\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
wxconn.GetWXAccount().GetUserInfo().NickName)
// 先发送心跳 如果心跳发送成功则等待10分钟后再调用二次 ,心跳发送失败直接调用二次确认在线状态
_, _, err := wxconn.GetWXReqInvoker().SendHeartBeatShortRequest()
if err != nil {
wxconn.HeartBeatTime = 0
err := wxconn.GetWXReqInvoker().SendAutoAuthRequest()
if err != nil ||
wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateNoLogin ||
wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateLogout {
return false
}
}
if state == baseinfo.MMLoginStateOffLine {
wxconn.GetWXAccount().SetLoginState(baseinfo.MMLoginStateOnLine)
db.UpdateLoginStatus(wxconn.GetWXAccount().GetUserInfo().GetUserName(), int32(wxconn.GetWXAccount().GetLoginState()), "重新上线成功!")
}
// 如果此时已经建立了长连接,则重启长连接
if wxconn.IsConnected() {
wxconn.restartLong()
} else {
// 最后尝试一次启动长连接
err := wxconn.Start()
if err == nil {
wxconn.SendHeartBeatWaitingSeconds(1)
minutes := uint32(rand.Int31n(30)) + 30
wxconn.SendAutoAuthWaitingMinutes(minutes)
}
}
go func() {
// 获取账号的wxProfile
_ = wxconn.GetWXReqInvoker().SendGetProfileRequest()
wxconn.GetWXCache().Clear()
// 判断是否初始化完成
initContact := db.QueryInitContactStatus(uuid)
if initContact == 0 {
_ = wxconn.GetWXReqInvoker().SendInitContactRequest(0)
}
wxconn.GetWXCache().SetInitContactFinished(true)
wxconn.GetWXCache().SetInitFavSyncFinished(true)
}()
return true
} else {
//真正掉线
return false
}
}
// 重新验证是否在线
func (wxconn *WXConnect) ReCheckOnLineStatus() bool {
// 先尝试恢复长连接
log.Printf("ReCheckOnLineStatus - [%s],[%s] 尝试恢复长连接 - \n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName)
// 如果已经是连接状态,先重启一下
if wxconn.IsConnected() {
wxconn.restartLong()
time.Sleep(2 * time.Second)
if wxconn.IsConnected() {
log.Printf("ReCheckOnLineStatus - [%s],[%s] 长连接重启成功 - \n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName)
if wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateOffLine {
wxconn.GetWXAccount().SetLoginState(baseinfo.MMLoginStateOnLine)
db.UpdateLoginStatus(wxconn.GetWXAccount().GetUserInfo().GetUserName(), int32(wxconn.GetWXAccount().GetLoginState()), "重新上线成功!")
}
return true
}
}
// 如果不是连接状态,尝试启动长连接
if !wxconn.IsConnected() {
err := wxconn.Start()
if err == nil {
time.Sleep(2 * time.Second)
if wxconn.IsConnected() {
log.Printf("ReCheckOnLineStatus - [%s],[%s] 长连接启动成功 - \n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName)
if wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateOffLine {
wxconn.GetWXAccount().SetLoginState(baseinfo.MMLoginStateOnLine)
db.UpdateLoginStatus(wxconn.GetWXAccount().GetUserInfo().GetUserName(), int32(wxconn.GetWXAccount().GetLoginState()), "重新上线成功!")
}
return true
}
}
}
// 长连接恢复失败,尝试短连接心跳
log.Printf("ReCheckOnLineStatus - [%s],[%s] 长连接恢复失败,尝试短连接心跳 - \n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName)
// 先发送心跳 如果心跳发送成功则等待10分钟后再调用二次 ,心跳发送失败直接调用二次确认在线状态
_, _, err := wxconn.GetWXReqInvoker().SendHeartBeatShortRequest()
log.Printf("ReCheckOnLineStatus - [%s],[%s] 发送心跳 - \n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName)
if err != nil {
wxconn.Stop()
wxconn.HeartBeatTime = 0
log.Printf("ReCheckOnLineStatus - [%s],[%s] 开始二次登录 - \n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(), wxconn.GetWXAccount().GetUserInfo().NickName)
err := wxconn.GetWXReqInvoker().SendAutoAuthRequest()
if err != nil ||
wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateNoLogin ||
wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateLogout {
wxconn.Stop()
return false
}
}
if wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateOffLine ||
(wxconn.wxAccount.GetLoginState() == baseinfo.MMLoginStateOnLine) {
wxconn.GetWXAccount().SetLoginState(baseinfo.MMLoginStateOnLine)
db.UpdateLoginStatus(wxconn.GetWXAccount().GetUserInfo().GetUserName(), int32(wxconn.GetWXAccount().GetLoginState()), "重新上线成功!")
}
// 最后尝试一次恢复长连接
wxconn.restartLong()
return true
}