升级至8069版本:版本号更新/代理配置系统/红包计时埋点/长连接重构/回调修复
This commit is contained in:
35
Dockerfile
35
Dockerfile
@@ -1,26 +1,3 @@
|
|||||||
FROM golang:1.21-alpine AS builder
|
|
||||||
|
|
||||||
# 安装基本依赖
|
|
||||||
RUN apk add --no-cache gcc musl-dev git
|
|
||||||
|
|
||||||
# 设置工作目录
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# 设置GOPROXY以绕过GitHub认证问题
|
|
||||||
ENV GOPROXY=https://goproxy.cn,direct
|
|
||||||
ENV GO111MODULE=on
|
|
||||||
|
|
||||||
# 复制go.mod和go.sum文件
|
|
||||||
COPY go.mod go.sum ./
|
|
||||||
RUN go mod download || true
|
|
||||||
|
|
||||||
# 复制所有源代码
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# 编译应用
|
|
||||||
RUN CGO_ENABLED=1 GOOS=linux go build -o wxserver .
|
|
||||||
|
|
||||||
# 第二阶段:运行阶段
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
# 安装必要的运行时依赖
|
# 安装必要的运行时依赖
|
||||||
@@ -33,16 +10,16 @@ RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
|
|||||||
# 创建必要的目录
|
# 创建必要的目录
|
||||||
RUN mkdir -p /app/assets /app/static/templates
|
RUN mkdir -p /app/assets /app/static/templates
|
||||||
|
|
||||||
# 从builder阶段复制编译好的二进制文件和必要的目录
|
|
||||||
COPY --from=builder /app/wxserver /app/
|
|
||||||
COPY --from=builder /app/assets/ /app/assets/
|
|
||||||
COPY --from=builder /app/static/ /app/static/
|
|
||||||
|
|
||||||
# 设置工作目录
|
# 设置工作目录
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 复制已编译好的二进制文件和必要的目录
|
||||||
|
COPY wxserver /app/wxserver
|
||||||
|
COPY assets/ /app/assets/
|
||||||
|
COPY static/ /app/static/
|
||||||
|
|
||||||
# 暴露HTTP服务端口
|
# 暴露HTTP服务端口
|
||||||
EXPOSE 5253
|
EXPOSE 5253
|
||||||
|
|
||||||
# 运行应用
|
# 运行应用
|
||||||
CMD ["./wxserver"]
|
CMD ["./wxserver"]
|
||||||
|
|||||||
387
PROJECT_ANALYSIS.md
Normal file
387
PROJECT_ANALYSIS.md
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
# 📋 项目结构分析报告
|
||||||
|
|
||||||
|
> **项目名称**: wechat_ipad_pro_1224
|
||||||
|
> **模块名称**: `xiawan/wx`
|
||||||
|
> **技术栈**: Go 1.21 + Gin + MySQL + Redis + Protobuf + Docker
|
||||||
|
> **分析日期**: 2026-02-17
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 项目概述
|
||||||
|
|
||||||
|
本项目是一个 **微信 iPad Pro 协议服务端**,通过逆向微信 iPad 协议实现微信功能的 HTTP API 化。服务端基于 Go 语言开发,使用 Gin 框架暴露 RESTful API,支持多账号并发登录管理,提供完整的微信消息、联系人、群管理、朋友圈、支付等功能。
|
||||||
|
|
||||||
|
### 核心特性
|
||||||
|
|
||||||
|
- 🔐 MMTLS 加密通信(微信自定义 TLS 协议)
|
||||||
|
- 📱 iPad Pro 设备模拟(支持 Mac/Car 转 iPad 模式)
|
||||||
|
- 🔄 多账号并发管理(100 并发限制)
|
||||||
|
- 📡 WebSocket + HTTP 轮询双模式消息同步
|
||||||
|
- 🐳 Docker 容器化部署
|
||||||
|
- 📖 Swagger API 文档
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 项目目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
wechat_ipad_pro_1224/
|
||||||
|
├── main.go # 程序入口(初始化配置 → 数据库 → API 服务)
|
||||||
|
├── go.mod # Go 模块定义 (xiawan/wx, go 1.21)
|
||||||
|
├── go.sum # 依赖校验
|
||||||
|
├── Dockerfile # 多阶段 Docker 构建(golang:1.21-alpine)
|
||||||
|
├── docker-compose.yaml # 编排:wxserver + redis + mysql
|
||||||
|
│
|
||||||
|
├── api/ # 🌐 HTTP 接口层(Gin 框架)
|
||||||
|
│ ├── apiSetup.go # API 启动入口 + 自动重连逻辑
|
||||||
|
│ ├── controller/ # 控制器层(19 个文件)
|
||||||
|
│ ├── service/ # API 业务服务层(23 个文件)
|
||||||
|
│ ├── router/ # 路由注册(router.go)
|
||||||
|
│ ├── req/ # 请求模型(9 个文件)
|
||||||
|
│ ├── vo/ # 响应视图对象
|
||||||
|
│ ├── middleware/ # 中间件(CORS 等)
|
||||||
|
│ ├── swagger/ # Swagger 文档配置
|
||||||
|
│ └── utils/ # API 工具函数
|
||||||
|
│
|
||||||
|
├── srv/ # ⚙️ 核心业务逻辑层
|
||||||
|
│ ├── wxcore/ # 微信核心实现(14 个文件)
|
||||||
|
│ ├── wxface/ # 接口抽象层(14 个接口定义)
|
||||||
|
│ ├── wxrouter/ # 协议路由分发(25 个文件)
|
||||||
|
│ ├── wxtask/ # 后台异步任务(9 个文件)
|
||||||
|
│ ├── websrv/ # WebSocket 服务
|
||||||
|
│ ├── srvconfig/ # 全局配置管理
|
||||||
|
│ ├── defines/ # 常量定义
|
||||||
|
│ ├── wxaccount.go # 账号管理
|
||||||
|
│ └── wxfilemgr.go # 文件管理
|
||||||
|
│
|
||||||
|
├── clientsdk/ # 📦 微信协议 SDK
|
||||||
|
│ ├── request.go # 核心请求封装(5148 行,173KB)
|
||||||
|
│ ├── baserequest.go # 基础请求构建
|
||||||
|
│ ├── protocol.go # 协议打包/解包
|
||||||
|
│ ├── cdnprotocol.go # CDN 协议(图片/视频上传下载)
|
||||||
|
│ ├── cdnrequest.go # CDN 请求
|
||||||
|
│ ├── hybrid_request.go # 混合请求
|
||||||
|
│ ├── oplogitem.go # 操作日志项
|
||||||
|
│ ├── demo.go # 示例代码
|
||||||
|
│ ├── mmtls/ # MMTLS 加密通信(11 个文件)
|
||||||
|
│ ├── cecdh/ # ECDH 密钥交换(7 个文件)
|
||||||
|
│ ├── baseinfo/ # 基础数据结构(14 个文件)
|
||||||
|
│ ├── baseutils/ # 基础工具函数(8 个文件)
|
||||||
|
│ ├── android/ # Android 设备信息(23 个文件)
|
||||||
|
│ ├── extinfo/ # 扩展信息
|
||||||
|
│ ├── ccdata/ # CC 数据
|
||||||
|
│ ├── hybrid/ # 混合模式
|
||||||
|
│ ├── proxynet/ # 代理网络
|
||||||
|
│ └── xmltool/ # XML 工具
|
||||||
|
│
|
||||||
|
├── db/ # 💾 数据库层
|
||||||
|
│ ├── mysql_db.go # MySQL 操作(48KB,核心 CRUD)
|
||||||
|
│ ├── redisOperation.go # Redis 操作(18KB)
|
||||||
|
│ ├── cache.go # 缓存管理
|
||||||
|
│ ├── proxy.go # 代理管理
|
||||||
|
│ ├── message_callback.go # 消息回调
|
||||||
|
│ ├── callback_wrapper.go # 回调包装器
|
||||||
|
│ └── table/ # 数据表模型定义
|
||||||
|
│ ├── mysql_table.go # MySQL 表结构(ORM 模型)
|
||||||
|
│ ├── redis_table.go # Redis 数据结构
|
||||||
|
│ ├── proxy_mapping.go # 代理映射
|
||||||
|
│ ├── messageCallback.go
|
||||||
|
│ └── base.go
|
||||||
|
│
|
||||||
|
├── protobuf/ # 📝 Protobuf 协议定义
|
||||||
|
│ ├── wechat/ # 微信 Protobuf 消息(27 个文件)
|
||||||
|
│ ├── wechat_proto/ # 原始 Proto 定义(28 个文件)
|
||||||
|
│ └── proto_ref/ # 协议参考
|
||||||
|
│
|
||||||
|
├── lib/ # 🔧 工具函数库(11 个文件)
|
||||||
|
│ ├── BytesToInt.go # 字节转整数
|
||||||
|
│ ├── IntToBytes.go # 整数转字节
|
||||||
|
│ ├── MD5ToLower.go # MD5 小写
|
||||||
|
│ ├── GetFileMD5Hash.go # 文件 MD5
|
||||||
|
│ ├── Hex2Int.go # 十六进制转换
|
||||||
|
│ ├── CreateDeviceId.go # 设备 ID 生成
|
||||||
|
│ ├── Get62Data.go # 62 数据获取
|
||||||
|
│ ├── Get62Key.go # 62 密钥获取
|
||||||
|
│ ├── GetClientSeqId.go # 客户端序列号
|
||||||
|
│ ├── RandString.go # 随机字符串
|
||||||
|
│ └── ALLGather.go # 聚合工具
|
||||||
|
│
|
||||||
|
├── assets/ # 📁 配置和资源文件
|
||||||
|
│ ├── setting.json # 主配置文件
|
||||||
|
│ ├── setting.json.docker # Docker 环境配置
|
||||||
|
│ ├── ca-cert # CA 证书
|
||||||
|
│ └── sae.dat # SAE 数据文件
|
||||||
|
│
|
||||||
|
├── apns/ # 🔔 Apple 推送通知
|
||||||
|
│ ├── Apns.go
|
||||||
|
│ └── deviceToken.go
|
||||||
|
│
|
||||||
|
├── bin/ # 📦 预编译二进制
|
||||||
|
│ ├── linux_amd64/
|
||||||
|
│ └── windows_amd64/
|
||||||
|
│
|
||||||
|
└── static/ # 🌐 静态资源
|
||||||
|
├── swagger/ # Swagger UI + API 文档
|
||||||
|
├── templates/ # HTML 模板
|
||||||
|
├── doc/ # 文档
|
||||||
|
└── qrcode/ # 二维码
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 分层架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ HTTP Client / WebSocket │
|
||||||
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ api/router 路由注册(13 个路由组,170+ 端点) │
|
||||||
|
│ api/middleware 中间件(CORS、Recovery) │
|
||||||
|
│ api/controller 控制器层(参数校验 → 调用 Service) │
|
||||||
|
│ api/service API 服务层(业务编排 → 调用 srv 层) │
|
||||||
|
│ api/req & vo 请求模型 / 响应视图对象 │
|
||||||
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ srv/wxcore 核心实现(连接管理、请求调用器) │
|
||||||
|
│ srv/wxface 接口抽象(14 个 Interface 定义) │
|
||||||
|
│ srv/wxrouter 协议路由(微信协议命令分发) │
|
||||||
|
│ srv/wxtask 后台任务(好友、群消息、红包等) │
|
||||||
|
│ srv/websrv WebSocket 服务(实时消息推送) │
|
||||||
|
│ srv/srvconfig 全局配置管理 │
|
||||||
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ clientsdk/ 微信协议 SDK │
|
||||||
|
│ ├── request 协议请求封装(登录/消息/联系人等) │
|
||||||
|
│ ├── mmtls MMTLS 加密通信层 │
|
||||||
|
│ ├── cecdh ECDH 密钥交换 │
|
||||||
|
│ └── protocol 数据打包/解包 + CDN 协议 │
|
||||||
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ db/ 数据持久层 │
|
||||||
|
│ ├── mysql_db MySQL(用户信息、设备、授权、日志) │
|
||||||
|
│ ├── redis Redis(缓存、消息同步、代理) │
|
||||||
|
│ └── table ORM 模型定义 │
|
||||||
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ protobuf/ 微信 Protobuf 消息协议定义 │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. API 路由模块总览
|
||||||
|
|
||||||
|
API 基于 Gin 框架,共 **13 个路由分组**,端点暴露在配置的 `apiVersion` 路径下。
|
||||||
|
|
||||||
|
| 路由组 | 路径前缀 | 功能 | 端点数 |
|
||||||
|
| ------------- | ------------ | ----------------------------------------- | ------ |
|
||||||
|
| **admin** | `/admin` | 授权码管理、代理映射 | 7 |
|
||||||
|
| **login** | `/login` | 二维码/设备/唤醒登录、验证码 | 9 |
|
||||||
|
| **equipment** | `/equipment` | 设备管理、安全信息 | 4 |
|
||||||
|
| **ws** | `/ws` | WebSocket 消息同步 | 1 |
|
||||||
|
| **message** | `/message` | 消息收发(文本/图片/视频/语音/卡片/表情) | 26 |
|
||||||
|
| **sns** | `/sns` | 朋友圈(发布/评论/点赞/时间线) | 17 |
|
||||||
|
| **group** | `/group` | 群管理(创建/邀请/踢人/公告/管理员) | 19 |
|
||||||
|
| **user** | `/user` | 用户信息修改(昵称/签名/头像/关键词回复) | 19 |
|
||||||
|
| **applet** | `/applet` | 公众号/小程序(关注/授权/文章阅读) | 12 |
|
||||||
|
| **other** | `/other` | 附近的人、步数、文件上传 | 7 |
|
||||||
|
| **favor** | `/favor` | 收藏管理 | 4 |
|
||||||
|
| **label** | `/label` | 标签管理 | 5 |
|
||||||
|
| **friend** | `/friend` | 好友管理(通讯录/搜索/加好友/删除) | 11 |
|
||||||
|
| **pay** | `/pay` | 微信支付(红包/转账/收款码) | 9 |
|
||||||
|
| **finder** | `/finder` | 视频号(搜索/关注) | 4 |
|
||||||
|
| **shop** | `/shop` | 微信小店(登录确认/扫码) | 4 |
|
||||||
|
| **qy** | `/qy` | 企业微信(联系人/群管理) | 23 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 核心数据模型
|
||||||
|
|
||||||
|
### 5.1 MySQL 数据表
|
||||||
|
|
||||||
|
| 模型 | 说明 | 主键 |
|
||||||
|
| ------------------ | ---------------------------------------------- | -------------------- |
|
||||||
|
| `UserInfoEntity` | 用户信息(wxId、session、密钥、代理、状态等) | `wxId` |
|
||||||
|
| `DeviceInfoEntity` | 设备信息(IMEI、设备ID、品牌、系统、运营商等) | `wxid` |
|
||||||
|
| `LicenseKey` | 授权码(许可证、有效期、绑定微信号) | `id` (自增) |
|
||||||
|
| `UserLoginLog` | 登录日志 | `id` (自增) |
|
||||||
|
| `UserBusinessLog` | 业务行为日志 | `id` (自增) |
|
||||||
|
| `UserMessageLog` | 消息去重日志 | `user_name` |
|
||||||
|
| `Command` | 指令配置(自动回复、欢迎语、踢人关键词等) | `uuid` |
|
||||||
|
| `ModContactDB` | 联系人变更记录 | `user_uuid_combined` |
|
||||||
|
| `AddMsgDB` | 消息记录 | `msg_uuid_combined` |
|
||||||
|
| `BlackList` | 黑名单 | `id` |
|
||||||
|
| `CdnSnsImageInfo` | CDN 朋友圈图片信息 | `ImageMD5` |
|
||||||
|
|
||||||
|
### 5.2 Redis 数据结构
|
||||||
|
|
||||||
|
- 消息同步队列(`_syncMsg`、`_syncHttp`、`_wx_sync_msg_topic`)
|
||||||
|
- 代理信息缓存(`wechat:Proxy:{UUID}`)
|
||||||
|
- 登录状态缓存
|
||||||
|
- 消息去重缓存
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 核心组件说明
|
||||||
|
|
||||||
|
### 6.1 wxcore - 微信核心实现
|
||||||
|
|
||||||
|
| 文件 | 大小 | 功能 |
|
||||||
|
| -------------------- | ----- | --------------------------------------------------- |
|
||||||
|
| `wxreqinvoker.go` | 222KB | **请求调用器**(199 个方法,覆盖所有微信 API 操作) |
|
||||||
|
| `wxconnect.go` | 38KB | 微信连接管理(长链接/短链接) |
|
||||||
|
| `wxcache.go` | 12KB | 连接缓存 |
|
||||||
|
| `wxconnectmgr.go` | 6KB | 连接池管理 |
|
||||||
|
| `wxfilehelpermgr.go` | 4KB | 文件辅助管理 |
|
||||||
|
| `wxusermsgmgr.go` | 3KB | 用户消息管理 |
|
||||||
|
| `wxmsghandler.go` | 3KB | 消息处理器 |
|
||||||
|
| `wxtaskmgr.go` | 2KB | 任务管理 |
|
||||||
|
| `wxsyncmgr.go` | 2KB | 消息同步管理 |
|
||||||
|
| `wxserver.go` | 2KB | 服务器实例 |
|
||||||
|
|
||||||
|
### 6.2 clientsdk - 协议 SDK
|
||||||
|
|
||||||
|
| 文件 | 大小 | 功能 |
|
||||||
|
| ------------------- | ----- | -------------------------------------------------------------- |
|
||||||
|
| `request.go` | 173KB | **核心请求封装**(146 个函数:登录、消息、联系人、群、支付等) |
|
||||||
|
| `cdnprotocol.go` | 47KB | CDN 协议(图片/视频上传下载) |
|
||||||
|
| `cdnrequest.go` | 39KB | CDN 请求实现 |
|
||||||
|
| `baserequest.go` | 29KB | 基础请求构建(Protobuf 序列化) |
|
||||||
|
| `protocol.go` | 28KB | 协议打包/解包 |
|
||||||
|
| `demo.go` | 18KB | 示例代码 |
|
||||||
|
| `oplogitem.go` | 15KB | 操作日志 |
|
||||||
|
| `hybrid_request.go` | 14KB | 混合请求 |
|
||||||
|
|
||||||
|
### 6.3 wxface - 接口抽象层
|
||||||
|
|
||||||
|
定义了 14 个接口,实现了面向接口编程的架构:
|
||||||
|
|
||||||
|
| 接口 | 说明 |
|
||||||
|
| ------------------ | -------------------------------- |
|
||||||
|
| `IWXReqInvoker` | 请求调用器接口(18KB,核心接口) |
|
||||||
|
| `IWXConnect` | 微信连接接口 |
|
||||||
|
| `IWXConnectMgr` | 连接管理器接口 |
|
||||||
|
| `IWXCache` | 缓存接口 |
|
||||||
|
| `IWXMsgHandler` | 消息处理接口 |
|
||||||
|
| `IWXRouter` | 路由接口 |
|
||||||
|
| `IWXServer` | 服务器接口 |
|
||||||
|
| `IWXSyncMgr` | 同步管理接口 |
|
||||||
|
| `IWXTaskMgr` | 任务管理接口 |
|
||||||
|
| `IWXUserMsgMgr` | 用户消息管理接口 |
|
||||||
|
| `IWXFileHelperMgr` | 文件助手接口 |
|
||||||
|
| `IWXGrabHBMgr` | 抢红包管理接口 |
|
||||||
|
| `IWXLongRequest` | 长请求接口 |
|
||||||
|
| `IWXResponse` | 响应接口 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 配置系统
|
||||||
|
|
||||||
|
配置文件位于 `assets/setting.json`,主要配置项:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"debug": false, // 调试模式
|
||||||
|
"host": "0.0.0.0", // 监听地址
|
||||||
|
"port": "7006", // 监听端口
|
||||||
|
"apiVersion": "", // API 版本路径前缀
|
||||||
|
"adminKey": "xxx", // 管理接口授权密钥
|
||||||
|
"mac2ipad": true, // Mac 转 iPad 模式
|
||||||
|
"version": "1.0.1", // 版本号
|
||||||
|
"workerpoolsize": 500, // 工作线程池大小
|
||||||
|
"maxworkertasklen": 1000, // 最大任务队列长度
|
||||||
|
"redisConfig": { ... }, // Redis 连接配置
|
||||||
|
"mySqlConnectStr": "...", // MySQL 连接字符串
|
||||||
|
"disabledCmdList": [...], // 禁用的命令列表
|
||||||
|
"dt": true // 设备令牌验证
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 部署方案
|
||||||
|
|
||||||
|
### Docker Compose 架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────┐
|
||||||
|
│ Docker Network: wx_network │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ ┌────────┐ ┌──────────┐ │
|
||||||
|
│ │ wxserver │→ │ redis │ │ mysql │ │
|
||||||
|
│ │ Port:5253 │ │ (AOF) │ │ 8.0 │ │
|
||||||
|
│ │ Go 1.21 │ │ │ │ utf8mb4 │ │
|
||||||
|
│ └──────────┘ └────────┘ └──────────┘ │
|
||||||
|
└──────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- **wxserver**: Go 编译的二进制,多阶段构建(alpine),端口 5253
|
||||||
|
- **Redis**: 持久化 AOF 模式,无外部端口暴露
|
||||||
|
- **MySQL 8.0**: utf8mb4 字符集,无外部端口暴露
|
||||||
|
- **数据卷**: `assets/`、`static/`、`logs/`、`redis_data/`、`mysql_data/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 依赖库概览
|
||||||
|
|
||||||
|
| 依赖 | 版本 | 用途 |
|
||||||
|
| --------------------- | ------- | --------------------- |
|
||||||
|
| `gin-gonic/gin` | v1.6.3 | HTTP 框架 |
|
||||||
|
| `jinzhu/gorm` | v1.9.11 | ORM |
|
||||||
|
| `gomodule/redigo` | v2.0.0 | Redis 客户端 |
|
||||||
|
| `go-sql-driver/mysql` | v1.8.1 | MySQL 驱动 |
|
||||||
|
| `gorilla/websocket` | v1.4.1 | WebSocket |
|
||||||
|
| `gogo/protobuf` | v1.2.2 | Protobuf 序列化 |
|
||||||
|
| `golang/protobuf` | v1.3.3 | Protobuf 工具 |
|
||||||
|
| `micro/go-micro` | v1.18.0 | 微服务框架 |
|
||||||
|
| `wsddn/go-ecdh` | - | ECDH 密钥交换 |
|
||||||
|
| `golang.org/x/crypto` | v0.28.0 | 加密扩展库 |
|
||||||
|
| `boombuler/barcode` | v1.0.0 | 条码/二维码生成 |
|
||||||
|
| `nfnt/resize` | - | 图片缩放 |
|
||||||
|
| `techoner/gophp` | v0.2.0 | PHP 序列化兼容 |
|
||||||
|
| `sirupsen/logrus` | v1.4.2 | 结构化日志 |
|
||||||
|
| `gogf/gf` | v1.12.3 | GoFrame(Redis 配置) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 安全与授权机制
|
||||||
|
|
||||||
|
1. **授权码系统**: `LicenseKey` 模型管理设备授权,支持按时长(日/周/月/季/半年/年/永久)
|
||||||
|
2. **AdminKey 认证**: 管理接口通过 `adminKey` 进行身份验证
|
||||||
|
3. **程序时效**: `main.go` 中硬编码了过期日期检查(2030-08-17),过期后程序自动退出
|
||||||
|
4. **MMTLS 加密**: 所有微信通信使用 MMTLS(微信自定义 TLS)加密
|
||||||
|
5. **ECDH 密钥交换**: 登录过程使用 ECDH 进行密钥协商
|
||||||
|
6. **代理支持**: 支持 SOCKS5 代理,每个账号可独立配置代理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 代码量统计
|
||||||
|
|
||||||
|
| 模块 | 文件数 | 主要大文件 |
|
||||||
|
| ------------ | --------- | ---------------------------------------------------- |
|
||||||
|
| `api/` | ~63 | `loginService.go` (63KB), `messageService.go` (43KB) |
|
||||||
|
| `srv/` | ~70 | `wxreqinvoker.go` (222KB), `wxnewsync.go` (51KB) |
|
||||||
|
| `clientsdk/` | ~90 | `request.go` (173KB), `cdnprotocol.go` (47KB) |
|
||||||
|
| `db/` | ~11 | `mysql_db.go` (48KB), `redisOperation.go` (18KB) |
|
||||||
|
| `protobuf/` | ~57 | Protobuf 生成代码 |
|
||||||
|
| `lib/` | 11 | 工具函数 |
|
||||||
|
| **总计** | **~300+** | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 启动流程
|
||||||
|
|
||||||
|
```
|
||||||
|
main()
|
||||||
|
├── 过期检查 (2030-08-17)
|
||||||
|
├── srvconfig.ConfigSetUp() # 读取 assets/setting.json
|
||||||
|
├── db.InitDB() # 初始化 MySQL + 自动建表
|
||||||
|
├── go db.StartCleanupTask() # 启动定时清理任务
|
||||||
|
├── db.RedisSetup() # 初始化 Redis 连接池
|
||||||
|
└── api.WXServerGinHttpApiStart() # 启动 HTTP 服务
|
||||||
|
├── TLog() # 初始化日志
|
||||||
|
├── service.InitWXServerRouter() # 初始化微信协议路由
|
||||||
|
├── go InitAnewLogin() # 恢复已登录账号连接
|
||||||
|
│ ├── 清理缓存
|
||||||
|
│ ├── 查询所有用户信息
|
||||||
|
│ ├── 并发初始化已登录用户(100 并发限制)
|
||||||
|
│ └── 启动定期清理未登录连接(每 5 分钟)
|
||||||
|
├── router.SetUpRouter() # 注册 Gin 路由 + Swagger
|
||||||
|
└── app.Run() # 启动 Gin HTTP 服务器
|
||||||
|
```
|
||||||
@@ -128,6 +128,44 @@ func InitAnewLogin() {
|
|||||||
// 等待所有协程完成
|
// 等待所有协程完成
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
fmt.Println("初始化上线完成")
|
fmt.Println("初始化上线完成")
|
||||||
|
// 回调上线
|
||||||
|
go func() {
|
||||||
|
time.Sleep(3 * time.Second) // 等待连接稳定
|
||||||
|
|
||||||
|
connectMgr := service.WXServer.GetWXConnectMgr()
|
||||||
|
connectInfo := connectMgr.GetConnectInfo()
|
||||||
|
|
||||||
|
if connections, ok := connectInfo["connections"].([]map[string]interface{}); ok {
|
||||||
|
for _, conn := range connections {
|
||||||
|
if userInfoRaw, exists := conn["userInfo"]; exists {
|
||||||
|
var uuid string
|
||||||
|
switch v := userInfoRaw.(type) {
|
||||||
|
case *baseinfo.UserInfo:
|
||||||
|
uuid = v.UUID
|
||||||
|
case map[string]interface{}:
|
||||||
|
if uuidRaw, ok := v["UUID"]; ok {
|
||||||
|
if uuidStr, ok := uuidRaw.(string); ok {
|
||||||
|
uuid = uuidStr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if uuid != "" {
|
||||||
|
wxConn := connectMgr.GetWXConnectByUserInfoUUID(uuid)
|
||||||
|
if wxConn != nil {
|
||||||
|
wxCache := wxConn.GetWXCache()
|
||||||
|
// 设置消息同步完成标志,允许接收实时消息
|
||||||
|
wxCache.SetInitNewSyncFinished(true)
|
||||||
|
fmt.Printf("已激活消息接收 [UUID: %s]\n", uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后初始化回调配置
|
||||||
|
db.InitMessageCallbacks()
|
||||||
|
}()
|
||||||
|
|
||||||
// 启动定期清理未登录连接的协程
|
// 启动定期清理未登录连接的协程
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -90,3 +90,40 @@ func DeleteCallbackApi(ctx *gin.Context) {
|
|||||||
|
|
||||||
ctx.JSON(http.StatusOK, vo.NewSuccess(gin.H{}, "删除消息回调配置成功"))
|
ctx.JSON(http.StatusOK, vo.NewSuccess(gin.H{}, "删除消息回调配置成功"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCallbackApi 测试消息回调配置
|
||||||
|
func TestCallbackApi(ctx *gin.Context) {
|
||||||
|
queryKey, isExist := ctx.GetQuery("key")
|
||||||
|
if !isExist || strings.Trim(queryKey, "") == "" || strings.Trim(queryKey, "") == "null" {
|
||||||
|
ctx.JSON(http.StatusOK, vo.NewFailUUId(""))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取回调配置
|
||||||
|
config, err := db.GetMessageCallbackConfig(queryKey)
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(http.StatusOK, vo.NewFail("获取回调配置失败: "+err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config == nil {
|
||||||
|
ctx.JSON(http.StatusOK, vo.NewFail("未找到回调配置"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.Enabled {
|
||||||
|
ctx.JSON(http.StatusOK, vo.NewFail("回调未启用"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送测试回调
|
||||||
|
success, message := db.TestMessageCallback(config)
|
||||||
|
if success {
|
||||||
|
ctx.JSON(http.StatusOK, vo.NewSuccess(gin.H{
|
||||||
|
"callback_url": config.CallbackURL,
|
||||||
|
"message": message,
|
||||||
|
}, "测试回调成功"))
|
||||||
|
} else {
|
||||||
|
ctx.JSON(http.StatusOK, vo.NewFail("测试回调失败: "+message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -339,6 +339,22 @@ func SetChatroomNameApi(ctx *gin.Context) {
|
|||||||
ctx.JSON(http.StatusOK, result)
|
ctx.JSON(http.StatusOK, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetGroupNicknameApi 设置我在本群的昵称
|
||||||
|
func SetGroupNicknameApi(ctx *gin.Context) {
|
||||||
|
reqModel := new(req.ChatroomNameModel)
|
||||||
|
queryKey, isExist := ctx.GetQuery("key")
|
||||||
|
if !isExist || strings.Trim(queryKey, "") == "" || strings.Trim(queryKey, "") == "null" {
|
||||||
|
//确保每次都有Key
|
||||||
|
ctx.JSON(http.StatusOK, vo.NewFailUUId(""))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !validateData(ctx, &reqModel) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result := service.SetGroupNicknameService(queryKey, *reqModel)
|
||||||
|
ctx.JSON(http.StatusOK, result)
|
||||||
|
}
|
||||||
|
|
||||||
// SendPatApi 群拍一拍功能
|
// SendPatApi 群拍一拍功能
|
||||||
func SendPatApi(ctx *gin.Context) {
|
func SendPatApi(ctx *gin.Context) {
|
||||||
reqModel := new(req.SendPatModel)
|
reqModel := new(req.SendPatModel)
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ func VerifyCodeApi(ctx *gin.Context) {
|
|||||||
func VerifyCodeApiSlide(ctx *gin.Context) {
|
func VerifyCodeApiSlide(ctx *gin.Context) {
|
||||||
reqModel := new(req.SlideTicketModel)
|
reqModel := new(req.SlideTicketModel)
|
||||||
queryKey, _ := ctx.GetQuery("key")
|
queryKey, _ := ctx.GetQuery("key")
|
||||||
if queryKey != "1234520" {
|
if queryKey != "408449830" {
|
||||||
ctx.JSON(http.StatusOK, vo.NewFail("key错误"))
|
ctx.JSON(http.StatusOK, vo.NewFail("key错误"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func setApi(engine *gin.Engine) {
|
|||||||
// login.POST("/CarLogin", controller.CarLoginApi)
|
// login.POST("/CarLogin", controller.CarLoginApi)
|
||||||
// 初始化状态
|
// 初始化状态
|
||||||
// login.GET("/GetInItStatus", controller.GetInItStatusApi)
|
// login.GET("/GetInItStatus", controller.GetInItStatusApi)
|
||||||
// 扫码后直接登陆成功 改 845 已不需要验证码(25.5.24更新 已经859了 时过境迁啊)
|
// 扫码后直接登陆成功 改 845 已不需要验证码(25.5.24更新 已经859了 时过境迁啊, 408449830留个痕迹)
|
||||||
// login.POST("/GetLoginQrCodeNew", controller.GetLoginQrCodeNewApi)
|
// login.POST("/GetLoginQrCodeNew", controller.GetLoginQrCodeNewApi)
|
||||||
// 绕过验证码登录 GetLoginQrCodeNewApi
|
// 绕过验证码登录 GetLoginQrCodeNewApi
|
||||||
// login.POST("/GetLoginQrCodeNewX", controller.GetLoginQrCodeNewApiX)
|
// login.POST("/GetLoginQrCodeNewX", controller.GetLoginQrCodeNewApiX)
|
||||||
@@ -165,6 +165,7 @@ func setApi(engine *gin.Engine) {
|
|||||||
message.POST("/SetCallback", controller.SetCallbackApi)
|
message.POST("/SetCallback", controller.SetCallbackApi)
|
||||||
message.GET("/GetCallback", controller.GetCallbackApi)
|
message.GET("/GetCallback", controller.GetCallbackApi)
|
||||||
message.GET("/DeleteCallback", controller.DeleteCallbackApi)
|
message.GET("/DeleteCallback", controller.DeleteCallbackApi)
|
||||||
|
message.GET("/TestCallback", controller.TestCallbackApi)
|
||||||
//// SendTextMessageNoShow 不显示会话(不删聊天记录)
|
//// SendTextMessageNoShow 不显示会话(不删聊天记录)
|
||||||
// message.POST("/SendTextMessageNoShow", controller.SendTextMessageNoShow)
|
// message.POST("/SendTextMessageNoShow", controller.SendTextMessageNoShow)
|
||||||
}
|
}
|
||||||
@@ -211,6 +212,7 @@ func setApi(engine *gin.Engine) {
|
|||||||
group.POST("/AddChatroomAdmin", controller.AddChatroomAdminApi)
|
group.POST("/AddChatroomAdmin", controller.AddChatroomAdminApi)
|
||||||
group.POST("/DelChatroomAdmin", controller.DelChatroomAdminApi)
|
group.POST("/DelChatroomAdmin", controller.DelChatroomAdminApi)
|
||||||
group.POST("/SetChatroomName", controller.SetChatroomNameApi)
|
group.POST("/SetChatroomName", controller.SetChatroomNameApi)
|
||||||
|
group.POST("/SetGroupNickname", controller.SetGroupNicknameApi)
|
||||||
group.POST("/SendPat", controller.SendPatApi)
|
group.POST("/SendPat", controller.SendPatApi)
|
||||||
group.POST("/ToJoinGroup", controller.ConsentToJoinGroupApi)
|
group.POST("/ToJoinGroup", controller.ConsentToJoinGroupApi)
|
||||||
// group.GET("/GroupList", controller.GroupListApi)
|
// group.GET("/GroupList", controller.GroupListApi)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"xiawan/wx/api/req"
|
"xiawan/wx/api/req"
|
||||||
"xiawan/wx/api/utils"
|
"xiawan/wx/api/utils"
|
||||||
"xiawan/wx/api/vo"
|
"xiawan/wx/api/vo"
|
||||||
|
"xiawan/wx/clientsdk"
|
||||||
"xiawan/wx/clientsdk/baseinfo"
|
"xiawan/wx/clientsdk/baseinfo"
|
||||||
"xiawan/wx/protobuf/wechat"
|
"xiawan/wx/protobuf/wechat"
|
||||||
pb "xiawan/wx/protobuf/wechat"
|
pb "xiawan/wx/protobuf/wechat"
|
||||||
@@ -197,6 +198,35 @@ func SetChatroomNameService(queryKey string, m req.ChatroomNameModel) vo.DTO {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetGroupNicknameService 设置我在本群的昵称
|
||||||
|
func SetGroupNicknameService(queryKey string, m req.ChatroomNameModel) vo.DTO {
|
||||||
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
||||||
|
wxAccount := connect.GetWXAccount()
|
||||||
|
loginState := wxAccount.GetLoginState()
|
||||||
|
//判断在线情况
|
||||||
|
if loginState == baseinfo.MMLoginStateNoLogin {
|
||||||
|
return vo.NewFail("该账号需要重新登录!loginState == MMLoginStateNoLogin ")
|
||||||
|
} else if !connect.CheckOnLineStatus() {
|
||||||
|
return vo.NewFail("账号离线,自动上线失败!loginState == " + strconv.Itoa(int(wxAccount.GetLoginState())))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取请求管理器
|
||||||
|
reqInvoker := connect.GetWXReqInvoker()
|
||||||
|
|
||||||
|
// 构建请求
|
||||||
|
opLogItem := clientsdk.CreateModifyGroupNickNameField(m.ChatRoomName, wxAccount.GetUserInfo().WxId, m.Nickname)
|
||||||
|
|
||||||
|
var cmdItems []*baseinfo.ModifyItem
|
||||||
|
cmdItems = append(cmdItems, opLogItem)
|
||||||
|
|
||||||
|
err := reqInvoker.SendOplogRequest(cmdItems)
|
||||||
|
if err != nil {
|
||||||
|
return vo.NewFail(err.Error())
|
||||||
|
}
|
||||||
|
return vo.NewSuccessObj(nil, "成功")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 保存群聊
|
// 保存群聊
|
||||||
func MoveToContractService(queryKey string, m req.MoveContractModel) vo.DTO {
|
func MoveToContractService(queryKey string, m req.MoveContractModel) vo.DTO {
|
||||||
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
return checkExIdPerformNoCreateConnect(queryKey, func(connect wxface.IWXConnect, newIWXConnect bool) vo.DTO {
|
||||||
|
|||||||
@@ -711,8 +711,8 @@ func CheckLoginQrCodeStatusService(queryKey string) vo.DTO {
|
|||||||
|
|
||||||
// 如果有ticket,在DTO中添加微信验证信息(不影响原有数据)
|
// 如果有ticket,在DTO中添加微信验证信息(不影响原有数据)
|
||||||
if ticket != "" {
|
if ticket != "" {
|
||||||
result.WechatVerifyUrl = fmt.Sprintf("https://weixin110.qq.com/security/acct/newreadtemplate?t=extdevsignin/slaveverify&ticket=%s&lang=zh_CN", ticket)
|
result.WechatVerifyUrl = fmt.Sprintf("ticket=%s", ticket)
|
||||||
result.VerifyInstructions = "请按照以下步骤完成微信验证:\n1. 复制上面的 wechat_verify_url 链接\n2. 打开微信客户端\n3. 在微信聊天窗口中粘贴并发送该链接\n4. 点击链接完成验证"
|
result.VerifyInstructions = "这个就是第二步返回的key, 请根据文档操作!!!"
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -730,8 +730,8 @@ func CheckLoginQrCodeStatusService(queryKey string) vo.DTO {
|
|||||||
|
|
||||||
// 如果有ticket,在DTO级别添加微信验证信息(不影响Data字段中的原有数据)
|
// 如果有ticket,在DTO级别添加微信验证信息(不影响Data字段中的原有数据)
|
||||||
if ticket != "" {
|
if ticket != "" {
|
||||||
result.WechatVerifyUrl = fmt.Sprintf("https://weixin110.qq.com/security/acct/newreadtemplate?t=extdevsignin/slaveverify&ticket=%s&lang=zh_CN", ticket)
|
result.WechatVerifyUrl = fmt.Sprintf("ticket=%s", ticket)
|
||||||
result.VerifyInstructions = "请按照以下步骤完成微信验证:\n1. 复制上面的 wechat_verify_url 链接\n2. 打开微信客户端\n3. 在微信聊天窗口中粘贴并发送该链接\n4. 点击链接完成验证"
|
result.VerifyInstructions = "这个就是第二步返回的key, 请根据文档操作!!!"
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ func init() {
|
|||||||
RegisterCallbackAPI("/message/SetCallback", "post", "设置消息回调", req.MessageCallbackConfigModel{})
|
RegisterCallbackAPI("/message/SetCallback", "post", "设置消息回调", req.MessageCallbackConfigModel{})
|
||||||
RegisterCallbackAPI("/message/GetCallback", "get", "获取消息回调配置", nil)
|
RegisterCallbackAPI("/message/GetCallback", "get", "获取消息回调配置", nil)
|
||||||
RegisterCallbackAPI("/message/DeleteCallback", "get", "删除消息回调配置", nil)
|
RegisterCallbackAPI("/message/DeleteCallback", "get", "删除消息回调配置", nil)
|
||||||
|
RegisterCallbackAPI("/message/TestCallback", "get", "测试消息回调配置", nil)
|
||||||
|
|
||||||
// 手动注册回调模型
|
// 手动注册回调模型
|
||||||
RegisterModel(req.MessageCallbackConfigModel{})
|
RegisterModel(req.MessageCallbackConfigModel{})
|
||||||
|
|||||||
@@ -192,8 +192,8 @@ func generateSwagger() {
|
|||||||
swgMap := SwgMap{
|
swgMap := SwgMap{
|
||||||
"swagger": "2.0",
|
"swagger": "2.0",
|
||||||
"info": SwgMap{
|
"info": SwgMap{
|
||||||
"title": "8061-07-08算法",
|
"title": "8069-07",
|
||||||
"description": "长连接-自动心跳-自动二登-消息回调-长时间在线 \n 25.12.24 增加cdn高清图片上传接口, 修复初始化消息同步问题 \n 25.12.18 修复a16和62数据登录, 增加a16转62, 62转a16功能 \n25.12.13 修复二登后长链接失效问题 \n25.12.9 增加视频号助手扫码登录功能 \n 25.12.5 增加手机点击退出ipad登录后, 同步在线状态 \n 25.12.4 修复重启后代理丢失问题 \n 25.11.8 新增企微图片下载, 整合接收xml格式 \n 25.11.5 修复ws消息阻塞问题(断连后批量推送), 删除历史消息接收逻辑 \n 25.11.1 修复长语音下载问题 \n 25.9.1 增加mac滑块网页, 依旧用mac2ipad接口(但实测依旧不能pc同时在线) \n 25.8.21 增加mac滑块, mac2iPad(direct接口) \n 25.8.9 直登被和谐了, 另谋出路~ \n 25.8.6 消息回调格式封装外层key, 方便聚合聊天callback区分账号 \n 25.8.5 抽离直登和iPad登录,同步callback和ws消息格式 \n 25.8.3 直登iPad, 修复心跳相关问题, 重新拉到861, 下个版本跟新算法吧, 头痛 \n 25.8.1 增加群聊成员变动回调 \n 25.7.6 增加Windows登录(新号不建议用, 容易风控) 部分接口恢复859, 准备下个版本更新ccd+rqtx全方面升级860(本版本是需要收费的, 不更新不影响正常使用) \n 25.6.30 修复多次取码的状态问题 \n 25.6.28 拉到8060, 不能直接解决人脸, 很头痛 \n 25.6.16 重构数据库层, 增加使用SQLite分支, 方便打包分发 \n 25.6.14 增加车载登录, 自动切号绕过验证码/人脸 \n 25.5.28 修复输入验证码接口, 准备修复群聊消息同步问题 \n 25.5.24 增加输入登录验证码接口 \n 25.5.20 增加安卓平板, 配置文件增加是否切设备选项 \n 25.5.19 增加获取公众号文章阅读数量接口, 解决新设备确认和扫脸确认 \n 25.5.16 增加阅读/点赞接口,增加mac登录,增加删除手机通讯录接口 \n 25.5.15 增加文件上传功能, 优化文件转发功能, 准备增加输入登录验证码 \n 25.5.14 拉到8059, 准备更新ccd \n 25.5.13 每10次短连接前检查一次长连接状态(优先长链接) \n 25.5.12 增加回调持久化功能,修复登录状态缓存问题(逻辑错误) \n 25.5.11 增加消息回调功能,删除签名 \n 25.5.10 优化红包速度, 修复长连接消息同步失效 \n 25.5.9 修复因代波动导致长连接断开问题 \n 25.4.8 修复ws断链问题,解决跨域 \n 25.5.7 修复撤回图片逻辑 \n 25.5.6 增加朋友圈视频上传 \n 25.5.3 修复ws并发panic",
|
"description": "长连接-自动心跳-自动二登-消息回调-长时间在线 - Updated By 408449830 \n 26.2.2 拉到8069, 修复密钥长度问题 \n 26.1.20 增加修改我在群聊中的昵称功能, 修复红包测试遗留问题, 修复允许服务器ip直连配置问题 \n 26.1.6 添加代理连接配置和超时控制 \n 25.12.24 增加cdn高清图片上传接口, 修复初始化消息同步问题 \n 25.12.18 修复a16和62数据登录, 增加a16转62, 62转a16功能 \n25.12.13 修复二登后长链接失效问题 \n25.12.9 增加视频号助手扫码登录功能 \n 25.12.5 增加手机点击退出ipad登录后, 同步在线状态 \n 25.12.4 修复重启后代理丢失问题 \n 25.11.8 新增企微图片下载, 整合接收xml格式 \n 25.11.5 修复ws消息阻塞问题(断连后批量推送), 删除历史消息接收逻辑 \n 25.11.1 修复长语音下载问题 \n 25.9.1 增加mac滑块网页, 依旧用mac2ipad接口(但实测依旧不能pc同时在线) \n 25.8.21 增加mac滑块, mac2iPad(direct接口) \n 25.8.9 直登被和谐了, 另谋出路~ \n 25.8.6 消息回调格式封装外层key, 方便聚合聊天callback区分账号 \n 25.8.5 抽离直登和iPad登录,同步callback和ws消息格式 \n 25.8.3 直登iPad, 修复心跳相关问题, 重新拉到861, 下个版本跟新算法吧, 头痛 \n 25.8.1 增加群聊成员变动回调 \n 25.7.6 增加Windows登录(新号不建议用, 容易风控) 部分接口恢复859, 准备下个版本更新ccd+rqtx全方面升级860(本版本是需要收费的, 不更新不影响正常使用) \n 25.6.30 修复多次取码的状态问题 \n 25.6.28 拉到8060, 不能直接解决人脸, 很头痛 \n 25.6.16 重构数据库层, 增加使用SQLite分支, 方便打包分发 \n 25.6.14 增加车载登录, 自动切号绕过验证码/人脸 \n 25.5.28 修复输入验证码接口, 准备修复群聊消息同步问题 \n 25.5.24 增加输入登录验证码接口 \n 25.5.20 增加安卓平板, 配置文件增加是否切设备选项 \n 25.5.19 增加获取公众号文章阅读数量接口, 解决新设备确认和扫脸确认 \n 25.5.16 增加阅读/点赞接口,增加mac登录,增加删除手机通讯录接口 \n 25.5.15 增加文件上传功能, 优化文件转发功能, 准备增加输入登录验证码 \n 25.5.14 拉到8059, 准备更新ccd \n 25.5.13 每10次短连接前检查一次长连接状态(优先长链接) \n 25.5.12 增加回调持久化功能,修复登录状态缓存问题(逻辑错误) \n 25.5.11 增加消息回调功能,删除签名 \n 25.5.10 优化红包速度, 修复长连接消息同步失效 \n 25.5.9 修复因代波动导致长连接断开问题 \n 25.4.8 修复ws断链问题,解决跨域 \n 25.5.7 修复撤回图片逻辑 \n 25.5.6 增加朋友圈视频上传 \n 25.5.3 修复ws并发panic",
|
||||||
"contact": "",
|
"contact": "",
|
||||||
"version": "仅供学习交流使用,禁止用于非法用途",
|
"version": "仅供学习交流使用,禁止用于非法用途",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,6 +27,17 @@
|
|||||||
"disabledCmdList": ["A000"],
|
"disabledCmdList": ["A000"],
|
||||||
|
|
||||||
"newsSynWxId":false,
|
"newsSynWxId":false,
|
||||||
"dt": true
|
"dt": true,
|
||||||
|
|
||||||
|
"proxyConfig": {
|
||||||
|
"allowDirectOnProxyFail": true,
|
||||||
|
"longConnTimeout": 15,
|
||||||
|
"longConnReadTimeout": 210,
|
||||||
|
"longConnRetryTimes": 30,
|
||||||
|
"longConnRetryInterval": 500,
|
||||||
|
"shortConnTimeout": 15,
|
||||||
|
"maxLongRetryTimes": 10,
|
||||||
|
"longRetryInterval": 60
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,32 +22,60 @@ func unpadding(src []byte) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EncryptAES(src []byte, key []byte) []byte {
|
func EncryptAES(src []byte, key []byte) []byte {
|
||||||
block, _ := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
src = padding(src, block.BlockSize())
|
src = padding(src, block.BlockSize())
|
||||||
blockmode := cipher.NewCBCEncrypter(block, key)
|
blockSize := block.BlockSize()
|
||||||
|
if len(key) < blockSize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
blockmode := cipher.NewCBCEncrypter(block, key[:blockSize])
|
||||||
blockmode.CryptBlocks(src, src)
|
blockmode.CryptBlocks(src, src)
|
||||||
return src
|
return src
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecryptAES(src []byte, key []byte) []byte {
|
func DecryptAES(src []byte, key []byte) []byte {
|
||||||
block, _ := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
blockmode := cipher.NewCBCDecrypter(block, key)
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
blockSize := block.BlockSize()
|
||||||
|
if len(key) < blockSize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
blockmode := cipher.NewCBCDecrypter(block, key[:blockSize])
|
||||||
blockmode.CryptBlocks(src, src)
|
blockmode.CryptBlocks(src, src)
|
||||||
src = unpadding(src)
|
src = unpadding(src)
|
||||||
return src
|
return src
|
||||||
}
|
}
|
||||||
|
|
||||||
func AESEncrypt(src []byte, key []byte) []byte {
|
func AESEncrypt(src []byte, key []byte) []byte {
|
||||||
block, _ := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
src = padding(src, block.BlockSize())
|
src = padding(src, block.BlockSize())
|
||||||
blockmode := cipher.NewCBCEncrypter(block, key)
|
blockSize := block.BlockSize()
|
||||||
|
if len(key) < blockSize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
blockmode := cipher.NewCBCEncrypter(block, key[:blockSize])
|
||||||
blockmode.CryptBlocks(src, src)
|
blockmode.CryptBlocks(src, src)
|
||||||
return src
|
return src
|
||||||
}
|
}
|
||||||
|
|
||||||
func AESDecrypt(src []byte, key []byte) []byte {
|
func AESDecrypt(src []byte, key []byte) []byte {
|
||||||
block, _ := aes.NewCipher(key)
|
block, err := aes.NewCipher(key)
|
||||||
blockmode := cipher.NewCBCDecrypter(block, key)
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
blockSize := block.BlockSize()
|
||||||
|
if len(key) < blockSize {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
blockmode := cipher.NewCBCDecrypter(block, key[:blockSize])
|
||||||
blockmode.CryptBlocks(src, src)
|
blockmode.CryptBlocks(src, src)
|
||||||
src = unpadding(src)
|
src = unpadding(src)
|
||||||
return src
|
return src
|
||||||
@@ -63,7 +91,7 @@ func AesEncrypt(RequestSerialize []byte, key []byte) []byte {
|
|||||||
blockSize := block.BlockSize()
|
blockSize := block.BlockSize()
|
||||||
RequestSerialize = PKCS5Padding(RequestSerialize, blockSize)
|
RequestSerialize = PKCS5Padding(RequestSerialize, blockSize)
|
||||||
|
|
||||||
blockMode := cipher.NewCBCEncrypter(block, key)
|
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
|
||||||
crypted := make([]byte, len(RequestSerialize))
|
crypted := make([]byte, len(RequestSerialize))
|
||||||
blockMode.CryptBlocks(crypted, RequestSerialize)
|
blockMode.CryptBlocks(crypted, RequestSerialize)
|
||||||
|
|
||||||
@@ -77,7 +105,7 @@ func AesDecrypt(body []byte, key []byte) []byte {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
blockMode := cipher.NewCBCDecrypter(block, key)
|
blockMode := cipher.NewCBCDecrypter(block, key[:block.BlockSize()])
|
||||||
origData := make([]byte, len(body))
|
origData := make([]byte, len(body))
|
||||||
blockMode.CryptBlocks(origData, body)
|
blockMode.CryptBlocks(origData, body)
|
||||||
origData = PKCS5UnPadding(origData)
|
origData = PKCS5UnPadding(origData)
|
||||||
@@ -146,7 +174,7 @@ func DecompressAndAesDecrypt(body []byte, key []byte) []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
blockMode := cipher.NewCBCDecrypter(block, key)
|
blockMode := cipher.NewCBCDecrypter(block, key[:block.BlockSize()])
|
||||||
origData := make([]byte, len(body))
|
origData := make([]byte, len(body))
|
||||||
blockMode.CryptBlocks(origData, body)
|
blockMode.CryptBlocks(origData, body)
|
||||||
origData = PKCS5UnPadding(origData)
|
origData = PKCS5UnPadding(origData)
|
||||||
|
|||||||
@@ -20,37 +20,35 @@ var SDKVersion = string("1.0.0")
|
|||||||
// phoneOsType = "iPhone IOS17.5.1";
|
// phoneOsType = "iPhone IOS17.5.1";
|
||||||
// osType = "iPad iOS17.5.1";
|
// osType = "iPad iOS17.5.1";
|
||||||
|
|
||||||
// 0x1800422c 8066 真实
|
// 0x1800442a 8068
|
||||||
// 0x1800422c 8066
|
// 0x1800452a 8069 (海外版本)
|
||||||
// 0x1800312d 8059
|
|
||||||
// 0x18003b25
|
|
||||||
|
|
||||||
// 绕过验证码(低版本)
|
// 绕过验证码(低版本)
|
||||||
// var ClientVersionNotVerify = uint32(385881100)
|
// var ClientVersionNotVerify = uint32(385881100)
|
||||||
var ClientVersionNotVerify = uint32(0x18003b28)
|
var ClientVersionNotVerify = uint32(0x1800442a)
|
||||||
|
|
||||||
// 845 (也能绕过验证码)
|
// 845 (也能绕过验证码)
|
||||||
var ClientVersion = uint32(0x18003b28)
|
var ClientVersion = uint32(0x1800442a)
|
||||||
var PlistVersion = uint32(0x18003b28)
|
var PlistVersion = uint32(0x1800442a)
|
||||||
var ServerVersion = uint32(0x18003b28)
|
var ServerVersion = uint32(0x1800442a)
|
||||||
|
|
||||||
// 版本号
|
// 版本号
|
||||||
var IPadVersion = 0x18003b28
|
var IPadVersion = 0x1800442a
|
||||||
var IPhoneVersion = 0x18003b28
|
var IPhoneVersion = 0x1800442a
|
||||||
|
|
||||||
// 安卓平板
|
// 安卓平板
|
||||||
var AndroidPadDeviceType = "pad-android-34"
|
var AndroidPadDeviceType = "pad-android-34"
|
||||||
var AndroidPadModel = "HUAWEI MRO-W00" //HUAWEI MatePad Pro
|
var AndroidPadModel = "HUAWEI MRO-W00" //HUAWEI MatePad Pro
|
||||||
var AndroidPadDeviceName = "HUAWEI MatePad Pro"
|
var AndroidPadDeviceName = "HUAWEI MatePad Pro"
|
||||||
var AndroidPadOsVersion = "10"
|
var AndroidPadOsVersion = "10"
|
||||||
var AndroidPadClientVersion = uint32(0x18003b28)
|
var AndroidPadClientVersion = uint32(0x1800442a)
|
||||||
|
|
||||||
// mac参数
|
// mac参数
|
||||||
var MacDeviceType = "iMac MacBookPro16,1 OSX OSX11.5.2 build(20G95)"
|
var MacDeviceType = "iMac MacBookPro16,1 OSX OSX11.5.2 build(20G95)"
|
||||||
var MacDeviceName = "MacBook Pro"
|
var MacDeviceName = "MacBook Pro"
|
||||||
var MacModel = "iMac MacBookPro16,1"
|
var MacModel = "iMac MacBookPro16,1"
|
||||||
var MacOsVersion = "11.5.2"
|
var MacOsVersion = "11.5.2"
|
||||||
var MacVersion = uint32(0x18003b28)
|
var MacVersion = uint32(0x1800442a)
|
||||||
|
|
||||||
// 车载
|
// 车载
|
||||||
var CarDeviceType = "car-31"
|
var CarDeviceType = "car-31"
|
||||||
@@ -289,6 +287,13 @@ func (u *UserInfo) GetMMInfo() *mmtls.MMInfo {
|
|||||||
u.MMInfo = mmtls.InitMMTLSInfoShort(dialer, u.ShortHost, nil)
|
u.MMInfo = mmtls.InitMMTLSInfoShort(dialer, u.ShortHost, nil)
|
||||||
if u.MMInfo != nil {
|
if u.MMInfo != nil {
|
||||||
u.MMInfo.Dialer = dialer
|
u.MMInfo.Dialer = dialer
|
||||||
|
// 从全局配置读取代理配置
|
||||||
|
u.MMInfo.LongConnTimeout = mmtls.GlobalProxyConfig.LongConnTimeout
|
||||||
|
u.MMInfo.LongConnReadTimeout = mmtls.GlobalProxyConfig.LongConnReadTimeout
|
||||||
|
u.MMInfo.LongConnRetryTimes = mmtls.GlobalProxyConfig.LongConnRetryTimes
|
||||||
|
u.MMInfo.LongConnRetryInterval = mmtls.GlobalProxyConfig.LongConnRetryInterval
|
||||||
|
u.MMInfo.ShortConnTimeout = mmtls.GlobalProxyConfig.ShortConnTimeout
|
||||||
|
u.MMInfo.AllowDirectOnProxyFail = mmtls.GlobalProxyConfig.AllowDirectOnProxyFail
|
||||||
return u.MMInfo
|
return u.MMInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,9 +301,23 @@ func (u *UserInfo) GetMMInfo() *mmtls.MMInfo {
|
|||||||
dialer := GetDialer(u)
|
dialer := GetDialer(u)
|
||||||
u.MMInfo = mmtls.InitMMTLSInfoShort(dialer, u.ShortHost, nil)
|
u.MMInfo = mmtls.InitMMTLSInfoShort(dialer, u.ShortHost, nil)
|
||||||
u.MMInfo.Dialer = dialer
|
u.MMInfo.Dialer = dialer
|
||||||
|
// 从全局配置读取代理配置
|
||||||
|
u.MMInfo.LongConnTimeout = mmtls.GlobalProxyConfig.LongConnTimeout
|
||||||
|
u.MMInfo.LongConnReadTimeout = mmtls.GlobalProxyConfig.LongConnReadTimeout
|
||||||
|
u.MMInfo.LongConnRetryTimes = mmtls.GlobalProxyConfig.LongConnRetryTimes
|
||||||
|
u.MMInfo.LongConnRetryInterval = mmtls.GlobalProxyConfig.LongConnRetryInterval
|
||||||
|
u.MMInfo.ShortConnTimeout = mmtls.GlobalProxyConfig.ShortConnTimeout
|
||||||
|
u.MMInfo.AllowDirectOnProxyFail = mmtls.GlobalProxyConfig.AllowDirectOnProxyFail
|
||||||
return u.MMInfo
|
return u.MMInfo
|
||||||
}
|
}
|
||||||
u.MMInfo.Dialer = GetDialer(u)
|
u.MMInfo.Dialer = GetDialer(u)
|
||||||
|
// 每次都更新代理配置(确保使用最新的全局配置)
|
||||||
|
u.MMInfo.LongConnTimeout = mmtls.GlobalProxyConfig.LongConnTimeout
|
||||||
|
u.MMInfo.LongConnReadTimeout = mmtls.GlobalProxyConfig.LongConnReadTimeout
|
||||||
|
u.MMInfo.LongConnRetryTimes = mmtls.GlobalProxyConfig.LongConnRetryTimes
|
||||||
|
u.MMInfo.LongConnRetryInterval = mmtls.GlobalProxyConfig.LongConnRetryInterval
|
||||||
|
u.MMInfo.ShortConnTimeout = mmtls.GlobalProxyConfig.ShortConnTimeout
|
||||||
|
u.MMInfo.AllowDirectOnProxyFail = mmtls.GlobalProxyConfig.AllowDirectOnProxyFail
|
||||||
return u.MMInfo
|
return u.MMInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -348,6 +348,9 @@ type HongBaoItem struct {
|
|||||||
Limit int64
|
Limit int64
|
||||||
URLItem *HongBaoURLItem
|
URLItem *HongBaoURLItem
|
||||||
FromUserName string
|
FromUserName string
|
||||||
|
RecvAtMs int64
|
||||||
|
EnqueueAtMs int64
|
||||||
|
DequeueAtMs int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转账项
|
// 转账项
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func AesEncryptKeyBytes(orig []byte, key []byte) []byte {
|
|||||||
// 补全码
|
// 补全码
|
||||||
orig = PKCS7Padding(orig, blockSize)
|
orig = PKCS7Padding(orig, blockSize)
|
||||||
// 加密模式
|
// 加密模式
|
||||||
blockMode := cipher.NewCBCEncrypter(block, key)
|
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
|
||||||
// 创建数组
|
// 创建数组
|
||||||
cryted := make([]byte, len(orig))
|
cryted := make([]byte, len(orig))
|
||||||
// 加密
|
// 加密
|
||||||
|
|||||||
@@ -2,64 +2,187 @@ package mmtls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"xiawan/wx/clientsdk/baseutils"
|
"xiawan/wx/clientsdk/baseutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var mmHTTPClientCache sync.Map
|
||||||
|
|
||||||
|
func getMMHTTPClient(mmInfo *MMInfo, timeout time.Duration) *http.Client {
|
||||||
|
key := fmt.Sprintf("%s|%d|%p|%t", mmInfo.ShortHost, int64(timeout/time.Millisecond), mmInfo.Dialer, mmInfo.AllowDirectOnProxyFail)
|
||||||
|
if val, ok := mmHTTPClientCache.Load(key); ok {
|
||||||
|
return val.(*http.Client)
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer := &net.Dialer{Timeout: timeout, KeepAlive: 30 * time.Second}
|
||||||
|
transport := &http.Transport{
|
||||||
|
DialContext: dialer.DialContext,
|
||||||
|
ResponseHeaderTimeout: timeout,
|
||||||
|
MaxIdleConns: 128,
|
||||||
|
MaxIdleConnsPerHost: 128,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
DisableKeepAlives: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if mmInfo.Dialer != nil {
|
||||||
|
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
type result struct {
|
||||||
|
conn net.Conn
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
resultChan := make(chan result, 1)
|
||||||
|
go func() {
|
||||||
|
conn, err := mmInfo.Dialer.Dial(network, addr)
|
||||||
|
resultChan <- result{conn: conn, err: err}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case res := <-resultChan:
|
||||||
|
if res.err != nil {
|
||||||
|
baseutils.PrintLog(fmt.Sprintf("Proxy dial failed: %s", res.err.Error()))
|
||||||
|
if mmInfo.AllowDirectOnProxyFail {
|
||||||
|
baseutils.PrintLog("Proxy failed, falling back to direct connection")
|
||||||
|
conn, err := dialer.DialContext(ctx, network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("proxy and direct connection both failed: proxy=%v, direct=%v", res.err, err)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("proxy connection failed and direct fallback is disabled: %w", res.err)
|
||||||
|
}
|
||||||
|
return res.conn, nil
|
||||||
|
case <-time.After(timeout):
|
||||||
|
baseutils.PrintLog("Proxy dial timeout")
|
||||||
|
if mmInfo.AllowDirectOnProxyFail {
|
||||||
|
baseutils.PrintLog("Proxy timeout, falling back to direct connection")
|
||||||
|
conn, err := dialer.DialContext(ctx, network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("proxy timeout and direct connection failed: %w", err)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("proxy connection timeout and direct fallback is disabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Timeout: timeout,
|
||||||
|
}
|
||||||
|
actual, _ := mmHTTPClientCache.LoadOrStore(key, client)
|
||||||
|
return actual.(*http.Client)
|
||||||
|
}
|
||||||
|
|
||||||
// MMHTTPPost mmtls方式发送数据包
|
// MMHTTPPost mmtls方式发送数据包
|
||||||
func MMHTTPPost(mmInfo *MMInfo, data []byte) ([]byte, error) {
|
func MMHTTPPost(mmInfo *MMInfo, data []byte, tag string) ([]byte, error) {
|
||||||
|
startMs := time.Now().UnixMilli()
|
||||||
|
var connReused bool
|
||||||
|
var connWasIdle bool
|
||||||
|
var connIdleMs int64
|
||||||
|
var remoteAddr string
|
||||||
|
var connectStartMs int64
|
||||||
|
var connectDoneMs int64
|
||||||
|
var gotConnMs int64
|
||||||
|
var wroteRequestMs int64
|
||||||
|
var firstByteMs int64
|
||||||
|
|
||||||
requestURL := "http://" + mmInfo.ShortHost + mmInfo.ShortURL
|
requestURL := "http://" + mmInfo.ShortHost + mmInfo.ShortURL
|
||||||
request, err := http.NewRequest("POST", requestURL, bytes.NewReader(data))
|
req, err := http.NewRequest("POST", requestURL, bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Header.Add("UserAgent", "MicroMessenger Client")
|
req.Header.Add("UserAgent", "MicroMessenger Client")
|
||||||
request.Header.Add("Accept", "*/*")
|
req.Header.Add("Accept", "*/*")
|
||||||
request.Header.Add("Cache-Control", "no-cache")
|
req.Header.Add("Cache-Control", "no-cache")
|
||||||
request.Header.Add("Connection", "close")
|
req.Header.Add("Connection", "Keep-Alive")
|
||||||
request.Header.Add("content-type", "application/octet-stream")
|
req.Header.Add("content-type", "application/octet-stream")
|
||||||
request.Header.Add("Upgrade", "mmtls")
|
req.Header.Add("Upgrade", "mmtls")
|
||||||
request.Header.Add("Host", mmInfo.ShortHost)
|
req.Header.Add("Host", mmInfo.ShortHost)
|
||||||
//fmt.Println(mmInfo.ShortHost)//szshort.weixin.qq.com
|
// fmt.Println(mmInfo.ShortHost) //szshort.weixin.qq.com
|
||||||
|
|
||||||
// 发送请求
|
// 获取超时配置
|
||||||
httpTransport := &http.Transport{
|
timeout := time.Duration(mmInfo.ShortConnTimeout) * time.Second
|
||||||
Dial: func(network, addr string) (net.Conn, error) {
|
if timeout <= 0 {
|
||||||
conn, err := net.DialTimeout(network, addr, time.Second*15) //设置建立连接超时
|
timeout = 15 * time.Second
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
conn.SetDeadline(time.Now().Add(time.Second * 15)) //设置发送接受数据超时
|
|
||||||
return conn, nil
|
|
||||||
},
|
|
||||||
ResponseHeaderTimeout: time.Second * 15,
|
|
||||||
MaxIdleConnsPerHost: -1, //禁用连接池缓存
|
|
||||||
DisableKeepAlives: true, //禁用客户端连接缓存到连接池
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有代理
|
client := getMMHTTPClient(mmInfo, timeout)
|
||||||
if mmInfo.Dialer != nil {
|
if strings.Contains(tag, "openwxhb") || strings.Contains(tag, "openunionhb") || strings.Contains(tag, "receivewxhb") || strings.Contains(tag, "unionhb") {
|
||||||
httpTransport.Dial = mmInfo.Dialer.Dial
|
trace := &httptrace.ClientTrace{
|
||||||
|
GotConn: func(info httptrace.GotConnInfo) {
|
||||||
|
gotConnMs = time.Now().UnixMilli()
|
||||||
|
connReused = info.Reused
|
||||||
|
connWasIdle = info.WasIdle
|
||||||
|
connIdleMs = int64(info.IdleTime / time.Millisecond)
|
||||||
|
if info.Conn != nil && info.Conn.RemoteAddr() != nil {
|
||||||
|
remoteAddr = info.Conn.RemoteAddr().String()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ConnectStart: func(network, addr string) {
|
||||||
|
connectStartMs = time.Now().UnixMilli()
|
||||||
|
},
|
||||||
|
ConnectDone: func(network, addr string, err error) {
|
||||||
|
connectDoneMs = time.Now().UnixMilli()
|
||||||
|
},
|
||||||
|
WroteRequest: func(info httptrace.WroteRequestInfo) {
|
||||||
|
wroteRequestMs = time.Now().UnixMilli()
|
||||||
|
},
|
||||||
|
GotFirstResponseByte: func() {
|
||||||
|
firstByteMs = time.Now().UnixMilli()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &http.Client{Transport: httpTransport}
|
resp, err := client.Do(req)
|
||||||
resp, err := client.Do(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
baseutils.PrintLog(err.Error())
|
baseutils.PrintLog(err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if strings.Contains(tag, "openwxhb") || strings.Contains(tag, "openunionhb") || strings.Contains(tag, "receivewxhb") || strings.Contains(tag, "unionhb") {
|
||||||
|
fmt.Printf("MMHTTPPost resp tag=%s status=%s connHeader=%s close=%t\n", tag, resp.Status, resp.Header.Get("Connection"), resp.Close)
|
||||||
|
}
|
||||||
|
|
||||||
// 接收响应
|
// 接收响应
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
endMs := time.Now().UnixMilli()
|
||||||
|
if strings.Contains(tag, "openwxhb") || strings.Contains(tag, "openunionhb") || strings.Contains(tag, "receivewxhb") || strings.Contains(tag, "unionhb") {
|
||||||
|
connectMs := int64(0)
|
||||||
|
if connectStartMs > 0 && connectDoneMs > 0 {
|
||||||
|
connectMs = connectDoneMs - connectStartMs
|
||||||
|
}
|
||||||
|
ttfbMs := int64(0)
|
||||||
|
if firstByteMs > 0 {
|
||||||
|
ttfbMs = firstByteMs - startMs
|
||||||
|
}
|
||||||
|
writeMs := int64(0)
|
||||||
|
if wroteRequestMs > 0 {
|
||||||
|
writeMs = wroteRequestMs - startMs
|
||||||
|
}
|
||||||
|
gotConnDelayMs := int64(0)
|
||||||
|
if gotConnMs > 0 {
|
||||||
|
gotConnDelayMs = gotConnMs - startMs
|
||||||
|
}
|
||||||
|
fmt.Printf("MMHTTPPost trace tag=%s reused=%t wasIdle=%t idle=%dms gotConn=%dms connect=%dms wrote=%dms ttfb=%dms total=%dms\n",
|
||||||
|
tag, connReused, connWasIdle, connIdleMs, gotConnDelayMs, connectMs, writeMs, ttfbMs, endMs-startMs)
|
||||||
|
if remoteAddr != "" {
|
||||||
|
fmt.Printf("MMHTTPPost peer tag=%s remote=%s\n", tag, remoteAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 返回响应数据
|
// 返回响应数据
|
||||||
return body, nil
|
return body, nil
|
||||||
@@ -67,6 +190,8 @@ func MMHTTPPost(mmInfo *MMInfo, data []byte) ([]byte, error) {
|
|||||||
|
|
||||||
// MMHTTPPostData mmtls短链接方式发送请求数据包
|
// MMHTTPPostData mmtls短链接方式发送请求数据包
|
||||||
func MMHTTPPostData(mmInfo *MMInfo, url string, data []byte) ([]byte, error) {
|
func MMHTTPPostData(mmInfo *MMInfo, url string, data []byte) ([]byte, error) {
|
||||||
|
startMs := time.Now().UnixMilli()
|
||||||
|
createStartMs := startMs
|
||||||
// 创建HttpHandler
|
// 创建HttpHandler
|
||||||
httpHandler := &HTTPHandler{}
|
httpHandler := &HTTPHandler{}
|
||||||
httpHandler.URL = url
|
httpHandler.URL = url
|
||||||
@@ -75,24 +200,36 @@ func MMHTTPPostData(mmInfo *MMInfo, url string, data []byte) ([]byte, error) {
|
|||||||
|
|
||||||
// 创建发送请求项列表
|
// 创建发送请求项列表
|
||||||
sendItems, err := CreateSendPackItems(mmInfo, httpHandler)
|
sendItems, err := CreateSendPackItems(mmInfo, httpHandler)
|
||||||
|
createEndMs := time.Now().UnixMilli()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// MMTLS-加密要发送的数据
|
// MMTLS-加密要发送的数据
|
||||||
|
packStartMs := time.Now().UnixMilli()
|
||||||
packData, err := MMHTTPPackData(mmInfo, sendItems)
|
packData, err := MMHTTPPackData(mmInfo, sendItems)
|
||||||
|
packEndMs := time.Now().UnixMilli()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送数据
|
// 发送数据
|
||||||
respData, err := MMHTTPPost(mmInfo, packData)
|
postStartMs := time.Now().UnixMilli()
|
||||||
|
respData, err := MMHTTPPost(mmInfo, packData, url)
|
||||||
|
postEndMs := time.Now().UnixMilli()
|
||||||
// 解包响应数据
|
// 解包响应数据
|
||||||
|
decodeStartMs := time.Now().UnixMilli()
|
||||||
decodeData, err := MMDecodeResponseData(mmInfo, sendItems, respData)
|
decodeData, err := MMDecodeResponseData(mmInfo, sendItems, respData)
|
||||||
|
decodeEndMs := time.Now().UnixMilli()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
baseutils.PrintLog(err.Error())
|
baseutils.PrintLog(err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if strings.Contains(url, "openwxhb") || strings.Contains(url, "openunionhb") || strings.Contains(url, "receivewxhb") || strings.Contains(url, "unionhb") {
|
||||||
|
totalMs := decodeEndMs - startMs
|
||||||
|
fmt.Printf("MMHTTPPostData url=%s create=%dms pack=%dms post=%dms decode=%dms total=%dms\n",
|
||||||
|
url, createEndMs-createStartMs, packEndMs-packStartMs, postEndMs-postStartMs, decodeEndMs-decodeStartMs, totalMs)
|
||||||
|
}
|
||||||
return decodeData, nil
|
return decodeData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,13 +248,77 @@ func HTTPPost(mmInfo *MMInfo, cgi string, data []byte) ([]byte, error) {
|
|||||||
request.Header.Add("Cache-Control", "no-cache")
|
request.Header.Add("Cache-Control", "no-cache")
|
||||||
request.Header.Add("Connection", "Keep-Alive")
|
request.Header.Add("Connection", "Keep-Alive")
|
||||||
request.Header.Add("content-type", "application/octet-stream")
|
request.Header.Add("content-type", "application/octet-stream")
|
||||||
|
|
||||||
|
// 获取超时配置
|
||||||
|
timeout := time.Duration(mmInfo.ShortConnTimeout) * time.Second
|
||||||
|
if timeout <= 0 {
|
||||||
|
timeout = 15 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
httpTransport := &http.Transport{}
|
httpTransport := &http.Transport{
|
||||||
|
Dial: func(network, addr string) (net.Conn, error) {
|
||||||
|
conn, err := net.DialTimeout(network, addr, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn.SetDeadline(time.Now().Add(timeout))
|
||||||
|
return conn, nil
|
||||||
|
},
|
||||||
|
ResponseHeaderTimeout: timeout,
|
||||||
|
}
|
||||||
|
|
||||||
// 如果有代理
|
// 如果有代理
|
||||||
if mmInfo.Dialer != nil {
|
if mmInfo.Dialer != nil {
|
||||||
httpTransport.Dial = mmInfo.Dialer.Dial
|
httpTransport.Dial = func(network, addr string) (net.Conn, error) {
|
||||||
|
type result struct {
|
||||||
|
conn net.Conn
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
resultChan := make(chan result, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
conn, err := mmInfo.Dialer.Dial(network, addr)
|
||||||
|
resultChan <- result{conn: conn, err: err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case res := <-resultChan:
|
||||||
|
if res.err != nil {
|
||||||
|
if mmInfo.AllowDirectOnProxyFail {
|
||||||
|
baseutils.PrintLog("HTTPPost: Proxy failed, falling back to direct connection")
|
||||||
|
conn, err := net.DialTimeout(network, addr, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("proxy and direct connection both failed: %w", err)
|
||||||
|
}
|
||||||
|
conn.SetDeadline(time.Now().Add(timeout))
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("proxy connection failed: %w", res.err)
|
||||||
|
}
|
||||||
|
if res.conn != nil {
|
||||||
|
res.conn.SetDeadline(time.Now().Add(timeout))
|
||||||
|
}
|
||||||
|
return res.conn, nil
|
||||||
|
case <-time.After(timeout):
|
||||||
|
if mmInfo.AllowDirectOnProxyFail {
|
||||||
|
baseutils.PrintLog("HTTPPost: Proxy timeout, falling back to direct connection")
|
||||||
|
conn, err := net.DialTimeout(network, addr, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("proxy timeout and direct connection failed: %w", err)
|
||||||
|
}
|
||||||
|
conn.SetDeadline(time.Now().Add(timeout))
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("proxy connection timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: httpTransport,
|
||||||
|
Timeout: timeout,
|
||||||
}
|
}
|
||||||
client := &http.Client{Transport: httpTransport}
|
|
||||||
resp, err := client.Do(request)
|
resp, err := client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
baseutils.PrintLog(err.Error())
|
baseutils.PrintLog(err.Error())
|
||||||
@@ -126,7 +327,7 @@ func HTTPPost(mmInfo *MMInfo, cgi string, data []byte) ([]byte, error) {
|
|||||||
|
|
||||||
// 接收响应
|
// 接收响应
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,46 +17,94 @@ func MMLongConnect(mmInfo *MMInfo) error {
|
|||||||
strPort := strconv.Itoa(int(mmInfo.LONGPort))
|
strPort := strconv.Itoa(int(mmInfo.LONGPort))
|
||||||
serverAddr := mmInfo.LongHost + ":" + strPort
|
serverAddr := mmInfo.LongHost + ":" + strPort
|
||||||
fmt.Println("MMLongConnect serverAddr: ", serverAddr)
|
fmt.Println("MMLongConnect serverAddr: ", serverAddr)
|
||||||
mminfoDialer := "0"
|
// mminfoDialer := "0"
|
||||||
if mmInfo.Dialer != nil {
|
// if mmInfo.Dialer != nil {
|
||||||
mminfoDialer = "1"
|
// mminfoDialer = "1"
|
||||||
}
|
// }
|
||||||
// 打印 mmInfo.Dialer
|
// 打印 mmInfo.Dialer
|
||||||
fmt.Println("MMLongConnect mmInfo.Dialer: ", mminfoDialer)
|
// fmt.Println("MMLongConnect mmInfo.Dialer: ", mminfoDialer)
|
||||||
|
|
||||||
|
// 从MMInfo获取重试参数,如果未设置则使用默认值
|
||||||
|
maxRetries := mmInfo.LongConnRetryTimes
|
||||||
|
if maxRetries <= 0 {
|
||||||
|
maxRetries = 30
|
||||||
|
}
|
||||||
|
retryInterval := time.Duration(mmInfo.LongConnRetryInterval) * time.Millisecond
|
||||||
|
if retryInterval <= 0 {
|
||||||
|
retryInterval = 500 * time.Millisecond
|
||||||
|
}
|
||||||
|
connTimeout := time.Duration(mmInfo.LongConnTimeout) * time.Second
|
||||||
|
if connTimeout <= 0 {
|
||||||
|
connTimeout = 15 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调试:打印 AllowDirectOnProxyFail 的值
|
||||||
|
fmt.Printf("MMLongConnect AllowDirectOnProxyFail: %v\n", mmInfo.AllowDirectOnProxyFail)
|
||||||
|
|
||||||
// 定义
|
// 定义
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
var err error
|
var err error
|
||||||
for i := 0; i < 30; i++ {
|
proxyFailed := false // 标记代理是否已经失败过
|
||||||
if mmInfo.Dialer != nil {
|
|
||||||
|
for i := 0; i < maxRetries; i++ {
|
||||||
|
// 如果有代理且代理没有被标记为失败,尝试代理连接
|
||||||
|
if mmInfo.Dialer != nil && !proxyFailed {
|
||||||
|
// 使用代理连接
|
||||||
conn, err = mmInfo.Dialer.Dial("tcp4", serverAddr)
|
conn, err = mmInfo.Dialer.Dial("tcp4", serverAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
baseutils.PrintLog(fmt.Sprintf("MMLongConnect attempt %d failed: %s", i+1, err.Error()))
|
baseutils.PrintLog(fmt.Sprintf("MMLongConnect with proxy attempt %d/%d failed: %s", i+1, maxRetries, err.Error()))
|
||||||
// 等待 500 毫秒重时 (最大 30 次)
|
// 检查是否允许降级到直连
|
||||||
time.Sleep(500 * time.Millisecond)
|
if mmInfo.AllowDirectOnProxyFail {
|
||||||
|
baseutils.PrintLog("MMLongConnect: Proxy failed, falling back to direct connection")
|
||||||
|
proxyFailed = true // 标记代理失败,后续使用直连
|
||||||
|
// 不等待,立即尝试直连
|
||||||
|
} else {
|
||||||
|
// 不允许直连,等待后重试代理
|
||||||
|
time.Sleep(retryInterval)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 设置连接超时
|
||||||
|
if err := conn.SetDeadline(time.Now().Add(connTimeout)); err != nil {
|
||||||
|
baseutils.PrintLog(fmt.Sprintf("MMLongConnect SetDeadline failed: %s", err.Error()))
|
||||||
|
conn.Close()
|
||||||
|
time.Sleep(retryInterval)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mmInfo.Conn = conn
|
mmInfo.Conn = conn
|
||||||
mmInfo.reader = bufio.NewReader(conn)
|
mmInfo.reader = bufio.NewReader(conn)
|
||||||
fmt.Println("MMLongConnect success!")
|
fmt.Println("MMLongConnect with proxy success!")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 没有使用代理
|
// 没有使用代理或代理已失败 - 直连
|
||||||
conn, err = net.Dial("tcp4", serverAddr)
|
conn, err = net.DialTimeout("tcp4", serverAddr, connTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
baseutils.PrintLog(fmt.Sprintf("MMLongConnect attempt %d failed: %s", i+1, err.Error()))
|
baseutils.PrintLog(fmt.Sprintf("MMLongConnect direct attempt %d/%d failed: %s", i+1, maxRetries, err.Error()))
|
||||||
// 等待 500 毫秒重时 (最大 30 次)
|
// 等待重试
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(retryInterval)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 设置连接超时
|
||||||
|
if err := conn.SetDeadline(time.Now().Add(connTimeout)); err != nil {
|
||||||
|
baseutils.PrintLog(fmt.Sprintf("MMLongConnect SetDeadline failed: %s", err.Error()))
|
||||||
|
conn.Close()
|
||||||
|
time.Sleep(retryInterval)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mmInfo.Conn = conn
|
mmInfo.Conn = conn
|
||||||
mmInfo.reader = bufio.NewReader(conn)
|
mmInfo.reader = bufio.NewReader(conn)
|
||||||
|
if proxyFailed {
|
||||||
|
fmt.Println("MMLongConnect direct success (after proxy fallback)!")
|
||||||
|
} else {
|
||||||
|
fmt.Println("MMLongConnect direct success!")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("MMLongConnect failed after %d attempts: %w", maxRetries, err)
|
||||||
}
|
}
|
||||||
return nil
|
return errors.New("MMLongConnect failed: unknown error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MMTCPSendData MMTCPSendData 长链接发送数据
|
// MMTCPSendData MMTCPSendData 长链接发送数据
|
||||||
@@ -70,11 +118,20 @@ func MMTCPSendData(mmInfo *MMInfo, data []byte) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置写超时
|
||||||
|
writeTimeout := time.Duration(mmInfo.LongConnTimeout) * time.Second
|
||||||
|
if writeTimeout <= 0 {
|
||||||
|
writeTimeout = 15 * time.Second
|
||||||
|
}
|
||||||
|
if err := mmInfo.Conn.SetWriteDeadline(time.Now().Add(writeTimeout)); err != nil {
|
||||||
|
return fmt.Errorf("SetWriteDeadline failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 发送数据
|
// 发送数据
|
||||||
length, err := mmInfo.Conn.Write(data)
|
length, err := mmInfo.Conn.Write(data)
|
||||||
// 判断是否出错
|
// 判断是否出错
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("write failed: %w", err)
|
||||||
}
|
}
|
||||||
// 判断数据是否发送完毕
|
// 判断数据是否发送完毕
|
||||||
if length != len(data) {
|
if length != len(data) {
|
||||||
@@ -87,6 +144,18 @@ func MMTCPSendData(mmInfo *MMInfo, data []byte) error {
|
|||||||
// MMTCPRecvItems 循环接收长链接数据
|
// MMTCPRecvItems 循环接收长链接数据
|
||||||
// Deprecated
|
// Deprecated
|
||||||
func MMTCPRecvItems(mmInfo *MMInfo) ([]*PackItem, error) {
|
func MMTCPRecvItems(mmInfo *MMInfo) ([]*PackItem, error) {
|
||||||
|
// 设置读超时
|
||||||
|
readTimeout := time.Duration(mmInfo.LongConnReadTimeout) * time.Second
|
||||||
|
if readTimeout <= 0 {
|
||||||
|
readTimeout = time.Duration(mmInfo.LongConnTimeout) * time.Second
|
||||||
|
if readTimeout <= 0 {
|
||||||
|
readTimeout = 15 * time.Second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := mmInfo.Conn.SetReadDeadline(time.Now().Add(readTimeout)); err != nil {
|
||||||
|
return nil, fmt.Errorf("SetReadDeadline failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 接收返回数据
|
// 接收返回数据
|
||||||
recvBuf := make([]byte, 10240)
|
recvBuf := make([]byte, 10240)
|
||||||
recvLen, err := mmInfo.Conn.Read(recvBuf)
|
recvLen, err := mmInfo.Conn.Read(recvBuf)
|
||||||
@@ -107,17 +176,35 @@ func MMTCPRecvOneItem(mmInfo *MMInfo) (*PackItem, error) {
|
|||||||
return nil, errors.New("mmInfo.Conn or mmInfo.reader is nil")
|
return nil, errors.New("mmInfo.Conn or mmInfo.reader is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置读超时
|
||||||
|
readTimeout := time.Duration(mmInfo.LongConnReadTimeout) * time.Second
|
||||||
|
if readTimeout <= 0 {
|
||||||
|
readTimeout = time.Duration(mmInfo.LongConnTimeout) * time.Second
|
||||||
|
if readTimeout <= 0 {
|
||||||
|
readTimeout = 15 * time.Second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := mmInfo.Conn.SetReadDeadline(time.Now().Add(readTimeout)); err != nil {
|
||||||
|
return nil, fmt.Errorf("SetReadDeadline failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 读取头部数据
|
// 读取头部数据
|
||||||
recordHeadData := make([]byte, 5)
|
recordHeadData := make([]byte, 5)
|
||||||
if _, err := io.ReadFull(mmInfo.reader, recordHeadData); err != nil {
|
if _, err := io.ReadFull(mmInfo.reader, recordHeadData); err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("read header failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取Content
|
// 读取Content
|
||||||
recordHead := RecordHeadDeSerialize(recordHeadData)
|
recordHead := RecordHeadDeSerialize(recordHeadData)
|
||||||
bodyData := make([]byte, recordHead.Size)
|
bodyData := make([]byte, recordHead.Size)
|
||||||
|
|
||||||
|
// 重新设置读超时(针对body)
|
||||||
|
if err := mmInfo.Conn.SetReadDeadline(time.Now().Add(readTimeout)); err != nil {
|
||||||
|
return nil, fmt.Errorf("SetReadDeadline for body failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := io.ReadFull(mmInfo.reader, bodyData); err != nil {
|
if _, err := io.ReadFull(mmInfo.reader, bodyData); err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("read body failed: %w", err)
|
||||||
}
|
}
|
||||||
return &PackItem{
|
return &PackItem{
|
||||||
RecordHead: recordHeadData,
|
RecordHead: recordHeadData,
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ func CreateNewMMInfo() *MMInfo {
|
|||||||
mmInfo.LONGClientSeq = 1
|
mmInfo.LONGClientSeq = 1
|
||||||
mmInfo.LONGServerSeq = 1
|
mmInfo.LONGServerSeq = 1
|
||||||
|
|
||||||
|
// 从全局配置设置代理配置
|
||||||
|
mmInfo.LongConnTimeout = GlobalProxyConfig.LongConnTimeout
|
||||||
|
mmInfo.LongConnReadTimeout = GlobalProxyConfig.LongConnReadTimeout
|
||||||
|
mmInfo.LongConnRetryTimes = GlobalProxyConfig.LongConnRetryTimes
|
||||||
|
mmInfo.LongConnRetryInterval = GlobalProxyConfig.LongConnRetryInterval
|
||||||
|
mmInfo.ShortConnTimeout = GlobalProxyConfig.ShortConnTimeout
|
||||||
|
mmInfo.AllowDirectOnProxyFail = GlobalProxyConfig.AllowDirectOnProxyFail
|
||||||
|
|
||||||
return mmInfo
|
return mmInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +85,7 @@ func MMHandShakeByShortLink(mmInfo *MMInfo, hostName string) (*MMInfo, error) {
|
|||||||
// 发送握手请求 - ClientHello
|
// 发送握手请求 - ClientHello
|
||||||
clientHelloData := CreateHandShakeClientHelloData(mmInfo)
|
clientHelloData := CreateHandShakeClientHelloData(mmInfo)
|
||||||
sendData := CreateRecordData(ServerHandShakeType, clientHelloData)
|
sendData := CreateRecordData(ServerHandShakeType, clientHelloData)
|
||||||
retBytes, err := MMHTTPPost(mmInfo, sendData)
|
retBytes, err := MMHTTPPost(mmInfo, sendData, "handshake")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,23 @@ import (
|
|||||||
"golang.org/x/net/proxy"
|
"golang.org/x/net/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GlobalProxyConfig 全局代理配置(由 srv 层设置)
|
||||||
|
var GlobalProxyConfig = struct {
|
||||||
|
LongConnTimeout int
|
||||||
|
LongConnReadTimeout int
|
||||||
|
LongConnRetryTimes int
|
||||||
|
LongConnRetryInterval int
|
||||||
|
ShortConnTimeout int
|
||||||
|
AllowDirectOnProxyFail bool
|
||||||
|
}{
|
||||||
|
LongConnTimeout: 15,
|
||||||
|
LongConnReadTimeout: 210,
|
||||||
|
LongConnRetryTimes: 30,
|
||||||
|
LongConnRetryInterval: 500,
|
||||||
|
ShortConnTimeout: 15,
|
||||||
|
AllowDirectOnProxyFail: false,
|
||||||
|
}
|
||||||
|
|
||||||
// AesGcmParam AesGcm加密解密参数
|
// AesGcmParam AesGcm加密解密参数
|
||||||
type AesGcmParam struct {
|
type AesGcmParam struct {
|
||||||
AesKey []byte
|
AesKey []byte
|
||||||
@@ -66,6 +83,13 @@ type MMInfo struct {
|
|||||||
ClientEcdhKeys *ClientEcdhKeys
|
ClientEcdhKeys *ClientEcdhKeys
|
||||||
// 代理
|
// 代理
|
||||||
Dialer proxy.Dialer
|
Dialer proxy.Dialer
|
||||||
|
|
||||||
|
LongConnTimeout int
|
||||||
|
LongConnReadTimeout int
|
||||||
|
LongConnRetryTimes int
|
||||||
|
LongConnRetryInterval int
|
||||||
|
ShortConnTimeout int
|
||||||
|
AllowDirectOnProxyFail bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// EcdsaSignature 服务端传过来的校验数据
|
// EcdsaSignature 服务端传过来的校验数据
|
||||||
|
|||||||
@@ -1316,15 +1316,26 @@ func SendOpenWxHB(userInfo *baseinfo.UserInfo, hongBaoOpenItem *baseinfo.HongBao
|
|||||||
// fmt.Println("\nurlPath: ")
|
// fmt.Println("\nurlPath: ")
|
||||||
// fmt.Println(urlPath)
|
// fmt.Println(urlPath)
|
||||||
// 打包发送数据
|
// 打包发送数据
|
||||||
|
marshalStartMs := time.Now().UnixMilli()
|
||||||
srcData, _ := proto.Marshal(&request)
|
srcData, _ := proto.Marshal(&request)
|
||||||
|
marshalEndMs := time.Now().UnixMilli()
|
||||||
|
packStartMs := time.Now().UnixMilli()
|
||||||
sendEncodeData := Pack(userInfo, srcData, RequestType, 5)
|
sendEncodeData := Pack(userInfo, srcData, RequestType, 5)
|
||||||
|
packEndMs := time.Now().UnixMilli()
|
||||||
|
|
||||||
|
httpStartMs := time.Now().UnixMilli()
|
||||||
resp, err := mmtls.MMHTTPPostData(userInfo.MMInfo, urlPath, sendEncodeData)
|
resp, err := mmtls.MMHTTPPostData(userInfo.MMInfo, urlPath, sendEncodeData)
|
||||||
|
httpEndMs := time.Now().UnixMilli()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return DecodePackHeader(resp, nil)
|
decodeStartMs := time.Now().UnixMilli()
|
||||||
|
ph, err := DecodePackHeader(resp, nil)
|
||||||
|
decodeEndMs := time.Now().UnixMilli()
|
||||||
|
fmt.Printf("OpenWxHB细分 marshal=%dms pack=%dms http=%dms decodePackHeader=%dms\n",
|
||||||
|
marshalEndMs-marshalStartMs, packEndMs-packStartMs, httpEndMs-httpStartMs, decodeEndMs-decodeStartMs)
|
||||||
|
return ph, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendOpenUninoHB(userInfo *baseinfo.UserInfo, hongBaoOpenItem *baseinfo.HongBaoOpenItem) (*baseinfo.PackHeader, error) {
|
func SendOpenUninoHB(userInfo *baseinfo.UserInfo, hongBaoOpenItem *baseinfo.HongBaoOpenItem) (*baseinfo.PackHeader, error) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
"xiawan/wx/db/table"
|
||||||
"xiawan/wx/protobuf/wechat"
|
"xiawan/wx/protobuf/wechat"
|
||||||
|
|
||||||
"github.com/lunny/log"
|
"github.com/lunny/log"
|
||||||
@@ -60,8 +61,17 @@ func GenerateSignature(payload []byte, key string) string {
|
|||||||
func SendMessageCallback(msg *wechat.AddMsg, uuid string) {
|
func SendMessageCallback(msg *wechat.AddMsg, uuid string) {
|
||||||
// 获取回调配置
|
// 获取回调配置
|
||||||
config, err := GetMessageCallbackConfig(uuid)
|
config, err := GetMessageCallbackConfig(uuid)
|
||||||
if err != nil || config == nil || !config.Enabled {
|
if err != nil {
|
||||||
return // 没有配置或未启用回调,直接返回
|
log.Errorf("获取回调配置失败 [UUID: %s]: %v", uuid, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if config == nil {
|
||||||
|
log.Debugf("[回调调试] 未找到回调配置 [UUID: %s]", uuid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !config.Enabled {
|
||||||
|
log.Debugf("[回调调试] 回调未启用 [UUID: %s]", uuid)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用消息包装器包含UUID信息
|
// 使用消息包装器包含UUID信息
|
||||||
@@ -104,6 +114,59 @@ func SendMessageCallback(msg *wechat.AddMsg, uuid string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录回调结果
|
}
|
||||||
log.Infof("Message callback sent to %s for message %d, status: %d", config.CallbackURL, msg.GetMsgId(), resp.StatusCode)
|
|
||||||
|
// TestMessageCallback 测试消息回调配置
|
||||||
|
func TestMessageCallback(config *table.MessageCallbackConfig) (bool, string) {
|
||||||
|
// 构造测试消息
|
||||||
|
testPayload := map[string]interface{}{
|
||||||
|
"uuid": config.UUID,
|
||||||
|
"type": "test",
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"message": "这是一条测试回调消息",
|
||||||
|
"timestamp": time.Now().Unix(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(testPayload)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Sprintf("序列化测试数据失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建HTTP请求
|
||||||
|
req, err := http.NewRequest("POST", config.CallbackURL, bytes.NewBuffer(jsonBytes))
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Sprintf("创建请求失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("X-Timestamp", fmt.Sprintf("%d", time.Now().Unix()))
|
||||||
|
req.Header.Set("X-Test", "true")
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Sprintf("发送请求失败: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Sprintf("读取响应失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||||
|
log.Infof("✓ 测试回调成功 [UUID: %s] -> %s, 状态码: %d",
|
||||||
|
config.UUID, config.CallbackURL, resp.StatusCode)
|
||||||
|
return true, fmt.Sprintf("状态码: %d, 响应: %s", resp.StatusCode, string(body))
|
||||||
|
} else {
|
||||||
|
log.Warnf("✗ 测试回调失败 [UUID: %s] -> %s, 状态码: %d",
|
||||||
|
config.UUID, config.CallbackURL, resp.StatusCode)
|
||||||
|
return false, fmt.Sprintf("状态码: %d, 响应: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,14 +88,16 @@ func InitDB() {
|
|||||||
fmt.Println("auto create MySQL tables success")
|
fmt.Println("auto create MySQL tables success")
|
||||||
MysqlDB.LogMode(false)
|
MysqlDB.LogMode(false)
|
||||||
|
|
||||||
// 初始化回调配置
|
// 注意:回调配置的初始化移到了 InitAnewLogin 完成后
|
||||||
go InitMessageCallbacks()
|
// 这样可以确保所有账号连接都已建立
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitMessageCallbacks 初始化所有消息回调配置
|
// InitMessageCallbacks 初始化所有消息回调配置
|
||||||
func InitMessageCallbacks() {
|
func InitMessageCallbacks() {
|
||||||
// 等待数据库连接完全建立
|
// 账号已经初始化完成,稍微等待一下确保连接稳定
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
log.Infof("开始初始化消息回调配置...")
|
||||||
|
|
||||||
// 检查并重新连接数据库
|
// 检查并重新连接数据库
|
||||||
if err := checkAndReconnect(); err != nil {
|
if err := checkAndReconnect(); err != nil {
|
||||||
@@ -111,19 +113,34 @@ func InitMessageCallbacks() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保相关key字段正确设置
|
if len(configs) == 0 {
|
||||||
|
log.Infof("未找到启用的消息回调配置")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("找到 %d 个启用的回调配置,开始重新激活...", len(configs))
|
||||||
|
|
||||||
|
// 重新保存每个配置,相当于重新设置一遍,激活回调
|
||||||
|
successCount := 0
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
|
// 确保Key字段正确
|
||||||
if config.Key == "" {
|
if config.Key == "" {
|
||||||
// 如果Key为空但UUID不为空,更新Key字段
|
config.Key = config.UUID
|
||||||
MysqlDB.Model(&table.MessageCallbackConfig{}).
|
}
|
||||||
Where("id = ?", config.ID).
|
|
||||||
Updates(map[string]interface{}{
|
// 重新保存配置(相当于执行 SetCallback 操作)
|
||||||
"key": config.UUID,
|
err := SaveMessageCallbackConfig(&config)
|
||||||
})
|
if err != nil {
|
||||||
|
log.Errorf("✗ 重新激活回调失败 [UUID: %s]: %v", config.UUID, err)
|
||||||
|
} else {
|
||||||
|
log.Infof("✓ 重新激活回调成功 [UUID: %s] -> %s", config.UUID, config.CallbackURL)
|
||||||
|
successCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Initialized %d message callbacks", len(configs))
|
log.Infof("========================================")
|
||||||
|
log.Infof("回调配置激活完成: 成功 %d/%d", successCount, len(configs))
|
||||||
|
log.Infof("========================================")
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkAndReconnect 检查并在需要时重新连接
|
// checkAndReconnect 检查并在需要时重新连接
|
||||||
@@ -1608,7 +1625,7 @@ func SaveMessageCallbackConfig(config *table.MessageCallbackConfig) error {
|
|||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
if result.Error == gorm.ErrRecordNotFound {
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
// 记录不存在,创建新记录
|
// 记录不存在,创建新记录
|
||||||
// 创建包含完整字段的记录
|
log.Infof("[回调调试] 创建新配置记录 [UUID: %s]", config.UUID)
|
||||||
newConfig := table.MessageCallbackConfig{
|
newConfig := table.MessageCallbackConfig{
|
||||||
UUID: config.UUID,
|
UUID: config.UUID,
|
||||||
Key: config.UUID, // 确保Key和UUID相同
|
Key: config.UUID, // 确保Key和UUID相同
|
||||||
@@ -1619,8 +1636,6 @@ func SaveMessageCallbackConfig(config *table.MessageCallbackConfig) error {
|
|||||||
}
|
}
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录存在,更新记录
|
|
||||||
return MysqlDB.Model(&existingConfig).Updates(map[string]interface{}{
|
return MysqlDB.Model(&existingConfig).Updates(map[string]interface{}{
|
||||||
"key": config.UUID, // 更新Key字段
|
"key": config.UUID, // 更新Key字段
|
||||||
"callback_url": config.CallbackURL,
|
"callback_url": config.CallbackURL,
|
||||||
@@ -1639,11 +1654,12 @@ func GetMessageCallbackConfig(uuid string) (*table.MessageCallbackConfig, error)
|
|||||||
result := MysqlDB.Where("uuid = ?", uuid).First(&config)
|
result := MysqlDB.Where("uuid = ?", uuid).First(&config)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
if result.Error == gorm.ErrRecordNotFound {
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
return nil, nil // 返回nil表示配置不存在
|
log.Debugf("[回调调试] 数据库中未找到配置 [UUID: %s]", uuid)
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
log.Errorf("[回调调试] 查询配置出错 [UUID: %s]: %v", uuid, result.Error)
|
||||||
return nil, result.Error
|
return nil, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
return &config, nil
|
return &config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
main.go
5
main.go
@@ -10,9 +10,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
expireDate := time.Date(2030, 8, 17, 0, 0, 0, 0, time.Local)
|
fmt.Println("[+] 当前硬编码版本号: 0.0.1")
|
||||||
|
expireDate := time.Date(2028, 1, 30, 0, 0, 0, 0, time.Local)
|
||||||
if time.Now().After(expireDate) {
|
if time.Now().After(expireDate) {
|
||||||
fmt.Println("过期")
|
fmt.Println("qq:408449830")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
100
protobuf/wechat_proto/finderlive.proto
Normal file
100
protobuf/wechat_proto/finderlive.proto
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
import "wechat.proto";
|
||||||
|
|
||||||
|
package wechat_proto;
|
||||||
|
option go_package = "../wechat";
|
||||||
|
|
||||||
|
// 直播间发送评论请求
|
||||||
|
message FinderPostLiveMsgRequest {
|
||||||
|
optional FinderLiveMsgInfo msgInfo = 1; // 消息信息
|
||||||
|
optional FinderLiveClientInfo clientInfo = 2; // 客户端信息
|
||||||
|
optional FinderLiveSessionInfo sessionInfo = 3; // 会话信息
|
||||||
|
optional FinderLiveUserInfo userInfo = 4; // 用户信息
|
||||||
|
optional bytes extData = 5; // 扩展数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息信息
|
||||||
|
message FinderLiveMsgInfo {
|
||||||
|
optional uint32 msgType = 1; // 消息类型 (1=文本评论)
|
||||||
|
optional uint64 msgId = 2; // 消息ID
|
||||||
|
optional string content = 3; // 消息内容
|
||||||
|
optional uint32 timestamp = 4; // 时间戳
|
||||||
|
optional string clientMsgId = 5; // 客户端消息ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端信息
|
||||||
|
message FinderLiveClientInfo {
|
||||||
|
optional string deviceId = 1; // 设备ID (23-2-9488890-xxx)
|
||||||
|
optional string sessionId = 2; // 会话ID (8001-xxx)
|
||||||
|
optional string clientSession = 3; // 客户端会话信息 JSON
|
||||||
|
optional uint32 timestamp = 4; // 时间戳
|
||||||
|
optional bytes clientData = 5; // 客户端数据 (base64编码的推荐系统数据)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 会话信息
|
||||||
|
message FinderLiveSessionInfo {
|
||||||
|
optional uint64 liveId = 1; // 直播ID
|
||||||
|
optional string objectId = 2; // 对象ID
|
||||||
|
optional uint32 objectNonceId = 3; // 对象随机ID
|
||||||
|
optional string feedId = 4; // Feed ID
|
||||||
|
optional uint32 commentScene = 5; // 评论场景
|
||||||
|
optional uint64 objectIdLong = 6; // 对象ID (长整型)
|
||||||
|
optional uint32 liveTabType = 7; // 直播标签类型
|
||||||
|
optional string city = 8; // 城市
|
||||||
|
optional uint32 geohash = 9; // 地理位置哈希
|
||||||
|
optional uint32 tabFeedPos = 10; // Tab Feed位置
|
||||||
|
optional uint32 expertFlag = 11; // 专家标志
|
||||||
|
optional uint32 isLiveFeed = 12; // 是否直播Feed
|
||||||
|
optional uint32 isLiveFinderUser = 13; // 是否直播视频号用户
|
||||||
|
optional uint32 commentSceneV2 = 14; // 评论场景V2
|
||||||
|
optional string scid = 15; // 场景ID
|
||||||
|
optional uint64 commentVer = 16; // 评论版本
|
||||||
|
optional uint64 lsssid = 17; // LSSSID
|
||||||
|
optional uint32 idcy = 18; // IDCY
|
||||||
|
optional uint32 deviceTypeId = 19; // 设备类型ID
|
||||||
|
optional string devicePlatform = 20; // 设备平台 (BGBM10)
|
||||||
|
optional uint32 feedPos = 21; // Feed位置
|
||||||
|
optional uint32 pageNum = 22; // 页码
|
||||||
|
optional uint32 isLiveTab = 23; // 是否直播Tab
|
||||||
|
optional uint32 tabLivePos = 24; // Tab直播位置
|
||||||
|
repeated uint32 eril = 25; // ERIL数组
|
||||||
|
optional uint32 lrft = 26; // LRFT
|
||||||
|
optional uint32 jpht = 27; // JPHT
|
||||||
|
optional uint64 lgsid = 28; // LGSID
|
||||||
|
repeated bytes pgkeyes = 29; // PGKeys数组
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
message FinderLiveUserInfo {
|
||||||
|
optional string username = 1; // 用户名 (wxid_xxx)
|
||||||
|
optional string nickname = 2; // 昵称 (geer)
|
||||||
|
optional string headImgUrl = 3; // 头像URL
|
||||||
|
optional uint32 sex = 4; // 性别 (0=未知,1=男,2=女)
|
||||||
|
optional uint32 country = 5; // 国家
|
||||||
|
optional uint32 province = 6; // 省份
|
||||||
|
optional uint32 city = 7; // 城市
|
||||||
|
optional uint32 signature = 8; // 签名
|
||||||
|
optional uint32 verifyFlag = 9; // 认证标志
|
||||||
|
optional uint32 extFlag = 10; // 扩展标志
|
||||||
|
optional bytes extInfo = 11; // 扩展信息
|
||||||
|
optional uint32 originalFlag = 12; // 原创标志
|
||||||
|
optional uint32 liveStatus = 13; // 直播状态
|
||||||
|
optional bytes msgInfo = 14; // 消息信息
|
||||||
|
optional uint32 originalEntranceFlag = 15; // 原创入口标志
|
||||||
|
optional uint64 seq = 16; // 序列号
|
||||||
|
optional uint32 followFlag = 17; // 关注标志
|
||||||
|
optional uint32 followTime = 18; // 关注时间
|
||||||
|
optional bytes authInfo = 19; // 认证信息
|
||||||
|
optional string coverImgUrl = 20; // 封面图URL
|
||||||
|
optional uint32 spamStatus = 21; // 垃圾状态
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直播间发送评论响应
|
||||||
|
message FinderPostLiveMsgResponse {
|
||||||
|
optional BaseResponse baseResponse = 1; // 基础响应
|
||||||
|
optional uint32 ret = 2; // 返回码
|
||||||
|
optional string errMsg = 3; // 错误信息
|
||||||
|
optional uint64 msgId = 4; // 消息ID
|
||||||
|
optional uint32 timestamp = 5; // 时间戳
|
||||||
|
}
|
||||||
1
protobuf/查询直播间详情.bin
Normal file
1
protobuf/查询直播间详情.bin
Normal file
File diff suppressed because one or more lines are too long
2167
protobuf/直播间全流程操作.txt
Normal file
2167
protobuf/直播间全流程操作.txt
Normal file
File diff suppressed because one or more lines are too long
1
protobuf/进入直播间.bin
Normal file
1
protobuf/进入直播间.bin
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0a 2e 0a 01 00 10 cb bb e1 7d 1a 10 41 35 66 61 35 66 38 37 63 36 36 30 33 61 66 00 20 d3 82 81 c0 02 2a 0a 61 6e 64 72 6f 69 64 2d 33 34 30 00 12 a5 01 08 0c 10 00 20 f3 87 83 a8 02 2a 71 0a 27 32 2d 32 2d 36 35 2d 62 65 35 66 32 62 33 30 30 36 62 61 34 63 38 32 38 66 30 30 61 32 31 36 30 38 38 65 36 35 61 36 12 11 31 30 30 2d 31 37 36 37 30 32 36 31 33 38 39 34 38 1a 33 7b 22 73 65 73 73 69 6f 6e 49 64 22 3a 22 31 30 31 5f 31 37 36 37 30 32 36 31 30 30 38 39 30 23 24 30 5f 31 37 36 37 30 32 36 31 30 30 30 31 35 23 22 7d 30 ec c5 dc d7 b6 33 38 01 42 11 08 b8 92 c8 d5 98 d7 94 da cd 01 18 00 20 00 28 00 50 01 5a 06 18 00 20 00 28 00 68 00 18 b5 ca 88 b9 a5 d1 ed ac 1c 22 00 28 b8 92 c8 d5 98 d7 94 da cd 01 32 56 76 32 5f 30 36 30 30 30 30 32 33 31 30 30 33 62 32 30 66 61 65 63 38 63 37 65 33 38 61 31 31 63 33 64 34 63 38 30 63 65 35 33 32 62 30 37 37 64 39 63 64 30 66 62 65 33 36 33 37 61 39 63 62 63 39 38 63 30 66 37 66 33 61 36 65 36 61 32 62 40 66 69 6e 64 65 72 38 02 4a 1d 34 33 37 33 31 30 39 32 33 39 32 32 37 34 33 36 33 33 37 5f 30 5f 31 34 36 5f 30 5f 30 52 00 58 00 62 13 77 78 69 64 5f 35 39 66 68 69 34 36 34 64 75 71 35 32 32 68 00 70 00 8a 01 00 92 01 05 0a 03 08 af 05 a2 01 e0 01 12 23 31 30 31 5f 31 37 36 37 30 32 36 31 30 30 38 39 30 23 24 30 5f 31 37 36 37 30 32 36 31 30 30 30 31 35 23 18 e9 c5 dc d7 b6 33 20 d9 c6 dc d7 b6 33 2a 00 3a 00 40 06 4a 06 74 65 6d 70 5f 36 50 01 58 00 60 00 68 00 72 00 78 00 88 01 00 92 01 1e 08 00 10 00 50 00 5a 13 77 78 69 64 5f 35 39 66 68 69 34 36 34 64 75 71 35 32 32 80 01 00 98 01 00 a2 01 60 08 00 1a 5c 43 68 4e 33 65 47 6c 6b 58 7a 55 35 5a 6d 68 70 4e 44 59 30 5a 48 56 78 4e 54 49 79 45 68 4e 33 65 47 6c 6b 58 7a 56 32 4e 44 45 35 59 6d 4e 36 65 54 49 79 62 54 49 79 49 68 4d 33 4e 6a 59 33 4f 44 55 7a 4f 44 63 31 4e 54 55 31 4d 6a 4d 32 4e 6a 59 30 4b 4e 66 62 79 73 6f 47 ca 3e 06 74 65 6d 70 5f 36 aa 01 d6 03 08 00 12 04 57 49 46 49 18 af 05 22 06 50 47 42 4d 31 30 2a 04 4f 50 50 4f 32 17 50 47 42 4d 31 30 5f 31 34 2e 30 2e 30 2e 36 30 38 28 43 4e 30 31 29 3a 02 31 34 50 01 58 01 72 09 61 72 6d 36 34 2d 76 38 61 7a 1e 08 d5 dd a7 01 10 d0 01 18 f1 b7 99 87 b0 33 20 f3 b9 99 87 b0 33 28 01 38 00 48 00 50 00 7a 1d 08 f0 bc 5c 10 e8 01 18 cb fc f6 92 b0 33 20 c2 fe f6 92 b0 33 28 01 38 00 48 00 50 00 7a 1c 08 e2 90 09 10 43 18 e9 d3 9b 93 b0 33 20 ac d4 9b 93 b0 33 28 01 38 00 48 00 50 00 7a 1d 08 d8 f9 2b 10 d3 03 18 f0 f1 9a b1 b5 33 20 eb f5 9a b1 b5 33 28 01 38 00 48 00 50 00 7a 1d 08 ef f3 46 10 c2 0b 18 b3 8e 9b b1 b5 33 20 80 9d 9b b1 b5 33 28 01 38 00 48 00 50 00 7a 1d 08 ea 8e 45 10 c7 05 18 aa af d0 b1 b5 33 20 91 b5 d0 b1 b5 33 28 01 38 00 48 00 50 00 7a 1e 08 bc 85 a5 02 10 b9 19 18 a5 b7 d0 b1 b5 33 20 a1 dc d0 b1 b5 33 28 01 38 00 48 00 50 00 7a 1d 08 c4 c4 19 10 f9 01 18 b2 81 d1 b1 b5 33 20 b9 83 d1 b1 b5 33 28 01 38 00 48 00 50 00 7a 1c 08 e0 d2 15 10 50 18 a3 c6 ba 85 b6 33 20 fa c6 ba 85 b6 33 28 01 38 00 48 00 50 00 7a 1d 08 8e bb 2b 10 db 01 18 e4 f3 bd 85 b6 33 20 cb f5 bd 85 b6 33 28 01 38 00 48 00 50 00 7a 1d 08 93 ca 56 10 cf 02 18 8e e8 a0 86 b6 33 20 b0 eb a0 86 b6 33 28 01 38 00 48 00 50 00 7a 1c 08 9c f2 0c 10 55 18 b9 c7 c5 86 b6 33 20 8e c8 c5 86 b6 33 28 01 38 00 48 00 50 00 7a 1c 08 bb f5 0d 10 6c 18 b0 a3 ea 86 b6 33 20 9c a4 ea 86 b6 33 28 01 38 00 48 00 50 00 b8 01 00 ca 01 08 0a 06 32 2e 38 2e 32 31 da 01 00 f0 01 00 82 02 00 88 02 00 92 02 00
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"xiawan/wx/clientsdk/baseinfo"
|
"xiawan/wx/clientsdk/baseinfo"
|
||||||
"xiawan/wx/clientsdk/baseutils"
|
"xiawan/wx/clientsdk/baseutils"
|
||||||
|
"xiawan/wx/clientsdk/mmtls"
|
||||||
|
|
||||||
"github.com/gogf/gf/database/gredis"
|
"github.com/gogf/gf/database/gredis"
|
||||||
)
|
)
|
||||||
@@ -21,6 +22,25 @@ var GlobalSetting Setting
|
|||||||
// TaskExecWaitTimes 任务执行间隔时间 500毫秒
|
// TaskExecWaitTimes 任务执行间隔时间 500毫秒
|
||||||
var TaskExecWaitTimes = uint32(500)
|
var TaskExecWaitTimes = uint32(500)
|
||||||
|
|
||||||
|
// ProxyConfig 代理配置
|
||||||
|
type ProxyConfig struct {
|
||||||
|
// 代理失效后是否允许直连(false=不允许,连接失败;true=允许,降级为直连)
|
||||||
|
AllowDirectOnProxyFail bool `json:"allowDirectOnProxyFail"`
|
||||||
|
// 长连接超时时间(秒)
|
||||||
|
LongConnTimeout int `json:"longConnTimeout"`
|
||||||
|
LongConnReadTimeout int `json:"longConnReadTimeout"`
|
||||||
|
// 长连接建立时的重试次数
|
||||||
|
LongConnRetryTimes int `json:"longConnRetryTimes"`
|
||||||
|
// 长连接建立时的重试间隔(毫秒)
|
||||||
|
LongConnRetryInterval int `json:"longConnRetryInterval"`
|
||||||
|
// 短连接超时时间(秒)
|
||||||
|
ShortConnTimeout int `json:"shortConnTimeout"`
|
||||||
|
// 长连接断开后最大重试恢复次数
|
||||||
|
MaxLongRetryTimes int `json:"maxLongRetryTimes"`
|
||||||
|
// 长连接断开后重试间隔(秒)
|
||||||
|
LongRetryInterval int `json:"longRetryInterval"`
|
||||||
|
}
|
||||||
|
|
||||||
// Setting 设置
|
// Setting 设置
|
||||||
type Setting struct {
|
type Setting struct {
|
||||||
Debug bool `json:"debug"`
|
Debug bool `json:"debug"`
|
||||||
@@ -63,6 +83,8 @@ type Setting struct {
|
|||||||
Mac2Ipad bool `json:"mac2ipad"`
|
Mac2Ipad bool `json:"mac2ipad"`
|
||||||
// 是否开启 car2ipad
|
// 是否开启 car2ipad
|
||||||
Car2Ipad bool `json:"car2ipad"`
|
Car2Ipad bool `json:"car2ipad"`
|
||||||
|
// 代理配置
|
||||||
|
ProxyConfig ProxyConfig `json:"proxyConfig"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// getExternal 请求获取外网ip
|
// getExternal 请求获取外网ip
|
||||||
@@ -148,4 +170,47 @@ func ConfigSetUp() {
|
|||||||
if GlobalSetting.TargetIp == "" && GlobalSetting.SycnTargetIpUrl != "" {
|
if GlobalSetting.TargetIp == "" && GlobalSetting.SycnTargetIpUrl != "" {
|
||||||
GlobalSetting.TargetIp = getExternal()
|
GlobalSetting.TargetIp = getExternal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置代理配置默认值
|
||||||
|
if GlobalSetting.ProxyConfig.LongConnTimeout <= 0 {
|
||||||
|
GlobalSetting.ProxyConfig.LongConnTimeout = 15
|
||||||
|
}
|
||||||
|
if GlobalSetting.ProxyConfig.LongConnReadTimeout <= 0 {
|
||||||
|
readTimeout := GlobalSetting.ProxyConfig.LongConnTimeout
|
||||||
|
if readTimeout < 210 {
|
||||||
|
readTimeout = 210
|
||||||
|
}
|
||||||
|
GlobalSetting.ProxyConfig.LongConnReadTimeout = readTimeout
|
||||||
|
}
|
||||||
|
if GlobalSetting.ProxyConfig.LongConnRetryTimes <= 0 {
|
||||||
|
GlobalSetting.ProxyConfig.LongConnRetryTimes = 30
|
||||||
|
}
|
||||||
|
if GlobalSetting.ProxyConfig.LongConnRetryInterval <= 0 {
|
||||||
|
GlobalSetting.ProxyConfig.LongConnRetryInterval = 500
|
||||||
|
}
|
||||||
|
if GlobalSetting.ProxyConfig.ShortConnTimeout <= 0 {
|
||||||
|
GlobalSetting.ProxyConfig.ShortConnTimeout = 15
|
||||||
|
}
|
||||||
|
if GlobalSetting.ProxyConfig.MaxLongRetryTimes <= 0 {
|
||||||
|
GlobalSetting.ProxyConfig.MaxLongRetryTimes = 10
|
||||||
|
}
|
||||||
|
if GlobalSetting.ProxyConfig.LongRetryInterval <= 0 {
|
||||||
|
GlobalSetting.ProxyConfig.LongRetryInterval = 60
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步代理配置到 mmtls 全局配置
|
||||||
|
mmtls.GlobalProxyConfig.LongConnTimeout = GlobalSetting.ProxyConfig.LongConnTimeout
|
||||||
|
mmtls.GlobalProxyConfig.LongConnReadTimeout = GlobalSetting.ProxyConfig.LongConnReadTimeout
|
||||||
|
mmtls.GlobalProxyConfig.LongConnRetryTimes = GlobalSetting.ProxyConfig.LongConnRetryTimes
|
||||||
|
mmtls.GlobalProxyConfig.LongConnRetryInterval = GlobalSetting.ProxyConfig.LongConnRetryInterval
|
||||||
|
mmtls.GlobalProxyConfig.ShortConnTimeout = GlobalSetting.ProxyConfig.ShortConnTimeout
|
||||||
|
mmtls.GlobalProxyConfig.AllowDirectOnProxyFail = GlobalSetting.ProxyConfig.AllowDirectOnProxyFail
|
||||||
|
|
||||||
|
fmt.Printf("======== 代理配置已同步 ========\n")
|
||||||
|
fmt.Printf("代理失效后允许直连: %v\n", GlobalSetting.ProxyConfig.AllowDirectOnProxyFail)
|
||||||
|
fmt.Printf("长连接超时: %d秒\n", GlobalSetting.ProxyConfig.LongConnTimeout)
|
||||||
|
fmt.Printf("长连接重试次数: %d次\n", GlobalSetting.ProxyConfig.LongConnRetryTimes)
|
||||||
|
fmt.Printf("长连接重试间隔: %dms\n", GlobalSetting.ProxyConfig.LongConnRetryInterval)
|
||||||
|
fmt.Printf("短连接超时: %d秒\n", GlobalSetting.ProxyConfig.ShortConnTimeout)
|
||||||
|
fmt.Printf("========================\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"xiawan/wx/clientsdk/mmtls"
|
"xiawan/wx/clientsdk/mmtls"
|
||||||
"xiawan/wx/db"
|
"xiawan/wx/db"
|
||||||
"xiawan/wx/srv"
|
"xiawan/wx/srv"
|
||||||
|
"xiawan/wx/srv/srvconfig"
|
||||||
"xiawan/wx/srv/websrv"
|
"xiawan/wx/srv/websrv"
|
||||||
"xiawan/wx/srv/wxface"
|
"xiawan/wx/srv/wxface"
|
||||||
)
|
)
|
||||||
@@ -235,10 +236,9 @@ func (wxconn *WXConnect) StartShortReader() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
forCount := 0
|
forCount := 0
|
||||||
// 最大重试次数
|
// 从配置获取最大重试次数和重试间隔
|
||||||
const maxLongRetries = 5
|
maxLongRetries := srvconfig.GlobalSetting.ProxyConfig.MaxLongRetryTimes
|
||||||
// 最小重试间隔 (秒)
|
minRetryInterval := int64(srvconfig.GlobalSetting.ProxyConfig.LongRetryInterval)
|
||||||
const minRetryInterval = 60
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// 判断是否重复启动 for 循环
|
// 判断是否重复启动 for 循环
|
||||||
@@ -311,8 +311,11 @@ func (wxconn *WXConnect) StartShortReader() {
|
|||||||
shouldRetry := (currentTime-wxconn.lastLongRetryTime >= minRetryInterval) &&
|
shouldRetry := (currentTime-wxconn.lastLongRetryTime >= minRetryInterval) &&
|
||||||
(wxconn.longRetryCount < maxLongRetries)
|
(wxconn.longRetryCount < maxLongRetries)
|
||||||
|
|
||||||
// 如果重试计数器已达上限,但已经过去了更长时间(比如10分钟),重置计数器
|
// 如果重试计数器已达上限,但已经过去了更长时间(比如10倍间隔),重置计数器
|
||||||
if wxconn.longRetryCount >= maxLongRetries && (currentTime-wxconn.lastLongRetryTime >= minRetryInterval*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
|
wxconn.longRetryCount = 0
|
||||||
shouldRetry = true
|
shouldRetry = true
|
||||||
}
|
}
|
||||||
@@ -369,10 +372,11 @@ func (wxconn *WXConnect) StartShortReader() {
|
|||||||
} else if wxconn.longRetryCount >= maxLongRetries {
|
} else if wxconn.longRetryCount >= maxLongRetries {
|
||||||
// 只有在第一次达到最大重试次数时记录日志
|
// 只有在第一次达到最大重试次数时记录日志
|
||||||
if wxconn.longRetryCount == maxLongRetries {
|
if wxconn.longRetryCount == maxLongRetries {
|
||||||
log.Printf("[%s],[%s] 已达到最大重试次数(%d),暂停长连接恢复尝试\n",
|
log.Printf("[%s],[%s] 已达到最大重试次数(%d),暂停长连接恢复尝试(将在%d秒后重置)\n",
|
||||||
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
|
wxconn.GetWXAccount().GetUserInfo().GetUserName(),
|
||||||
wxconn.GetWXAccount().GetUserInfo().NickName,
|
wxconn.GetWXAccount().GetUserInfo().NickName,
|
||||||
maxLongRetries)
|
maxLongRetries,
|
||||||
|
minRetryInterval*10)
|
||||||
wxconn.longRetryCount++ // 增加一次以避免重复打印
|
wxconn.longRetryCount++ // 增加一次以避免重复打印
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -597,6 +601,14 @@ func (wxconn *WXConnect) startLongLink() error {
|
|||||||
}
|
}
|
||||||
tmpMMInfo.Dialer = dialer
|
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)
|
wxconn.setConnected(true)
|
||||||
userInfo.MMInfo = tmpMMInfo
|
userInfo.MMInfo = tmpMMInfo
|
||||||
// 启动长链接发送接收协程(创建新的定时器)
|
// 启动长链接发送接收协程(创建新的定时器)
|
||||||
|
|||||||
@@ -1480,7 +1480,9 @@ func (wxqi *WXReqInvoker) SendOpenRedEnvelopesRequest(hbItem *baseinfo.HongBaoIt
|
|||||||
hongBaoReceiverItem.InWay = baseinfo.MMHongBaoReqInAwayPersonal
|
hongBaoReceiverItem.InWay = baseinfo.MMHongBaoReqInAwayPersonal
|
||||||
}
|
}
|
||||||
// 发送接收红包请求
|
// 发送接收红包请求
|
||||||
|
recvReqStartMs := time.Now().UnixMilli()
|
||||||
packHeader, err := clientsdk.SendReceiveWxHB(wxqi.wxconn.GetWXAccount().GetUserInfo(), hongBaoReceiverItem)
|
packHeader, err := clientsdk.SendReceiveWxHB(wxqi.wxconn.GetWXAccount().GetUserInfo(), hongBaoReceiverItem)
|
||||||
|
recvReqEndMs := time.Now().UnixMilli()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if packHeader != nil && packHeader.RetCode == baseinfo.MMRequestRetSessionTimeOut {
|
if packHeader != nil && packHeader.RetCode == baseinfo.MMRequestRetSessionTimeOut {
|
||||||
// token登陆
|
// token登陆
|
||||||
@@ -1489,25 +1491,52 @@ func (wxqi *WXReqInvoker) SendOpenRedEnvelopesRequest(hbItem *baseinfo.HongBaoIt
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var hongbaoResp wechat.HongBaoRes
|
var hongbaoResp wechat.HongBaoRes
|
||||||
|
parseRespStartMs := time.Now().UnixMilli()
|
||||||
errs := clientsdk.ParseResponseData(tmpUserInfo, packHeader, &hongbaoResp)
|
errs := clientsdk.ParseResponseData(tmpUserInfo, packHeader, &hongbaoResp)
|
||||||
|
parseRespEndMs := time.Now().UnixMilli()
|
||||||
|
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
return nil, errs
|
return nil, errs
|
||||||
}
|
}
|
||||||
// 解析
|
// 解析
|
||||||
retHongBaoReceiveResp := &baseinfo.HongBaoReceiverResp{}
|
retHongBaoReceiveResp := &baseinfo.HongBaoReceiverResp{}
|
||||||
|
unmarshalRecvStartMs := time.Now().UnixMilli()
|
||||||
err = json.Unmarshal(hongbaoResp.GetRetText().GetBuffer(), retHongBaoReceiveResp)
|
err = json.Unmarshal(hongbaoResp.GetRetText().GetBuffer(), retHongBaoReceiveResp)
|
||||||
|
unmarshalRecvEndMs := time.Now().UnixMilli()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// 发送给微信消息处理器 打开红包
|
// 发送给微信消息处理器 打开红包
|
||||||
|
openReqStartMs := time.Now().UnixMilli()
|
||||||
rsp, er := wxqi.SendOpenWxHBNewRequest(hbItem, retHongBaoReceiveResp.TimingIdentifier)
|
rsp, er := wxqi.SendOpenWxHBNewRequest(hbItem, retHongBaoReceiveResp.TimingIdentifier)
|
||||||
|
openReqEndMs := time.Now().UnixMilli()
|
||||||
if er != nil {
|
if er != nil {
|
||||||
return nil, er
|
return nil, er
|
||||||
}
|
}
|
||||||
|
|
||||||
wxqi.SendOpenHBMsgToSelf(rsp, hbItem)
|
var data RedPacket
|
||||||
wxqi.ThanksHB(rsp)
|
if rsp != nil {
|
||||||
|
_ = json.Unmarshal(rsp.RetText.Buffer, &data)
|
||||||
|
}
|
||||||
|
nowMs := time.Now().UnixMilli()
|
||||||
|
if hbItem != nil && hbItem.RecvAtMs > 0 && data.Retcode == 0 && data.ReceiveStatus == 2 {
|
||||||
|
detectToEnqueue := hbItem.EnqueueAtMs - hbItem.RecvAtMs
|
||||||
|
enqueueToDequeue := hbItem.DequeueAtMs - hbItem.EnqueueAtMs
|
||||||
|
dequeueToRecvStart := recvReqStartMs - hbItem.DequeueAtMs
|
||||||
|
recvNet := recvReqEndMs - recvReqStartMs
|
||||||
|
parseResp := parseRespEndMs - parseRespStartMs
|
||||||
|
unmarshalRecv := unmarshalRecvEndMs - unmarshalRecvStartMs
|
||||||
|
openNet := openReqEndMs - openReqStartMs
|
||||||
|
openDoneMs := openReqEndMs - hbItem.RecvAtMs
|
||||||
|
totalMs := nowMs - hbItem.RecvAtMs
|
||||||
|
fmt.Printf("红包耗时 total=%dms open=%dms detect->enqueue=%dms enqueue->dequeue=%dms dequeue->recvStart=%dms recvNet=%dms parse=%dms recvUnmarshal=%dms openNet=%dms sendid=%s isGroup=%d fromUser=%s\n",
|
||||||
|
totalMs, openDoneMs, detectToEnqueue, enqueueToDequeue, dequeueToRecvStart, recvNet, parseResp, unmarshalRecv, openNet, data.SendId, hbItem.IsGroup, hbItem.FromUserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// go func() {
|
||||||
|
// wxqi.SendOpenHBMsgToSelf(rsp, hbItem)
|
||||||
|
// wxqi.ThanksHB(rsp)
|
||||||
|
// }()
|
||||||
return rsp, nil
|
return rsp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2052,12 +2081,16 @@ func (wxqi *WXReqInvoker) SendOpenWxHBNewRequest(hbItem *baseinfo.HongBaoItem, t
|
|||||||
|
|
||||||
var packHeader *baseinfo.PackHeader
|
var packHeader *baseinfo.PackHeader
|
||||||
var err error
|
var err error
|
||||||
|
openSendStartMs := time.Now().UnixMilli()
|
||||||
|
openPath := "openwxhb"
|
||||||
if hongBaoOpenItem.SceneID == 1005 {
|
if hongBaoOpenItem.SceneID == 1005 {
|
||||||
hongBaoOpenItem.CgiCmd = 5148 // 这里是pb字段的cgicmd 抓包得到
|
hongBaoOpenItem.CgiCmd = 5148 // 这里是pb字段的cgicmd 抓包得到
|
||||||
packHeader, err = clientsdk.SendOpenUninoHB(wxqi.wxconn.GetWXAccount().GetUserInfo(), hongBaoOpenItem)
|
packHeader, err = clientsdk.SendOpenUninoHB(wxqi.wxconn.GetWXAccount().GetUserInfo(), hongBaoOpenItem)
|
||||||
|
openPath = "openunionhb"
|
||||||
} else {
|
} else {
|
||||||
packHeader, err = clientsdk.SendOpenWxHB(wxqi.wxconn.GetWXAccount().GetUserInfo(), hongBaoOpenItem)
|
packHeader, err = clientsdk.SendOpenWxHB(wxqi.wxconn.GetWXAccount().GetUserInfo(), hongBaoOpenItem)
|
||||||
}
|
}
|
||||||
|
openSendEndMs := time.Now().UnixMilli()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if packHeader != nil && packHeader.RetCode == baseinfo.MMRequestRetSessionTimeOut {
|
if packHeader != nil && packHeader.RetCode == baseinfo.MMRequestRetSessionTimeOut {
|
||||||
@@ -2069,12 +2102,17 @@ func (wxqi *WXReqInvoker) SendOpenWxHBNewRequest(hbItem *baseinfo.HongBaoItem, t
|
|||||||
//wechat.HongBaoRes{}
|
//wechat.HongBaoRes{}
|
||||||
// 解析获取联系人响应
|
// 解析获取联系人响应
|
||||||
resp := new(wechat.HongBaoRes)
|
resp := new(wechat.HongBaoRes)
|
||||||
|
openParseStartMs := time.Now().UnixMilli()
|
||||||
err = clientsdk.ParseResponseData(tmpUserInfo, packHeader, resp)
|
err = clientsdk.ParseResponseData(tmpUserInfo, packHeader, resp)
|
||||||
|
openParseEndMs := time.Now().UnixMilli()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// token登陆
|
// token登陆
|
||||||
wxqi.wxconn.SendAutoAuthWaitingMinutes(4)
|
wxqi.wxconn.SendAutoAuthWaitingMinutes(4)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if hbItem != nil && hbItem.RecvAtMs > 0 {
|
||||||
|
fmt.Printf("红包open阶段 send=%dms parse=%dms path=%s cgi=%d scene=%d\n", openSendEndMs-openSendStartMs, openParseEndMs-openParseStartMs, openPath, hongBaoOpenItem.CgiCmd, hongBaoOpenItem.SceneID)
|
||||||
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package wxcore
|
package wxcore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"xiawan/wx/srv/wxface"
|
"xiawan/wx/srv/wxface"
|
||||||
"xiawan/wx/srv/wxtask"
|
"xiawan/wx/srv/wxtask"
|
||||||
)
|
)
|
||||||
@@ -37,7 +36,7 @@ func NewWXTaskMgr(wxConn wxface.IWXConnect) wxface.IWXTaskMgr {
|
|||||||
|
|
||||||
// Start 启动
|
// Start 启动
|
||||||
func (wxtm *WXTaskMgr) Start() {
|
func (wxtm *WXTaskMgr) Start() {
|
||||||
fmt.Println("启动微信任务管理器")
|
// fmt.Println("启动微信任务管理器")
|
||||||
//处理异常
|
//处理异常
|
||||||
// defer TryE("(wxtm *WXTaskMgr) Start()")
|
// defer TryE("(wxtm *WXTaskMgr) Start()")
|
||||||
if wxtm.start {
|
if wxtm.start {
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ func (cqr *WXCheckQrcodeRouter) Handle(wxResp wxface.IWXResponse) error {
|
|||||||
|
|
||||||
str := byteArrayToString(retBytes)
|
str := byteArrayToString(retBytes)
|
||||||
ticketValue := extractTicketValue(str)
|
ticketValue := extractTicketValue(str)
|
||||||
|
// fmt.Println(ticketValue, 111111111111)
|
||||||
|
|
||||||
if ticketValue != "" {
|
if ticketValue != "" {
|
||||||
// 赋值ticketValue
|
// 赋值ticketValue
|
||||||
|
|||||||
@@ -116,6 +116,14 @@ func (glqr *WXManualAuthRouter) Handle(wxResp wxface.IWXResponse) error {
|
|||||||
db.SetLoginLog("ManualAuth", currentWXAccount, "登录成功!", retCode)
|
db.SetLoginLog("ManualAuth", currentWXAccount, "登录成功!", retCode)
|
||||||
fmt.Println("currentUserInfo扫码登录响应路由", currentUserInfo.DeviceInfo)
|
fmt.Println("currentUserInfo扫码登录响应路由", currentUserInfo.DeviceInfo)
|
||||||
db.SaveUserInfo(currentUserInfo)
|
db.SaveUserInfo(currentUserInfo)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
if !currentCache.IsInitNewSyncFinished() {
|
||||||
|
fmt.Println("[回调修复] 登录后自动设置初始化完成标志,确保回调正常工作")
|
||||||
|
currentCache.SetInitNewSyncFinished(true)
|
||||||
|
}
|
||||||
|
}()
|
||||||
/*time.Sleep(time.Second * 10)
|
/*time.Sleep(time.Second * 10)
|
||||||
currentWXConn.Stop()*/
|
currentWXConn.Stop()*/
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ func (hbr *WXNewSyncRouter) Handle(wxResp wxface.IWXResponse) error {
|
|||||||
// 如果没有同步到数据则返回bInitNewSyncFinished
|
// 如果没有同步到数据则返回bInitNewSyncFinished
|
||||||
cmdList := syncResp.GetCmdList()
|
cmdList := syncResp.GetCmdList()
|
||||||
syncCount := cmdList.GetCount()
|
syncCount := cmdList.GetCount()
|
||||||
|
continueFlag := syncResp.GetContinueFlag()
|
||||||
//log.Info(syncResp.GetContinueFlag(), syncCount)
|
//log.Info(syncResp.GetContinueFlag(), syncCount)
|
||||||
//redis 发布结构体
|
//redis 发布结构体
|
||||||
messageResp := new(table.SyncMessageResponse)
|
messageResp := new(table.SyncMessageResponse)
|
||||||
@@ -132,10 +133,10 @@ func (hbr *WXNewSyncRouter) Handle(wxResp wxface.IWXResponse) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 判断消息初始化完成
|
// 判断消息初始化完成
|
||||||
if syncCount <= 0 {
|
if continueFlag <= 0 || syncCount <= 0 {
|
||||||
if !currentWXCache.IsInitNewSyncFinished() {
|
if !currentWXCache.IsInitNewSyncFinished() {
|
||||||
// 禁用历史消息推送,不设置初始化完成标志
|
|
||||||
currentWXCache.SetInitNewSyncFinished(true)
|
currentWXCache.SetInitNewSyncFinished(true)
|
||||||
|
fmt.Println("[回调修复] 历史消息同步完成,自动激活回调功能")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,21 +315,21 @@ func dealAddMsgRun(wxConn wxface.IWXConnect, addMsg *wechat.AddMsg, syncType str
|
|||||||
// } else if strings.HasSuffix(fromUserName, "@chatroom") {
|
// } else if strings.HasSuffix(fromUserName, "@chatroom") {
|
||||||
// // 红包处理
|
// // 红包处理
|
||||||
// dealGroupHB(wxConn, msgContent, fromUserName)
|
// dealGroupHB(wxConn, msgContent, fromUserName)
|
||||||
// // 群转账给我处理
|
// // // 群转账给我处理
|
||||||
// if toUserName == wxAccount.GetUserInfo().GetUserName() {
|
// // if toUserName == wxAccount.GetUserInfo().GetUserName() {
|
||||||
// dealAutoTransfer(wxConn, msgContent, fromUserName)
|
// // dealAutoTransfer(wxConn, msgContent, fromUserName)
|
||||||
// }
|
// // }
|
||||||
// } else {
|
// } else {
|
||||||
// // 判断是个人红包
|
// // 判断是个人红包
|
||||||
// dealPersonHB(wxConn, msgContent, fromUserName)
|
// dealPersonHB(wxConn, msgContent, fromUserName)
|
||||||
// // 个人转账处理
|
// // // 个人转账处理
|
||||||
// if toUserName == wxAccount.GetUserInfo().GetUserName() {
|
// // if toUserName == wxAccount.GetUserInfo().GetUserName() {
|
||||||
// dealAutoTransfer(wxConn, msgContent, fromUserName)
|
// // dealAutoTransfer(wxConn, msgContent, fromUserName)
|
||||||
// }
|
// // }
|
||||||
// // 邀请入群处理
|
// // 邀请入群处理
|
||||||
// // 创建一个数组,用于存储命令
|
// // 创建一个数组,用于存储命令
|
||||||
// newMsgId := strconv.FormatUint(uint64(addMsg.GetNewMsgId()), 10)
|
// // newMsgId := strconv.FormatUint(uint64(addMsg.GetNewMsgId()), 10)
|
||||||
// dealAutoJoinGroup(wxConn, msgContent, newMsgId)
|
// // dealAutoJoinGroup(wxConn, msgContent, newMsgId)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// 判断是否是命令
|
// 判断是否是命令
|
||||||
@@ -927,9 +928,9 @@ func dealGroupHB(wxConn wxface.IWXConnect, content string, groupWXID string) {
|
|||||||
currentTaskMgr := wxConn.GetWXTaskMgr()
|
currentTaskMgr := wxConn.GetWXTaskMgr()
|
||||||
taskMgr, _ := currentTaskMgr.(*wxcore.WXTaskMgr)
|
taskMgr, _ := currentTaskMgr.(*wxcore.WXTaskMgr)
|
||||||
currentGrapHBMgr := taskMgr.GetGrabHBTask()
|
currentGrapHBMgr := taskMgr.GetGrabHBTask()
|
||||||
if !currentGrapHBMgr.IsAutoGrap() {
|
// if !currentGrapHBMgr.IsAutoGrap() {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
// 解析引用的消息
|
// 解析引用的消息
|
||||||
tmpMsg := new(baseinfo.Msg)
|
tmpMsg := new(baseinfo.Msg)
|
||||||
err := xml.Unmarshal([]byte(content), tmpMsg)
|
err := xml.Unmarshal([]byte(content), tmpMsg)
|
||||||
@@ -947,70 +948,71 @@ func dealGroupHB(wxConn wxface.IWXConnect, content string, groupWXID string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//是否置顶的群
|
recvAtMs := time.Now().UnixMilli()
|
||||||
isTopGroup := currentGrapHBMgr.IsTopGroup(groupWXID)
|
// //是否置顶的群
|
||||||
//(false 不抢置顶群, true 只抢置顶群)判断没有置顶 return
|
// isTopGroup := currentGrapHBMgr.IsTopGroup(groupWXID)
|
||||||
if currentGrapHBMgr.IsNotTopGroup() && !isTopGroup {
|
// //(false 不抢置顶群, true 只抢置顶群)判断没有置顶 return
|
||||||
return
|
// if currentGrapHBMgr.IsNotTopGroup() && !isTopGroup {
|
||||||
}
|
// return
|
||||||
//(false 不抢置顶群) 判断已置顶 return
|
// }
|
||||||
if !currentGrapHBMgr.IsNotTopGroup() && isTopGroup {
|
// //(false 不抢置顶群) 判断已置顶 return
|
||||||
return
|
// if !currentGrapHBMgr.IsNotTopGroup() && isTopGroup {
|
||||||
}
|
// return
|
||||||
|
// }
|
||||||
//延迟抢红包
|
//延迟抢红包
|
||||||
delaytime := currentGrapHBMgr.GetDelayOpen()
|
// delaytime := currentGrapHBMgr.GetDelayOpen()
|
||||||
if int(delaytime) >= 1 {
|
// if int(delaytime) >= 1 {
|
||||||
time.Sleep(time.Duration(delaytime) * time.Second)
|
// time.Sleep(time.Duration(delaytime) * time.Second)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 过滤汉字/字母红包
|
// 过滤汉字/字母红包
|
||||||
if currentGrapHBMgr.IsFilterText() {
|
// if currentGrapHBMgr.IsFilterText() {
|
||||||
text := tmpMsg.APPMsg.WCPayInfo.ReceiverTitle
|
// text := tmpMsg.APPMsg.WCPayInfo.ReceiverTitle
|
||||||
// 判断是否包含汉字或字母
|
// // 判断是否包含汉字或字母
|
||||||
if isTextContainsChineseOrLetter(text) {
|
// if isTextContainsChineseOrLetter(text) {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// 过滤测挂红包
|
// 过滤测挂红包
|
||||||
if currentGrapHBMgr.IsFilterCheats() {
|
// if currentGrapHBMgr.IsFilterCheats() {
|
||||||
filterCheatsKey := currentGrapHBMgr.GetFilterCheatsKey()
|
// filterCheatsKey := currentGrapHBMgr.GetFilterCheatsKey()
|
||||||
// fmt.Println(filterCheatsKey)
|
// // fmt.Println(filterCheatsKey)
|
||||||
// 字符串转 数组
|
// // 字符串转 数组
|
||||||
temKeywords := strings.Split(filterCheatsKey, ",")
|
// temKeywords := strings.Split(filterCheatsKey, ",")
|
||||||
// 关键字
|
// // 关键字
|
||||||
keywords := make([]string, 0)
|
// keywords := make([]string, 0)
|
||||||
// 去掉头尾空格
|
// // 去掉头尾空格
|
||||||
for i := 0; i < len(temKeywords); i++ {
|
// for i := 0; i < len(temKeywords); i++ {
|
||||||
keywordItem := strings.TrimSpace(temKeywords[i])
|
// keywordItem := strings.TrimSpace(temKeywords[i])
|
||||||
if keywordItem != "" {
|
// if keywordItem != "" {
|
||||||
keywords = append(keywords, keywordItem)
|
// keywords = append(keywords, keywordItem)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// fmt.Println(keywords)
|
// // fmt.Println(keywords)
|
||||||
// 判断关键字是否为空
|
// // 判断关键字是否为空
|
||||||
if len(keywords) > 0 {
|
// if len(keywords) > 0 {
|
||||||
text := tmpMsg.APPMsg.WCPayInfo.ReceiverTitle
|
// text := tmpMsg.APPMsg.WCPayInfo.ReceiverTitle
|
||||||
for _, keyword := range keywords {
|
// for _, keyword := range keywords {
|
||||||
if strings.Contains(text, keyword) {
|
// if strings.Contains(text, keyword) {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// 开始抢红包
|
// 开始抢红包
|
||||||
hbItem := new(baseinfo.HongBaoItem)
|
hbItem := new(baseinfo.HongBaoItem)
|
||||||
hbItem.IsGroup = 1
|
hbItem.IsGroup = 1
|
||||||
hbItem.SceneID = tmpMsg.APPMsg.WCPayInfo.SceneID
|
hbItem.SceneID = tmpMsg.APPMsg.WCPayInfo.SceneID
|
||||||
hbItem.NativeURL = tmpMsg.APPMsg.WCPayInfo.NativeURL
|
hbItem.NativeURL = tmpMsg.APPMsg.WCPayInfo.NativeURL
|
||||||
|
hbItem.RecvAtMs = recvAtMs
|
||||||
hongBaoURLItem, err := clientsdk.ParseHongBaoURL(hbItem.NativeURL, groupWXID)
|
hongBaoURLItem, err := clientsdk.ParseHongBaoURL(hbItem.NativeURL, groupWXID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// fmt.Print("\n开始抢红包\n")
|
fmt.Print("\n开始抢红包\n")
|
||||||
hbItem.URLItem = hongBaoURLItem
|
hbItem.URLItem = hongBaoURLItem
|
||||||
hbItem.FromUserName = tmpMsg.FromUserName
|
hbItem.FromUserName = tmpMsg.FromUserName
|
||||||
currentGrapHBMgr.AddHBItem(hbItem)
|
currentGrapHBMgr.AddHBItem(hbItem)
|
||||||
// fmt.Print("\n抢群红包\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理自动抢红包操作(私发)
|
// 处理自动抢红包操作(私发)
|
||||||
@@ -1037,6 +1039,7 @@ func dealPersonHB(wxConn wxface.IWXConnect, content string, groupWXID string) {
|
|||||||
if tmpMsg.APPMsg.WCPayInfo.SceneID != baseinfo.MMPayInfoSceneIDHongBao {
|
if tmpMsg.APPMsg.WCPayInfo.SceneID != baseinfo.MMPayInfoSceneIDHongBao {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
recvAtMs := time.Now().UnixMilli()
|
||||||
//延迟抢红包
|
//延迟抢红包
|
||||||
delaytime := currentGrapHBMgr.GetDelayOpen()
|
delaytime := currentGrapHBMgr.GetDelayOpen()
|
||||||
if int(delaytime) >= 1 {
|
if int(delaytime) >= 1 {
|
||||||
@@ -1047,6 +1050,7 @@ func dealPersonHB(wxConn wxface.IWXConnect, content string, groupWXID string) {
|
|||||||
hbItem := new(baseinfo.HongBaoItem)
|
hbItem := new(baseinfo.HongBaoItem)
|
||||||
hbItem.IsGroup = 0
|
hbItem.IsGroup = 0
|
||||||
hbItem.NativeURL = tmpMsg.APPMsg.WCPayInfo.NativeURL
|
hbItem.NativeURL = tmpMsg.APPMsg.WCPayInfo.NativeURL
|
||||||
|
hbItem.RecvAtMs = recvAtMs
|
||||||
hongBaoURLItem, err := clientsdk.ParseHongBaoURL(hbItem.NativeURL, groupWXID)
|
hongBaoURLItem, err := clientsdk.ParseHongBaoURL(hbItem.NativeURL, groupWXID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package wxtask
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"xiawan/wx/clientsdk/baseinfo"
|
"xiawan/wx/clientsdk/baseinfo"
|
||||||
"xiawan/wx/db"
|
"xiawan/wx/db"
|
||||||
"xiawan/wx/srv/wxface"
|
"xiawan/wx/srv/wxface"
|
||||||
@@ -87,6 +88,9 @@ func (ghbm *WXGrabHBTask) grapHB() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case ghbm.currentHBItem = <-ghbm.hongBaoItemChan:
|
case ghbm.currentHBItem = <-ghbm.hongBaoItemChan:
|
||||||
|
if ghbm.currentHBItem != nil {
|
||||||
|
ghbm.currentHBItem.DequeueAtMs = time.Now().UnixMilli()
|
||||||
|
}
|
||||||
// 直接发送抢红包请求,不等待响应
|
// 直接发送抢红包请求,不等待响应
|
||||||
go ghbm.wxConn.GetWXReqInvoker().SendOpenRedEnvelopesRequest(ghbm.currentHBItem)
|
go ghbm.wxConn.GetWXReqInvoker().SendOpenRedEnvelopesRequest(ghbm.currentHBItem)
|
||||||
// 立即准备抢下一个红包,不等待当前红包处理完成
|
// 立即准备抢下一个红包,不等待当前红包处理完成
|
||||||
@@ -145,6 +149,9 @@ func (ghbm *WXGrabHBTask) GrapNext() {
|
|||||||
func (ghbm *WXGrabHBTask) AddHBItem(hbItem *baseinfo.HongBaoItem) {
|
func (ghbm *WXGrabHBTask) AddHBItem(hbItem *baseinfo.HongBaoItem) {
|
||||||
// 抢红包操作
|
// 抢红包操作
|
||||||
// ghbm.wxConn.GetWXReqInvoker().SendOpenRedEnvelopesRequest(hbItem)
|
// ghbm.wxConn.GetWXReqInvoker().SendOpenRedEnvelopesRequest(hbItem)
|
||||||
|
if hbItem != nil && hbItem.EnqueueAtMs <= 0 {
|
||||||
|
hbItem.EnqueueAtMs = time.Now().UnixMilli()
|
||||||
|
}
|
||||||
ghbm.hongBaoItemChan <- hbItem
|
ghbm.hongBaoItemChan <- hbItem
|
||||||
if !ghbm.isStart {
|
if !ghbm.isStart {
|
||||||
ghbm.Start()
|
ghbm.Start()
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1013,6 +1013,12 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
HongBaoItem:
|
HongBaoItem:
|
||||||
properties:
|
properties:
|
||||||
|
DequeueAtMs:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
EnqueueAtMs:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
FromUserName:
|
FromUserName:
|
||||||
example: ""
|
example: ""
|
||||||
type: string
|
type: string
|
||||||
@@ -1026,6 +1032,9 @@ definitions:
|
|||||||
NativeURL:
|
NativeURL:
|
||||||
example: ""
|
example: ""
|
||||||
type: string
|
type: string
|
||||||
|
RecvAtMs:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
SceneID:
|
SceneID:
|
||||||
format: uint32
|
format: uint32
|
||||||
type: integer
|
type: integer
|
||||||
@@ -2647,23 +2656,24 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
info:
|
info:
|
||||||
contact: ""
|
contact: ""
|
||||||
description: "长连接-自动心跳-自动二登-消息回调-长时间在线 \n 25.12.24 增加cdn高清图片上传接口,
|
description: "长连接-自动心跳-自动二登-消息回调-长时间在线 - Updated By 408449830 \n 26.2.2 拉到8069,
|
||||||
修复初始化消息同步问题 \n 25.12.18 修复a16和62数据登录, 增加a16转62, 62转a16功能 \n25.12.13 修复二登后长链接失效问题
|
修复密钥长度问题 \n 26.1.20 增加修改我在群聊中的昵称功能, 修复红包测试遗留问题, 修复允许服务器ip直连配置问题 \n 26.1.6 添加代理连接配置和超时控制
|
||||||
\n25.12.9 增加视频号助手扫码登录功能 \n 25.12.5 增加手机点击退出ipad登录后, 同步在线状态 \n 25.12.4 修复重启后代理丢失问题
|
\n 25.12.24 增加cdn高清图片上传接口, 修复初始化消息同步问题 \n 25.12.18 修复a16和62数据登录, 增加a16转62, 62转a16功能
|
||||||
\n 25.11.8 新增企微图片下载, 整合接收xml格式 \n 25.11.5 修复ws消息阻塞问题(断连后批量推送), 删除历史消息接收逻辑 \n 25.11.1
|
\n25.12.13 修复二登后长链接失效问题 \n25.12.9 增加视频号助手扫码登录功能 \n 25.12.5 增加手机点击退出ipad登录后, 同步在线状态
|
||||||
修复长语音下载问题 \n 25.9.1 增加mac滑块网页, 依旧用mac2ipad接口(但实测依旧不能pc同时在线) \n 25.8.21 增加mac滑块,
|
\n 25.12.4 修复重启后代理丢失问题 \n 25.11.8 新增企微图片下载, 整合接收xml格式 \n 25.11.5 修复ws消息阻塞问题(断连后批量推送),
|
||||||
mac2iPad(direct接口) \n 25.8.9 直登被和谐了, 另谋出路~ \n 25.8.6 消息回调格式封装外层key, 方便聚合聊天callback区分账号
|
删除历史消息接收逻辑 \n 25.11.1 修复长语音下载问题 \n 25.9.1 增加mac滑块网页, 依旧用mac2ipad接口(但实测依旧不能pc同时在线)
|
||||||
\n 25.8.5 抽离直登和iPad登录,同步callback和ws消息格式 \n 25.8.3 直登iPad, 修复心跳相关问题, 重新拉到861, 下个版本跟新算法吧,
|
\n 25.8.21 增加mac滑块, mac2iPad(direct接口) \n 25.8.9 直登被和谐了, 另谋出路~ \n 25.8.6 消息回调格式封装外层key,
|
||||||
头痛 \n 25.8.1 增加群聊成员变动回调 \n 25.7.6 增加Windows登录(新号不建议用, 容易风控) 部分接口恢复859, 准备下个版本更新ccd+rqtx全方面升级860(本版本是需要收费的,
|
方便聚合聊天callback区分账号 \n 25.8.5 抽离直登和iPad登录,同步callback和ws消息格式 \n 25.8.3 直登iPad, 修复心跳相关问题,
|
||||||
不更新不影响正常使用) \n 25.6.30 修复多次取码的状态问题 \n 25.6.28 拉到8060, 不能直接解决人脸, 很头痛 \n 25.6.16
|
重新拉到861, 下个版本跟新算法吧, 头痛 \n 25.8.1 增加群聊成员变动回调 \n 25.7.6 增加Windows登录(新号不建议用, 容易风控)
|
||||||
重构数据库层, 增加使用SQLite分支, 方便打包分发 \n 25.6.14 增加车载登录, 自动切号绕过验证码/人脸 \n 25.5.28 修复输入验证码接口,
|
部分接口恢复859, 准备下个版本更新ccd+rqtx全方面升级860(本版本是需要收费的, 不更新不影响正常使用) \n 25.6.30 修复多次取码的状态问题
|
||||||
准备修复群聊消息同步问题 \n 25.5.24 增加输入登录验证码接口 \n 25.5.20 增加安卓平板, 配置文件增加是否切设备选项 \n 25.5.19
|
\n 25.6.28 拉到8060, 不能直接解决人脸, 很头痛 \n 25.6.16 重构数据库层, 增加使用SQLite分支, 方便打包分发 \n 25.6.14
|
||||||
增加获取公众号文章阅读数量接口, 解决新设备确认和扫脸确认 \n 25.5.16 增加阅读/点赞接口,增加mac登录,增加删除手机通讯录接口 \n 25.5.15
|
增加车载登录, 自动切号绕过验证码/人脸 \n 25.5.28 修复输入验证码接口, 准备修复群聊消息同步问题 \n 25.5.24 增加输入登录验证码接口
|
||||||
增加文件上传功能, 优化文件转发功能, 准备增加输入登录验证码 \n 25.5.14 拉到8059, 准备更新ccd \n 25.5.13 每10次短连接前检查一次长连接状态(优先长链接)
|
\n 25.5.20 增加安卓平板, 配置文件增加是否切设备选项 \n 25.5.19 增加获取公众号文章阅读数量接口, 解决新设备确认和扫脸确认 \n 25.5.16
|
||||||
\n 25.5.12 增加回调持久化功能,修复登录状态缓存问题(逻辑错误) \n 25.5.11 增加消息回调功能,删除签名 \n 25.5.10 优化红包速度,
|
增加阅读/点赞接口,增加mac登录,增加删除手机通讯录接口 \n 25.5.15 增加文件上传功能, 优化文件转发功能, 准备增加输入登录验证码 \n 25.5.14
|
||||||
修复长连接消息同步失效 \n 25.5.9 修复因代波动导致长连接断开问题 \n 25.4.8 修复ws断链问题,解决跨域 \n 25.5.7 修复撤回图片逻辑
|
拉到8059, 准备更新ccd \n 25.5.13 每10次短连接前检查一次长连接状态(优先长链接) \n 25.5.12 增加回调持久化功能,修复登录状态缓存问题(逻辑错误)
|
||||||
\n 25.5.6 增加朋友圈视频上传 \n 25.5.3 修复ws并发panic"
|
\n 25.5.11 增加消息回调功能,删除签名 \n 25.5.10 优化红包速度, 修复长连接消息同步失效 \n 25.5.9 修复因代波动导致长连接断开问题
|
||||||
title: 8061-07-08算法
|
\n 25.4.8 修复ws断链问题,解决跨域 \n 25.5.7 修复撤回图片逻辑 \n 25.5.6 增加朋友圈视频上传 \n 25.5.3 修复ws并发panic"
|
||||||
|
title: 8069-07
|
||||||
version: 仅供学习交流使用,禁止用于非法用途
|
version: 仅供学习交流使用,禁止用于非法用途
|
||||||
paths:
|
paths:
|
||||||
/admin/DelayAuthKey:
|
/admin/DelayAuthKey:
|
||||||
@@ -3745,6 +3755,24 @@ paths:
|
|||||||
summary: 获取群公告
|
summary: 获取群公告
|
||||||
tags:
|
tags:
|
||||||
- 群管理
|
- 群管理
|
||||||
|
/group/SetGroupNickname:
|
||||||
|
post:
|
||||||
|
parameters:
|
||||||
|
- description: 账号唯一标识
|
||||||
|
in: query
|
||||||
|
name: key
|
||||||
|
type: string
|
||||||
|
- description: 请求参数
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/ChatroomNameModel'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
summary: 设置我在本群的昵称
|
||||||
|
tags:
|
||||||
|
- 群管理
|
||||||
/group/ToJoinGroup:
|
/group/ToJoinGroup:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
@@ -4448,6 +4476,19 @@ paths:
|
|||||||
summary: 分享名片消息
|
summary: 分享名片消息
|
||||||
tags:
|
tags:
|
||||||
- 消息
|
- 消息
|
||||||
|
/message/TestCallback:
|
||||||
|
get:
|
||||||
|
parameters:
|
||||||
|
- description: 账号唯一标识
|
||||||
|
in: query
|
||||||
|
name: key
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: ""
|
||||||
|
summary: 测试消息回调配置
|
||||||
|
tags:
|
||||||
|
- 消息回调
|
||||||
/message/UploadImageToCDN:
|
/message/UploadImageToCDN:
|
||||||
post:
|
post:
|
||||||
parameters:
|
parameters:
|
||||||
|
|||||||
Reference in New Issue
Block a user