全栈之路6---web集成与呈现

前情提要

前面我们讲到了如何实时、安全、可扩展地解决底层通信和数据采集的问题,并对数据进行建模和告警实现。数据在数据库里活了,告警在通知里响了,最后一步:让人看得见、管得了。

这一篇讲 web 集成与呈现,也是全栈之路的收尾,已经有了开源仓库,感兴趣的铜须可以了解:

Gitee 仓库:https://gitee.com/xuzhigang66/zmmonitor

演示环境:http://39.105.217.235:8090/index 注意需要修改登录口令为zm@007

一、为什么选 RuoYi 3.9.2

前后端分离的框架很多,我选 RuoYi 出于务实:

|------------|------------------------------|--------------|
| 考量 | RuoYi 给什么 | 我省什么 |
| 安全 | 登录认证、JWT、菜单权限、按钮权限 | 不写用户模块,不碰密码学 |
| 组织 | 用户-角色-部门三级模型,数据切面 | 不造权限轮子 |
| 前端 | Vue3 + Element Plus,表格表单代码生成 | 不搭前端工程化 |
| 生态 | 社区大, issue 多,AI 训练数据足 | Kimi 生成代码准 |

核心判断:监控系统的竞争力不在 web 框架,在数据实时性和告警准确性。web 层要快、稳、不拖后腿。

二、技术栈总览

