fix(api): 修复自动墙检父节点筛选与启用语义

统一自动墙检查询逻辑,兼容 parent_id 为 null 或 0 的父节点
将 gfw_check_enabled 的空值视为开启,避免页面显示已启用却未入队

同时调整管理端自动墙检统计仅计算父节点
并更新 CI 配置以忽略 .helloagents 变更触发后端发布
This commit is contained in:
yinjianm
2026-04-28 14:53:19 +08:00
parent 4c66d1fbe0
commit a62a124710
18 changed files with 506 additions and 13 deletions
+1
View File
@@ -5,6 +5,7 @@ on:
branches: ["master", "new-dev"]
paths-ignore:
- "admin-frontend/**"
- ".helloagents/**"
- ".github/workflows/admin-frontend-docker-publish.yml"
workflow_dispatch:
+14
View File
@@ -1,5 +1,19 @@
# CHANGELOG
## [0.6.10] - 2026-04-28
### 修复
- **[node-gfw-check]**: 修复 `parent_id=0` 的父节点在管理端显示已开启墙检测托管、但不会被自动墙检任务入队而长期显示“未检测”的问题;自动墙检现在同时兼容 `parent_id IS NULL``parent_id=0`,并把未明确关闭的 `gfw_check_enabled` 视为开启,管理端自动墙检统计也改为只统计父节点 — by yinjianm
- 方案: [202604281441_fix-admin-node-gfw-null-enabled](archive/2026-04/202604281441_fix-admin-node-gfw-null-enabled/)
- 决策: fix-admin-node-gfw-null-enabled#D001(自动墙检查询对齐项目父节点与启用语义)
## [0.6.9] - 2026-04-28
### 修复
- **[ci-workflows]**: 修复仅修改 `admin-frontend/**` 且附带 `.helloagents/**` 知识库记录时仍误触发后端 Docker 发布的问题;后端 workflow 现在会忽略 `.helloagents/**`,但混有后端相关文件时仍会正常运行 — by yinjianm
- 方案: [202604281432_ci-ignore-helloagents-for-backend-docker](archive/2026-04/202604281432_ci-ignore-helloagents-for-backend-docker/)
- 决策: ci-ignore-helloagents-for-backend-docker#D001(后端 Docker workflow 忽略 HelloAGENTS 知识库路径)
## [0.6.8] - 2026-04-28
### 快速修改
+1
View File
@@ -16,6 +16,7 @@ active_package: 无
## 活跃模块
- [admin-frontend](modules/admin-frontend.md): 管理端登录、主布局、仪表盘、用户/节点/订阅/系统管理与管理 API 前端封装
- [ci-workflows](modules/ci-workflows.md): GitHub Actions 后端与管理端前端镜像发布工作流、路径触发边界和 GHCR 发布规则
- [deploy](modules/deploy.md): 可复制到服务器的 Xboard Compose 部署模板、环境变量模板和运维脚本
- [node-gfw-check](modules/node-gfw-check.md): 节点墙状态检测任务、父/子节点继承规则、mi-node 检测上报链路
- [order-payment](modules/order-payment.md): 订单支付成功快照、第三方回调元信息透传与后台支付成功信息展示
@@ -0,0 +1 @@
{"status":"completed","completed":3,"failed":0,"pending":0,"total":3,"percent":100,"current":"任务全部完成,准备归档方案包","updated_at":"2026-04-28 14:36:00"}
@@ -0,0 +1,128 @@
# 变更提案: ci-ignore-helloagents-for-backend-docker
## 元信息
```yaml
类型: 优化
方案类型: implementation
优先级: P2
状态: 已规划
创建: 2026-04-28
```
---
## 1. 需求
### 背景
上一个 commit 只改动了 `admin-frontend/src/views/nodes/NodesView.vue``.helloagents/CHANGELOG.md`。当前后端 Docker 发布工作流 `.github/workflows/docker-publish.yml` 使用 `paths-ignore`,只忽略了 `admin-frontend/**` 和前端发布 workflow,本次 `.helloagents/CHANGELOG.md` 变更仍会触发后端 Docker 构建。
### 目标
只修当前误触发问题:让 `.helloagents/**` 知识库和方案记录变更不触发 `Backend Docker Build and Publish`,同时保留后端相关文件变更触发后端镜像构建的现有行为。
### 约束条件
```yaml
时间约束:
性能约束: 不增加 CI 运行成本
兼容性约束: 保持现有 master/new-dev 分支触发策略和 workflow_dispatch 手动触发能力
业务约束: 不改变前端 Docker 发布 workflow,不扩大到完整后端 paths 白名单重构
```
### 验收标准
- [ ] `.github/workflows/docker-publish.yml``paths-ignore` 包含 `.helloagents/**`
- [ ]`.helloagents/**``admin-frontend/**` 变更时不会触发后端 Docker 发布工作流
- [ ] 后端代码、后端 Dockerfile 或后端依赖文件变更仍会触发后端 Docker 发布工作流
- [ ] workflow YAML 语法可解析,变更 diff 范围可人工核对
---
## 2. 方案
### 技术方案
`.github/workflows/docker-publish.yml``on.push.paths-ignore` 中追加 `.helloagents/**`。该路径仅排除 HelloAGENTS 知识库、方案包、归档和变更记录等本地协作元数据,不影响后端源码、部署模板、Dockerfile、Composer 依赖或 GitHub Actions workflow 自身的正常触发。
### 影响范围
```yaml
涉及模块:
- ci: 后端 Docker 发布工作流路径过滤规则
- knowledge-base: 记录 CI 触发规则的项目知识
预计变更文件: 2-3
```
### 风险评估
| 风险 | 等级 | 应对 |
|------|------|------|
| `.helloagents/**` 下未来放入真正影响镜像构建的文件时不会触发后端构建 | 低 | `.helloagents/**` 按项目约定只存储知识库和方案记录,不作为运行时构建输入 |
| `paths-ignore` 语义被误解为所有路径都忽略才跳过 workflow | 中 | 验证 GitHub Actions 规则:当 push 中所有变更路径都匹配 ignore 时才跳过;混有后端文件时仍触发 |
### 方案取舍
```yaml
唯一方案理由: 本次误触发来自 `.helloagents/CHANGELOG.md`,追加 `.helloagents/**` 是最小、可回滚、影响面最小的修复。
放弃的替代路径:
- 改成后端正向 paths 白名单: 可更严格控制触发,但需要完整梳理后端构建输入,当前需求不需要承担该范围和漏触发风险。
- 扩大忽略文档/元数据文件: 可减少更多 CI 噪音,但会引入额外路径判断,超过本次已确认范围。
回滚边界: 删除 `.github/workflows/docker-publish.yml` 中新增的 `.helloagents/**` ignore 项即可恢复原行为。
```
---
## 3. 技术设计
N/A。本次不改变架构、API 或数据模型。
---
## 4. 核心场景
### 场景: 管理端变更附带知识库记录
**模块**: ci
**条件**: push 仅包含 `admin-frontend/**``.helloagents/**` 文件变更
**行为**: GitHub Actions 根据后端 workflow 的 `paths-ignore` 判断后端发布工作流无需运行
**结果**: 只运行管理端前端 Docker 发布工作流,后端 Docker 发布工作流被跳过
### 场景: 后端变更附带知识库记录
**模块**: ci
**条件**: push 包含后端源码或构建输入文件,同时包含 `.helloagents/**` 文件变更
**行为**: GitHub Actions 发现存在未被 `paths-ignore` 覆盖的后端相关路径
**结果**: 后端 Docker 发布工作流正常运行
---
## 5. 技术决策
### ci-ignore-helloagents-for-backend-docker#D001: 后端 Docker workflow 忽略 HelloAGENTS 知识库路径
**日期**: 2026-04-28
**状态**: 采纳
**背景**: HelloAGENTS 知识库更新是开发协作记录,不参与后端镜像构建,却会因为 `paths-ignore` 未覆盖而误触发后端 Docker 发布。
**选项分析**:
| 选项 | 优点 | 缺点 |
|------|------|------|
| A: 在 `paths-ignore` 中追加 `.helloagents/**` | 最小改动,直接修复当前误触发,可快速验证和回滚 | 仍依赖 `paths-ignore` 维护排除列表 |
| B: 改为后端 `paths` 白名单 | 触发范围更严格 | 需要完整列出所有后端构建输入,漏列会导致后端镜像不发布 |
| C: 只在 commit 时不提交知识库记录 | 避免触发 CI | 破坏项目知识库同步要求,且不能解决未来同类元数据变更 |
**决策**: 选择方案 A
**理由**: 当前问题来源明确,方案 A 影响范围最小并符合用户选择的“只修当前问题”。
**影响**: 后端 Docker 发布工作流不会再因 `.helloagents/**` 单独变更而运行。
---
## 6. 验证策略
```yaml
verifyMode: review-first
reviewerFocus:
- .github/workflows/docker-publish.yml 的 paths-ignore 缩进和路径匹配语义
- 是否仅新增 `.helloagents/**`,避免误改前端 workflow 或构建步骤
testerFocus:
- 解析 workflow YAML
- 核对上一 commit 路径集合在新规则下是否全部被 ignore 覆盖
uiValidation: none
riskBoundary:
- 不执行 Docker build/push
- 不改 GitHub Actions secrets、registry、镜像 tag 或分支策略
```
---
## 7. 成果设计
N/A。本次不涉及视觉产出。
@@ -0,0 +1,69 @@
# 任务清单: ci-ignore-helloagents-for-backend-docker
> **@status:** completed | 2026-04-28 14:39
```yaml
@feature: ci-ignore-helloagents-for-backend-docker
@created: 2026-04-28
@status: completed
@mode: R2
```
## LIVE_STATUS
```json
{"status":"completed","completed":3,"failed":0,"pending":0,"total":3,"percent":100,"current":"任务全部完成,准备归档方案包","updated_at":"2026-04-28 14:36:00"}
```
## 进度概览
| 完成 | 失败 | 跳过 | 总数 |
|------|------|------|------|
| 3 | 0 | 0 | 3 |
---
## 任务列表
### 1. CI 触发规则
- [√] 1.1 修改 `.github/workflows/docker-publish.yml`
- 预期变更: 在后端 Docker 发布 workflow 的 `on.push.paths-ignore` 中追加 `.helloagents/**`
- 完成标准: `.helloagents/**` 变更被后端 workflow 忽略,现有 `admin-frontend/**` 和前端 workflow ignore 保持不变
- 验证方式: 读取文件并解析 YAML,核对 `paths-ignore` 列表
- depends_on: []
### 2. 知识库同步
- [√] 2.1 更新项目知识库中的 CI 行为记录
- 预期变更: 在项目上下文或对应模块记录后端 Docker workflow 会忽略 `.helloagents/**`
- 完成标准: 知识库描述与 workflow 实际行为一致
- 验证方式: 读取更新后的知识库文件并核对描述
- depends_on: [1.1]
### 3. 验证与收尾
- [√] 3.1 验证 workflow 语法和触发路径推断
- 预期变更: 使用本地 YAML 解析和路径集合核对证明改动满足需求
- 完成标准: workflow YAML 可解析;上一 commit 的 `.helloagents/CHANGELOG.md``admin-frontend/**` 在新规则下不会触发后端 workflow
- 验证方式: PowerShell/Python 本地验证脚本或等效命令输出
- depends_on: [1.1, 2.1]
---
## 执行日志
| 时间 | 任务 | 状态 | 备注 |
|------|------|------|------|
| 2026-04-28 14:35 | 3.1 | completed | 结构化读取 `paths-ignore` 通过;上一 commit 路径全部命中 ignore;后端示例路径未被 ignore;`git diff --check` 通过 |
| 2026-04-28 14:34 | 2.1 | completed | 已新增 `ci-workflows` 模块并在项目上下文记录后端 workflow ignore 规则 |
| 2026-04-28 14:33 | 1.1 | completed | 已在后端 Docker workflow 的 `paths-ignore` 中追加 `.helloagents/**` |
| 2026-04-28 14:32 | DESIGN | completed | 已确定最小方案:后端 workflow 追加 `.helloagents/**` ignore |
---
## 执行备注
本次只处理用户确认的最小范围,不切换到后端 `paths` 白名单,不修改前端 Docker 发布 workflow,不执行 Docker build/push。
验证备注:本机缺少 Python `PyYAML`、Node `yaml/js-yaml` 和 Ruby,因此未执行通用 YAML parser 校验;已通过缩进结构读取确认 `paths-ignore` 列表,并用 `git diff --check` 完成空白错误检查。
@@ -0,0 +1 @@
{"status":"completed","completed":4,"failed":0,"pending":0,"total":4,"percent":100,"current":"代码修复、前端构建和知识库同步已完成;后端目标测试因本机缺少 PHP 未执行","updated_at":"2026-04-28 14:50:00"}
@@ -0,0 +1,153 @@
# 变更提案: fix-admin-node-gfw-null-enabled
## 元信息
```yaml
类型: 修复
方案类型: implementation
优先级: P1
状态: 已规划
创建: 2026-04-28
```
---
## 1. 需求
### 背景
管理后台 `#/nodes` 页面中,部分节点显示已开启“墙检测托管”和“自动上线”,但墙状态长期为“未检测”。代码检查显示项目大部分逻辑把 `parent_id = 0``NULL` 都视为父节点,但自动墙检入队只匹配 `parent_id IS NULL`;同时前端、服务层默认语义均把 `gfw_check_enabled` 的空值视为开启,而自动查询只匹配数据库值 `true`。这两处语义不一致会导致页面显示可自动检测的父节点没有被自动任务入队。
### 目标
- 统一父节点判断:`parent_id = 0``parent_id = NULL` 均参与自动墙检父节点筛选。
- 统一 `gfw_check_enabled` 的启用判断:仅明确为 `false` 时关闭,`true``NULL` 均按开启处理。
- 自动墙检命令 `sync:server-gfw-checks` 能为启用托管的父节点创建检测任务。
- 自动上线联动能读取启用托管节点的最新墙检状态,避免节点被墙状态漏管。
- 前端统计与后端默认语义一致,减少“开关显示开启但实际未参与自动检测”的误导。
### 约束条件
```yaml
时间约束: 当前轮完成代码修复、测试和构建验证
性能约束: 自动任务查询仍保持按父节点、排序和可选 limit 过滤,不引入全表重计算
兼容性约束: 保留既有字段与 API 响应结构,不新增迁移破坏已有数据库
业务约束: 子节点仍不独立检测,只继承父节点墙检状态
```
### 验收标准
- [ ] `parent_id = 0` 的父节点会参与自动墙检入队。
- [ ] `gfw_check_enabled = NULL` 的父节点在兼容旧库结构时会参与自动墙检入队。
- [ ] `gfw_check_enabled = false` 的父节点仍不会参与自动墙检入队。
- [ ] 自动上线联动对 `NULL` 启用语义保持一致,可读取对应父节点最新墙状态。
- [ ] 管理端自动墙检统计只统计父节点,并与后端自动入队范围一致。
- [ ] 相关后端单元测试与 `admin-frontend` 构建通过,若环境缺依赖需明确记录。
---
## 2. 方案
### 技术方案
`ServerGfwCheckService` 中抽出父节点查询方法和启用过滤查询方法,父节点查询使用 `(parent_id IS NULL OR parent_id = 0)`,启用查询使用 `(gfw_check_enabled = true OR gfw_check_enabled IS NULL)`,与项目现有父/子节点判断和 `isGfwCheckEnabled()` 的服务层判断保持一致。同步更新前端节点工具函数,让“自动墙检”统计只统计启用托管的父节点。补充单元测试覆盖 `parent_id = 0` 的自动入队回归。
### 影响范围
```yaml
涉及模块:
- app/Services/ServerGfwCheckService.php: 统一自动墙检启用查询和最新状态查询
- admin-frontend/src/utils/nodes.ts: 调整自动墙检统计口径
- tests/Unit/ServerGfwCheckServiceTest.php: 补充空值启用语义回归测试
预计变更文件: 3
```
### 风险评估
| 风险 | 等级 | 应对 |
|------|------|------|
| 历史 `parent_id = 0` 父节点突然进入自动检测队列 | 中 | 该行为与前端父节点展示、手动检测和服务层父节点判断一致 |
| 历史 `NULL` 启用值节点进入自动检测队列 | 低 | 该行为与前端开关和服务默认语义一致;仅明确 `false` 的节点继续关闭 |
| 自动墙检统计口径变化导致数量下降 | 低 | 旧统计包含子节点,新统计改为父节点,与实际检测范围一致 |
| 本地 PHP 依赖不可用导致后端测试无法执行 | 中 | 优先运行目标测试;失败时记录环境缺口并至少完成前端构建验证 |
### 方案取舍
```yaml
唯一方案理由: 根因是父节点和启用语义在 UI/服务层与查询层不一致,修复查询条件能直接解决自动检测漏入队问题,同时保持现有 API 和数据模型不变。
放弃的替代路径:
- 新增数据迁移批量回填 NULL: 只能修复一次历史数据,不能防止未来异常数据或旧库结构导入导致同类问题。
- 前端把 NULL 显示为关闭: 与服务层 `isGfwCheckEnabled()` 默认开启语义冲突,会改变用户已经看到的托管状态。
- 只提示用户重新切换开关: 属于人工绕过,不能解决自动任务与状态查询的根因。
回滚边界: 可独立回退 `ServerGfwCheckService` 查询辅助方法、前端统计函数和新增测试,不涉及数据库结构回滚。
```
---
## 3. 技术设计
### 服务层父节点与启用语义
```php
where(function ($query) {
$query->whereNull('parent_id')
->orWhere('parent_id', 0);
})
```
```php
where(function ($query) {
$query->where('gfw_check_enabled', true)
->orWhereNull('gfw_check_enabled');
})
```
该语义只作用于自动墙检父节点筛选和最新墙状态查询;手动开关保存、批量更新和子节点继承逻辑保持不变。
---
## 4. 核心场景
### 场景: 历史节点参与自动墙检
**模块**: 节点墙检服务
**条件**: 父节点 `parent_id``0``NULL`,且无未完成墙检任务。
**行为**: 执行 `sync:server-gfw-checks` 或调用 `startAutomaticChecks()`
**结果**: 为该父节点创建 `pending` 墙检任务,管理端刷新后显示“等待节点领取”或“检测中”。
### 场景: 显式关闭节点不参与自动墙检
**模块**: 节点墙检服务
**条件**: 父节点 `gfw_check_enabled``false`
**行为**: 执行自动墙检。
**结果**: 不创建墙检任务。
---
## 5. 技术决策
### fix-admin-node-gfw-null-enabled#D001: 自动墙检查询对齐项目父节点与启用语义
**日期**: 2026-04-28
**状态**: ✅采纳
**背景**: 管理端和大部分后端逻辑把 `parent_id = 0``NULL` 都视为父节点,并把 `gfw_check_enabled``false` 视为开启;自动墙检查询只匹配 `parent_id IS NULL``gfw_check_enabled = true`,导致部分页面显示开启的父节点没有被自动入队。
**选项分析**:
| 选项 | 优点 | 缺点 |
|------|------|------|
| A: 查询层兼容 `parent_id=0/NULL``gfw_check_enabled=true/NULL` | 与现有 UI、手动检测和服务层语义一致,不改变 API 与数据库结构 | 历史 `parent_id=0` 节点会重新进入自动墙检范围 |
| B: 数据迁移统一回填父节点与启用字段 | 数据更整洁 | 对已存在但结构不同的部署风险更高,且不能防止导入旧数据后复发 |
**决策**: 选择方案 A。
**理由**: 当前问题本质是查询条件与既有运行时语义不一致,查询层对齐能最小化修复漏检,并避免数据库结构变更风险。
**影响**: `ServerGfwCheckService` 自动入队、最新墙状态查询和管理端统计口径。
---
## 6. 验证策略
```yaml
verifyMode: test-first
reviewerFocus:
- app/Services/ServerGfwCheckService.php 中父节点过滤、启用过滤与服务默认语义是否一致
- 前端统计是否仍与实际自动墙检范围一致
testerFocus:
- php artisan test --filter=ServerGfwCheckServiceTest
- npm run buildadmin-frontend
uiValidation: optional
riskBoundary:
- 不新增数据库迁移
- 不改变节点端上报 API
- 不改变子节点继承检测规则
```
---
## 7. 成果设计
N/A。本次不是视觉改版,只做状态语义与统计口径修复。
@@ -0,0 +1,76 @@
# 任务清单: fix-admin-node-gfw-null-enabled
> **@status:** completed | 2026-04-28 14:48
```yaml
@feature: fix-admin-node-gfw-null-enabled
@created: 2026-04-28
@status: completed
@mode: R2
```
## LIVE_STATUS
```json
{"status":"completed","completed":4,"failed":0,"pending":0,"total":4,"percent":100,"current":"代码修复、前端构建和知识库同步已完成;后端目标测试因本机缺少 PHP 未执行","updated_at":"2026-04-28 14:50:00"}
```
## 进度概览
| 完成 | 失败 | 跳过 | 总数 |
|------|------|------|------|
| 4 | 0 | 0 | 4 |
---
## 任务列表
### 1. 后端墙检启用语义
- [√] 1.1 修改 `app/Services/ServerGfwCheckService.php`
- 预期变更: 抽出父节点和启用过滤查询方法,让自动墙检入队同时覆盖 `parent_id = NULL``parent_id = 0`,并让最新墙状态查询把 `NULL` 启用值视为开启。
- 完成标准: `startAutomaticChecks()``getLatestStatusesForServers()` 的查询条件均与项目父节点判断和 `isGfwCheckEnabled()` 语义一致。
- 验证方式: 阅读代码并运行 `php artisan test --filter=ServerGfwCheckServiceTest`
- depends_on: []
### 2. 前端统计口径
- [√] 2.1 修改 `admin-frontend/src/utils/nodes.ts`
- 预期变更: `countAutoGfwCheckNodes()` 只统计开启墙检托管的父节点,避免把不会独立检测的子节点计入自动墙检数量。
- 完成标准: 统计口径与 `startAutomaticChecks()` 的父节点范围一致。
- 验证方式: 运行 `npm run build``admin-frontend`)。
- depends_on: [1.1]
### 3. 回归测试
- [√] 3.1 修改 `tests/Unit/ServerGfwCheckServiceTest.php`
- 预期变更: 补充 `parent_id = 0` 父节点会被自动入队、显式关闭仍跳过的测试。
- 完成标准: 新测试能失败于旧父节点查询语义,成功于修复后的查询语义。
- 验证方式: 运行 `php artisan test --filter=ServerGfwCheckServiceTest`
- depends_on: [1.1]
### 4. 知识库与验收
- [√] 4.1 同步知识库并执行目标验证
- 预期变更: 更新节点管理上下文或模块文档,记录 `gfw_check_enabled` 空值按开启处理的长期约定;执行后端目标测试和前端构建。
- 完成标准: 知识库反映代码事实;验证结果明确记录。
- 验证方式: 检查 `.helloagents/context.md` 或相关模块文档,查看测试/构建输出。
- depends_on: [2.1, 3.1]
---
## 执行日志
| 时间 | 任务 | 状态 | 备注 |
|------|------|------|------|
| 2026-04-28 14:41 | DESIGN | completed | 已完成上下文收集、唯一方案规划与方案包创建 |
| 2026-04-28 14:45 | 1.1/2.1/3.1 | completed | 已完成后端查询语义、前端统计口径和回归测试用例修改 |
| 2026-04-28 14:50 | 4.1 | completed | 前端构建通过;知识库已同步;后端目标测试因本机缺少 PHP 未执行 |
---
## 执行备注
- 本次修复不新增迁移,优先保证查询语义对历史空值数据鲁棒。
- 当前工作树存在其他未提交变更和未完成 CI 方案包,本方案只修改墙检相关文件。
- 验证记录:`ADMIN_BUILD_OUT_DIR=dist npm run build` 通过;`php artisan test --filter=ServerGfwCheckServiceTest` 因本机 `php` 不在 PATH 未执行;`git diff --check` 对本次代码文件通过,仅提示 Windows 换行转换。
+3
View File
@@ -7,6 +7,8 @@
| 时间戳 | 名称 | 类型 | 涉及模块 | 决策 | 结果 |
|--------|------|------|---------|------|------|
| 202604281441 | fix-admin-node-gfw-null-enabled | implementation | node-gfw-check,admin-frontend | fix-admin-node-gfw-null-enabled#D001 | ✅完成 |
| 202604281432 | ci-ignore-helloagents-for-backend-docker | implementation | ci-workflows | ci-ignore-helloagents-for-backend-docker#D001 | ✅完成 |
| 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 | ✅完成 |
@@ -42,6 +44,7 @@
## 按月归档
### 2026-04
- [202604281441_fix-admin-node-gfw-null-enabled](./2026-04/202604281441_fix-admin-node-gfw-null-enabled/) - 修复 `parent_id=0` 父节点不会被自动墙检入队导致长期显示“未检测”的问题,并让自动墙检查询对齐项目父节点与启用语义
- [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 状态
+2 -1
View File
@@ -14,6 +14,7 @@
- 前端容器会通过 `XBOARD_BACKEND_UPSTREAM``/api` 反向代理到后端 `web` 服务;compose 分支当前默认值为 `http://web:7001`
- 前端容器会通过 `XBOARD_UPLOAD_UPSTREAM``/upload/*` 去掉 `/upload` 前缀后反向代理到图片上传服务,默认值为 `https://pic.535888.xyz`
- GHCR 前端镜像发布工作流位于 `.github/workflows/admin-frontend-docker-publish.yml`,镜像名为 `ghcr.io/<owner>/xboard-admin-frontend`
- 后端镜像发布工作流位于 `.github/workflows/docker-publish.yml`,使用 `paths-ignore` 排除 `admin-frontend/**``.helloagents/**` 与前端发布 workflow;仅这些路径变化时不触发后端镜像发布,混有后端相关文件时仍会触发
- 管理端 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`
@@ -115,7 +116,7 @@
- 管理端当前业务路由包含 `/dashboard``/users``/tickets``/nodes``/node-groups``/node-routes``/subscriptions/plans``/subscriptions/orders``/subscriptions/coupons``/subscriptions/gift-cards``/system/config``/system/notices``/system/payments``/system/plugins``/system/themes``/system/knowledge`
- `#/nodes` 当前已升级为真实节点工作台:支持搜索、在线 / 离线筛选、父/子节点筛选、墙状态筛选、分页浏览、显隐切换、自动上线托管开关、墙检测托管开关、刷新数据、复制、单节点置顶、仅对已勾选节点生效的批量修改 / 批量删除,以及 11 种协议的新增 / 编辑弹窗和排序对话框
- 节点自动上线由后端 `sync:server-auto-online` 定时命令执行,只处理 `auto_online=1` 的节点:在线 / 待同步时自动 `show=1`,离线时自动 `show=0`;未开启自动上线的节点继续保持手动显隐控制;墙状态为 `blocked` 或仍处于 `gfw_auto_hidden` 且未恢复正常时会否决自动显示
- 节点自动墙检测由后端 `sync:server-gfw-checks` 定时命令执行,只为开启 `gfw_check_enabled` 的父节点创建检测任务;子节点不独立检测,但可控制是否随父节点自动隐藏 / 恢复
- 节点自动墙检测由后端 `sync:server-gfw-checks` 定时命令执行,只为开启 `gfw_check_enabled` 的父节点创建检测任务;父节点兼容 `parent_id IS NULL` 与历史 `parent_id=0` 两种表示,`gfw_check_enabled` 仅明确为 `false` 时关闭;子节点不独立检测,但可控制是否随父节点自动隐藏 / 恢复
- Compose 部署必须确保 Laravel Scheduler 持续运行;`deploy/xboard-server/compose.yaml` 通过独立 `scheduler` 服务执行 `php artisan schedule:work`,否则自动墙检测只会在手动触发时创建任务
- Bearer Token 存储于 `sessionStorage/localStorage`
- `admin-frontend` 的视觉方向当前以 Apple 风格为基线,优先纯色分区、系统字体栈和低装饰成本
+1
View File
@@ -3,6 +3,7 @@
| 模块名 | 说明 | 最近更新 |
|--------|------|----------|
| [admin-frontend](admin-frontend.md) | 管理端前端登录、布局、仪表盘、用户管理、节点管理与管理 API 封装 | 2026-04-28 |
| [ci-workflows](ci-workflows.md) | GitHub Actions 镜像发布工作流、路径触发规则与前后端镜像发布边界 | 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 |
+1 -1
View File
@@ -44,7 +44,7 @@
- 节点列表现支持本地分页、在线 / 离线筛选、父/子节点筛选,以及跨分页稳定勾选;批量修改 / 批量删除仅作用于已勾选节点,其中批量修改可统一更新 `host / group_ids / rate / auto_online`
- 节点管理页新增“自动上线”托管开关;开启后后台 `sync:server-auto-online` 会按节点在线状态自动同步 `show`,在线 / 待同步时显示、离线时隐藏,未开启的节点仍保持手动显隐控制;疑似被墙或仍处于墙检测自动隐藏状态的节点不会被自动上线重新发布
- 节点管理页现支持墙状态展示、墙状态筛选与关键词搜索;父节点可通过行级或批量操作发起检测,子节点不单独检测并显示“随父节点”的继承状态
- 节点管理页现支持“墙检测托管”开关、批量设置和刷新数据按钮;父节点开启后参与 `sync:server-gfw-checks` 自动检测,子节点不独立检测但可控制是否随父节点自动隐藏 / 恢复
- 节点管理页现支持“墙检测托管”开关、批量设置和刷新数据按钮;父节点开启后参与 `sync:server-gfw-checks` 自动检测,自动墙检统计只计算父节点;子节点不独立检测但可控制是否随父节点自动隐藏 / 恢复
- 节点行级菜单现已补齐“置顶节点”,会复用当前排序结果生成新的顺序 payload 并提交到 `server/manage/sort`
- 权限组管理页使用真实后端 `server/group/fetch``server/group/save``server/group/drop`,支持关键字搜索、新增/编辑中央弹窗、删除确认,以及从节点数量列跳转到 `#/nodes?group={id}` 的筛选联动
- 路由管理页使用真实后端 `server/route/fetch``server/route/save``server/route/drop`,支持路由列表、关键词搜索、新增/编辑中央弹窗、删除与动作值展示
+21
View File
@@ -0,0 +1,21 @@
# ci-workflows
## 职责
- 维护 GitHub Actions 镜像发布工作流的触发边界
- 区分 Laravel 后端镜像与 `admin-frontend` 独立静态镜像的构建发布链路
- 记录不参与镜像构建的协作元数据路径,避免无意义触发 Docker 发布任务
## 行为规范
- 后端镜像发布工作流位于 `.github/workflows/docker-publish.yml`,名称为 `Backend Docker Build and Publish`
- 后端 workflow 在 `master``new-dev` 分支 push 时触发,并保留 `workflow_dispatch` 手动触发入口
- 后端 workflow 使用 `paths-ignore` 排除 `admin-frontend/**``.helloagents/**``.github/workflows/admin-frontend-docker-publish.yml`
- GitHub Actions 的 `paths-ignore` 语义是:push 中所有变更路径都被 ignore 覆盖时跳过 workflow;只要混有未被 ignore 的后端相关路径,后端 workflow 仍会运行
- 管理端前端镜像发布工作流位于 `.github/workflows/admin-frontend-docker-publish.yml`,只关注 `admin-frontend/**` 和自身 workflow 变更
## 依赖关系
- 依赖 GitHub Actions 的 `paths-ignore` 过滤行为
- 依赖 GHCR 作为镜像发布目标
- 依赖 `.helloagents/**` 仅作为知识库、方案包、归档和协作记录,不作为后端镜像构建输入
+3 -3
View File
@@ -9,14 +9,14 @@
## 行为规范
- 检测任务只对父节点创建;`parent_id` 的子节点不单独下发任务
- 检测任务只对父节点创建;父节点兼容 `parent_id IS NULL` 与历史 `parent_id=0` 两种表示,带真实父节点 ID 的子节点不单独下发任务
- 子节点列表展示继承父节点最新 `gfw_check`,并返回 `inherited=true``source_node_id`
- `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`
- 后端定时命令 `sync:server-gfw-checks` 会自动为未明确关闭墙检托管的父节点创建检测任务;`gfw_check_enabled=1` `NULL` 均视为开启,只有 `false/0` 视为关闭;已有未超时的 `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` 作为显示否决条件,防止自动上线重新发布疑似被墙节点
+1 -1
View File
@@ -300,5 +300,5 @@ export function countAutoOnlineNodes(nodes: AdminNodeItem[]): number {
}
export function countAutoGfwCheckNodes(nodes: AdminNodeItem[]): number {
return nodes.filter((node) => node.gfw_check_enabled !== false).length
return nodes.filter((node) => !node.parent_id && node.gfw_check_enabled !== false).length
}
+20 -5
View File
@@ -4,6 +4,7 @@ namespace App\Services;
use App\Models\Server;
use App\Models\ServerGfwCheck;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
class ServerGfwCheckService
@@ -76,9 +77,7 @@ class ServerGfwCheckService
public function startAutomaticChecks(?int $limit = null): array
{
$query = Server::query()
->whereNull('parent_id')
->where('gfw_check_enabled', true)
$query = $this->whereGfwCheckEnabled($this->parentNodeQuery())
->orderBy('sort', 'ASC')
->orderBy('id', 'ASC');
@@ -158,8 +157,7 @@ class ServerGfwCheckService
return [];
}
$enabledSourceIds = Server::whereIn('id', $sourceIds)
->where('gfw_check_enabled', true)
$enabledSourceIds = $this->whereGfwCheckEnabled(Server::whereIn('id', $sourceIds))
->pluck('id')
->map(fn ($id) => (int) $id)
->values();
@@ -402,6 +400,23 @@ class ServerGfwCheckService
return (bool) ($server->gfw_check_enabled ?? true);
}
private function parentNodeQuery(): Builder
{
return Server::query()
->where(function (Builder $query): void {
$query->whereNull('parent_id')
->orWhere('parent_id', 0);
});
}
private function whereGfwCheckEnabled(Builder $query): Builder
{
return $query->where(function (Builder $query): void {
$query->where('gfw_check_enabled', true)
->orWhereNull('gfw_check_enabled');
});
}
private function determineStatus(?array $operators, string $reportedStatus, string $errorMessage): string
{
if ($errorMessage !== '') {
+10 -2
View File
@@ -15,6 +15,10 @@ class ServerGfwCheckServiceTest extends TestCase
public function test_start_automatic_checks_only_enqueues_enabled_parent_nodes_without_active_task(): void
{
$eligible = $this->makeServer(['name' => 'eligible-parent']);
$zeroParent = $this->makeServer([
'name' => 'zero-parent',
'parent_id' => 0,
]);
$active = $this->makeServer(['name' => 'active-parent']);
$stale = $this->makeServer(['name' => 'stale-parent']);
$this->makeServer([
@@ -41,15 +45,19 @@ class ServerGfwCheckServiceTest extends TestCase
$result = app(ServerGfwCheckService::class)->startAutomaticChecks();
$this->assertSame(3, $result['total']);
$this->assertSame(4, $result['total']);
$this->assertSame(1, $result['active']);
$this->assertSame(1, $result['expired']);
$this->assertSame([$eligible->id, $stale->id], array_column($result['started'], 'id'));
$this->assertSame([$eligible->id, $zeroParent->id, $stale->id], array_column($result['started'], 'id'));
$this->assertCount(1, $result['skipped']);
$this->assertDatabaseHas('server_gfw_checks', [
'server_id' => $eligible->id,
'status' => ServerGfwCheck::STATUS_PENDING,
]);
$this->assertDatabaseHas('server_gfw_checks', [
'server_id' => $zeroParent->id,
'status' => ServerGfwCheck::STATUS_PENDING,
]);
$this->assertDatabaseHas('server_gfw_checks', [
'id' => $staleCheck->id,
'status' => ServerGfwCheck::STATUS_FAILED,