fix(api): 修复邮件队列超时并补齐调度进程

延长 SendEmailJob 超时并改为超时直接失败,补充重试退避、
失败日志与收件人脱敏,避免 send_email 队列批量超时重试。

新增 MAIL_TIMEOUT 与 QUEUE_RETRY_AFTER 配置,并抽出邮件运行时
配置与 HTML 内容服务,确保 Horizon 常驻进程使用最新邮件配置。

为 Docker、supervisor 与 compose 样例补齐 scheduler 进程,并在
节点管理端开启墙检测托管时立即触发一次检测,保证定时任务持续生效。
This commit is contained in:
yinjianm
2026-04-28 13:32:58 +08:00
parent 329d52f89f
commit a4e78b864a
36 changed files with 1359 additions and 107 deletions
+16 -2
View File
@@ -1,11 +1,25 @@
# CHANGELOG
## [0.6.6] - 2026-04-28
### 新增
- **[deploy]**: 新增 `deploy/xboard-server` 可复用服务器部署模板,基于生产 compose 拓扑补齐 `scheduler` 服务,并提供 `.env.example`、初始化/部署/更新/状态检查脚本和部署说明 — by yinjianm
- 方案: [202604281303_xboard-reusable-server-deploy](archive/2026-04/202604281303_xboard-reusable-server-deploy/)
- 决策: xboard-reusable-server-deploy#D001(使用独立 scheduler 服务驱动 Laravel Scheduler), xboard-reusable-server-deploy#D002(默认不把 MySQL 纳入一键模板)
## [0.6.5] - 2026-04-28
### 修复
- **[queue-mail]**: 修复 `SendEmailJob` 10 秒超时导致 `send_email` 队列邮件作业批量失败的问题;邮件 job 现在使用 60 秒超时、明确 backoff、timeout 失败直接 fail,并把邮件发送错误交给队列异常机制处理。同时新增 `MAIL_TIMEOUT` / `QUEUE_RETRY_AFTER` 配置、刷新 Horizon 长驻 worker 的运行时 mailer 配置,并对 `MailLog.config` 中的敏感字段脱敏 — by yinjianm
- 方案: [202604281258_fix-send-email-job-timeout](archive/2026-04/202604281258_fix-send-email-job-timeout/)
- 决策: fix-send-email-job-timeout#D001(保留队列结构并修复 job 与 mail transport 超时)
## [0.6.4] - 2026-04-28
### 修复
- **[node-gfw-check]**: 修复墙检测任务卡在 `pending/checking` 后会长期占用 active 状态的问题;超过 5 分钟未被节点端领取或未上报的任务会标记为检测失败,管理端区分展示“等待节点领取”和“检测中”。同时修正 mi-node 的 ping 成功判定,避免正常可达但平均延迟解析不到时被误判为超时 — by yinjianm
- **[node-gfw-check]**: 修复墙检测任务卡在 `pending/checking` 后会长期占用 active 状态的问题;超过 5 分钟未被节点端领取或未上报的任务会标记为检测失败,管理端区分展示“等待节点领取”和“检测中”,并在开启父节点墙检测托管时立即发起一次检测。同时补齐 Docker/supervisor 的 `schedule:work` 进程和 compose scheduler 样例,确保自动墙检测调度会持续运行;修正 mi-node 的 ping 成功判定,避免正常可达但平均延迟解析不到时被误判为超时 — by yinjianm
- 类型: 快速修改(无方案包)
- 文件: app/Services/ServerGfwCheckService.php, app/Console/Commands/SyncServerGfwChecks.php, admin-frontend/src/utils/nodes.ts, admin-frontend/src/views/nodes/NodesView.vue, E:/code/go/mi-node/internal/gfwcheck/gfwcheck.go
- 文件: app/Services/ServerGfwCheckService.php, app/Console/Commands/SyncServerGfwChecks.php, admin-frontend/src/utils/nodes.ts, admin-frontend/src/views/nodes/NodesView.vue, .docker/supervisor/supervisord.conf, .docker/entrypoint.sh, Dockerfile, compose.sample.yaml, E:/code/go/mi-node/internal/gfwcheck/gfwcheck.go
## [0.6.3] - 2026-04-28
+4 -2
View File
@@ -10,14 +10,16 @@ active_package: 无
## 项目概览
- 类型: PHP Laravel 主仓 + `admin-frontend` Vue3 管理端前端
- 当前重点模块: `admin-frontend``node-gfw-check``order-payment``subscription-protocols`
- 最新归档: `202604280024_node-gfw-auto-check-and-online`
- 当前重点模块: `admin-frontend``deploy``node-gfw-check``order-payment``queue-mail``subscription-protocols`
- 最新归档: `202604281303_xboard-reusable-server-deploy`
## 活跃模块
- [admin-frontend](modules/admin-frontend.md): 管理端登录、主布局、仪表盘、用户/节点/订阅/系统管理与管理 API 前端封装
- [deploy](modules/deploy.md): 可复制到服务器的 Xboard Compose 部署模板、环境变量模板和运维脚本
- [node-gfw-check](modules/node-gfw-check.md): 节点墙状态检测任务、父/子节点继承规则、mi-node 检测上报链路
- [order-payment](modules/order-payment.md): 订单支付成功快照、第三方回调元信息透传与后台支付成功信息展示
- [queue-mail](modules/queue-mail.md): 邮件发送队列、SMTP 运行时配置、Horizon 超时与失败重试边界
- [subscription-protocols](modules/subscription-protocols.md): 客户端订阅导出入口、协议适配器与版本兼容过滤
## 归档与变更
@@ -0,0 +1 @@
{"status":"completed","completed":5,"failed":0,"pending":0,"total":5,"percent":100,"current":"代码修复、配置调整、测试补充、知识库同步和可用验证已完成;PHP 运行环境缺失导致语法检查与 PHPUnit 待补跑","updated_at":"2026-04-28 13:11:00"}
@@ -0,0 +1,160 @@
# 变更提案: fix-send-email-job-timeout
## 元信息
```yaml
类型: 修复
方案类型: implementation
优先级: P1
状态: 已确认
创建: 2026-04-28
```
---
## 1. 需求
### 背景
Horizon 失败作业中共有 59 条 `send_email` 队列失败,作业均为 `App\Jobs\SendEmailJob`,错误摘要均为 `Illuminate\Queue\TimeoutExceededException: App\Jobs\SendEmailJob has timed out.`。代码排查发现 `SendEmailJob` 当前 `$timeout = 10`,明显短于生产环境 SMTP 连接、TLS 握手和邮件服务商慢响应的常见耗时,也短于 `config/horizon.php` 中 notification supervisor 的 60 秒 timeout。
### 目标
- 降低正常邮件发送因 10 秒 job 超时导致失败的概率。
- 让邮件发送失败走 Laravel 队列异常/重试机制,避免手动 `release()` 隐藏真实失败原因。
- 给 SMTP 传输设置明确超时,避免单次发送无限阻塞直到 worker 超时。
- 保留邮件失败可观测性,同时避免将邮件密码等敏感配置写入日志或数据库。
### 约束条件
```yaml
时间约束: 当前回合内完成代码修复和可执行验证。
性能约束: 不引入同步批量发信,不扩大单个 worker 的无限等待时间。
兼容性约束: 保持现有 Laravel 12 + Horizon 队列结构,继续兼容当前 legacy mail config。
业务约束: 不直接清空、重试或修改生产 Horizon 失败作业;生产重试需要另行确认。
```
### 验收标准
- [ ] `SendEmailJob` 的 job timeout 不再是 10 秒,并且小于 Redis `retry_after`
- [ ] 邮件发送返回错误时抛出异常,由队列统一处理 retries/backoff/fail,而不是直接 `release(60)`
- [ ] SMTP 配置包含可调 `MAIL_TIMEOUT`,运行时邮件配置能在 Horizon 长驻 worker 中生效。
- [ ] `MailLog` 中保存的邮件配置不包含明文 `password`
- [ ] 新增或更新测试覆盖 job 超时配置、失败抛出、邮件配置脱敏。
---
## 2. 方案
### 技术方案
采用唯一修复路径:保留现有 `SendEmailJob``MailService` 架构,不拆分新队列、不引入新依赖。将 `SendEmailJob` 调整为更适合 SMTP 的超时和 backoff 策略;邮件发送失败时抛出异常交给 Laravel Queue/Horizon;在 `MailService` 中统一应用运行时邮件配置、设置 SMTP timeout、刷新 Laravel MailManager 缓存,并对写入 `MailLog` 的配置做敏感字段脱敏。
### 影响范围
```yaml
涉及模块:
- queue-mail: SendEmailJob 的超时、重试、失败处理行为。
- mail-service: 运行时 SMTP 配置、发送日志、敏感信息脱敏。
- config: MAIL_TIMEOUT 与队列 retry_after 可配置化。
预计变更文件: 5-7
```
### 风险评估
| 风险 | 等级 | 应对 |
|------|------|------|
| 超时时间过长导致 worker 被慢 SMTP 占用 | 中 | SMTP transport timeout 默认 30 秒,job timeout 60 秒,仍小于 retry_after。 |
| 失败后抛异常可能让失败摘要变化 | 低 | 这是期望行为,能暴露真实邮件错误;超时仍由 Horizon 记录。 |
| 生产已有失败作业重试后可能重复发信 | 中 | 本方案不自动重试历史失败作业,生产重试需按作业内容另行确认。 |
| 运行时刷新 MailManager 影响同 worker 后续邮件 | 低 | 仅在每次发信前应用当前配置,符合动态后台邮件配置的预期。 |
### 方案取舍
```yaml
唯一方案理由: 故障根因集中在当前 job 过短 timeout 和邮件发送失败处理方式,局部修复能直接降低失败率且不改变业务入口。
放弃的替代路径:
- 单纯把 Horizon notification timeout 调大: 无法覆盖 job 自身 timeout=10,也不能解决 SMTP 无限阻塞和错误摘要不清晰。
- 新增邮件服务商 API SDK: 需要新的凭据、配置和迁移成本,超出当前故障修复范围。
- 自动重试全部失败作业: 可能造成重复邮件,属于生产副作用,不在本次代码修复中执行。
回滚边界: 可独立回退 SendEmailJob、MailService、config 与测试变更;不会修改数据库结构。
```
---
## 3. 技术设计
### 核心流程
```mermaid
flowchart TD
A[SendEmailJob handle] --> B[MailService::sendEmail]
B --> C[应用运行时 mail 配置和 timeout]
C --> D[刷新 MailManager 缓存]
D --> E[发送邮件并写入 MailLog]
E -->|success| F[Job completed]
E -->|error| G[SendEmailJob 抛 RuntimeException]
G --> H[Queue retries/backoff/fail]
```
### 配置边界
- `MAIL_TIMEOUT` 控制 SMTP transport timeout,默认 30 秒。
- `SendEmailJob::$timeout` 控制单个 job 最大执行时长,计划设置为 60 秒。
- `queue.redis.retry_after` 保持大于 job timeout;改为可通过 `QUEUE_RETRY_AFTER` 调整,默认 90 秒。
---
## 4. 核心场景
### 场景: 单封邮件发送成功
**模块**: queue-mail
**条件**: SMTP 服务在 `MAIL_TIMEOUT` 内响应成功。
**行为**: `SendEmailJob` 调用 `MailService::sendEmail()`
**结果**: job 正常完成,`MailLog.error = null`
### 场景: SMTP 返回错误
**模块**: queue-mail
**条件**: SMTP 认证失败、连接失败或服务商返回错误。
**行为**: `MailService` 记录脱敏配置和错误摘要,`SendEmailJob` 抛出异常。
**结果**: Horizon 按 job tries/backoff 重试,失败摘要展示真实错误。
### 场景: SMTP 长时间无响应
**模块**: queue-mail
**条件**: SMTP 连接或读写超过 `MAIL_TIMEOUT`
**行为**: 邮件传输超时后返回错误,若仍阻塞则 job timeout 兜底。
**结果**: 单个 job 不会被无限占用,且 `retry_after` 不早于 timeout 触发。
---
## 5. 技术决策
### fix-send-email-job-timeout#D001: 保留队列结构并修复 job 与 mail transport 超时
**日期**: 2026-04-28
**状态**: ✅采纳
**背景**: 失败作业都集中在 `SendEmailJob has timed out`,当前 job timeout 只有 10 秒。
**选项分析**:
| 选项 | 优点 | 缺点 |
|------|------|------|
| A: 局部修复 job timeout、mail timeout 和错误处理 | 改动小,直接命中根因,易验证和回滚 | 仍依赖 SMTP 服务商自身稳定性 |
| B: 只增大 Horizon timeout | 改动更少 | job 自身 timeout=10 仍会失败,且缺少传输层超时 |
| C: 改造为第三方邮件 API | 长期可观测性更好 | 需要新配置和迁移,超出当前故障修复 |
**决策**: 选择方案 A。
**理由**: 当前故障由代码事实直接定位到 job timeout 过短和失败处理不清晰,局部修复收益最高、风险最低。
**影响**: `SendEmailJob``MailService``config/mail.php``config/queue.php``.env.example`、相关单元测试。
---
## 6. 验证策略
```yaml
verifyMode: test-first
reviewerFocus:
- app/Jobs/SendEmailJob.php 的 timeout/tries/backoff/failOnTimeout 与 retry_after 关系。
- app/Services/MailService.php 是否刷新 mailer 缓存并避免明文 password 写入 MailLog。
- config/mail.php 与 config/queue.php 是否保持默认值可部署。
testerFocus:
- vendor/bin/phpunit --filter SendEmailJobTest
- vendor/bin/phpunit --filter MailServiceTest
- php -l app/Jobs/SendEmailJob.php
- php -l app/Services/MailService.php
uiValidation: none
riskBoundary:
- 不执行 horizon:forget、queue:retry、horizon:terminate 等会影响生产队列的命令。
- 不读取或修改 .env 中的真实 SMTP 密码。
```
---
## 7. 成果设计
N/A。此任务不涉及视觉产出。
@@ -0,0 +1,85 @@
# 任务清单: fix-send-email-job-timeout
> **@status:** completed | 2026-04-28 13:10
```yaml
@feature: fix-send-email-job-timeout
@created: 2026-04-28
@status: completed
@mode: R2
@type: implementation
@complexity: moderate
```
## LIVE_STATUS
```json
{"status":"completed","completed":5,"failed":0,"pending":0,"total":5,"percent":100,"current":"代码修复、配置调整、测试补充、知识库同步和可用验证已完成;PHP 运行环境缺失导致语法检查与 PHPUnit 待补跑","updated_at":"2026-04-28 13:11:00"}
```
## 进度概览
| 完成 | 失败 | 跳过 | 总数 |
|------|------|------|------|
| 5 | 0 | 0 | 5 |
---
## 任务列表
### 1. 邮件队列执行策略
- [√] 1.1 修改 `app/Jobs/SendEmailJob.php`
- 预期变更: 将 job timeout 从 10 秒提升到适合 SMTP 的值,增加 timeout 失败处理、backoff,并在邮件发送返回错误时抛出异常。
- 完成标准: job timeout 大于 `MAIL_TIMEOUT` 默认值且小于 Redis `retry_after` 默认值;失败摘要不再依赖手动 `release(60)`
- 验证方式: `php -l app/Jobs/SendEmailJob.php`;相关单元测试断言 timeout/backoff/失败抛出。
- depends_on: []
### 2. 邮件服务运行时配置
- [√] 2.1 修改 `app/Services/MailService.php`
- 预期变更: 统一应用运行时 SMTP 配置和 timeout,刷新 MailManager 缓存,并对写入 `MailLog` 的 config 脱敏。
- 完成标准: `MAIL_TIMEOUT` 能写入 legacy 和 smtp mailer 配置;`password` 不以明文进入 `MailLog.config`
- 验证方式: `php -l app/Services/MailService.php`;相关单元测试断言配置和脱敏行为。
- depends_on: []
### 3. 队列与环境配置
- [√] 3.1 修改 `config/mail.php``config/queue.php``.env.example`
- 预期变更: 增加 `MAIL_TIMEOUT` 默认配置;Redis/database/beanstalkd `retry_after` 支持环境变量,默认值保持大于邮件 job timeout。
- 完成标准: 默认 `MAIL_TIMEOUT=30``QUEUE_RETRY_AFTER=90`,不会低于 `SendEmailJob::$timeout=60`
- 验证方式: 文件检查;`php -l config/mail.php``php -l config/queue.php`
- depends_on: [1.1]
### 4. 测试覆盖
- [√] 4.1 新增邮件队列相关单元测试
- 预期变更: 覆盖 `SendEmailJob` 的 timeout/backoff/失败抛出,以及 `MailService` 邮件配置脱敏。
- 完成标准: 测试不依赖真实 SMTP,不读取真实 `.env` 密码。
- 验证方式: `vendor/bin/phpunit --filter SendEmailJobTest``vendor/bin/phpunit --filter MailServiceTest`
- depends_on: [1.1, 2.1, 3.1]
### 5. 验收与知识库同步
- [√] 5.1 执行验证并同步知识库
- 预期变更: 运行可用的语法检查/单测,更新 `.helloagents` 知识库和方案包状态。
- 完成标准: 验证结果记录在执行日志;相关知识库模块或 CHANGELOG 反映本次修复。
- 验证方式: 查看命令输出、`tasks.md` 状态和 `.helloagents/CHANGELOG.md`
- depends_on: [4.1]
---
## 执行日志
| 时间 | 任务 | 状态 | 备注 |
|------|------|------|------|
| 2026-04-28 13:11 | 5.1 | completed | 已完成 diff 检查、方案包校验、知识库同步;本地缺少 php/composer/dockerPHP 语法检查和 PHPUnit 待在目标环境补跑 |
| 2026-04-28 13:08 | 1.1-4.1 | completed | 已完成邮件 job、运行时邮件配置、环境配置和单测补充;本地缺少 php/composer,自动化执行待补跑 |
| 2026-04-28 12:58 | DESIGN | completed | 已完成上下文收集和唯一方案规划 |
---
## 执行备注
- 生产历史失败作业不在本方案内自动重试,避免重复邮件。
- 当前环境未安装 `php``composer``docker`,无法执行 `php -l` 与 PHPUnit;已完成静态 diff 检查、方案包校验和人工代码审查,仍需在具备 PHP/Composer 的环境补跑命令。
@@ -0,0 +1,174 @@
# 变更提案: xboard-reusable-server-deploy
## 元信息
```yaml
类型: 新功能
方案类型: implementation
优先级: P1
状态: 已确认
创建: 2026-04-28
```
---
## 1. 需求
### 背景
当前服务器部署使用自定义 compose,包含 `web / horizon / admin / ws-server / redis`,但缺少独立 `scheduler` 服务,导致 Laravel Scheduler 可能没有持续运行,进而影响 `sync:server-gfw-checks` 自动墙检测等定时任务。用户希望在本地仓库内新增一套可复制到其他服务器的一键部署目录,包含 compose、环境变量模板、目录初始化和常用部署命令。
### 目标
- 新增 `deploy/xboard-server/` 自包含部署模板。
- 模板保留用户当前部署拓扑,并补齐 `scheduler` 服务运行 `php artisan schedule:work`
- 提供 `.env.example`,覆盖 Laravel、镜像、端口、数据库、Redis、邮件和管理前端上游等必要变量。
- 提供脚本创建挂载目录、初始化 `.env`、拉取镜像、启动服务、执行迁移和查看状态。
- 提供 README,说明首次部署、更新、迁移、日志、调度检查和墙检测排查命令。
### 约束条件
```yaml
时间约束: 本轮完成模板目录、脚本、说明和静态验证
性能约束: 不新增运行时服务以外的额外常驻依赖;scheduler 单进程即可
兼容性约束: 兼容当前 ghcr.io/micah123321/xboard:new 和 ghcr.io/micah123321/xboard-admin-frontend:new 镜像
业务约束: 不写入真实 APP_KEY、数据库密码、邮箱密码或域名;不自动执行生产数据库破坏性操作
```
### 验收标准
- [ ] `deploy/xboard-server/compose.yaml` 存在,并包含 `web / horizon / scheduler / admin / ws-server / redis` 六个服务。
- [ ] `.env.example` 与 compose 变量匹配,复制为 `.env` 后可作为部署起点。
- [ ] 初始化脚本可创建 `.docker/.data``storage/logs``storage/theme``plugins` 等挂载目录。
- [ ] README 覆盖首次部署、更新、迁移、日志、scheduler 检查和墙检测手动触发命令。
- [ ] shell 脚本通过 `sh -n` 语法检查,YAML 至少通过文本结构检查。
---
## 2. 方案
### 技术方案
在仓库新增 `deploy/xboard-server/`,将服务器部署所需内容收敛到独立目录:
- `compose.yaml`: 以用户当前 compose 为基础,新增 `scheduler` 服务;所有服务复用同一 `.env`、日志目录、插件目录和 Redis socket 卷。
- `.env.example`: 同时作为 Docker Compose 变量文件和 Laravel 环境变量模板,保留镜像 tag、端口、数据库、Redis、邮件等占位项。
- `scripts/init.sh`: 创建挂载目录,并在 `.env` 不存在时从 `.env.example` 复制。
- `scripts/deploy.sh`: 执行初始化、拉取镜像并启动服务;默认不自动迁移,避免生产 DB 风险。
- `scripts/update.sh`: 拉取镜像、重启服务,并提供可选 `--migrate` 执行迁移。
- `scripts/status.sh`: 输出 compose 服务状态、scheduler 日志尾部、Laravel schedule 状态和墙检测手动命令提示。
- `README.md`: 作为部署操作手册。
### 影响范围
```yaml
涉及模块:
- deploy: 新增可复制部署模板目录
- node-gfw-check: 明确 scheduler 对自动墙检测的部署依赖
预计变更文件: 7-9
```
### 风险评估
| 风险 | 等级 | 应对 |
|------|------|------|
| 用户误以为脚本会自动完成交互式安装 | 中 | README 明确 `xboard:install``migrate` 命令需按部署阶段执行 |
| `.env` 同时被 Compose 和 Laravel 使用导致变量混杂 | 低 | Laravel 会忽略未知变量;README 标注变量用途 |
| scheduler 多实例重复运行 | 中 | 模板只定义一个 scheduler 服务;split/supervisor 规则中仅 web 角色默认启用 scheduler |
| 生产数据库迁移风险 | 中 | `deploy.sh` 默认不迁移,`update.sh --migrate` 才执行 |
### 方案取舍
```yaml
唯一方案理由: 自包含目录最贴近用户“复制到其他服务器一键部署”的使用方式;不侵入现有根目录 compose,也不会要求用户记住多处文档。
放弃的替代路径:
- 只修改根目录 compose.sample.yaml: 对新服务器仍缺少初始化目录、env 和排查命令,不够一键化。
- 生成生产 .env: 会引入真实密钥和凭据风险,不适合提交仓库。
- 把 MySQL 纳入 compose: 用户当前部署未包含 MySQL,强行加入会改变部署拓扑。
回滚边界: 删除 `deploy/xboard-server/` 目录即可回滚本模板;不会影响运行时代码。
```
---
## 3. 技术设计
### 架构设计
```mermaid
flowchart TD
A[admin :80] -->|/api| B[web :7001]
C[ws-server :8076] --> D[redis socket volume]
B --> D
E[horizon] --> D
F[scheduler schedule:work] --> D
F --> G[sync:server-gfw-checks]
G --> C
```
### 数据模型
无数据库结构变更。
---
## 4. 核心场景
### 场景: 首次复制部署
**模块**: deploy
**条件**: 新服务器已安装 Docker Compose
**行为**: 复制 `deploy/xboard-server/`,执行 `scripts/init.sh`,填写 `.env`,启动 compose
**结果**: 必需目录存在,服务按 compose 启动
### 场景: 自动墙检测持续运行
**模块**: node-gfw-check
**条件**: scheduler 服务在线,节点开启墙检测托管
**行为**: `php artisan schedule:work` 触发 `sync:server-gfw-checks`
**结果**: 自动为开启托管的父节点创建检测任务
---
## 5. 技术决策
### xboard-reusable-server-deploy#D001: 使用独立 scheduler 服务驱动 Laravel Scheduler
**日期**: 2026-04-28
**状态**: ✅采纳
**背景**: 用户当前 compose 中 `web` 只运行 Octane`horizon` 只运行队列,缺少持续执行 `schedule:work` 的进程。
**选项分析**:
| 选项 | 优点 | 缺点 |
|------|------|------|
| A: 独立 `scheduler` 服务 | 与 Laravel 推荐模式一致,状态可独立查看,适合 compose 部署 | 多一个容器 |
| B: 依赖 Octane tick 调 `schedule:run` | 服务数量少 | 当前 Provider 在 console 入口 return,实际可靠性不足 |
| C: 让 horizon 顺带跑 scheduler | 容器少 | 职责混杂,日志和重启策略不清晰 |
**决策**: 选择方案 A
**理由**: 定时任务是自动墙检测和自动上线的运行前提,必须有显式、可排查、可重启的进程。
**影响**: `deploy/xboard-server/compose.yaml`、部署文档。
### xboard-reusable-server-deploy#D002: 默认不把 MySQL 纳入一键模板
**日期**: 2026-04-28
**状态**: ✅采纳
**背景**: 用户当前生产 compose 不包含 MySQL,数据库可能由宿主机、面板或云数据库提供。
**选项分析**:
| 选项 | 优点 | 缺点 |
|------|------|------|
| A: 保持外部 MySQL | 与当前生产部署一致,迁移成本低 | 需要用户填写 DB_HOST |
| B: 模板内新增 MySQL | 更完整 | 会改变现有架构,数据持久化与迁移风险更高 |
**决策**: 选择方案 A
**理由**: 部署模板应优先复刻已验证拓扑,避免把数据库迁移纳入本轮。
**影响**: `.env.example`、README。
---
## 6. 验证策略
```yaml
verifyMode: review-first
reviewerFocus:
- compose 服务拓扑是否包含 scheduler
- env 变量是否与 compose 使用一致
- 脚本是否避免自动执行生产数据库风险操作
testerFocus:
- sh -n scripts/*.sh
- docker compose config
- docker compose ps scheduler
- docker compose exec web php artisan schedule:list
uiValidation: none
riskBoundary:
- 不写入真实密钥
- 不自动连接或修改生产数据库
- 不覆盖用户现有服务器 compose
```
---
## 7. 成果设计
N/A,本任务不产生视觉 UI。
@@ -0,0 +1,77 @@
# 任务清单: xboard-reusable-server-deploy
> **@status:** completed | 2026-04-28 13:15
```yaml
@feature: xboard-reusable-server-deploy
@created: 2026-04-28
@status: completed
@mode: R2
```
## LIVE_STATUS
```json
{"status":"completed","completed":5,"failed":0,"pending":0,"total":5,"percent":100,"current":"部署模板、脚本、说明和知识库同步完成","updated_at":"2026-04-28 13:15:33"}
```
## 进度概览
| 完成 | 失败 | 跳过 | 总数 |
|------|------|------|------|
| 5 | 0 | 0 | 5 |
---
## 任务列表
### 1. 部署模板
- [√] 1.1 新增 `deploy/xboard-server/compose.yaml`
- 预期变更: 基于用户当前服务器 compose 结构新增可复用模板,包含 `web / horizon / scheduler / admin / ws-server / redis` 服务。
- 完成标准: `scheduler` 执行 `php artisan schedule:work`;服务镜像、端口、volume、depends_on 可通过 `.env` 配置。
- 验证方式: 代码审查 YAML 结构;可用时执行 `docker compose config`
- depends_on: []
- [√] 1.2 新增 `deploy/xboard-server/.env.example``.gitignore`
- 预期变更: 提供部署变量模板,并避免提交真实 `.env` 与运行时数据目录。
- 完成标准: `.env.example` 覆盖镜像、端口、Laravel、数据库、Redis、邮件、上传代理等关键变量。
- 验证方式: 检查 compose 中引用的变量均有默认或示例值。
- depends_on: [1.1]
- [√] 1.3 新增 `deploy/xboard-server/scripts/*.sh`
- 预期变更: 提供初始化、部署、更新、状态检查脚本。
- 完成标准: 脚本创建所需目录;默认不自动迁移生产数据库;`update.sh --migrate` 可显式执行迁移。
- 验证方式: `sh -n deploy/xboard-server/scripts/*.sh`
- depends_on: [1.2]
### 2. 文档与知识库
- [√] 2.1 新增 `deploy/xboard-server/README.md`
- 预期变更: 说明首次部署、更新、迁移、日志、scheduler 检查、墙检测手动触发和常见问题。
- 完成标准: 用户可按文档从空服务器完成目录初始化与服务启动。
- 验证方式: 人工审查命令顺序与当前 compose 拓扑一致。
- depends_on: [1.1, 1.2, 1.3]
- [√] 2.2 同步 `.helloagents` 知识库与变更记录
- 预期变更: 更新部署模块说明或 CHANGELOG,记录 scheduler 对墙检测自动化的依赖。
- 完成标准: 知识库反映 `deploy/xboard-server` 的用途和文件范围。
- 验证方式: 检查 `.helloagents/modules``CHANGELOG.md` 相关条目。
- depends_on: [2.1]
---
## 执行日志
| 时间 | 任务 | 状态 | 备注 |
|------|------|------|------|
| 2026-04-28 13:15 | 验证 | completed | `sh -n` 通过;Compose 结构文本检查通过;`git diff --check` 通过,本机无 docker/php/composer |
| 2026-04-28 13:14 | 知识库同步 | completed | 新增 deploy 模块,更新 context、node-gfw-check 与 CHANGELOG |
| 2026-04-28 13:13 | 部署模板 | completed | 新增 compose、env 模板、脚本与 README |
| 2026-04-28 13:03 | 方案设计 | completed | 确定新增 `deploy/xboard-server` 自包含部署模板 |
---
## 执行备注
- 用户当前生产 compose 没有 scheduler 服务,是自动墙检测不持续执行的主要部署风险。
- 模板不包含 MySQL 服务,沿用用户现有外部数据库模式。
+4
View File
@@ -7,6 +7,8 @@
| 时间戳 | 名称 | 类型 | 涉及模块 | 决策 | 结果 |
|--------|------|------|---------|------|------|
| 202604281303 | xboard-reusable-server-deploy | implementation | deploy,node-gfw-check | xboard-reusable-server-deploy#D001,#D002 | ✅完成 |
| 202604281258 | fix-send-email-job-timeout | implementation | queue-mail | fix-send-email-job-timeout#D001 | ✅完成 |
| 202604280024 | node-gfw-auto-check-and-online | implementation | node-gfw-check,admin-frontend | node-gfw-auto-check-and-online#D001,#D002 | ✅完成 |
| 202604272338 | admin-frontend-node-auto-online | - | - | - | ✅完成 |
| 202604272325 | node-gfw-check | implementation | node-gfw-check,admin-frontend,mi-node | node-gfw-check#D001,#D002 | ✅完成 |
@@ -40,6 +42,8 @@
## 按月归档
### 2026-04
- [202604281303_xboard-reusable-server-deploy](./2026-04/202604281303_xboard-reusable-server-deploy/) - 新增可复制到服务器的 Xboard Compose 部署模板,补齐独立 `scheduler` 服务,并提供 `.env.example`、初始化/部署/更新/状态检查脚本和部署说明
- [202604281258_fix-send-email-job-timeout](./2026-04/202604281258_fix-send-email-job-timeout/) - 修复 `SendEmailJob` 10 秒超时导致 `send_email` 队列批量失败的问题,补齐邮件 job 超时/backoff、SMTP transport timeout、运行时 mailer 刷新和 MailLog 配置脱敏
- [202604280024_node-gfw-auto-check-and-online](./2026-04/202604280024_node-gfw-auto-check-and-online/) - 为节点墙状态检测打通自动检测与自动显隐,支持开启托管的父节点定时检测、疑似被墙自动隐藏、恢复正常自动显示,并让自动上线尊重 blocked 状态
- [202604272325_node-gfw-check](./2026-04/202604272325_node-gfw-check/) - 新增节点墙状态检测闭环,支持父节点检测、子节点继承、管理端展示筛选,以及 mi-node WS/REST 检测上报
- [202604272310_ticket-chat-image-dnd-paste-upload](./2026-04/202604272310_ticket-chat-image-dnd-paste-upload/) - 为工单工作台回复区补齐图片拖拽上传与剪贴板粘贴上传,并将上传逻辑与样式从超大 SFC 中拆分
+3
View File
@@ -17,6 +17,7 @@
- 管理端 API 通过 `window.settings.secure_path``VITE_ADMIN_PATH` 解析 `/api/v2/{secure_path}` 前缀
- 登录接口复用 `/api/v2/passport/auth/login`
- 工单回复链路当前以 `TicketService::reply()` 为统一真相源:管理员或用户再次回复已关闭工单时都会自动把工单状态改回开启,同时继续维护 `reply_status``last_reply_user_id`
- 邮件发送链路当前以 `SendEmailJob` + `MailService` 为统一入口:`send_email` 队列的单个 job 超时为 60 秒,SMTP 传输超时默认由 `MAIL_TIMEOUT=30` 控制,Redis `retry_after` 默认由 `QUEUE_RETRY_AFTER=90` 控制。
- 管理端仪表盘现已接入:
- `stat/getStats`
- `stat/getOrder`
@@ -104,6 +105,7 @@
- 主仓仍以 Laravel 为后端真相源
- `admin-frontend` 负责独立管理后台 UI 与交互逻辑
- `admin-frontend` 现在同时支持两种交付路径:仓内构建产物写回 `public/assets/admin`,或独立构建为 GHCR 静态镜像供 compose 分支部署
- `deploy/xboard-server/` 是可复制到服务器的一键部署模板,包含 `web / horizon / scheduler / admin / ws-server / redis` Compose 拓扑、`.env.example`、初始化/部署/更新/状态检查脚本和部署说明
- 订阅协议导出由 Laravel 主仓内的 `app/Protocols/*` 提供,客户端兼容问题需以对应导出器实现为准
- `public/assets/admin` 为构建产物输出位置
@@ -114,6 +116,7 @@
- `#/nodes` 当前已升级为真实节点工作台:支持搜索、在线 / 离线筛选、父/子节点筛选、墙状态筛选、分页浏览、显隐切换、自动上线托管开关、墙检测托管开关、刷新数据、复制、单节点置顶、仅对已勾选节点生效的批量修改 / 批量删除,以及 11 种协议的新增 / 编辑弹窗和排序对话框
- 节点自动上线由后端 `sync:server-auto-online` 定时命令执行,只处理 `auto_online=1` 的节点:在线 / 待同步时自动 `show=1`,离线时自动 `show=0`;未开启自动上线的节点继续保持手动显隐控制;墙状态为 `blocked` 或仍处于 `gfw_auto_hidden` 且未恢复正常时会否决自动显示
- 节点自动墙检测由后端 `sync:server-gfw-checks` 定时命令执行,只为开启 `gfw_check_enabled` 的父节点创建检测任务;子节点不独立检测,但可控制是否随父节点自动隐藏 / 恢复
- Compose 部署必须确保 Laravel Scheduler 持续运行;`deploy/xboard-server/compose.yaml` 通过独立 `scheduler` 服务执行 `php artisan schedule:work`,否则自动墙检测只会在手动触发时创建任务
- Bearer Token 存储于 `sessionStorage/localStorage`
- `admin-frontend` 的视觉方向当前以 Apple 风格为基线,优先纯色分区、系统字体栈和低装饰成本
+2
View File
@@ -3,6 +3,8 @@
| 模块名 | 说明 | 最近更新 |
|--------|------|----------|
| [admin-frontend](admin-frontend.md) | 管理端前端登录、布局、仪表盘、用户管理、节点管理与管理 API 封装 | 2026-04-28 |
| [deploy](deploy.md) | 可复制到服务器的 Xboard Compose 部署模板、环境变量模板和运维脚本 | 2026-04-28 |
| [node-gfw-check](node-gfw-check.md) | 节点墙状态检测任务、父/子节点继承规则、mi-node 检测上报链路 | 2026-04-28 |
| [order-payment](order-payment.md) | 订单支付成功快照、第三方回调元信息透传与后台支付成功信息展示 | 2026-04-25 |
| [queue-mail](queue-mail.md) | 邮件发送队列、SMTP 运行时配置、Horizon 超时与失败重试边界 | 2026-04-28 |
| [subscription-protocols](subscription-protocols.md) | 用户订阅导出入口、协议适配器与 Stash / Clash 系列兼容过滤 | 2026-04-24 |
+28
View File
@@ -0,0 +1,28 @@
# deploy
## 职责
- 维护可复制到服务器的一键部署模板
- 收敛 Docker Compose 服务拓扑、环境变量模板、运行目录初始化脚本和常用运维命令
- 为依赖 Laravel Scheduler 的后台任务提供明确的部署进程入口
## 行为规范
- `deploy/xboard-server/` 是面向服务器复制部署的自包含目录,不依赖仓库根目录的 compose 样例
- `compose.yaml` 默认包含 `web / horizon / scheduler / admin / ws-server / redis` 六个服务
- `scheduler` 服务固定执行 `php artisan schedule:work`,用于持续触发 `sync:server-gfw-checks``sync:server-auto-online` 和其他 Laravel Scheduler 任务
- 模板默认使用外部 MySQL,不在 compose 中创建数据库服务,避免改变现有生产拓扑
- `.env.example` 同时覆盖 Docker Compose 变量和 Laravel 运行变量,但不得包含真实 `APP_KEY`、数据库密码、邮箱密码或真实业务域名
- `scripts/init.sh` 只创建挂载目录并在 `.env` 不存在时复制模板,不执行数据库迁移
- `scripts/deploy.sh` 只负责初始化、拉取镜像和启动服务,不自动执行生产数据库迁移
- `scripts/update.sh --migrate` 才会显式执行 `php artisan migrate --force`
- `scripts/status.sh` 输出 compose 状态、scheduler 日志、`schedule:list` 结果和手动墙检测同步命令
## 依赖关系
- 依赖 `ghcr.io/micah123321/xboard:new` 作为后端默认镜像
- 依赖 `ghcr.io/micah123321/xboard-admin-frontend:new` 作为管理端默认镜像
- 依赖 `redis:8-alpine` 提供 `/data/redis.sock`
- 依赖外部 MySQL,由 `.env` 中的 `DB_*` 配置提供
- 依赖 `admin-frontend/Caddyfile` 支持 `XBOARD_BACKEND_UPSTREAM``XBOARD_UPLOAD_UPSTREAM`
- 依赖 `app/Console/Kernel.php` 注册 `sync:server-gfw-checks` 等定时任务
+3 -1
View File
@@ -14,8 +14,9 @@
- `server_gfw_checks.status` 使用 `pending / checking / normal / blocked / partial / failed / skipped`
- 管理端 `POST server/manage/checkGfw` 接收 `{ ids: number[] }`,响应中区分 `started``skipped`
- 后端定时命令 `sync:server-gfw-checks` 会自动为 `gfw_check_enabled=1` 的父节点创建检测任务;已有未超时的 `pending/checking` 任务时跳过,超过 5 分钟未领取或未上报的任务会自动标记为 `failed`
- Docker all-in-one 镜像通过 supervisor 独立运行 `php artisan schedule:work``compose.sample.yaml` 的分进程样例和 `deploy/xboard-server/compose.yaml` 服务器部署模板也包含 `scheduler` 服务,确保 `sync:server-gfw-checks` 和其他 Laravel Scheduler 任务会持续执行
- 节点端 `GET server/gfw/task` 只向父节点返回待执行任务;节点端 `POST server/gfw/report` 必须校验 `check_id` 归属当前节点
- `v2_server.gfw_check_enabled` 控制节点是否参与自动墙检测与墙状态自动显隐;父节点开启时会自动创建检测任务,子节点不独立检测但可单独关闭随父节点自动隐藏 / 恢复
- `v2_server.gfw_check_enabled` 控制节点是否参与自动墙检测与墙状态自动显隐;管理端开启父节点墙检测托管时会立即发起一次检测,后续由定时命令持续检测;子节点不独立检测但可单独关闭随父节点自动隐藏 / 恢复
- `blocked` 结果会自动隐藏仍开启墙检测托管且当前显示中的父节点及其子节点,并设置 `gfw_auto_hidden=1`
- `normal` 结果只恢复 `gfw_auto_hidden=1` 的节点,避免误恢复管理员手动隐藏的节点;`partial/failed` 只记录状态,不触发自动上线或下线
- `sync:server-auto-online` 会把最新墙状态 `blocked` 和未恢复的 `gfw_auto_hidden` 作为显示否决条件,防止自动上线重新发布疑似被墙节点
@@ -32,6 +33,7 @@
- 依赖 `app/Http/Controllers/V2/Server/ServerController.php` 暴露节点端任务领取和上报接口
- 依赖 `app/Services/NodeSyncService.php` 与 Workerman WS 通道向在线节点推送 `gfw.check`
- 依赖 `app/Console/Commands/SyncServerGfwChecks.php` 与 Laravel Scheduler 自动创建检测任务
- 依赖 `.docker/supervisor/supervisord.conf``deploy/xboard-server/compose.yaml` 中的 `scheduler` 服务,或部署环境中的 `schedule:work` / `cron + schedule:run` 持续驱动 Laravel Scheduler
- 依赖 `app/Services/ServerAutoOnlineService.php` 在自动上线同步时尊重墙状态否决
- 依赖 `E:/code/go/mi-node/internal/gfwcheck` 执行 ping 检测和结果判定
- 依赖 `E:/code/go/mi-node/internal/panel``internal/controlplane``internal/service` 接收任务、轮询兜底并上报结果
+32
View File
@@ -0,0 +1,32 @@
# queue-mail
## 职责
- 承接注册验证码、登录链接、工单通知、订阅到期/流量提醒和后台群发邮件的异步发送。
- 通过 `App\Jobs\SendEmailJob` 统一进入 `send_email``send_email_mass` 队列。
- 通过 `App\Services\MailService` 统一渲染邮件模板、应用运行时 SMTP 配置、发送邮件并写入 `MailLog`
## 行为规范
- `SendEmailJob` 的默认 `timeout` 为 60 秒,`tries` 为 3`backoff()``[60, 300]`
- `SendEmailJob::$failOnTimeout = true`,超时作业应直接失败,避免同一封邮件在不确定是否已发出的情况下反复重试。
- 邮件发送返回错误时,`SendEmailJob` 抛出 `RuntimeException`,由 Laravel Queue/Horizon 统一处理重试和失败记录;不再手动 `release(60)`
- SMTP 传输超时由 `MAIL_TIMEOUT` 控制,默认 30 秒;`QUEUE_RETRY_AFTER` 默认 90 秒,必须大于邮件 job timeout。
- Horizon 长驻 worker 每次发送前会通过 `MailRuntimeConfig` 应用后台邮件配置,并刷新已解析 mailer,避免后台 SMTP 配置变更后仍使用旧连接。
- `MailLog.config` 只保存脱敏后的邮件配置,`password``secret``token``key` 字段不得以明文持久化。
- `send_email_mass` 队列仍会在邮件正文追加 `[Send-Time: ...]` 标记,用于区分批量发送内容。
## 依赖关系
- 队列配置: `config/queue.php`
- Horizon supervisor: `config/horizon.php`
- 邮件配置: `config/mail.php`
- 运行时配置: `App\Services\MailRuntimeConfig`
- HTML 通知内容: `App\Services\MailHtmlContent`
- 邮件日志模型: `App\Models\MailLog`
## 验证要点
- `SendEmailJob::$timeout` 小于 `config('queue.connections.redis.retry_after')`
- `MAIL_TIMEOUT` 小于 `SendEmailJob::$timeout`,确保网络层先于 job 层超时。
- 单测应覆盖 job 超时/backoff、邮件错误抛出、批量邮件发送时间标记和 `MailLog` 配置脱敏。