前情提要
前面我们讲到了如何实时、安全、可扩展地解决底层通信和数据采集的问题,并对数据进行建模和告警实现。数据在数据库里活了,告警在通知里响了,最后一步:让人看得见、管得了。
这一篇讲 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、要设计文档、问实现细节、一起吐槽全栈的坑。
技术以服务业务为指引,业务以服务民生为指引,活就好干,事就能成。
全文完