fix(api): 修复节点流量限额共享统计与父子显隐联动

统一节点流量统计与限额展示口径,节点详情新增昨日流量,
并让今日、昨日和本月使用清晰的半开时间窗口聚合

同 machine_id 或同 host 的节点现在共享当前账期已用流量,
管理端优先使用后端 traffic_limit_snapshot 展示月额度状态,
mi-node 下发的 current_used 也改为共享账期统计

新增 parent_auto_hidden 标记与父节点显隐联动服务,父节点
因自动上线或流量限额变为不可展示时会隐藏当前显示的子节点,
恢复时只恢复这批自动隐藏的子节点,避免覆盖手动操作
This commit is contained in:
yinjianm
2026-04-29 02:24:57 +08:00
parent 922e86070d
commit e847252e12
27 changed files with 2078 additions and 47 deletions
@@ -0,0 +1,10 @@
{
"status": "completed",
"completed": 5,
"failed": 0,
"pending": 0,
"total": 5,
"percent": 100,
"current": "节点昨日流量统计已实现并完成验证",
"updated_at": "2026-04-29 01:50:00"
}
@@ -0,0 +1,181 @@
# 变更提案: node-traffic-yesterday-stats
## 元信息
```yaml
类型: 修复 + 新功能
方案类型: implementation
优先级: P1
状态: 已规划
创建: 2026-04-29
```
---
## 1. 需求
### 背景
管理后台 `#/nodes` 的节点流量详情卡当前展示“今日 / 本月 / 累计”。用户反馈某节点“今日下行很多,但本月上行最多”看起来不匹配,并要求统计加入“昨日”。
### 目标
- 核对节点统计的上行/下行字段映射,确认是否存在前后端反转或聚合口径错误。
- 在节点流量详情卡中新增“昨日”统计,口径与“今日”一致。
- 收紧统计窗口边界,保证“今日 / 昨日 / 本月 / 累计”各自窗口清晰。
### 约束条件
```yaml
时间约束:
性能约束: 节点列表接口仍按当前批量聚合方式查询,避免逐节点查询
兼容性约束: 保持现有 traffic_stats.today/month/total 字段兼容,新增 yesterday 字段
业务约束: 不改变 StatServer.u/d 的含义,不迁移历史数据
```
### 验收标准
- [ ] `server/manage/getNodes` 响应中的 `traffic_stats` 包含 `yesterday`
- [ ] `today` 只统计当天 `[today, tomorrow)``yesterday` 只统计 `[yesterday, today)``month` 只统计 `[monthStart, nextMonthStart)`
- [ ] 前端节点流量详情卡按“今日 / 昨日 / 本月 / 累计”展示。
- [ ] 后端测试覆盖新窗口边界,前端构建通过。
---
## 2. 方案
### 技术方案
`ManageController` 内扩展节点流量窗口构建:
- `emptyNodeTrafficStats()` 增加 `yesterday` 默认值。
- `buildNodeTrafficStats()` 使用 `strtotime('today')``strtotime('tomorrow')``strtotime('yesterday')` 和下月月初计算窗口。
- `fillTrafficWindow()` 支持可选结束时间,查询使用半开区间:`record_at >= startAt``record_at < endAt`
前端同步:
- `AdminNodeTrafficStats` 增加 `yesterday: TrafficAmount`
- `NodeTrafficDetail.key` 增加 `yesterday`
- `getNodeTrafficDetails()` 在“今日”和“本月”之间插入“昨日”。
测试:
- 新增/扩展 `ManageController` 单元测试,通过反射调用私有构建方法,构造跨天、跨月和未来记录,验证各窗口不会互相污染。
### 影响范围
```yaml
涉及模块:
- admin-frontend: 节点流量详情卡类型与展示行
- backend-admin-api: 节点列表接口 traffic_stats 聚合窗口
- tests: 节点统计窗口单元测试
预计变更文件: 4-6
```
### 风险评估
| 风险 | 等级 | 应对 |
|------|------|------|
| 历史数据中 u/d 语义本身来自节点端上报,若节点端上报方向定义与面板相反,面板无法单独纠正 | 中 | 本次只核对面板字段链路;不改变历史语义,避免误修 |
| 新增窗口可能增加查询次数 | 低 | 仍为按 server_id 批量聚合,仅从 3 个窗口增至 4 个窗口 |
| 月统计加入上界后不再包含未来 record_at | 低 | 这是更严格的窗口口径,符合预期 |
### 方案取舍
```yaml
唯一方案理由: 后端统一输出 yesterday 字段,前端只按接口字段展示,可以保持统计口径单一且兼容现有调用方。
放弃的替代路径:
- 仅前端用 today/month/total 推导昨日: 无法准确还原昨日上行/下行。
- 修改 StatServerJob 的 u/d 写入方向: 当前面板链路内 u=上行、d=下行一致,贸然反转会破坏历史数据和用户统计。
- 新增独立节点详情接口: 本次只影响列表详情卡,新增接口会扩大维护面。
回滚边界: 可独立回退 ManageController 的 yesterday/window 变更、前端类型/展示变更和测试文件,不涉及数据库迁移。
```
---
## 3. 技术设计
### API 设计
#### GET server/manage/getNodes
- **响应新增字段**: `traffic_stats.yesterday`
- **结构**:
```json
{
"traffic_stats": {
"today": {"upload": 0, "download": 0, "total": 0},
"yesterday": {"upload": 0, "download": 0, "total": 0},
"month": {"upload": 0, "download": 0, "total": 0},
"total": {"upload": 0, "download": 0, "total": 0}
}
}
```
### 数据模型
不新增数据表或字段,继续读取 `v2_stat_server` 的日统计记录:
| 字段 | 类型 | 说明 |
|------|------|------|
| `u` | bigint | 上行流量 |
| `d` | bigint | 下行流量 |
| `record_at` | int | 日统计归属日的 00:00:00 Unix 时间戳 |
| `record_type` | char | 本节点页只读取 `d` |
---
## 4. 核心场景
### 场景: 节点流量详情卡查看昨日统计
**模块**: admin-frontend
**条件**: 管理员打开 `#/nodes` 并悬停节点名称
**行为**: 前端读取 `traffic_stats.yesterday` 并渲染“昨日”行
**结果**: 管理员可以直接对比今日、昨日、本月和累计的上行/下行分布
### 场景: 节点列表接口按清晰窗口聚合
**模块**: backend-admin-api
**条件**: `v2_stat_server` 存在昨天、今天、本月其他日期和未来日期记录
**行为**: `server/manage/getNodes` 构建半开时间窗口
**结果**: 今日、昨日、本月统计互不串窗,累计仍覆盖全部历史记录
---
## 5. 技术决策
### node-traffic-yesterday-stats#D001: 保持 u/d 语义并新增后端 yesterday 字段
**日期**: 2026-04-29
**状态**: ✅采纳
**背景**: 用户反馈上行/下行看起来不匹配,同时要求加入昨日统计。代码链路显示前端、接口和入库任务均使用 `u=upload``d=download`
**选项分析**:
| 选项 | 优点 | 缺点 |
|------|------|------|
| A: 后端新增 `traffic_stats.yesterday` 并收紧窗口 | 口径统一、可测试、兼容当前字段 | 增加一个聚合查询 |
| B: 前端推导昨日 | 不改后端 | 无法准确得到昨日上行/下行 |
| C: 反转 u/d 字段 | 可能符合某些节点端方向理解 | 会破坏现有面板语义和历史统计 |
**决策**: 选择方案 A
**理由**: 问题核心是缺少可对比的昨日窗口和窗口边界不够明确,不是面板链路内字段反转。
**影响**: `server/manage/getNodes` 响应字段增加,节点页详情卡增加一行展示。
---
## 6. 验证策略
```yaml
verifyMode: test-first
reviewerFocus:
- app/Http/Controllers/V2/Admin/Server/ManageController.php 的窗口边界和兼容字段
- admin-frontend/src/utils/nodes.ts 的展示顺序与空值兜底
testerFocus:
- php artisan test --filter NodeTrafficStatsTest
- npm run buildadmin-frontend
uiValidation: optional
riskBoundary:
- 不执行数据库删除、重置或生产环境操作
- 不修改历史 StatServer 数据
```
---
## 7. 成果设计
### 设计方向
- **美学基调**: 延续现有 Apple 风格节点详情卡,新增“昨日”作为同等层级数据行,不引入额外视觉系统。
- **记忆点**: 今日与昨日紧邻展示,便于直接比较日流量方向变化。
- **参考**: 现有节点流量 popover。
### 视觉要素
- **配色**: 沿用现有白底、浅灰行底和蓝色总量强调。
- **字体**: 沿用现有管理端字体栈,不新增字体依赖。
- **布局**: 维持纵向统计行结构,顺序为今日、昨日、本月、累计。
- **动效**: 沿用 Element Plus Popover 行为,不新增动效。
- **氛围**: 与当前节点页一致。
### 技术约束
- **可访问性**: 不改变现有 hover/focus 触发方式。
- **响应式**: Popover 宽度维持现状,新增一行不改变表格布局。
@@ -0,0 +1,84 @@
# 任务清单: node-traffic-yesterday-stats
> **@status:** completed | 2026-04-29 01:37
```yaml
@feature: node-traffic-yesterday-stats
@created: 2026-04-29
@status: completed
@mode: R2
```
## LIVE_STATUS
```json
{"status":"completed","completed":5,"failed":0,"pending":0,"total":5,"percent":100,"current":"节点昨日流量统计已实现并完成验证","updated_at":"2026-04-29 01:50:00"}
```
## 进度概览
| 完成 | 失败 | 跳过 | 总数 |
|------|------|------|------|
| 5 | 0 | 0 | 5 |
---
## 任务列表
### 1. 后端统计窗口
- [√] 1.1 修改 `app/Http/Controllers/V2/Admin/Server/ManageController.php`
- 预期变更: `traffic_stats` 增加 `yesterday`,今日/昨日/本月使用半开时间窗口聚合。
- 完成标准: 接口保留 `today/month/total`,新增 `yesterday`,空数据返回 0。
- 验证方式: `php -l app/Http/Controllers/V2/Admin/Server/ManageController.php`; `vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit/Admin/NodeTrafficStatsWindowTest.php`
- depends_on: []
- 完成备注: 已新增 `resolveNodeTrafficWindows()` 并让 `fillTrafficWindow()` 使用 `record_at >= start``record_at < end` 的半开窗口。
### 2. 前端展示
- [√] 2.1 修改 `admin-frontend/src/types/api.d.ts`
- 预期变更: `AdminNodeTrafficStats` 类型增加 `yesterday: TrafficAmount`
- 完成标准: TypeScript 类型与后端响应字段一致。
- 验证方式: `npm run build`
- depends_on: [1.1]
- 完成备注: `AdminNodeTrafficStats` 已包含 `yesterday`
- [√] 2.2 修改 `admin-frontend/src/utils/nodes.ts`
- 预期变更: `getNodeTrafficDetails()` 在今日后展示昨日。
- 完成标准: 节点详情卡顺序为今日、昨日、本月、累计,缺失字段时显示 0。
- 验证方式: `npm run build`
- depends_on: [2.1]
- 完成备注: 节点流量详情顺序已调整为今日、昨日、本月、累计。
### 3. 验证与知识库
- [√] 3.1 新增或更新后端单元测试
- 预期变更: 覆盖今日、昨日、本月和累计窗口边界。
- 完成标准: 测试能证明未来记录不进入今日/月统计,昨日记录独立统计。
- 验证方式: `vendor/bin/phpunit --bootstrap vendor/autoload.php tests/Unit/Admin/NodeTrafficStatsWindowTest.php`
- depends_on: [1.1]
- 完成备注: 已新增窗口边界单元测试,覆盖普通日期和月初日期。
- [√] 3.2 执行构建/测试并同步知识库
- 预期变更: 运行可用验证命令,更新 `.helloagents` 模块文档和变更日志。
- 完成标准: 验证结果记录在执行日志,知识库反映 `traffic_stats.yesterday`
- 验证方式: 文件检查 + 命令输出
- depends_on: [2.2, 3.1]
- 完成备注: 已通过 PHP 语法检查、PHPUnit 单元测试和管理端前端构建,知识库与 CHANGELOG 已同步。
---
## 执行日志
| 时间 | 任务 | 状态 | 备注 |
|------|------|------|------|
| 2026-04-29 01:23:00 | DESIGN | in_progress | 已完成上下文收集和方案包创建 |
| 2026-04-29 01:38:00 | DEVELOP 1.1 | completed | 后端新增 yesterday 窗口并收紧 today/month 上界 |
| 2026-04-29 01:40:00 | DEVELOP 2.1-2.2 | completed | 前端类型和节点详情卡展示已加入“昨日” |
| 2026-04-29 01:44:00 | DEVELOP 3.1 | completed | 新增节点流量窗口边界单元测试 |
| 2026-04-29 01:50:00 | DEVELOP 3.2 | completed | 验证命令通过,知识库同步完成 |
---
## 执行备注
- 面板链路中 `StatServer.u` 对应上行、`StatServer.d` 对应下行;本次不反转历史语义。
- 用户截图中的“今日下行多、本月上行多”本身可能是正常数据分布,因为本月包含今天及之前日期;新增昨日后便于判断差异来自哪一天。
@@ -0,0 +1,179 @@
# 变更提案: shared-node-traffic-limit
## 元信息
```yaml
类型: 修复
方案类型: implementation
优先级: P1
状态: 草稿
创建: 2026-04-29
```
---
## 1. 需求
### 背景
节点管理页的“流量统计”弹层中,“月额度”当前优先使用单个节点的 mi-node metrics 或 `v2_server.u + v2_server.d`。当同一台机器上配置多个节点时,机器月流量额度是共享的,单节点口径会低估或拆散真实使用量。现有“本月”统计也按自然月聚合,不等同于机器供应商从指定重置日开始的账期。
### 目标
- 同一台机器 / 同一 IP 下的多个节点共享月额度使用量。
- 月额度使用量按当前限额配置的上一个重置边界到现在统计,而不是按自然月或单节点累计。
- 节点管理页的“月额度 used / limit”、进度条和限额状态使用后端统一快照。
- 保留“今日 / 昨日 / 本月 / 累计”节点流量统计的现有展示口径,不把自然月统计改成账期统计。
### 约束条件
```yaml
时间约束:
性能约束: 节点列表接口需要避免全表扫描式重复统计,按当前节点集合和共享范围聚合
兼容性约束: 不新增必填数据库字段;未配置 machine_id 的节点仍可按 host/IP 共享
业务约束: 不执行生产数据修复;不改变节点显隐和用户订阅筛选规则
```
### 验收标准
- [ ] 同一 `machine_id` 的两个限额节点,节点管理页显示相同的月额度已用量。
- [ ] 未配置 `machine_id``host` 相同的两个限额节点,节点管理页显示相同的月额度已用量。
- [ ] 月额度已用量从当前时间之前最近一次重置边界开始统计,例如重置日 18 日则按最近一个 18 日 00:00 起算。
- [ ] 不同 `machine_id` 或不同 `host` 的节点不互相累加。
- [ ] 前端在后端缺少共享快照时仍能回退到原有 metrics / `u + d` 展示。
---
## 2. 方案
### 技术方案
`ServerTrafficLimitService` 中集中新增共享限额快照能力:
- 共享范围优先使用 `machine_id`;未绑定机器时使用规范化后的 `host`。空 host 回退为单节点范围。
- 新增当前账期起点计算:根据节点的 `traffic_limit_reset_day / traffic_limit_reset_time / traffic_limit_timezone` 计算“当前时间之前最近一次重置时间”。
- 新增共享账期用量统计:从 `v2_stat_server` 中按共享范围内的 `server_id` 聚合 `record_type='d'``u/d`,统计窗口为账期起点到当前日。若节点未持久化或没有统计来源,再回退到共享范围内的 `u + d`
- `buildNodeConfig()` 继续返回 mi-node 既有 `traffic_limit` 结构,但 `current_used` 改用共享账期用量。
- 管理端 `server/manage/getNodes` 为每个节点追加 `traffic_limit_snapshot`,前端月额度优先使用该快照展示。
- 管理端 TypeScript 类型和 `getNodeTrafficLimitDetail()` 同步增加快照优先级,保持缺省兼容。
### 影响范围
```yaml
涉及模块:
- node-traffic-limit: 限额账期、共享范围和当前用量计算
- admin-frontend: 节点管理页月额度展示数据源
预计变更文件: 5-7
```
### 风险评估
| 风险 | 等级 | 应对 |
|------|------|------|
| `v2_stat_server.record_at` 是日粒度,非 00:00 重置时间无法做到小时级切分 | 中 | 按重置日所在自然日作为统计起点,保留 `traffic_limit_last_reset_at` 和 mi-node metrics 作为运行态辅助;在测试中覆盖 00:00 主路径 |
| 同 host 但实际不是同一机器的节点会被共享统计 | 中 | 优先使用 `machine_id`;未绑定机器时按用户明确要求的同 IP/host 兜底,并在知识库记录规则 |
| 前端旧数据结构缺少新快照 | 低 | 前端保留原有 metrics / `u + d` 回退 |
| 改动影响 mi-node 下发的 `current_used` | 中 | 仅改变限额模块中的用量口径,不改变配置字段名;用单元测试覆盖共享和非共享场景 |
### 方案取舍
```yaml
唯一方案理由: 共享用量属于限额领域逻辑,集中在 ServerTrafficLimitService 能同时服务节点配置下发和管理端展示,避免前端自行猜测同机节点。
放弃的替代路径:
- 仅前端按 host 汇总: 只能修展示,无法修正 mi-node 下发的 current_used,且会复制业务规则
- 新增 machine_quota 表: 能表达机器级额度,但超出本次问题范围,需要新增配置入口和迁移
- 每次 DNS 解析 host 后按真实 IP 汇总: 接口性能和网络副作用不可控,且域名解析会受环境影响
回滚边界: 可独立回退 ServerTrafficLimitService 的共享快照、ManageController 的 traffic_limit_snapshot 返回和前端快照优先展示;不涉及数据库结构回滚
```
---
## 3. 技术设计
### 架构设计
```mermaid
flowchart TD
A[ManageController getNodes] --> B[ServerTrafficLimitService buildSnapshotsForServers]
C[ServerService buildNodeConfig] --> D[ServerTrafficLimitService buildNodeConfig]
B --> E[shared scope: machine_id or host]
D --> E
E --> F[v2_stat_server cycle usage]
B --> G[traffic_limit_snapshot]
G --> H[admin-frontend getNodeTrafficLimitDetail]
```
### API 设计
#### GET `server/manage/getNodes`
- **请求**: 保持不变。
- **响应**: 每个节点新增可选字段:
```json
{
"traffic_limit_snapshot": {
"enabled": true,
"limit": 1073741824000,
"used": 616327110656,
"percent": 57,
"suspended": false,
"status": "normal",
"cycle_start_at": 1776441600,
"last_reset_at": 1776441600,
"next_reset_at": 1779033600,
"scope_key": "host:82.40.33.225",
"scope_node_ids": [327, 272]
}
}
```
### 数据模型
不新增数据库字段。共享范围由现有 `v2_server.machine_id``v2_server.host` 推导。
---
## 4. 核心场景
### 场景: 同 IP 节点共享月额度
**模块**: node-traffic-limit
**条件**: 两个节点 `host` 相同,均启用月流量限额,重置日一致。
**行为**: 管理端打开节点列表并悬停任一节点名称。
**结果**: 两个节点的“月额度”已用量均为该 host 下节点账期流量合计。
### 场景: 绑定机器优先共享
**模块**: node-traffic-limit
**条件**: 两个节点绑定相同 `machine_id`host 可以不同。
**行为**: 后端生成节点列表或 mi-node 配置。
**结果**: 共享范围按 `machine_id` 聚合,不再按 host 分裂。
---
## 5. 技术决策
### shared-node-traffic-limit#D001: 共享范围优先 machine_id,兜底 host
**日期**: 2026-04-29
**状态**: ✅采纳
**背景**: 项目已有 `v2_server_machine``machine_id`,但用户当前问题来自同 IP 多节点共享机器额度,不能要求所有旧节点先补机器绑定。
**选项分析**:
| 选项 | 优点 | 缺点 |
|------|------|------|
| A: 只按 machine_id | 语义最准确 | 旧节点或未绑定机器的同 IP 节点无法修复 |
| B: 只按 host/IP | 满足截图场景 | 已绑定机器但 host 不同的同机节点会被拆散 |
| C: machine_id 优先,host 兜底 | 覆盖新旧两类场景,改动较小 | host 相同但非同机的节点会共享统计 |
**决策**: 选择方案 C。
**理由**: 在不新增配置入口的前提下,C 能覆盖已有机器模型和用户明确的同 IP 场景。
**影响**: `ServerTrafficLimitService` 成为共享范围规则的唯一实现位置,管理端和节点配置下发共用该规则。
---
## 6. 验证策略
```yaml
verifyMode: test-first
reviewerFocus:
- app/Services/ServerTrafficLimitService.php 的账期起点、共享范围和回退逻辑
- app/Http/Controllers/V2/Admin/Server/ManageController.php 的响应兼容性
- admin-frontend/src/utils/nodes.ts 的快照优先级和旧数据回退
testerFocus:
- vendor/bin/phpunit tests/Unit/ServerTrafficLimitServiceTest.php
- vendor/bin/phpunit tests/Unit/Admin/NodeTrafficStatsWindowTest.php
- php -l app/Services/ServerTrafficLimitService.php
- php -l app/Http/Controllers/V2/Admin/Server/ManageController.php
uiValidation: optional
riskBoundary:
- 不执行数据库迁移或生产数据更新
- 不修改删除节点、重置节点流量等破坏性接口语义
```
---
## 7. 成果设计
N/A。本次不调整节点管理页视觉结构,只修正“月额度”展示数据源。
@@ -0,0 +1,81 @@
# 任务清单: shared-node-traffic-limit
> **@status:** completed | 2026-04-29 01:56
```yaml
@feature: shared-node-traffic-limit
@created: 2026-04-29
@status: completed
@mode: R2
```
## LIVE_STATUS
```json
{"status":"completed","completed":5,"failed":0,"pending":0,"total":5,"percent":100,"current":"开发实施、验证和知识库同步完成","updated_at":"2026-04-29 02:08:00"}
```
## 进度概览
| 完成 | 失败 | 跳过 | 总数 |
|------|------|------|------|
| 5 | 0 | 0 | 5 |
---
## 任务列表
### 1. 后端共享限额口径
- [√] 1.1 修改 `app/Services/ServerTrafficLimitService.php`
- 预期变更: 新增共享范围解析、当前账期起点计算、共享账期用量聚合和批量快照输出;`buildNodeConfig()``current_used` 使用共享账期口径。
- 完成标准: 同 `machine_id` 或同 `host` 节点可得到一致 used;不同范围节点互不累加;未启用限额仍返回 disabled。
- 验证方式: `vendor/bin/phpunit tests/Unit/ServerTrafficLimitServiceTest.php`,并执行 `php -l app/Services/ServerTrafficLimitService.php`
- depends_on: []
- [√] 1.2 修改 `app/Http/Controllers/V2/Admin/Server/ManageController.php`
- 预期变更: `getNodes` 批量生成并返回 `traffic_limit_snapshot`;保留 `traffic_stats` 现有自然日/自然月统计。
- 完成标准: 响应中每个节点包含可选快照字段;没有快照时不影响原节点列表返回。
- 验证方式: `php -l app/Http/Controllers/V2/Admin/Server/ManageController.php`,并用相关单元测试覆盖窗口不回归。
- depends_on: [1.1]
### 2. 管理端展示兼容
- [√] 2.1 修改 `admin-frontend/src/types/api.d.ts`
- 预期变更: 为节点接口补充 `AdminNodeTrafficLimitSnapshot``traffic_limit_snapshot` 类型。
- 完成标准: TypeScript 能识别新字段,旧字段类型不被破坏。
- 验证方式: 运行可用的前端类型检查或构建命令;不可用时至少静态检查引用。
- depends_on: [1.2]
- [√] 2.2 修改 `admin-frontend/src/utils/nodes.ts`
- 预期变更: `getNodeTrafficLimitDetail()` 优先使用 `traffic_limit_snapshot` 的 limit、used、status 和 reset 时间,缺失时回退到 metrics / `u + d`
- 完成标准: 有快照时展示共享账期用量;无快照时展示行为与旧版一致。
- 验证方式: 前端类型检查或构建;人工核对逻辑分支。
- depends_on: [2.1]
### 3. 测试与知识库
- [√] 3.1 修改 `tests/Unit/ServerTrafficLimitServiceTest.php``.helloagents/modules/node-traffic-limit.md``.helloagents/modules/admin-frontend.md`
- 预期变更: 增加共享 host / machine 账期用量测试;更新知识库记录共享限额口径和管理端快照字段。
- 完成标准: 测试覆盖同范围聚合、不同范围隔离、账期起点;知识库与代码行为一致。
- 验证方式: 运行后端测试和语法检查;检查知识库描述不再声称限额只按单节点 `u/d`
- depends_on: [1.1, 1.2, 2.2]
---
## 执行日志
| 时间 | 任务 | 状态 | 备注 |
|------|------|------|------|
| 2026-04-29 02:08 | 3.1 | completed | 已补充共享 host / machine / 账期起点 / runtime suspended 测试,并完成知识库同步 |
| 2026-04-29 02:06 | 验证 | completed | PHPUnit 10 tests / 44 assertions 通过;admin-frontend 构建通过 |
| 2026-04-29 02:03 | 2.1-2.2 | completed | 管理端类型与月额度展示已优先消费 `traffic_limit_snapshot` |
| 2026-04-29 01:58 | 1.1-1.2 | completed | 后端已生成共享账期快照并接入 `server/manage/getNodes` |
| 2026-04-29 01:32 | DESIGN | in_progress | 已完成方案设计与任务拆分 |
---
## 执行备注
- 当前执行模式: INTERACTIVE。
- 不新增数据库字段,不执行生产数据操作。
@@ -0,0 +1,171 @@
# 变更提案: parent-node-auto-visibility
## 元信息
```yaml
类型: 修复
方案类型: implementation
优先级: P1
状态: 已设计
创建: 2026-04-29
```
---
## 1. 需求
### 背景
当前父节点自动状态链路存在不一致:
- `ServerAutoOnlineService` 只同步开启 `auto_online` 的节点自身;父节点因离线被自动隐藏时,不会保证其子节点同步隐藏。
- `ServerTrafficLimitService` 将流量限额超额状态写入 `traffic_limit_status`,既有知识库明确记录“不修改 `show`”,因此父节点超额后子节点仍可能保持展示。
- 墙检测已有 `gfw_auto_hidden` 标记,只恢复上次由墙检测自动隐藏的节点;本次需求需要为“父节点自动下线”建立同等可追溯标记,避免误恢复原本手动隐藏的子节点。
### 目标
- 父节点因系统自动逻辑变为不可展示时,自动隐藏当时仍展示的子节点。
- 父节点由系统自动逻辑恢复可展示时,只恢复上一次由该父节点联动逻辑自动隐藏的子节点。
- 原本 `show=0`、管理员手动隐藏、后续被手动调整的子节点不能被误上线。
- 覆盖自动上线同步、流量限额超额/恢复等当前可定位的自动状态入口,并保留墙检测自身的独立标记逻辑。
### 约束条件
```yaml
时间约束: 本轮完成后端实现、迁移、测试和知识库同步
性能约束: 子节点联动只按单个父节点查询/更新,不引入全表循环外的额外扫描
兼容性约束: 不改变现有管理端 API 请求结构,不改变 mi-node 下发协议
业务约束: 只修改系统自动联动产生的 show 状态;不修改 enabled、auto_online、gfw_check_enabled
```
### 验收标准
- [ ] 父节点自动下线时,当前 `show=1` 的子节点被隐藏并打上父级自动隐藏标记。
- [ ] 父节点自动恢复时,仅 `parent_auto_hidden=1` 的子节点恢复展示,原本隐藏的子节点保持隐藏。
- [ ] 管理员手动修改子节点 `show` 时会清除父级自动隐藏标记,后续父节点恢复不会覆盖人工决定。
- [ ] 流量限额从 normal 变为 suspended 时触发子节点隐藏,从 suspended/超额状态恢复为 normal 时触发标记子节点恢复。
- [ ] 相关单元测试通过,至少覆盖自动上线和流量限额两条入口。
---
## 2. 方案
### 技术方案
新增一组父节点联动标记字段到 `v2_server`
- `parent_auto_hidden`: 子节点是否由父节点自动状态联动隐藏。
- `parent_auto_action_at`: 最近一次父节点联动操作时间。
新增 `ServerParentVisibilityService` 作为集中服务:
- `hideChildrenForParent(Server $parent)`: 只隐藏当前 `show=1` 的直接子节点,并设置 `parent_auto_hidden=1`
- `restoreChildrenForParent(Server $parent)`: 只恢复 `parent_auto_hidden=1` 且未被其他自动隐藏原因阻断的直接子节点,并清除标记。
- `clearParentAutoHidden(Server $server)`: 管理员手动调整节点展示状态时清除标记,防止后续自动恢复覆盖人工操作。
接入点:
- `ServerAutoOnlineService`: 父节点自动同步后,根据父节点最终 `show` 决定隐藏或恢复子节点;即使父节点自身状态未变化,也根据当前最终状态补齐子节点联动。
- `ServerTrafficLimitService`: `refreshSchedule()``resetServer()``applyRuntimeMetrics()` 写入限额运行状态后,对父节点执行子节点隐藏/恢复。超额或节点端上报 suspended 时隐藏;恢复 normal 或重置后恢复标记子节点。
- `ManageController`: 在单节点保存、快速更新、批量更新中,手动传入 `show` 时同步清除 `parent_auto_hidden`
### 影响范围
```yaml
涉及模块:
- node-traffic-limit: 限额 suspended/normal 状态影响子节点展示联动
- node-auto-online: 自动上线同步影响父节点子节点展示联动
- admin-server-manage: 手动 show 修改时清理自动联动标记
预计变更文件: 8
```
### 风险评估
| 风险 | 等级 | 应对 |
|------|------|------|
| 恢复子节点时覆盖其他自动隐藏原因 | 中 | 恢复时跳过 `gfw_auto_hidden=1` 的节点,并只处理 `parent_auto_hidden=1` 的子节点 |
| 流量限额状态频繁上报导致重复更新 | 低 | 服务方法先按当前状态筛选,只更新需要变化的子节点 |
| 新字段未迁移导致运行时异常 | 中 | 添加幂等迁移、模型 casts 和测试覆盖 |
### 方案取舍
```yaml
唯一方案理由: 独立 `parent_auto_hidden` 标记能精确表达“上次由父节点联动自动下线”的来源,满足只恢复自动下线子节点的要求,且不会污染墙检测专用字段。
放弃的替代路径:
- 复用 `gfw_auto_hidden`: 会把墙检测和父节点自动联动混在一起,恢复时无法区分原因。
- 不加字段、只按当前 show 推断: 无法判断子节点原本是否手动隐藏,会误上线。
回滚边界: 可回退新增服务接入、模型字段和迁移;数据库字段保留时不会影响旧逻辑,删除字段需单独迁移。
```
---
## 3. 技术设计
### 架构设计
```mermaid
flowchart TD
A[ServerAutoOnlineService] --> C[ServerParentVisibilityService]
B[ServerTrafficLimitService] --> C
D[ManageController manual show] --> C
C --> E[v2_server parent_auto_hidden]
C --> F[v2_server show]
```
### 数据模型
| 字段 | 类型 | 说明 |
|------|------|------|
| parent_auto_hidden | boolean default false | 子节点是否由父节点自动状态联动隐藏 |
| parent_auto_action_at | unsignedBigInteger nullable | 最近一次父节点联动隐藏或恢复时间戳 |
---
## 4. 核心场景
### 场景: 父节点自动下线联动子节点
**模块**: node-auto-online / node-traffic-limit
**条件**: 父节点因自动上线检测离线、流量限额超额或其他系统自动状态变为不可展示;子节点 A 当前 `show=1`,子节点 B 当前 `show=0`
**行为**: 服务隐藏子节点 A 并设置 `parent_auto_hidden=1`,子节点 B 保持隐藏且不设置标记。
**结果**: 父节点恢复时只恢复子节点 A。
### 场景: 父节点自动恢复只恢复上次自动下线子节点
**模块**: node-auto-online / node-traffic-limit
**条件**: 父节点恢复可展示;子节点 A `parent_auto_hidden=1`,子节点 B 是手动隐藏。
**行为**: 服务恢复子节点 A 并清除标记,子节点 B 不变。
**结果**: 不误上线原本未展示的子节点。
### 场景: 管理员手动修改子节点展示状态
**模块**: admin-server-manage
**条件**: 子节点此前由父节点联动隐藏,管理员手动设置 `show`
**行为**: 控制器清除 `parent_auto_hidden``parent_auto_action_at`
**结果**: 后续父节点自动恢复不会覆盖管理员最新选择。
---
## 5. 技术决策
### parent-node-auto-visibility#D001: 使用独立父级自动隐藏标记
**日期**: 2026-04-29
**状态**: ✅采纳
**背景**: 需求要求恢复“上次自动下线”的子节点,不能恢复原本未启用或手动隐藏的子节点。
**选项分析**:
| 选项 | 优点 | 缺点 |
|------|------|------|
| A: 独立 `parent_auto_hidden` 标记 | 语义清晰,可与墙检测、手动隐藏并存 | 需要新增迁移和模型字段 |
| B: 复用 `gfw_auto_hidden` | 改动少 | 原因混淆,容易误恢复墙检测隐藏节点 |
| C: 不持久化标记 | 无数据库变更 | 不能跨进程、跨重启准确恢复 |
**决策**: 选择方案 A
**理由**: 只有持久化来源标记能准确表达“上次被父节点自动联动下线”的子节点集合。
**影响**: `v2_server` 表、节点自动上线服务、流量限额服务、管理端节点状态接口、相关测试。
---
## 6. 验证策略
```yaml
verifyMode: test-first
reviewerFocus:
- app/Services/ServerParentVisibilityService.php 的恢复条件是否避免误上线
- ServerAutoOnlineService 与 ServerTrafficLimitService 的触发时机是否覆盖状态变化
- ManageController 手动 show 修改是否清除自动标记
testerFocus:
- vendor/bin/phpunit tests/Unit/ServerAutoOnlineServiceTest.php tests/Unit/ServerTrafficLimitServiceTest.php
- php -l 新增/修改的 PHP 文件
uiValidation: none
riskBoundary:
- 不执行生产数据库迁移
- 不调用生产 API
- 不修改 mi-node 协议
```
---
## 7. 成果设计
N/A。此次为后端状态联动与数据标记修复,不涉及视觉产出。
@@ -0,0 +1,97 @@
# 任务清单: parent-node-auto-visibility
> **@status:** completed | 2026-04-29 02:07
```yaml
@feature: parent-node-auto-visibility
@created: 2026-04-29
@status: completed
@mode: R2
@type: implementation
```
## LIVE_STATUS
```json
{"status":"completed","completed":6,"failed":0,"pending":0,"total":6,"percent":100,"current":"父节点自动下线联动子节点隐藏与恢复已完成并通过验证","updated_at":"2026-04-29 02:14:00"}
```
## 进度概览
| 完成 | 失败 | 跳过 | 总数 |
|------|------|------|------|
| 6 | 0 | 0 | 6 |
---
## 任务列表
### 1. 数据模型与联动服务
- [√] 1.1 新增 `v2_server` 父级自动隐藏标记字段
- 文件路径或作用范围: `database/migrations/*_add_parent_auto_visibility_fields_to_v2_server_table.php`, `app/Models/Server.php`
- 预期变更: 增加 `parent_auto_hidden``parent_auto_action_at` 字段,补充模型 docblock 和 casts。
- 完成标准: 新字段迁移幂等,模型可布尔/整数转换字段。
- 验证方式: `php -l` 检查新增迁移和模型语法。
- depends_on: []
- [√] 1.2 新增父节点子节点展示联动服务
- 文件路径或作用范围: `app/Services/ServerParentVisibilityService.php`
- 预期变更: 实现隐藏当前展示子节点、恢复被标记子节点、清除手动标记的集中方法。
- 完成标准: 只标记本次自动隐藏的子节点;恢复时不恢复未标记或仍被墙检测隐藏的节点。
- 验证方式: 单元测试覆盖自动隐藏和恢复行为。
- depends_on: [1.1]
### 2. 自动状态入口接入
- [√] 2.1 接入自动上线同步
- 文件路径或作用范围: `app/Services/ServerAutoOnlineService.php`
- 预期变更: 父节点自动同步后根据最终展示状态隐藏或恢复直接子节点;结果统计包含子节点联动更新。
- 完成标准: 父节点离线自动隐藏时同步隐藏展示中的子节点;父节点恢复在线时只恢复 `parent_auto_hidden=1` 的子节点。
- 验证方式: `tests/Unit/ServerAutoOnlineServiceTest.php` 新增断言。
- depends_on: [1.2]
- [√] 2.2 接入流量限额超额和恢复
- 文件路径或作用范围: `app/Services/ServerTrafficLimitService.php`
- 预期变更: `refreshSchedule()``resetServer()``applyRuntimeMetrics()` 状态落库后触发父节点子节点隐藏/恢复。
- 完成标准: suspended 隐藏子节点,normal/reset 恢复被标记子节点,未启用限额不触发误恢复。
- 验证方式: `tests/Unit/ServerTrafficLimitServiceTest.php` 新增断言。
- depends_on: [1.2]
- [√] 2.3 清理手动 show 修改的自动联动标记
- 文件路径或作用范围: `app/Http/Controllers/V2/Admin/Server/ManageController.php`
- 预期变更: 单节点保存、快速更新、批量更新接收 `show` 时清除 `parent_auto_hidden``parent_auto_action_at`
- 完成标准: 手动显示/隐藏子节点后,后续父节点恢复不会覆盖人工决定。
- 验证方式: 代码检查和相关服务测试覆盖标记清除方法。
- depends_on: [1.1, 1.2]
### 3. 验证与知识库
- [√] 3.1 补充测试、知识库和变更记录
- 文件路径或作用范围: `tests/Unit/ServerAutoOnlineServiceTest.php`, `tests/Unit/ServerTrafficLimitServiceTest.php`, `.helloagents/modules/node-traffic-limit.md`, `.helloagents/context.md`, `.helloagents/CHANGELOG.md`
- 预期变更: 增加自动上线与流量限额联动测试,更新知识库说明和变更记录。
- 完成标准: 目标测试通过或明确记录环境阻塞;知识库反映代码事实。
- 验证方式: `vendor/bin/phpunit tests/Unit/ServerAutoOnlineServiceTest.php tests/Unit/ServerTrafficLimitServiceTest.php`
- depends_on: [2.1, 2.2, 2.3]
---
## 执行日志
| 时间 | 任务 | 状态 | 备注 |
|------|------|------|------|
| 2026-04-29 01:53 | 方案设计 | in_progress | 已完成上下文收集与任务拆分 |
| 2026-04-29 02:03 | 1.1-2.3 | completed | 已完成迁移、模型、联动服务和入口接入 |
| 2026-04-29 02:08 | 3.1 | completed | 已补充自动上线和流量限额测试 |
| 2026-04-29 02:14 | 验证 | completed | PHP 语法、PHPStan 和目标 PHPUnit 测试通过 |
---
## 执行备注
- 当前已有遗留方案包 `202604250006_ticket-closed-reply-reopen` 标记 `in_progress`,本任务作为新方案包独立执行。
- 不执行生产数据库迁移,仅提交迁移文件和测试。
- 验证命令:
- `php -l` 检查新增/修改 PHP 文件。
- `vendor\bin\phpstan analyse app\Services\ServerParentVisibilityService.php app\Services\ServerAutoOnlineService.php app\Services\ServerTrafficLimitService.php app\Http\Controllers\V2\Admin\Server\ManageController.php app\Models\Server.php --memory-limit=1G`
- 使用一次性 SQLite 文件执行 `vendor\bin\phpunit tests\Unit\ServerAutoOnlineServiceTest.php tests\Unit\ServerTrafficLimitServiceTest.php`,结果 17 tests / 83 assertions 通过。
+5
View File
@@ -7,6 +7,9 @@
| 时间戳 | 名称 | 类型 | 涉及模块 | 决策 | 结果 |
|--------|------|------|---------|------|------|
| 202604290153 | parent-node-auto-visibility | - | - | - | ✅完成 |
| 202604290132 | shared-node-traffic-limit | implementation | node-traffic-limit,admin-frontend | shared-node-traffic-limit#D001 | ✅完成 |
| 202604290123 | node-traffic-yesterday-stats | implementation | admin-frontend,backend-admin-api | node-traffic-yesterday-stats#D001 | ✅完成 |
| 202604281921 | node-traffic-limit-enforcement | implementation | node-traffic-limit,admin-frontend,mi-node | node-traffic-limit-enforcement#D001,#D002 | ✅完成 |
| 202604281625 | admin-frontend-node-traffic-hover | - | - | - | ✅完成 |
| 202604281632 | admin-frontend-node-auto-online-immediate-sync | - | - | - | ✅完成 |
@@ -47,6 +50,8 @@
## 按月归档
### 2026-04
- [202604290132_shared-node-traffic-limit](./2026-04/202604290132_shared-node-traffic-limit/) - 修正节点管理月额度使用量口径,同 `machine_id` 或同 host 节点共享当前账期用量,并由后端快照统一服务管理端展示和 mi-node 下发
- [202604290123_node-traffic-yesterday-stats](./2026-04/202604290123_node-traffic-yesterday-stats/) - 节点流量详情卡新增“昨日”统计,并让今日、昨日和本月统计按半开窗口聚合,便于核对上行/下行流量分布
- [202604281921_node-traffic-limit-enforcement](./2026-04/202604281921_node-traffic-limit-enforcement/) - 新增节点月流量限额强制下线能力,Xboard 负责配置、重置调度和状态展示,mi-node 负责本地额度累计、内核停止与重置恢复
- [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`、初始化/部署/更新/状态检查脚本和部署说明