perf(api): 优化节点统计查询与 Docker 构建缓存
节点流量累计统计改为统一从 v2_stat_server 聚合, 避免节点当前累计字段重置后出现月统计大于累计的错误。 Docker 构建改为先复制 composer 清单并缓存依赖安装, 同时移除 composer.lock 的忽略规则以提升缓存命中率。
This commit is contained in:
@@ -18,7 +18,6 @@ Homestead.yaml
|
|||||||
npm-debug.log
|
npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
composer.phar
|
composer.phar
|
||||||
composer.lock
|
|
||||||
yarn.lock
|
yarn.lock
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## [0.6.16] - 2026-04-28
|
||||||
|
|
||||||
|
### 快速修改
|
||||||
|
- **[ci-workflows]**: 优化后端 Docker 构建缓存命中;`composer.lock` 现在进入镜像构建上下文,Composer 依赖安装提前到源码复制前并使用 BuildKit 缓存挂载,构建期不再重复执行全量 `chown/chmod` — by yinjianm
|
||||||
|
- 类型: 快速修改(无方案包)
|
||||||
|
- 文件: Dockerfile:1-44, .dockerignore:18-22
|
||||||
|
|
||||||
|
## [0.6.15] - 2026-04-28
|
||||||
|
|
||||||
|
### 快速修改
|
||||||
|
- **[admin-frontend]**: 修正节点 hover 流量详情的累计统计口径,今日、本月、累计现在全部从 `v2_stat_server` 按节点聚合,避免节点当前累计字段被重置后出现“本月大于累计”的展示错误 — by yinjianm
|
||||||
|
- 类型: 快速修改(无方案包)
|
||||||
|
- 文件: app/Http/Controllers/V2/Admin/Server/ManageController.php:34-66, .helloagents/modules/admin-frontend.md:49
|
||||||
|
|
||||||
## [0.6.14] - 2026-04-28
|
## [0.6.14] - 2026-04-28
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
|
|||||||
+3
-4
@@ -42,7 +42,7 @@
|
|||||||
## 2. 方案
|
## 2. 方案
|
||||||
|
|
||||||
### 技术方案
|
### 技术方案
|
||||||
在后端 `ManageController::getNodes()` 中基于当前节点 ID 集合批量聚合 `v2_stat_server`,按今日起点和本月起点计算 `SUM(u)`、`SUM(d)`、`SUM(u + d)`;累计流量以 `v2_server.u/d` 为节点当前累计真相源。三组结果统一挂载到每个节点的 `traffic_stats` 字段。前端扩展节点类型定义和 `nodes.ts` 工具函数,提供统一的流量格式化与详情数据结构;`NodesView.vue` 将节点名称区域包裹为 Element Plus popover,并以 Apple 风格的克制统计网格展示日、月、总三组上下行数据。
|
在后端 `ManageController::getNodes()` 中基于当前节点 ID 集合批量聚合 `v2_stat_server`,按今日起点、本月起点和全量统计三种窗口计算 `SUM(u)`、`SUM(d)`、`SUM(u + d)`。三组结果统一挂载到每个节点的 `traffic_stats` 字段。前端扩展节点类型定义和 `nodes.ts` 工具函数,提供统一的流量格式化与详情数据结构;`NodesView.vue` 将节点名称区域包裹为 Element Plus popover,并以 Apple 风格的克制统计网格展示日、月、总三组上下行数据。
|
||||||
|
|
||||||
### 影响范围
|
### 影响范围
|
||||||
```yaml
|
```yaml
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
|
|
||||||
### 方案取舍
|
### 方案取舍
|
||||||
```yaml
|
```yaml
|
||||||
唯一方案理由: 当前需求需要“日、月、总”三种统计,后端已有 StatServer 日志表和 Server.u/d 累计字段,最合理路径是在 getNodes 返回时一次性挂载聚合结果,前端只负责展示。
|
唯一方案理由: 当前需求需要“日、月、总”三种统计,后端已有 StatServer 日志表,最合理路径是在 getNodes 返回时一次性挂载聚合结果,前端只负责展示。
|
||||||
放弃的替代路径:
|
放弃的替代路径:
|
||||||
- 前端逐节点请求统计接口: 会产生 N+1 网络请求,表格分页和 hover 体验不稳定
|
- 前端逐节点请求统计接口: 会产生 N+1 网络请求,表格分页和 hover 体验不稳定
|
||||||
- 只展示 v2_server.u/d: 只能表示节点当前累计字段,不能满足日/月维度
|
- 只展示 v2_server.u/d: 只能表示节点当前累计字段,不能满足日/月维度
|
||||||
@@ -77,7 +77,6 @@
|
|||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
A[StatServer v2_stat_server] --> B[ManageController getNodes 批量聚合]
|
A[StatServer v2_stat_server] --> B[ManageController getNodes 批量聚合]
|
||||||
G[Server u/d] --> B
|
|
||||||
C[ServerService getAllServers] --> B
|
C[ServerService getAllServers] --> B
|
||||||
B --> D[traffic_stats today/month/total]
|
B --> D[traffic_stats today/month/total]
|
||||||
D --> E[AdminNodeItem 类型]
|
D --> E[AdminNodeItem 类型]
|
||||||
@@ -103,7 +102,7 @@ traffic_stats?: {
|
|||||||
| `traffic_stats.today.download` | number | 今日节点下行字节数 |
|
| `traffic_stats.today.download` | number | 今日节点下行字节数 |
|
||||||
| `traffic_stats.today.total` | number | 今日节点总流量 |
|
| `traffic_stats.today.total` | number | 今日节点总流量 |
|
||||||
| `traffic_stats.month.*` | number | 本月节点流量统计 |
|
| `traffic_stats.month.*` | number | 本月节点流量统计 |
|
||||||
| `traffic_stats.total.*` | number | 节点当前累计流量统计,来源为 `v2_server.u/d` |
|
| `traffic_stats.total.*` | number | 节点全量统计流量,来源为 `v2_stat_server` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
- 节点管理页现支持墙状态展示、墙状态筛选与关键词搜索;父节点可通过行级或批量操作发起检测,子节点不单独检测并显示“随父节点”的继承状态
|
- 节点管理页现支持墙状态展示、墙状态筛选与关键词搜索;父节点可通过行级或批量操作发起检测,子节点不单独检测并显示“随父节点”的继承状态
|
||||||
- 节点管理页现支持“墙检测托管”开关、批量设置和刷新数据按钮;父节点开启后参与 `sync:server-gfw-checks` 自动检测,自动墙检统计只计算父节点;子节点不独立检测但可控制是否随父节点自动隐藏 / 恢复
|
- 节点管理页现支持“墙检测托管”开关、批量设置和刷新数据按钮;父节点开启后参与 `sync:server-gfw-checks` 自动检测,自动墙检统计只计算父节点;子节点不独立检测但可控制是否随父节点自动隐藏 / 恢复
|
||||||
- 节点行级菜单现已补齐“置顶节点”,会复用当前排序结果生成新的顺序 payload 并提交到 `server/manage/sort`
|
- 节点行级菜单现已补齐“置顶节点”,会复用当前排序结果生成新的顺序 payload 并提交到 `server/manage/sort`
|
||||||
- 节点列表中鼠标悬停节点名称会显示节点流量详情卡;`server/manage/getNodes` 会返回 `traffic_stats.today/month/total`,其中今日和本月来自 `v2_stat_server` 按节点聚合,累计来自 `v2_server.u/d`,前端统一按 B/KB/MB/GB/TB 自适应格式化展示上行、下行和合计
|
- 节点列表中鼠标悬停节点名称会显示节点流量详情卡;`server/manage/getNodes` 会返回 `traffic_stats.today/month/total`,三组数据均来自 `v2_stat_server` 按节点聚合,前端统一按 B/KB/MB/GB/TB 自适应格式化展示上行、下行和合计
|
||||||
- 权限组管理页使用真实后端 `server/group/fetch`、`server/group/save` 与 `server/group/drop`,支持关键字搜索、新增/编辑中央弹窗、删除确认,以及从节点数量列跳转到 `#/nodes?group={id}` 的筛选联动
|
- 权限组管理页使用真实后端 `server/group/fetch`、`server/group/save` 与 `server/group/drop`,支持关键字搜索、新增/编辑中央弹窗、删除确认,以及从节点数量列跳转到 `#/nodes?group={id}` 的筛选联动
|
||||||
- 路由管理页使用真实后端 `server/route/fetch`、`server/route/save` 与 `server/route/drop`,支持路由列表、关键词搜索、新增/编辑中央弹窗、删除与动作值展示
|
- 路由管理页使用真实后端 `server/route/fetch`、`server/route/save` 与 `server/route/drop`,支持路由列表、关键词搜索、新增/编辑中央弹窗、删除与动作值展示
|
||||||
- 路由管理页的节点引用摘要由 `server/manage/getNodes` 返回的 `route_ids` 推导,不在前端伪造额外接口
|
- 路由管理页的节点引用摘要由 `server/manage/getNodes` 返回的 `route_ids` 推导,不在前端伪造额外接口
|
||||||
|
|||||||
+24
-10
@@ -1,33 +1,47 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
FROM phpswoole/swoole:php8.2-alpine
|
FROM phpswoole/swoole:php8.2-alpine
|
||||||
|
|
||||||
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
|
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
|
||||||
|
|
||||||
# Install PHP extensions one by one with lower optimization level for ARM64 compatibility
|
# Install PHP extensions one by one with lower optimization level for ARM64 compatibility
|
||||||
RUN CFLAGS="-O0" install-php-extensions pcntl && \
|
RUN --mount=type=cache,target=/var/cache/apk,sharing=locked \
|
||||||
|
CFLAGS="-O0" install-php-extensions pcntl && \
|
||||||
CFLAGS="-O0 -g0" install-php-extensions bcmath && \
|
CFLAGS="-O0 -g0" install-php-extensions bcmath && \
|
||||||
install-php-extensions zip && \
|
install-php-extensions zip && \
|
||||||
install-php-extensions redis && \
|
install-php-extensions redis && \
|
||||||
apk --no-cache add shadow sqlite mysql-client mysql-dev mariadb-connector-c git patch supervisor redis caddy && \
|
apk add --update-cache --no-progress shadow sqlite mysql-client mysql-dev mariadb-connector-c git patch supervisor redis caddy && \
|
||||||
addgroup -S -g 1000 www && adduser -S -G www -u 1000 www && \
|
addgroup -S -g 1000 www && adduser -S -G www -u 1000 www && \
|
||||||
(getent group redis || addgroup -S redis) && \
|
(getent group redis || addgroup -S redis) && \
|
||||||
(getent passwd redis || adduser -S -G redis -H -h /data redis)
|
(getent passwd redis || adduser -S -G redis -H -h /data redis) && \
|
||||||
|
mkdir -p /data && \
|
||||||
|
chown redis:redis /data
|
||||||
|
|
||||||
WORKDIR /www
|
WORKDIR /www
|
||||||
|
|
||||||
COPY .docker /
|
ENV COMPOSER_ALLOW_SUPERUSER=1
|
||||||
|
|
||||||
|
COPY composer.json composer.lock ./
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/tmp/composer-cache,sharing=locked \
|
||||||
|
COMPOSER_CACHE_DIR=/tmp/composer-cache composer install \
|
||||||
|
--no-dev \
|
||||||
|
--prefer-dist \
|
||||||
|
--no-interaction \
|
||||||
|
--no-progress \
|
||||||
|
--no-security-blocking \
|
||||||
|
--no-scripts \
|
||||||
|
--no-autoloader
|
||||||
|
|
||||||
COPY . /www
|
COPY . /www
|
||||||
|
|
||||||
|
COPY .docker /
|
||||||
COPY .docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
COPY .docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
COPY .docker/caddy/Caddyfile /etc/caddy/Caddyfile
|
COPY .docker/caddy/Caddyfile /etc/caddy/Caddyfile
|
||||||
COPY .docker/php/zz-xboard.ini /usr/local/etc/php/conf.d/zz-xboard.ini
|
COPY .docker/php/zz-xboard.ini /usr/local/etc/php/conf.d/zz-xboard.ini
|
||||||
|
|
||||||
RUN composer install --no-cache --no-dev --no-security-blocking \
|
RUN composer dump-autoload --no-dev --optimize --no-interaction \
|
||||||
&& php artisan storage:link \
|
&& php artisan storage:link
|
||||||
&& chown -R www:www /www \
|
|
||||||
&& chmod -R 775 /www \
|
|
||||||
&& mkdir -p /data \
|
|
||||||
&& chown redis:redis /data
|
|
||||||
|
|
||||||
ENV ENABLE_WEB=true \
|
ENV ENABLE_WEB=true \
|
||||||
ENABLE_HORIZON=true \
|
ENABLE_HORIZON=true \
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class ManageController extends Controller
|
|||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$serverId = (int) $server->id;
|
$serverId = (int) $server->id;
|
||||||
$stats[$serverId] = $this->emptyNodeTrafficStats();
|
$stats[$serverId] = $this->emptyNodeTrafficStats();
|
||||||
$stats[$serverId]['total'] = $this->buildTrafficAmount($server->u ?? 0, $server->d ?? 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($stats)) {
|
if (empty($stats)) {
|
||||||
@@ -46,17 +45,20 @@ class ManageController extends Controller
|
|||||||
|
|
||||||
$this->fillTrafficWindow($stats, 'today', strtotime('today'));
|
$this->fillTrafficWindow($stats, 'today', strtotime('today'));
|
||||||
$this->fillTrafficWindow($stats, 'month', strtotime(date('Y-m-01')));
|
$this->fillTrafficWindow($stats, 'month', strtotime(date('Y-m-01')));
|
||||||
|
$this->fillTrafficWindow($stats, 'total');
|
||||||
|
|
||||||
return $stats;
|
return $stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function fillTrafficWindow(array &$stats, string $key, int $startAt): void
|
private function fillTrafficWindow(array &$stats, string $key, ?int $startAt = null): void
|
||||||
{
|
{
|
||||||
$rows = StatServer::query()
|
$rows = StatServer::query()
|
||||||
->selectRaw('server_id, COALESCE(SUM(u), 0) as upload, COALESCE(SUM(d), 0) as download')
|
->selectRaw('server_id, COALESCE(SUM(u), 0) as upload, COALESCE(SUM(d), 0) as download')
|
||||||
->whereIn('server_id', array_keys($stats))
|
->whereIn('server_id', array_keys($stats))
|
||||||
->where('record_type', 'd')
|
->where('record_type', 'd')
|
||||||
->where('record_at', '>=', $startAt)
|
->when($startAt !== null, function ($query) use ($startAt) {
|
||||||
|
$query->where('record_at', '>=', $startAt);
|
||||||
|
})
|
||||||
->groupBy('server_id')
|
->groupBy('server_id')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user