采集层:Shell 脚本(check_resource.sh /check_ disk.sh /check_ proc.sh

通信层:C++ Socket(zmagent / zmserver)

入库层:Python + psycopg2(zm_db_service.py)

服务层:Java Spring Boot(RuoYi 后端)

前端层:Vue3 + Element Plus + ECharts(RuoYi 前端)

五层五种语言,全栈的代价是上下文切换。我的应对:每层只做一件事,接口定死后不跨层思考。

三、主机管理实现:从表到页面

以主机管理为例,看 RuoYi 怎么接我的 CM/PM/FM 数据。

3.1 数据表 → Java Entity

java

// domain/CmHost.java

@Data

public class CmHost extends BaseEntity {

private Long hostid;

private String ip;

private String hostname;

private String osType;

private String agentVersion;

private Long deptId; // 关键:关联部门,数据切面用

private Integer status; // 0离线 1在线 2未注册

private Date lastRegister;

private Date lastHeartbeat;

// 非表字段,关联查询用

private String deptName;

private String statusLabel;

}

3.2 Mapper XML → 数据切面

xml

<!-- mapper/CmHostMapper.xml -->

<select id="selectCmHostList" resultType="com.zm.monitor.domain.CmHost">

SELECT h.*, d.dept_name

FROM cm_hosts h

LEFT JOIN sys_dept d ON h.dept_id = d.dept_id

WHERE h.status != -1

<!-- 数据切面:RuoYi 自动注入 ${params.dataScope} -->

${params.dataScope}

<if test="ip != null and ip != ''">

AND h.ip::text LIKE concat('%', #{ip}, '%')

</if>

<if test="status != null">

AND h.status = #{status}

</if>

ORDER BY h.last_heartbeat DESC

</select>

${params.dataScope} 是 RuoYi 的魔法。Controller 加 @DataScope(deptAlias = "h") ,框架自动根据当前用户的部门权限,拼接 AND h.dept_id IN (...) 。

3.3 Controller → 权限注解

java

@RestController

@RequestMapping("/monitor/host")

public class CmHostController extends BaseController {

@Autowired

private ICmHostService hostService;

/**

* 查询主机列表(支持数据切面)

*/

@PreAuthorize("@ss.hasPermi('monitor:host:list')")

@DataScope(deptAlias = "h")

@GetMapping("/list")

public TableDataInfo list(CmHost host) {

startPage();

List<CmHost> list = hostService.selectCmHostList(host);

return getDataTable(list);

}

/**

* 获取主机详情(含实时指标)

*/

@PreAuthorize("@ss.hasPermi('monitor:host:query')")

@GetMapping(value = "/{hostid}")

public AjaxResult getInfo(@PathVariable("hostid") Long hostid) {

CmHost host = hostService.selectCmHostById(hostid);

// 关联查询最新性能数据

host.setLatestStats(pmResourceStatsService.getLatestByHostid(hostid));

host.setAlarms(fmAlarmDataService.getActiveByHostid(hostid));

return AjaxResult.success(host);

}

/**

* 下发命令到 Agent(通过 zmserver)

*/

@PreAuthorize("@ss.hasPermi('monitor:host:cmd')")

@Log(title = "主机管理", businessType = BusinessType.UPDATE)

@PostMapping("/cmd")

public AjaxResult sendCmd(@RequestBody CmdRequest request) {

// 调用 zmserver 的 HTTP 接口或写 admin 命令表

// 实际:通过 zmadmin client 或直接写 CommandDispatcher 的 pending 队列

return hostService.sendCmdToAgent(request);

}

}

3.4 Vue3 前端 → 页面呈现

RuoYi 的代码生成器根据表结构,自动生成基础 CRUD 页面。我主要改三处:

列表页:加状态标签和心跳时间

vue

<!-- views/monitor/host/index.vue -->

<el-table-column label="状态" align="center" width="100">

<template #default="scope">

<el-tag :type="statusType(scope.row.status)">

{{ statusLabel(scope.row.status) }}

</el-tag>

</template>

</el-table-column>

<el-table-column label="最后心跳" align="center" prop="lastHeartbeat" width="180">

<template #default="scope">

<span :class="{'text-danger': isHeartbeatTimeout(scope.row.lastHeartbeat)}">

{{ parseTime(scope.row.lastHeartbeat) }}

</span>

</template>

</el-table-column>

详情页:ECharts 性能曲线

vue

<!-- views/monitor/host/detail.vue -->

<el-row>

<el-col :span="12">

<div ref="cpuChart" style="height: 300px;"></div>

</el-col>

<el-col :span="12">

<div ref="memChart" style="height: 300px;"></div>

</el-col>

</el-row>

<script setup>

import * as echarts from 'echarts';

// 加载历史数据,初始化图表

function loadCharts(hostid) {

getHostStats(hostid, { hours: 24 }).then(response => {

const cpuOption = {

title: { text: 'CPU 使用率 (24h)' },

xAxis: { type: 'time' },

yAxis: { type: 'value', max: 100 },

series: [{

data: response.data.map(d => d.clock, d.cpuPct),

type: 'line',

smooth: true,

areaStyle: { color: 'rgba(58, 77, 233, 0.1)' }

}]

};

echarts.init(cpuChart.value).setOption(cpuOption);

});

}

</script>

命令下发:对话框 + 执行结果轮询

vue

<el-dialog title="下发命令" v-model="cmdVisible" width="500px">

<el-form>

<el-form-item label="脚本">

<el-select v-model="cmdForm.script">

<el-option label="CPU 诊断" value="cpu_diag.sh" />

<el-option label="内存清理" value="mem_clean.sh" />

</el-select>

</el-form-item>

<el-form-item label="参数">

<el-input v-model="cmdForm.params" placeholder="-t 60" />

</el-form-item>

</el-form>

<template #footer>

<el-button type="primary" @click="submitCmd">执行</el-button>

</template>

</el-dialog>

<script>

// 下发后轮询结果

function submitCmd() {

sendHostCmd(hostid, cmdForm).then(res => {

const taskid = res.data.taskid;

const timer = setInterval(() => {

getCmdResult(taskid).then(r => {

if (r.data.status !== 'PENDING') {

clearInterval(timer);

result.value = r.data.output;

}

});

}, 2000);

});

}

</script>

四、 关键 界面

4.1 统计看板

4.2 主机详情

4.3告警门限

五、AI 辅助开发:从马拉松到驾车行驶

这次 web 层开发,与AI的协作有所不同:

|-------|--------------|----------------------------------|
| 阶段 | 我做什么 | Kimi 做什么 |
| 表设计确认 | 定字段、定索引、定触发器 | 生成 RuoYi 的 Entity / Mapper / XML |
| 页面原型 | 画草图、定交互 | 生成 Vue 组件骨架、ECharts 配置 |
| 权限规则 | 定部门映射逻辑 | 生成 `@DataScope` 注解和切面 SQL |
| 调测 | 看效果、报 bug | 根据报错改代码、解释 RuoYi 源码行为 |

六、反思:全栈的代价与 AI 的边界

6.1 五层五语言,真的值得吗?

|------------|------------|-------------------------------------|
| 层级 | 语言 | 切换成本 |
| 采集 | Shell | 低,但脚本兼容性坑多(CentOS vs Ubuntu vs AIX) |
| 通信 | C++ | 高,编译慢,内存管理费心 |
| 入库 | Python | 低,但 GIL 和异步模型让人迷惑 |
| 服务 | Java | 高,Spring 生态重,启动慢 |
| 前端 | JS/Vue | 中,npm 地狱,版本兼容性 |

**劣势:**一个人维护,每层都是上下文切换。有时候改个 bug,上午在调 C++ 的 epoll ,下午在写 Vue 的 computed ,晚上在查 PostgreSQL 的锁等待,对于开发者来说,挺累的。而且,容易浮躁,每一块要深入研究,也不是一朝一夕的事情。

**优势:**但好处也明显,没有"等后端排期"的问题,没有"前端说接口不对"的扯皮,需求从想法到上线,一个人就可以出完整的方案,最短路径。

全栈开发,并不适合需要大规模协作的大项目开发,特别适合于有难度的破冰之旅。

但随着AI的进一步智能化,开发者的能力边界只会越来越大,我这里,还只是开了一个小头,往后开发者的能力覆盖,大的你想不到。

6.2 AI 让开发从马拉松变成开车,但车也会抛锚

以前写 RuoYi 的 CRUD,一天一个模块。现在有了 Kimi,一天三个模块。但调试时间没有同比减少------AI 生成的代码能跑,但边界情况、性能陷阱、框架版本差异,还是要人踩。

真正省下的时间: 代码生成、文档查阅、框架配置。

没有省下的时间: 需求澄清、架构权衡、线上排障、用户沟通。

七、全栈之路系列总结

|---------------|-----------------|-------------------------------------|
| 篇目 | 核心问题 | 关键技术 |
| 第1篇 源起与通信 | 为什么造轮子?怎么打通链路? | C++ Socket、自定义协议、RSA + Token |
| 第2篇 架构设计 | 实时、安全、扩展怎么兼顾? | 8字节协议头、双链路、CM/PM/FM 分层 |
| 第3篇 zmagent | 终端怎么采集、执行、保活? | 多线程、双任务通道、Shell 转接 |
| 第4篇 zmserver | 枢纽怎么扛并发、路由、调度? | per-connection、HostID 中心、pending 队列 |
| 第5篇 数据建模与告警 | 数据怎么存、怎么判、怎么通知? | 专用表、触发器、RuoYi 切面 |
| 第6篇 web 集成与呈现 | 人怎么看见、怎么管? | Vue3、ECharts、代码生成、权限模型 |

贯穿始终的原则:

1、通信层尽量透传,业务层预留扩展

2、设计先行,从一开始就把事情从头到尾想清楚,每一层留好接口和应对

3、与AI协作好,控制好任务入口规模,尽量在模块级实现修订。

八、联系方式

Gitee 仓库:https://gitee.com/xuzhigang66/zmmonitor

演示环境:http://39.105.217.235:8090/index 注意需要修改登录口令为zm@007

邮件:xuzhigang66@139.com

欢迎:提 issue、要设计文档、问实现细节、一起吐槽全栈的坑。

技术以服务业务为指引,业务以服务民生为指引,活就好干,事就能成。

全文完

相关推荐
前端snow1 小时前
ai agent -- Memory汇总
前端
ITresearchGuest2 小时前
AI 焦虑下,前端该何去何从
前端·人工智能
Codiggerworld2 小时前
Chrome DevTools 隐藏神技:这 5 个调试技巧你可能从来没用过
前端·chrome·chrome devtools
旋生万物2 小时前
【终极实战】用Python从零“生成“一个宇宙:螺旋干涉模型的代码实现
开发语言·前端·人工智能·react.js·php·wpf
IT_陈寒2 小时前
被Java的final坑惨了,这些细节你可能也忽略了
前端·人工智能·后端
kyriewen3 小时前
我带着DeepSeek Harness跑了一周真实需求——这份避坑速查表请收好
前端·ai编程·deepseek
计算机魔术师3 小时前
OpenAI不单独发o3了,Altman说GPT-5才是终局
前端
一座古城3 小时前
Claude Code 架构源码解析:AI 应用开发的范式跃迁
前端·后端
K哥爬虫3 小时前
【JS 逆向百例】Vaptcha V4 手势验证码逆向分析
前端·后端