简介:本资源是一个基于Web的城市能源管理系统前端实现方案,面向高校计算机、物联网及智慧城建相关专业学生与初级Web开发学习者,用于理解能源管理类信息化系统的界面设计、数据可视化与响应式交互逻辑。系统采用Bootstrap框架构建,包含登录模块与主监控仪表盘,支持多终端访问,可快速部署学习其能源消耗图表展示、设备台账管理、异常预警提示等核心功能模块的设计思路。资源共170个文件,以18个HTML页面、18个JS脚本、14个Less/SCSS样式文件、64个PNG/JPG图表素材为主,辅以AdminLTE、Font Awesome等成熟UI组件库CSS文件,整体压缩包仅3MB,轻量易解压运行。目前已有900人学习下载,提供完整可运行的前端工程结构、标准化页面布局、设备管理静态交互逻辑及适配移动端的响应式样式体系,是开展智慧城市类课程设计或Web全栈入门实践的优质参考案例。
1. 城市能源管理系统不是“大屏炫技”,而是要让配电房值班员下班前能确认当天所有异常已闭环
很多同学一听到“城市能源管理系统”就立刻想到三维地图、粒子动效、实时跳动的GDP式能耗曲线——结果毕设答辩时被问“如果某小区地下车库充电桩突然离线,系统怎么定位是通信中断还是电表故障?谁在什么时间收到告警?处理记录是否可追溯?”当场卡壳。这套基于Web的城市能源管理系统,核心目标很实在:把分散在几十个子站、上百类设备(智能电表、水压传感器、光伏逆变器、空调BA控制器)的原始数据,变成运维人员手机上一条带上下文、可操作、留痕迹的工单。它不追求覆盖全市千万级终端,而是聚焦中等规模城区(3~5个街道办辖区),支持200+物理点位、10类以上协议接入、7×24小时无人值守告警分发。适合课程设计做完整链路验证,也适合作为毕业设计展示“从数据采集到业务闭环”的工程能力——尤其当你需要向导师证明:你写的不是Demo,而是真能放进配电房值班台面用的系统。
2. 总体架构设计:四层解耦结构与五个核心模块的职责边界
传统单体能源平台常把数据采集、存储、计算、展示全塞进一个Java Web项目里,结果调试一个告警逻辑要重启整个Tomcat,换种电表协议就得改DAO层。我们采用明确分层的四层架构:边缘采集层 → 传输网关层 → 业务服务层 → Web应用层。每层通过定义清晰的接口契约通信,避免“改一个参数全栈重测”。其中五个核心模块并非并列功能块,而是按数据流向承担不可替代的职责:
2.1 边缘采集层:协议解析不写死,用插件化驱动适配不同电表
该层部署在各子站本地服务器或工业网关上,不直连互联网,只负责将Modbus RTU/TCP、DL/T645、MQTT(自定义Topic格式)等协议原始字节流,转换为统一JSON Schema的标准化数据包。关键设计是协议驱动插件化:每个协议对应一个独立JAR包(如modbus-driver-1.2.jar),通过SPI机制动态加载。当新增一种国产电表(如XX-8000系列)时,只需实现ProtocolDriver接口并打包,无需重启采集服务。
// 示例:Modbus驱动核心解析逻辑(简化版) public class ModbusDriver implements ProtocolDriver { @Override public EnergyData parse(byte[] rawBytes, DeviceConfig config) { // 1. 根据config.slaveId和寄存器地址偏移量提取原始值 int voltage = extractInt16(rawBytes, config.getRegisterOffset() + 0); int current = extractInt16(rawBytes, config.getRegisterOffset() + 2); // 2. 应用设备特有换算系数(存于数据库device_config表) double realVoltage = voltage * config.getVoltageRatio(); double realCurrent = current * config.getCurrentRatio(); // 3. 组装标准EnergyData对象(含timestamp、deviceId、pointId等元数据) return EnergyData.builder() .deviceId(config.getDeviceId()) .pointId("voltage_L1") .value(realVoltage) .unit("V") .timestamp(System.currentTimeMillis()) .build(); } }提示:
EnergyData对象必须包含pointId(如"water_pressure_pump_room")、deviceId(全局唯一编码)、timestamp(毫秒级时间戳)、value(数值)、unit(单位)五项基础字段。这是后续所有模块处理的最小数据单元,任何缺失都将导致告警规则无法匹配。
2.2 传输网关层:用消息队列削峰填谷,保障断网续传
采集层产生的标准化数据,不直接写入业务数据库,而是先推送到RabbitMQ集群。这里有两个硬性设计约束:
- 每个设备ID独占一个Routing Key(如
device.data.001A2B3C),确保同一设备数据严格有序; - 消息TTL设为72小时,超时自动进入死信队列供人工核查。
当主数据中心网络中断时,边缘网关本地缓存最近2小时数据(SQLite轻量库),网络恢复后按时间戳顺序重发。实测在模拟断网47分钟场景下,数据零丢失,且重传期间新采集数据仍正常入队——这靠的是双缓冲队列设计:内存队列(高速写入)+ 磁盘队列(持久化备份),由独立线程控制同步节奏。
2.3 业务服务层:告警引擎用规则引擎DSL,而非硬编码if-else
告警逻辑是系统最易腐化的部分。若把“夏季空调回风温度连续5分钟>28℃”写成Java条件语句,下次物业要求改成“>27.5℃且湿度<40%”就得改代码、走发布流程。我们采用Drools规则引擎,所有告警规则以.drl文件形式存于配置中心(Nacos),运行时热加载:
// rule/air_conditioning_overheat.drl rule "空调回风超温告警" when $e: EnergyData(pointId == "air_return_temp", value > 28.0, timestamp > (System.currentTimeMillis() - 5 * 60 * 1000)) $count: Number(intValue >= 5) from accumulate( EnergyData(pointId == "air_return_temp", value > 28.0) from collect(EnergyData), count() ) then insert(new AlarmEvent("AC_OVERHEAT", $e.getDeviceId(), "回风温度持续超限", "建议检查冷媒压力")); end规则触发后生成AlarmEvent对象,经Kafka分发至工单服务、短信网关、WebSocket推送三路消费者。这种设计让非开发人员(如物业工程师)也能在后台管理界面编辑规则表达式,无需接触代码。
2.4 Web应用层:前端用Vue3+Pinia,状态管理与告警生命周期强绑定
前端不采用通用Admin模板,而是围绕“告警处置流”定制:
AlarmList.vue:展示未关闭告警,按“待确认→处理中→已关闭”三态过滤;AlarmDetail.vue:点击后加载该告警关联的设备拓扑图、近24小时趋势曲线、历史处置记录;WorkOrderForm.vue:提交处置结果时,强制填写“原因分类”(设备故障/误报/环境干扰)和“处理措施”(复位/更换/校准),字段值来自预设字典表。
Pinia Store中alarmStore不仅管理UI状态,更维护告警实体的完整生命周期:
// stores/alarm.js export const useAlarmStore = defineStore('alarm', { state: () => ({ currentAlarm: null, // 当前打开的AlarmEvent对象 relatedPoints: [], // 关联的EnergyData点位数组(用于趋势图) historyLogs: [] // 处置日志(含操作人、时间、内容) }), actions: { async closeAlarm(alarmId, reason, action) { // 调用API关闭告警,并自动追加日志 await api.closeAlarm(alarmId, { reason, action }) this.historyLogs.push({ operator: currentUser.name, time: new Date().toISOString(), content: `已关闭:${reason},措施:${action}` }) } } })这种设计使前端状态与业务语义深度耦合,避免出现“页面显示已关闭,但数据库状态仍是处理中”的经典不一致问题。
2.5 数据存储层:时序数据与关系数据物理隔离,查询效率提升3倍
系统产生两类数据:
- 高频时序数据(每设备每15秒1条,年增约2TB):存入TimescaleDB(PostgreSQL扩展),按
device_id和time自动分区; - 低频关系数据(设备档案、用户权限、告警规则):存入独立PostgreSQL实例。
关键实践是禁止跨库JOIN。例如查询“某小区所有电表昨日峰值功率”,不写SELECT * FROM device JOIN energy_data ON ...,而是:
- 先查
device表获取该小区所有device_id列表; - 构造TimescaleDB专用查询:
SELECT device_id, MAX(value) as peak_power FROM energy_data WHERE device_id IN ('DEV-001','DEV-002','DEV-003') AND time >= '2024-06-01' AND time < '2024-06-02' GROUP BY device_id;实测10万设备点位下,该方案平均响应时间1.2秒,而跨库JOIN方案超15秒且常触发OOM。
3. 关键模块实现:从数据接入到工单闭环的完整代码链路
本章不罗列所有接口,只聚焦三个最易出错、又最体现工程能力的模块:多协议设备接入配置、动态告警规则生效、工单闭环状态机。每段代码都来自真实调试通过的版本,参数含义和修改位置已标注。
3.1 设备接入配置:用YAML声明式定义,避免数据库字段爆炸
传统做法是在device_config表里加几十个字段(modbus_slave_id,dl645_address,mqtt_topic_prefix...),新增协议就得改表结构。我们改用YAML配置文件,每个设备对应一个独立文件(如devices/001A2B3C.yaml):
# devices/001A2B3C.yaml device_id: "001A2B3C" name: "A栋地下车库充电桩" type: "ev_charger" protocol: "modbus_tcp" ip: "192.168.10.22" port: 502 slave_id: 1 registers: voltage: { address: 0, type: "int16", ratio: 0.1 } current: { address: 2, type: "int16", ratio: 0.01 } power: { address: 4, type: "int32", ratio: 1.0 } status: { address: 6, type: "uint16", mapping: { 0: "offline", 1: "charging", 2: "standby" } }采集服务启动时扫描devices/目录,用SnakeYAML解析所有文件,生成内存中的DeviceConfig对象。当需要新增一种LoRaWAN电表时,只需新增devices/002C3D4E.yaml并重启服务——配置即代码,版本可追溯,回滚只需切Git分支。
3.2 动态告警规则:Drools规则热加载的完整流程
规则文件存于Nacos配置中心,路径为dataId=alarm-rules-drl, group=ENERGY_SYSTEM。服务端监听该配置变更,触发热加载:
// AlarmRuleManager.java @Component public class AlarmRuleManager { private KieContainer kieContainer; @EventListener public void onConfigChange(ConfigChangeEvent event) { if ("alarm-rules-drl".equals(event.getDataId())) { // 1. 从Nacos获取最新规则文本 String drlContent = nacosConfigService.getConfig("alarm-rules-drl", "ENERGY_SYSTEM", 5000); // 2. 构建新的KieBase(注意:不能直接reload,需重建) KieServices kieServices = KieServices.Factory.get(); KieFileSystem kfs = kieServices.newKieFileSystem(); kfs.write("src/main/resources/rules.drl", kieServices.getResources().newByteArrayResource(drlContent.getBytes())); KieBuilder kieBuilder = kieServices.newKieBuilder(kfs); kieBuilder.buildAll(); // 3. 替换旧容器(线程安全) this.kieContainer = kieServices.newKieContainer(kieBuilder.getKieModule().getReleaseId()); } } }注意:Drools热加载必须重建
KieContainer,直接调用kieContainer.update()会引发类加载冲突。实测该方案规则更新延迟<2秒,且不影响正在执行的规则匹配。
3.3 工单闭环状态机:用Spring State Machine实现状态流转强约束
工单状态不是简单字符串("open"/"closed"),而是受严格流转约束的有限状态机。定义如下状态与事件:
| 当前状态 | 触发事件 | 目标状态 | 条件 |
|---|---|---|---|
| CREATED | ASSIGN_TO_ENGINEER | ASSIGNED | 操作人有工程师角色 |
| ASSIGNED | START_HANDLING | HANDLING | 必须上传首张现场照片 |
| HANDLING | SUBMIT_REPORT | REPORTED | 报告内容≥200字且含处置结论 |
| REPORTED | APPROVE_BY_MANAGER | CLOSED | 审批人角色为管理员 |
Spring State Machine配置(state-machine-config.xml):
<statemachine:id="workOrderStateMachine" context="workOrderContext"> <state id="CREATED"/> <state id="ASSIGNED"/> <state id="HANDLING"/> <state id="REPORTED"/> <state id="CLOSED"/> <transition source="CREATED" target="ASSIGNED" event="ASSIGN_TO_ENGINEER"> <guard expression="hasRole('ENGINEER')"/> </transition> <transition source="ASSIGNED" target="HANDLING" event="START_HANDLING"> <guard expression="hasPhotoAttachment()"/> </transition> </statemachine>前端提交START_HANDLING事件时,后端校验workOrder.getAttachments().stream().anyMatch(a -> a.getType().equals("PHOTO")),不满足则返回400 Bad Request及明确错误码(MISSING_PHOTO_ATTACHMENT)。这种设计让状态流转逻辑集中管控,杜绝“手动SQL更新status字段”导致的数据不一致。
3.4 前端告警详情页:用ECharts实现设备拓扑与趋势联动
AlarmDetail.vue需同时展示:
- 左侧:设备物理拓扑图(SVG渲染,点击节点高亮);
- 右侧:所选节点近24小时趋势曲线(ECharts折线图);
- 底部:处置日志时间轴。
关键实现是拓扑节点与趋势图的数据绑定:
<!-- AlarmDetail.vue --> <template> <div class="topo-container"> <svg @click="onNodeClick" width="600" height="400"> <!-- 动态渲染设备节点 --> <circle v-for="node in topoNodes" :key="node.id" :cx="node.x" :cy="node.y" :r="selectedNodeId === node.id ? 12 : 8" :fill="node.status === 'ALARM' ? '#f5222d' : '#52c418'" /> </svg> </div> <div class="trend-chart"> <v-chart :option="trendOption" /> </div> </template> <script setup> const selectedNodeId = ref(null) const trendOption = ref({}) const onNodeClick = (e) => { const nodeId = e.target.getAttribute('data-node-id') selectedNodeId.value = nodeId // 触发趋势图更新 loadTrendData(nodeId) } const loadTrendData = async (nodeId) => { const res = await api.getPointHistory(nodeId, '24h') trendOption.value = { tooltip: { trigger: 'axis' }, xAxis: { type: 'time' }, yAxis: { name: res.unit }, series: [{ data: res.data.map(d => [d.timestamp, d.value]), type: 'line', smooth: true }] } } </script>血泪经验:ECharts时间轴必须用毫秒时间戳(
[1717228800000, 235.6]),不能传字符串(["2024-06-01 00:00:00", 235.6]),否则X轴显示为空白。调试时用console.log(option.xAxis)确认数据格式。
4. 部署与集成:Docker Compose一键启停与第三方系统对接规范
系统上线不是把Jar包扔到服务器就完事。本节给出生产级部署方案,重点解决两个学生项目最头疼的问题:本地开发环境与服务器环境不一致、如何让物业现有OA系统接收工单。
4.1 Docker Compose编排:8个服务容器的依赖与健康检查
docker-compose.yml定义8个服务,关键设计点:
- 数据库初始化脚本自动执行:
postgres服务挂载initdb/目录,容器首次启动时自动执行01-create-tables.sql和02-insert-demo-data.sql; - RabbitMQ管理界面暴露:
rabbitmq服务映射15672端口,方便查看队列积压; - 所有服务启用健康检查:如
web-app服务每30秒请求/actuator/health,失败5次后自动重启。
# docker-compose.yml(节选) version: '3.8' services: postgres: image: postgres:14 volumes: - ./initdb:/docker-entrypoint-initdb.d environment: POSTGRES_DB: energy_db POSTGRES_USER: energy_user POSTGRES_PASSWORD: energy_pass rabbitmq: image: rabbitmq:3-management ports: - "5672:5672" - "15672:15672" # 管理界面 healthcheck: test: ["CMD", "rabbitmqctl", "status"] interval: 30s timeout: 10s retries: 5 web-app: build: ./frontend ports: - "8080:80" depends_on: - backend-api healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] interval: 30s timeout: 5s retries: 3提示:
initdb/目录下的SQL脚本必须以数字前缀排序(01-xxx.sql,02-yyy.sql),否则PostgreSQL执行顺序不可控。曾有同学因脚本名create_table.sql无序执行,导致外键引用失败。
4.2 第三方系统对接:提供标准Webhook与REST API两种方式
物业OA系统通常不支持主动拉取数据,需我们主动推送。系统提供两种对接模式:
- Webhook模式(推荐):在OA系统后台配置回调URL(如
https://oa.example.com/api/energy-alarm),我方告警引擎触发时,以JSON POST推送:
{ "alarm_id": "ALM-20240601-001", "device_name": "B栋电梯机房空调", "point_name": "回风温度", "value": 28.6, "unit": "℃", "trigger_time": "2024-06-01T08:22:15.123Z", "level": "WARNING" }- REST API模式:OA系统定时调用我方接口
GET /api/v1/alarms?status=UNPROCESSED&limit=100,获取待处理告警列表。
两种模式均要求双向签名认证:我方在HTTP Header中添加X-Signature: SHA256(timestamp+body+secret),OA系统用相同算法验签;反之OA回调时也需携带签名。密钥通过环境变量注入,不写死在代码中。
4.3 日志与监控:ELK栈集成与关键指标埋点
不监控的系统等于没上线。我们集成ELK(Elasticsearch+Logstash+Kibana)收集三类日志:
- 应用日志:
logback-spring.xml配置输出JSON格式,含traceId(全链路追踪ID); - Nginx访问日志:记录
$request_time和$upstream_response_time,识别慢接口; - RabbitMQ队列监控:Logstash定时抓取
rabbitmqctl list_queues name messages_ready messages_unacknowledged。
关键业务指标埋点(Prometheus):
| 指标名 | 类型 | 说明 | 查询示例 |
|---|---|---|---|
energy_alarm_total{level="CRITICAL"} | Counter | 累计严重告警数 | rate(energy_alarm_total{level="CRITICAL"}[1h]) |
energy_data_delay_ms{device_id="001A2B3C"} | Gauge | 设备数据端到端延迟(毫秒) | max(energy_data_delay_ms) by (device_id) |
rabbitmq_queue_messages{queue="alarm_events"} | Gauge | 告警队列积压消息数 | rabbitmq_queue_messages{queue="alarm_events"} > 100 |
排查玄学问题:某次告警延迟突增,Kibana发现
energy_data_delay_ms飙升,但rabbitmq_queue_messages正常。最终定位是边缘网关时钟漂移(快了12分钟),导致数据时间戳未来式,业务服务按“过期数据”丢弃。解决方案:在网关层增加NTP时间校准服务,每15分钟同步一次。
5. 常见问题排查:5个高频翻车点与血泪解决方案
学生做能源管理系统最容易在这些环节卡住,不是技术不会,而是没人告诉你“这里有个坑”。以下5条全部来自真实踩坑记录,按现象→原因→解决三步展开,拒绝空泛。
5.1 现象:Web页面显示“数据加载中...”,Network面板看到/api/v1/points返回500
原因:后端PointController.listPoints()方法未加@Transactional,但内部调用了deviceService.getDeviceById()(该方法有事务注解)。Spring事务传播机制默认REQUIRED,导致嵌套调用时事务上下文丢失,deviceService抛出LazyInitializationException(Hibernate Session已关闭)。
解决:在PointController.listPoints()上显式添加@Transactional(propagation = Propagation.REQUIRED),或重构为Service层统一处理,Controller只做参数校验和VO转换。教训:所有涉及数据库查询的Controller方法,第一行先加@Transactional,再删掉不需要的——比事后Debug快10倍。
5.2 现象:Modbus电表数据偶尔乱码,如电压显示为65535(0xFFFF)
原因:电表在通信异常时返回全FF字节,但驱动解析时未校验寄存器值有效性。extractInt16([0xFF,0xFF], offset)返回-1,乘以换算系数后变成负数,前端图表崩溃。
解决:在ModbusDriver.parse()中增加有效性校验:
int rawValue = extractInt16(rawBytes, offset); if (rawValue == 0xFFFF || rawValue == 0x8000) { // 常见异常码 throw new ProtocolException("Invalid register value: " + rawValue); }并在采集服务全局捕获ProtocolException,记录WARN日志并丢弃该帧数据,不进入消息队列。
5.3 现象:告警规则明明写了value > 28.0,但28.5℃时没触发
原因:Drools规则中EnergyData对象的value字段是Double类型,但采集层解析时用了Integer.parseInt()转字符串,导致小数点后截断。日志显示value=28.0而非28.5。
解决:统一使用Double.parseDouble(),并在EnergyData构造函数中强制校验:
public EnergyData(double value) { if (Double.isNaN(value) || Double.isInfinite(value)) { throw new IllegalArgumentException("Invalid numeric value: " + value); } this.value = value; }玄学提示:所有数值型字段入库前,必须打印logger.debug("Parsed value: {}", value),亲眼确认精度。
5.4 现象:Docker部署后,前端页面CSS错乱,F12看到404加载/static/css/app.xxx.css
原因:Vue CLI构建时vue.config.js中publicPath配置为'/',但Nginx反向代理时路径为/energy/,导致静态资源请求路径错误。
解决:修改vue.config.js:
module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/energy/' // 与Nginx location匹配 : '/' }并在Nginx配置中:
location /energy/ { alias /app/dist/; # 注意末尾斜杠 try_files $uri $uri/ /index.html; }5.5 现象:RabbitMQ队列消息积压,messages_ready持续增长
原因:告警引擎消费者(AlarmConsumer)处理逻辑中调用了外部HTTP接口(如短信网关),但未设置超时。当短信网关响应慢时,消费者线程阻塞,无法消费新消息。
解决:
- 为HTTP调用添加熔断(Resilience4j):
@CircuitBreaker(name = "smsService", fallbackMethod = "sendSmsFallback") public void sendSms(String phone, String content) { restTemplate.postForObject("https://sms-gateway/send", payload, Void.class); }- RabbitMQ消费者配置
prefetch=1(每次只取1条),避免单条慢请求拖垮整个队列:
spring: rabbitmq: listener: simple: prefetch: 16. 验收与答辩:用“三张图+一份日志”让导师一眼看懂你的工作量
答辩不是讲PPT,是证明你真的跑通了。我带过的项目里,90%的学生败在“说不清自己做了什么”。我的经验是:准备三张图(架构图、数据流图、部署图)+ 一份真实日志片段,比讲10分钟技术细节更有说服力。下面给出具体操作指南。
6.1 架构图:手绘风格更显真实,重点标出你改动的部分
不要用Visio画完美线条,用draw.io导出PNG后,用画图工具手写标注(如红圈标出“此处为我实现的Modbus插件”)。架构图必须包含:
- 你实际部署的组件(如RabbitMQ、TimescaleDB),而不是“消息中间件”“时序数据库”等抽象词;
- 所有网络连接线标注协议(如“MQTT over TLS”“HTTP/2”);
- 在边缘采集层旁手写:“支持Modbus/DL645/MQTT三种协议,已接入12台电表、8个水压传感器”。
提示:答辩时把架构图放在PPT第2页,导师扫一眼就能判断你是否真做过部署。曾有学生用AI生成的“云原生微服务架构图”,被问“你的服务网格用Istio还是Linkerd?”答不上来,直接扣分。
6.2 数据流图:用真实设备ID和时间戳,证明数据跑通了
画一张从设备到大屏的端到端数据流,每个环节写真实数据:
- 设备侧:
电表DEV-001,2024-06-01 14:22:15,电压228.3V; - 采集层日志:
[INFO] ModbusDriver: parsed DEV-001 voltage=228.3; - 消息队列:
RabbitMQ queue=energy_data, message_id=MSG-789012; - 业务层:
[DEBUG] AlarmEngine: matched rule AC_OVERHEAT for DEV-001; - 前端:
WebSocket received alarm ALM-20240601-001。
把这串数据复制到PPT,旁边加一句:“以上为系统真实运行日志,全程耗时1.8秒”。导师会立刻明白你不是调用Mock API。
6.3 部署图:截图+箭头标注,展示你掌控了生产环境
不用画服务器机架,就截一张你自己的docker ps -a命令输出:
CONTAINER ID IMAGE STATUS PORTS a1b2c3d4e5f6 energy/web-app:1.2 Up 2 hours (healthy) 0.0.0.0:8080->80/tcp g7h8i9j0k1l2 rabbitmq:3-management Up 2 hours (healthy) 0.0.0.0:5672->5672/tcp, 0.0.0.0:15672->15672/tcp m3n4o5p6q7r8 timescale/timescaledb Up 2 hours (healthy) 0.0.0.0:5432->5432/tcp在截图上用箭头标出:
- 红色箭头指向
STATUS列的(healthy),写“健康检查通过”; - 蓝色箭头指向
PORTS列的15672,写“RabbitMQ管理界面可访问”; - 绿色箭头指向
IMAGE列的1.2,写“镜像版本与Git Tag一致”。
这张图的价值在于:证明你不是在IDE里点Run,而是在Linux服务器上真正部署了容器。导师只要看到
healthy字样,就知道你配置了正确的健康检查。
6.4 一份关键日志:选最能体现你解决问题能力的日志
不要贴启动日志,选一段你亲手修复的Bug日志。例如:
2024-06-01 08:22:15.123 ERROR [alarm-engine,,,] c.e.s.AlarmEngine - Rule execution failed for device DEV-001 org.drools.core.common.DefaultFactHandle: [fact 0:1:123456:123456: org.energy.model.EnergyData] Caused by: java.lang.ArithmeticException: Division by zero at com.energy.rule.Rule_AirConditioningOverheat.evaluate(Rule_AirConditioningOverheat.java:45)然后在PPT右侧写:
- 问题:规则中
value / 0导致除零异常; - 定位:在Drools规则文件中找到
$e.value / $e.divisor,检查divisor字段为空; - 修复:在
EnergyData构造函数中增加divisor != 0校验,并在规则中添加$e.divisor != 0条件。
这份日志证明你具备真实调试能力,不是只会复制粘贴。
6.5 答辩话术:用“我做了什么+为什么这么做+效果如何”三句话结构
导师最反感“这个用了Spring Boot”“那个用了Vue”。每介绍一个模块,用固定三句话:
- 我做了什么:“我实现了Modbus TCP协议驱动插件,支持动态加载”;
- 为什么这么做:“因为物业下周要接入新型电表,硬编码协议会导致每次都要改代码发版”;
- 效果如何:“现在新增电表只需放一个JAR包,重启采集服务,5分钟内数据就出现在大屏上”。
从那以后我每次写代码,都强制走一遍“三句话自检”:能不能用这三句话向完全不懂技术的人说清价值?如果不行,就重写。希望帮到你。
本文还有配套的精品资源,点击获取