# wx-push **Repository Path**: jsicu/wechat-notify ## Basic Information - **Project Name**: wx-push - **Description**: 微信公众号消息模板推送 - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-04 - **Last Updated**: 2026-07-05 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 微信公众号裁判安排推送 这是一个基于 Spring Boot 的微信公众号模板消息推送服务。项目保留了原有的测试文本推送能力,并在此基础上增加了裁判赛程 Excel 上传、按日期预览、手动/定时推送和推送记录查询。 ## 当前功能 - 微信公众号模板消息推送,支持多名接收人 openid。 - 默认每天 7:30 自动推送次日裁判安排,推送时间可在页面中实时修改。 - 上传口令可在页面中修改,修改后保存到本地数据库并立即生效。 - 支持手动触发次日或指定日期的裁判安排推送。 - 支持上传 `.xlsx` 裁判安排表,自动保存到本地数据目录,并在 `data/excel` 保留上传原件。 - 支持按日期解析 Excel 工作表并生成推送预览。 - 支持查看最近推送记录,记录保存在本地 H2 数据库;定时任务会跳过已有成功推送记录的目标日期。 - 支持发送自定义测试文本消息。 ## 技术栈 - Java 17 - Spring Boot 2.7.3 - Maven Wrapper - Spring JDBC - H2 本地文件数据库 - Apache POI,解析 `.xlsx` 裁判安排表 - Fastjson ## 目录说明 ```text config/ application-local.example.yml 本地配置示例 src/main/java/com/aguo/wxpush/ controller/ HTTP 接口 service/ 推送、赛程解析等业务逻辑 repository/ 推送记录持久化 config/ 赛程配置 src/main/resources/ application.yml 默认配置和环境变量占位 schema.sql H2 表结构初始化脚本 static/schedule.html 裁判安排上传/预览/推送页面 data/ 本地上传文件和 H2 数据库,已忽略提交 ``` ## 快速开始 ### 1. 准备配置 复制配置示例: ```powershell Copy-Item config/application-local.example.yml config/application-local.yml ``` 然后填写 `config/application-local.yml`: ```yaml wx: config: appId: "微信公众号 AppID" appSecret: "微信公众号 AppSecret" templateId: "模板消息 ID" openidList: - "测试文本或无安排提醒的兜底 openid" schedule: filePath: "data/referee-schedule.xlsx" originalFileDirectory: "data/excel" uploadToken: "自定义上传口令" pushTime: "20:00" tips: "推送数据可能存在变动,一切以群通知为准!" server: port: 8088 ``` `config/application-local.yml` 已经写入 `.gitignore`,不要提交真实密钥。 首次进入裁判安排页面仍需要通过 `schedule.uploadToken` 或环境变量 `SCHEDULE_UPLOAD_TOKEN` 提供初始上传口令。进入页面后,可以在“推送设置”中修改上传口令;修改后的口令会以 `sha256:` 前缀哈希写入 `app_config` 表,应用重启后继续生效。 裁判安排推送不会再依赖固定写死的 `wx.config.openidList`。请进入页面的“裁判人员”维护姓名和 openid,系统会把 Excel 中提取出的裁判、监督、实习裁判员和开车人员姓名与通讯录匹配;存在未绑定 openid 的人员时,本次推送会被拦截并提示缺失名单。 ### 2. 配置微信公众号模板 当前代码会向模板填充这些字段: ```text first title homeTeam awayTeam address referee referee1 fourth supervise tips ``` 裁判安排会拆分写入 `title.DATA`、`homeTeam.DATA`、`awayTeam.DATA`、`address.DATA`、`referee.DATA`、`referee1.DATA`、`fourth.DATA`、`supervise.DATA` 和 `tips.DATA`。`title.DATA` 是日期标题,例如 `7月4日裁判任务安排`;其他字段是当前这场比赛的具体信息。这些字段会去掉换行并限制在 20 个字符内,超过会自动压缩为省略内容。如果是新建模板,建议使用: ```text {{first.DATA}} 安排:{{title.DATA}} 主队:{{homeTeam.DATA}} 客队:{{awayTeam.DATA}} 时间地点:{{address.DATA}} 裁判员:{{referee.DATA}} 助理:{{referee1.DATA}} 第四官员:{{fourth.DATA}} 监督:{{supervise.DATA}} 提示:{{tips.DATA}} ``` 普通变量建议写成 `标签:{{变量名.DATA}}`,不要把标签和占位符拆成两行。 ### 3. 启动项目 Windows 本地开发推荐使用一键重启脚本: ```powershell .\scripts\restart-local.cmd ``` 这个脚本会先停止旧服务,再执行 `clean package -DskipTests`,随后启动新的 jar 并访问 `/schedule` 验证是否可用。改了 Java 源码后请优先用它,避免只重启到旧的 `target` 产物。 如果希望打包时顺便跑测试: ```powershell .\scripts\restart-local.cmd -RunTests ``` 也可以用 Maven 前台运行,适合需要观察实时日志的场景: ```powershell .\mvnw.cmd spring-boot:run ``` macOS 或 Linux 可直接用 Maven 前台运行: ```bash ./mvnw spring-boot:run ``` 启动后访问: ```text http://localhost:8088/schedule ``` ## 裁判安排表格式 上传文件只支持 `.xlsx`。 每个日期对应一个工作表。工作表第一行第一列需要包含日期,例如: ```text 裁判任务安排(2026年7月4日)(周六) ``` 表头行至少需要包含: ```text 比赛名称 比赛时间 比赛队伍 ``` 可识别的字段包括: ```text 比赛名称、赛制、轮次、比赛日期、赛点、组别、场序、比赛时间、比赛队伍、 R、AR1、AR2、R2、4TH、裁判监督、实习裁判员、开车人员、裁判服装、是否需要回复短信报告时间 ``` 解析时会自动处理合并单元格、换行和多余空格。Excel 时间数字会格式化为 `HH:mm`。 ## 页面和接口 ### 裁判安排页面 ```text GET /schedule ``` 打开裁判安排管理页面,可以上传 Excel、预览指定日期、确认推送、查看最近推送记录。 页面中的“裁判人员”可以新增、编辑、删除姓名和 openid,并支持按当前日期检查 Excel 提取出的人员是否都已绑定。 ### 上传并预览 ```text POST /schedule/upload X-Schedule-Token-Hash: 上传口令的 SHA-256 十六进制哈希 ``` 参数: - `file`:`.xlsx` 文件 - `date`:可选,格式 `yyyy-MM-dd`,默认次日 上传成功后会保存到 `schedule.filePath`,同时在 `schedule.originalFileDirectory` 中保存一份带时间戳的上传原件,并返回指定日期的预览内容。 ### 预览当前表 ```text GET /schedule/preview?date=2026-07-04 X-Schedule-Token-Hash: 上传口令的 SHA-256 十六进制哈希 ``` `date` 可选,默认次日。 ### 推送指定日期 ```text POST /schedule/send?date=2026-07-04 X-Schedule-Token-Hash: 上传口令的 SHA-256 十六进制哈希 ``` `date` 可选,默认次日。接口会解析 Excel、发送微信公众号模板消息,并写入推送记录;发送失败时会返回 `success: false`。 发送前会按 Excel 中的人员姓名匹配“裁判人员”通讯录。只要当天安排中存在未绑定 openid 的人员,接口会返回失败并列出缺失姓名,不会进行部分推送。 ### 查看裁判人员 ```text GET /schedule/referees?date=2026-07-04 X-Schedule-Token-Hash: 上传口令的 SHA-256 十六进制哈希 ``` `date` 可选。传入日期时会同时返回该日期 Excel 提取人员、已匹配人员和未绑定人员。 ### 保存裁判人员 ```text POST /schedule/referees X-Schedule-Token-Hash: 上传口令的 SHA-256 十六进制哈希 ``` 参数: - `id`:可选,传入时更新已有人员,不传时新增 - `name`:裁判姓名,需要和 Excel 中姓名一致 - `openid`:对应微信公众号 openid - `enabled`:是否启用,默认 `true` - `remark`:可选备注 ### 删除裁判人员 ```text DELETE /schedule/referees?id=1 X-Schedule-Token-Hash: 上传口令的 SHA-256 十六进制哈希 ``` ### 查看推送记录 ```text GET /schedule/records?limit=20 X-Schedule-Token-Hash: 上传口令的 SHA-256 十六进制哈希 ``` `limit` 默认 20,最大 100。 ### 查看推送配置 ```text GET /schedule/config X-Schedule-Token-Hash: 上传口令的 SHA-256 十六进制哈希 ``` 返回当前每日自动推送次日安排的时间,以及上传口令是否已配置。接口不会返回真实上传口令。 ### 修改推送配置 ```text POST /schedule/config X-Schedule-Token-Hash: 上传口令的 SHA-256 十六进制哈希 ``` 参数: - `pushTime`:可选,每日自动推送次日安排的时间,格式 `HH:mm`,分钟只能是 `00` 或 `30` - `tips`:可选,推送提示,不能超过 20 个字 - `newUploadTokenHash`:可选,新上传口令的 SHA-256 十六进制哈希 至少需要提交 `pushTime`、`tips` 或 `newUploadTokenHash` 中的一项。保存推送时间成功后会立即取消旧的自动推送任务并按新时间重新注册;保存上传口令成功后,后续 `/schedule/*` 接口需要使用新口令。页面保存的设置会写入本地数据库,应用重启后仍会保留。 > 口令不再放在 URL 查询参数中,页面会用浏览器 Web Crypto 计算 SHA-256 后放到 `X-Schedule-Token-Hash` 请求头。请在正式部署时开启 HTTPS;否则请求头中的哈希仍可能被截获并重放。 ### 手动触发次日推送 ```text GET /send ``` 等价于推送次日裁判安排。系统默认每天 7:30 自动执行一次,可通过 `schedule.pushTime` 或页面“推送设置”修改。 ### 发送测试文本 ```text GET /sendText?content=你好 ``` 用于验证公众号配置、模板 ID 和接收人 openid 是否可用。 ## 数据存储 默认使用 H2 文件数据库: ```text jdbc:h2:file:./data/wechat-notify;MODE=MySQL;DATABASE_TO_UPPER=false ``` 应用启动时会执行 `src/main/resources/schema.sql`,自动创建 `send_record`、`app_config` 和 `referee_contact` 表。上传的 Excel 默认保存为: ```text data/referee-schedule.xlsx ``` 上传原件默认按时间戳和原文件名保存到: ```text data/excel/ ``` `data/` 目录已忽略提交。 页面中修改的自动推送时间、推送提示和上传口令哈希会保存到 `app_config` 表,启动时会覆盖配置文件中的初始值。 如需开启 H2 控制台,可以设置: ```yaml spring: h2: console: enabled: true ``` 然后访问: ```text http://localhost:8088/h2-console ``` ## 配置项 常用配置在 `config/application-local.yml` 中维护: | 配置 | 说明 | | --- | --- | | `wx.config.appId` | 微信公众号 AppID | | `wx.config.appSecret` | 微信公众号 AppSecret | | `wx.config.templateId` | 模板消息 ID | | `wx.config.openidList` | 测试文本推送和无安排提醒的兜底 openid 列表;裁判安排接收人请在页面“裁判人员”维护 | | `schedule.filePath` | 裁判安排表保存路径 | | `schedule.originalFileDirectory` | 上传原件保存目录,默认 `data/excel` | | `schedule.uploadToken` | `/schedule/*` 接口初始口令,可在页面“推送设置”中修改 | | `schedule.pushTime` | 自动推送次日安排的时间初始值,默认 `20:00`,分钟只能是 `00` 或 `30` | | `schedule.tips` | 推送提示,默认 `推送数据可能存在变动,一切以群通知为准!`,不能超过 20 个字 | | `server.port` | 服务端口,默认 8088 | | `spring.datasource.*` | 数据库配置,默认 H2 | 也可以通过环境变量覆盖部分默认值,例如 `SERVER_PORT`、`WX_APP_ID`、`WX_APP_SECRET`、`WX_TEMPLATE_ID`、`SCHEDULE_FILE_PATH`、`SCHEDULE_ORIGINAL_FILE_DIRECTORY`、`SCHEDULE_UPLOAD_TOKEN`、`SCHEDULE_PUSH_TIME`、`SCHEDULE_TIPS`。 ## 测试 运行全部测试: ```powershell .\mvnw.cmd test ``` 当前测试覆盖: - 裁判安排 Excel 解析。 - 合并单元格、可变表头、Excel 时间格式处理。 - 上传、预览、推送、记录查询和裁判人员维护接口。 - 推送记录保存和查询。 ## 打包部署 打包: ```powershell .\mvnw.cmd clean package ``` 运行: ```powershell java -jar target/wx-push-0.0.1-SNAPSHOT.jar ``` `java -jar target/wx-push-0.0.1-SNAPSHOT.jar` 适合部署运行;本地改完源码后必须先重新打包。Windows 本地测试建议使用 `.\scripts\restart-local.cmd`,避免旧 jar 被进程锁住或误跑旧代码。 如果把 jar 单独部署到服务器,请把 `config/application-local.yml` 放在 jar 所在目录的 `config/` 子目录下,并确认 `data/` 目录有写入权限。 Linux 后台运行示例: ```bash nohup java -jar wx-push-0.0.1-SNAPSHOT.jar > wx-push.log 2>&1 & ``` ## 常见问题 ### 访问 `/schedule` 提示上传口令未配置 首次部署时需要先通过配置文件或环境变量提供初始上传口令。检查 `config/application-local.yml` 是否存在,并确认已填写: ```yaml schedule: uploadToken: "自定义上传口令" ``` 如果已经在页面中修改过上传口令,系统会优先使用数据库 `app_config` 表里的 `schedule.uploadToken`。 ### 推送失败,提示 token 获取失败 检查 `wx.config.appId` 和 `wx.config.appSecret`。如果使用微信测试号,请确认复制的是测试号页面里的 AppID 和 AppSecret。 ### 推送失败,提示人员未配置 openid 进入 `/schedule` 的“裁判人员”,先点击“检查当前日期缺失人员”,再为缺失姓名新增对应 openid。姓名需要和 Excel 中提取出的姓名一致;如果一个单元格里有多个姓名,可以用顿号、逗号、斜杠、分号、空格等分隔。 ### 测试文本失败,提示接收人为空 检查: ```yaml wx: config: openidList: - "接收人 openid" ``` ### 上传后预览为空 确认目标日期和 Excel 工作表标题中的日期一致,例如预览 `2026-07-04` 时,工作表第一行第一列需要包含 `2026年7月4日`。 ### 模板消息只有标题,没有裁判安排正文 检查公众号模板是否包含 `{{title.DATA}}`、`{{homeTeam.DATA}}`、`{{awayTeam.DATA}}`、`{{address.DATA}}`、`{{referee.DATA}}`、`{{referee1.DATA}}`、`{{fourth.DATA}}` 或 `{{supervise.DATA}}`,字段名必须和代码发送的字段完全一致。