# sendweworks **Repository Path**: phpjishu/sendweworks ## Basic Information - **Project Name**: sendweworks - **Description**: 企业微信推送服务 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-10-27 - **Last Updated**: 2025-11-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # WeWork Message Service 基于 Fastify + TypeScript + Sequelize 的企业微信通知服务。统一维护 access_token,支持多消息类型发送、配置缓存、企业微信事件回调与 Swagger 文档。 ## 功能特性 - 基于 MySQL 的配置存储(Sequelize 管理、自动迁移) - 配置缓存:内存索引(按 `uniqueCode` 快速定位) - 统一 access_token 维护与缓存(服务端自动刷新,统一获取接口) - **手机号转 userId**:调用企业微信 API,结果缓存到 Redis(7天有效期) - **IP 白名单安全验证**:可配置多个 IP 白名单,支持 CIDR 格式 - **通用 API 代理**:支持转发任意企业微信 API 请求 - **消息推送日志**:自动记录所有消息推送,每天凌晨自动清理7天前的日志(新增) - 消息发送:text/markdown/textcard/image/voice/video/file/news/miniprogram_notice/template_card - 企业微信回调(URL 验证 + 事件接收,验签解密、XML 解析) - Swagger 2.0 文档:`/swagger.json` ## 消息推送日志功能(新增) 系统会自动记录所有消息推送和API转发的详细信息,包括: ### 消息推送日志 - 发送状态(成功/失败/部分成功) - 消息内容和接收人信息 - 企业微信返回结果 - 错误信息和失败的手机号 - 客户端IP地址 ### API转发日志 - API路径和请求方法 - 请求参数和响应结果 - 请求状态(成功/失败) - 响应时间(毫秒) - 错误详情 **定时清理**:每天凌晨 0:00 自动清理7天前的所有日志(包括消息日志和转发日志) **API接口**: - `POST /message-logs/stats` - 查看消息推送日志统计 - `POST /proxy-logs/stats` - 查看API转发日志统计 - `POST /message-logs/cleanup` - 手动清理所有7天前的日志 ## 快速开始 ### 环境准备 - Node.js 18+ - MySQL 5.7+/8.0+ - Redis 5+(用于手机号转userId缓存) ### 环境变量(.env) ``` # Server PORT=3000 HOST=0.0.0.0 # Swagger 访问主机(可选) SWAGGER_HOST=127.0.0.1 # MySQL 连接 MYSQL_HOST=127.0.0.1 MYSQL_PORT=3306 MYSQL_USER=root MYSQL_PASSWORD=your_password MYSQL_DATABASE=wework MYSQL_CONN_LIMIT=10 # Redis 连接(用于手机号转userId缓存) REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_PASSWORD= REDIS_DB=0 # IP 白名单(逗号分隔,留空表示允许所有IP) # 支持单个IP或CIDR格式,如: 127.0.0.1,192.168.1.100,10.0.0.0/8 IP_WHITELIST= ``` ### 安装与运行 ``` # 安装依赖 npm i # 开发运行(热重载) npm run dev # 生产构建 npm run build # 生产启动 npm start ``` 服务启动后将: 1) 连接并 `sync` 数据库(自动迁移表结构) 2) 预加载配置到缓存 ## 数据模型(核心) - 表:`wework_app_config` - 主键:`wework_app_config_id` 自增 - 唯一短码:`unique_code`(短 Base62),对外标识应用 - 主要字段:`corp_id`、`agent_id`、`secret`、`token`、`aes_key`、`create_time`、`update_time` - 代码模型见 `src/models/weworkAppConfig.model.ts` ## 重要概念:uniqueCode - 对外仅使用 `uniqueCode`(短唯一编号)定位应用 - 所有涉及应用定位的接口都使用 `uniqueCode` - `/configs` 返回每条配置的 `uniqueCode` 便于调用方保存 ## API 一览 ### 公开接口(无需 IP 白名单) - `GET /health` - 健康检查 - `GET /swagger.json` - Swagger 2.0 文档 - `GET/POST /wecom/callback` - 企业微信回调统一入口(GET 验证、POST 事件) ### 受保护接口(需配置 IP 白名单) > 以下接口均为 **POST** 请求,需在 .env 配置 IP_WHITELIST - `POST /configs` - 获取缓存中的配置(脱敏,含 `uniqueCode`) - `POST /send` - 发送消息(用 `uniqueCode` 定位) - `POST /send-by-mobile` - 通过手机号发送消息(支持多个,自动转换为 userId) - `POST /reload-config` - 重新加载缓存 - `POST /access-token` - 通过 `uniqueCode` 获取企业微信 `access_token` - `POST /mobile-to-userid` - 手机号转企业微信 userId(带 Redis 缓存) - `POST /wework-proxy` - 企业微信 API 通用代理转发 ### IP 白名单说明 - 若 `IP_WHITELIST` 为空,则允许所有 IP 访问受保护接口 - 支持单个 IP:`127.0.0.1,192.168.1.100` - 支持 CIDR 格式:`10.0.0.0/8,172.16.0.0/12` - 获取客户端 IP 优先级:`X-Forwarded-For` > `X-Real-IP` > `request.ip` - IP 不在白名单时返回 403 错误 ### 发送消息(POST /send) 请求体(示例:text): ```json { "uniqueCode": "yourShortCode", "msgType": "text", "text": "hello", "toUser": "zhangsan" } ``` 支持的 `msgType`:`text`、`markdown`、`textcard`、`image`、`voice`、`video`、`file`、`news`、`miniprogram_notice`、`template_card` - text:`text: string` - markdown:`markdown: string` - textcard:`{ title, description, url, btntxt? }` - image/voice/file:`{ media_id }` - video:`{ media_id, title?, description? }` - news:`{ articles: [{ title, url, description?, picurl? }] }` - miniprogram_notice:`{ appid, title, page?, description?, emphasis_first_item?, content_item? }` - template_card:`Record`(透传) 返回:`{ ok: true, result }` ### 通过手机号发送消息(POST /send-by-mobile) 直接用手机号发送,无需提前知道 userId,服务端自动转换(带 Redis 缓存)。支持多个手机号(用 `|` 分隔)。 请求体(示例:单个手机号): ```json { "uniqueCode": "yourShortCode", "mobile": "13800138000", "msgType": "text", "text": "你好,这是一条测试消息" } ``` 请求体(示例:多个手机号): ```json { "uniqueCode": "yourShortCode", "mobile": "13800138000|13900139000|13700137000", "msgType": "text", "text": "群发消息" } ``` - 支持的 `msgType` 与 `/send` 一致 - 返回: ```json { "ok": true, "mobile": "13800138000|13900139000", "successCount": 2, "failedCount": 0, "successMobiles": ["13800138000", "13900139000"], "userIds": ["zhangsan", "lisi"], "toUser": "zhangsan|lisi", "result": { "errcode": 0, "errmsg": "ok" } } ``` - 说明: - 自动调用 `mobileToUserId` 批量转换手机号为企业微信成员 userId - 转换结果缓存到 Redis(7天),减少重复请求 - 支持部分失败容错:即使部分手机号转换失败,也会发送给成功的用户 - 失败信息会在 `failedMobiles` 字段中返回 ### 统一获取 access_token(POST /access-token) - 请求体: ```json { "uniqueCode": "yourShortCode" } ``` - 响应: ```json { "ok": true, "token": "ACCESS_TOKEN", "expiresAt": 1730023456123 } ``` 服务端内部做缓存与提前刷新(剩余 < 60s 时刷新)。 ### 手机号转 userId(POST /mobile-to-userid) - 请求体: ```json { "uniqueCode": "yourShortCode", "mobile": "13800138000" } ``` - 响应: ```json { "ok": true, "mobile": "13800138000", "userId": "ZhangSan" } ``` - 说明: - 调用企业微信「根据手机号获取成员」API(需通讯录权限) - 结果缓存到 Redis,7天过期,减少重复请求 - 若手机号未绑定企业微信成员,返回 500 错误 ### 企业微信 API 通用代理(POST /wework-proxy) 转发任意企业微信 API 请求,自动注入 `access_token`。 请求体示例(获取用户信息): ```json { "uniqueCode": "yourShortCode", "path": "/cgi-bin/user/get", "method": "GET", "data": { "userid": "zhangsan" } } ``` 请求体示例(更新模板卡片): ```json { "uniqueCode": "yourShortCode", "path": "/cgi-bin/message/update_template_card", "method": "POST", "data": { "userids": ["zhangsan", "lisi"], "response_code": "response_code_xxx_1234567890", "button": { "replace_name": "已处理" } } } ``` - 响应:`{ ok: true, result: { ...企业微信API原始响应 } }` - 说明: - 支持 GET 和 POST 请求 - 自动管理和注入 access_token - 基础 URL 固定为 `https://qyapi.weixin.qq.com` - `path` 参数支持任意企业微信 API 路径 ### 获取配置(POST /configs) - 响应示例: ```json { "ok": true, "count": 1, "items": [ { "wework_app_config_id": 1, "uniqueCode": "abC123xyZ", "corpId": "ww123...", "agentId": 1000002, "updateTime": "2025-01-01T00:00:00.000Z" } ] } ``` ### 企业微信回调(/wecom/callback) - GET 验证:`/wecom/callback?uniqueCode=...&msg_signature=...×tamp=...&nonce=...&echostr=...` - POST 事件:`/wecom/callback?uniqueCode=...&msg_signature=...×tamp=...&nonce=...` - Body:企业微信推送的加密 XML 文本 - 服务端:验签 + AES 解密 + 明文 XML 解析,最后返回 `success` ## Swagger 文档 - 访问:`GET /swagger.json` - 可导入 Apifox 进行接口调试 ## 目录结构 ``` src/ server.ts # Fastify 入口与路由 wework.ts # 企业微信客户端(token缓存、发送、手机号转userId) redis.ts # Redis 客户端(用于手机号缓存) swagger.ts # Swagger 文档生成 configCache.ts # 配置缓存(id/code 双索引) types.ts # TS 类型定义 wecomCrypto.ts # 企业微信回调验签/解密 models/ db.ts # Sequelize 连接与 sync weworkAppConfig.model.ts # 配置表模型(含 unique_code 生成) configRepository.ts # 配置仓储(读库到领域模型) ``` ## 常见问题 - 发送 `news` 报错 JSON:检查请求体是否有尾逗号,以及 `url/picurl` 必须是合法 `http/https` URL。 - 回调验签失败:确认 `token/aes_key` 与企业微信后台设置一致,URL 参数需 `encodeURIComponent`。 - 未找到配置:请先插入数据库并重启或调用 `/reload-config` 重新加载缓存。 ## 安全建议 - **IP 白名单**:生产环境务必配置 `IP_WHITELIST`,限制只有受信任的 IP 才能访问核心接口 - 不要对外暴露 `/access-token` 接口给不受信方;若必须暴露,请在网关层做鉴权与限流 - `uniqueCode` 应视为敏感标识,避免泄露 - 企业微信 `secret`、`token`、`aes_key` 等敏感信息只存储在数据库,不对外返回 ## 许可 MIT