Files
wechat_ipad_pro/srv/wxcore/wxconnect.go

1219 lines
38 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 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/srvconfig"
"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
// 从配置获取最大重试次数和重试间隔
maxLongRetries := srvconfig.GlobalSetting.ProxyConfig.MaxLongRetryTimes
minRetryInterval := int64(srvconfig.GlobalSetting.ProxyConfig.LongRetryInterval)
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) {
log.Printf("[%s],[%s] 重置长连接重试计数器(已过去足够长时间)\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
wxconn.GetWXAccount().GetUserInfo().NickName)
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),暂停长连接恢复尝试(将在%d秒后重置\n",
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
wxconn.GetWXAccount().GetUserInfo().NickName,
maxLongRetries,
minRetryInterval*10)
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
// 设置代理配置到MMInfo
tmpMMInfo.LongConnTimeout = srvconfig.GlobalSetting.ProxyConfig.LongConnTimeout
tmpMMInfo.LongConnReadTimeout = srvconfig.GlobalSetting.ProxyConfig.LongConnReadTimeout
tmpMMInfo.LongConnRetryTimes = srvconfig.GlobalSetting.ProxyConfig.LongConnRetryTimes
tmpMMInfo.LongConnRetryInterval = srvconfig.GlobalSetting.ProxyConfig.LongConnRetryInterval
tmpMMInfo.ShortConnTimeout = srvconfig.GlobalSetting.ProxyConfig.ShortConnTimeout
tmpMMInfo.AllowDirectOnProxyFail = srvconfig.GlobalSetting.ProxyConfig.AllowDirectOnProxyFail
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
}