Documentary · 纪实 walkthrough

全栈开发者的一天 — 独立交付纪实

从需求评审到部署上线,记录一个全栈开发者如何独立完成"设备告警通知"功能的全部过程。没有前后端分工,没有运维协助 — 一个人搞定一切。

👤 小王 · 5年经验全栈 🏢 菲克斯能源科技 🔧 Spring Boot + Vue + Docker ⏱️ 一天完成交付

小王 · Full Stack Developer

5年经验,前后端都能做。在小公司里,一个人就是一支队伍。

Java/Spring Boot Vue.js MySQL Docker 钉钉集成

今天的目标:独立完成"设备告警通知"功能 — 告警规则配置、实时检测、钉钉消息推送、系统内通知列表,从需求讨论到部署上线,全部一个人搞定。

小公司的生存法则:什么都要会,什么都得做,出了问题自己兜底。

🏗️ 系统架构图 — 完整请求链路
🌐
浏览器
用户访问页面
HTTP请求
🎨
Vue 前端
SPA + Vue Router
API调用
⚙️
Nginx
反向代理 + 静态资源
转发
Spring Boot
后端 API 服务
🗄️
MySQL
持久化存储
t_alert_rule
t_alert_log
Redis
缓存 + 会话
Token 存储
告警频率限制
Spring Boot → MySQL (MyBatis)   |   Spring Boot → Redis (Spring Data Redis)
📋

需求评审会 09:00

和PM确认需求,理清功能边界

💬 钉钉群 — 菲克斯能源项目组
钉钉 · 菲克斯能源项目组 (5人)
菲克斯能源项目组 5人在线
PM小赵 09:02
各位早上好,这周需要加一个设备告警通知功能。

当设备温度超过阈值时,自动发通知给运维人员。这个比较紧急,客户那边已经提了好几次了。
小王 09:03
收到。我理一下具体需求:
1. 阈值谁配置?是运维手动设还是系统预设?
2. 通知发到哪里?钉钉消息?系统内通知?还是都要?
3. 告警级别需要分等级吗?比如提醒/警告/严重?
4. 哪些设备需要监控?所有类型还是只有传感器?
PM小赵 09:05
好问题,我确认了一下:
1. 阈值在系统里配置,运维自己设
2. 钉钉消息 + 系统内通知都要
3. 告警分3级:提醒、警告、严重。严重的发钉钉
4. 先做温度传感器的,后面再加其他设备
小王 09:08
OK,需求明确了。我来理一下要做的事情:

1. 告警规则配置页 — 运维可以增删改查规则
2. 设备数据实时检测 — 定时任务检查设备数据
3. 钉钉消息推送 — 严重告警发钉钉
4. 系统内通知列表 — 所有告警记录可查看

我今天开始做,预计一天能搞定。明天联调测试一下。
PM小赵 09:09
👍 太好了,辛苦小王!有什么问题随时群里沟通。
运维小李 09:10
赞!期待这个功能,我们天天盯着数据看太累了。钉钉消息通知很重要,希望告警级别高的时候一定要发!

需求不算复杂,但涉及前后端 + 消息推送 + 定时任务,对于一个人来说工作量还是有的。

优先级排序:先搞定核心链路(检测→告警→通知),再做管理页面。先设计表和API,然后并行写前后端。

🗄️

设计表结构 + API 09:30

DataGrip 设计数据库表,明确 API 接口

DataGrip — energy_system@localhost:3306
-- ============================================= -- 设备告警通知功能 · 表结构设计 -- Author: 小王 -- Date: 2026-03-18 -- ============================================= CREATE DATABASE IF NOT EXISTS energy_system; USE energy_system; -- 告警规则表:运维人员配置的告警规则 CREATE TABLE t_alert_rule ( id BIGINT PRIMARY KEY AUTO_INCREMENT, device_type VARCHAR(50) NOT NULL COMMENT '设备类型(sensor/meter/transformer)', metric VARCHAR(50) NOT NULL COMMENT '监控指标(temperature/voltage/current)', operator VARCHAR(10) NOT NULL COMMENT '比较运算符(>/=/<=)', threshold DECIMAL(10,2) NOT NULL COMMENT '阈值', level TINYINT NOT NULL COMMENT '告警级别 1-提醒 2-警告 3-严重', enabled TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) COMMENT '告警规则配置表'; -- 告警记录表:每次告警的详细记录 CREATE TABLE t_alert_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, rule_id BIGINT NOT NULL, device_id VARCHAR(50) NOT NULL, metric_value DECIMAL(10,2) NOT NULL COMMENT '实际值', threshold DECIMAL(10,2) NOT NULL COMMENT '阈值', level TINYINT NOT NULL, notified TINYINT DEFAULT 0 COMMENT '是否已通知 0-未通知 1-已通知', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_rule_id (rule_id), INDEX idx_device_id (device_id), INDEX idx_create_time (create_time) ) COMMENT '告警记录表';
✓ Execute Successful — 2 statements executed in 0.047s
🗄️ 数据库 ER 图 — 表关系
📋 t_alert_rule 告警规则配置表
🔑 id BIGINT PK
device_type VARCHAR(50)
metric VARCHAR(50)
operator VARCHAR(10)
threshold DECIMAL(10,2)
level TINYINT
enabled TINYINT
create_time DATETIME
update_time DATETIME
FOREIGN KEY
rule_id → id
一对多
📝 t_alert_log 告警记录表
🔑 id BIGINT PK
🔗 rule_id BIGINT
device_id VARCHAR(50)
metric_value DECIMAL(10,2)
threshold DECIMAL(10,2)
level TINYINT
notified TINYINT
create_time DATETIME
小王:"两张表搞定。t_alert_rule 存规则配置,t_alert_log 存告警记录。索引也建好了,后面查询不会慢。"
📐 API 接口设计

RESTful 风格,5个接口覆盖所有需求:

Method URL Description 备注
POST /api/alert/rules 创建告警规则 运维配置新规则
GET /api/alert/rules 查询规则列表 支持分页
GET /api/alert/logs 查询告警记录 分页 + 筛选
PUT /api/alert/rules/{id} 修改规则 更新阈值等
DELETE /api/alert/rules/{id} 删除规则 软删除
📡 API 请求/响应示例
📤 请求示例 — POST 创建告警规则
POST /api/alert/rules
Content-Type: application/json

{
  "deviceType": "sensor",
  "metric": "temperature",
  "operator": ">",
  "threshold": 80.00,
  "level": 3
}
📥 响应示例 — POST 201 Created
HTTP 201 Created

{ // code:0 表示成功(部分团队用 code:200,本项目统一用 0)
  "code": 0,
  "message": "success",
  "data": {
    "id": 1,
    "deviceType": "sensor",
    "metric": "temperature",
    "operator": ">",
    "threshold": 80.00,
    "level": 3,
    "enabled": true,
    "createTime": "2026-03-18T09:35:00"
  }
}
📤 请求示例 — GET 查询规则列表
GET /api/alert/rules
📥 响应示例 — GET 200 OK
HTTP 200 OK

{
  "code": 0,
  "message": "success",
  "data": [
    {
      "id": 1,
      "deviceType": "sensor",
      "metric": "temperature",
      "operator": ">",
      "threshold": 80.00,
      "level": 3,
      "enabled": true
    },
    {
      "id": 2,
      "deviceType": "sensor",
      "metric": "temperature",
      "operator": ">",
      "threshold": 65.00,
      "level": 2,
      "enabled": true
    }
  ]
}
📤 请求示例 — PUT 修改规则
PUT /api/alert/rules/1
Content-Type: application/json

{
  "threshold": 85.00,
  "level": 2
}
📥 响应示例 — PUT 200 OK
HTTP 200 OK

{
  "code": 0,
  "message": "success",
  "data": {
    "id": 1,
    "deviceType": "sensor",
    "metric": "temperature",
    "operator": ">",
    "threshold": 85.00,
    "level": 2,
    "enabled": true,
    "updateTime": "2026-03-18T09:40:00"
  }
}
📥 错误响应示例 — 400 Bad Request
HTTP 400 Bad Request

{
  "code": 400,
  "message": "参数校验失败: threshold 不能为空",
  "data": null
}
📥 错误响应示例 — 404 Not Found
HTTP 404 Not Found

{
  "code": 404,
  "message": "告警规则不存在: id=999",
  "data": null
}

表设计和API都规划好了。接下来开始写代码 — 先写后端(Controller + Service + Mapper),确保接口通了再写前端页面。

全栈的好处是:自己设计API自己用,前后端接口不用来回沟通对齐。

写后端代码 10:00

Spring Boot 后端开发 — Controller / Service / Mapper

打开 IntelliJ IDEA,开始写后端。先写 AlertController,再写 AlertService,最后写 Mapper XML。
IntelliJ IDEA — AlertController.java
AlertController.java
AlertService.java
AlertMapper.xml
application.yml
📁 src
📁 main/java/com/fix/energy
📄 AlertController.java
📄 AlertService.java
📄 AlertRule.java
📄 AlertLog.java
📁 mapper
📄 AlertRuleMapper.java
📄 AlertLogMapper.java
📁 config
📄 DingTalkService.java
📁 resources
📄 application.yml
📁 mapper
📄 AlertRuleMapper.xml
1package com.fix.energy.controller;
2
3import com.fix.energy.model.*;
4import com.fix.energy.service.AlertService;
5import org.springframework.beans.factory.annotation.Autowired;
6import org.springframework.web.bind.annotation.*;
7
8import java.util.List;
9
10/**
11 * 设备告警控制器
12 * @author 小王
13 */
14@RestController
15@RequestMapping("/api/alert")
16public class AlertController {
17
18 @Autowired
19 private AlertService alertService;
20
21 /** 创建告警规则 */
22 @PostMapping("/rules")
23 public Result<Long> createRule(@RequestBody AlertRule rule) {
24 return Result.ok(alertService.createRule(rule));
25 }
26
27 /** 查询规则列表 */
28 @GetMapping("/rules")
29 public Result<List<AlertRule>> listRules() {
30 return Result.ok(alertService.listRules());
31 }
32
33 /** 修改规则 */
34 @PutMapping("/rules/{id}")
35 public Result<Void> updateRule(@PathVariable Long id,
36 @RequestBody AlertRule rule) {
37 rule.setId(id);
38 alertService.updateRule(rule);
39 return Result.ok();
40 }
41
42 /** 删除规则 */
43 @DeleteMapping("/rules/{id}")
44 public Result<Void> deleteRule(@PathVariable Long id) {
45 alertService.deleteRule(id);
46 return Result.ok();
47 }
48
49 /** 查询告警记录 */
50 @GetMapping("/logs")
51 public Result<PageResult<AlertLog>> listLogs(
52 @RequestParam(defaultValue="1") Integer page,
53 @RequestParam(defaultValue="20") Integer size) {
54 return Result.ok(alertService.listLogs(page, size));
55 }
56}
IntelliJ IDEA — AlertService.java
AlertController.java
AlertService.java
AlertMapper.xml
📁 src
📁 main/java/com/fix/energy
📄 AlertController.java
📄 AlertService.java
📄 AlertRule.java
📄 AlertLog.java
📁 mapper
📄 AlertRuleMapper.java
📁 config
📄 DingTalkService.java
1package com.fix.energy.service;
2
3import com.fix.energy.mapper.*;
4import com.fix.energy.model.*;
5import com.fix.energy.config.DingTalkService;
6import org.springframework.beans.factory.annotation.Autowired;
7import org.springframework.stereotype.Service;
8
9import java.math.BigDecimal;
10import java.util.*;
11
12@Service
13public class AlertService {
14
15 @Autowired AlertRuleMapper ruleMapper;
16 @Autowired AlertLogMapper logMapper;
17 @Autowired DingTalkService dingTalkService;
18
19 /** 核心方法:检测设备数据是否触发告警 */
20 public void checkAlert(DeviceData data) {
21 List<AlertRule> rules = ruleMapper.findByDeviceType(
22 data.getDeviceType());
23 for (AlertRule rule : rules) {
24 if (!rule.isEnabled()) continue;
25 if (isTriggered(data, rule)) {
26 AlertLog log = saveAlertLog(data, rule);
27 if (rule.getLevel() >= 3) {
28 dingTalkService.sendAlert(log);
29 }
30 }
31 }
32 }
33
34 /** 判断是否触发告警 */
35 private boolean isTriggered(DeviceData d, AlertRule r) {
36 BigDecimal val = d.getMetric(r.getMetric());
37 return switch (r.getOperator()) {
38 case ">" -> val.compareTo(r.getThreshold()) > 0;
39 case ">=" -> val.compareTo(r.getThreshold()) >= 0;
40 case "<" -> val.compareTo(r.getThreshold()) < 0;
41 case "<=" -> val.compareTo(r.getThreshold()) <= 0;
42 default -> false;
43 };
44 }
45
46 /** 保存告警记录 */
47 private AlertLog saveAlertLog(DeviceData d, AlertRule r) {
48 AlertLog log = new AlertLog();
49 log.setRuleId(r.getId());
50 log.setDeviceId(d.getDeviceId());
51 log.setMetricValue(d.getMetric(r.getMetric()));
52 log.setThreshold(r.getThreshold());
53 log.setLevel(r.getLevel());
54 logMapper.insert(log);
55 return log;
56 }
57
58 // createRule / updateRule / deleteRule / listRules ...
59}
IntelliJ IDEA — AlertRuleMapper.xml
AlertController.java
AlertService.java
AlertMapper.xml
📁 src/main/resources
📁 mapper
📄 AlertRuleMapper.xml
📄 AlertLogMapper.xml
📁 application.yml
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD
3 Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4<mapper namespace="com.fix.energy.mapper.AlertRuleMapper">
5
6 <select id="findByDeviceType" resultType="AlertRule">
7 SELECT * FROM t_alert_rule
8 WHERE device_type = #{deviceType}
9 AND enabled = 1 AND deleted = 0
10 </select>
11
12 <select id="findAll" resultType="AlertRule">
13 SELECT * FROM t_alert_rule
14 WHERE deleted = 0
15 ORDER BY create_time DESC
15 </select>
16
17 <insert id="insert" useGeneratedKeys="true"
18 keyProperty="id">
19 INSERT INTO t_alert_rule
20 (device_type, metric, operator, threshold,
21 level, enabled)
22 VALUES
23 (#{deviceType}, #{metric}, #{operator},
24 #{threshold}, #{level}, #{enabled})
25 </insert>
26
27 <update id="update">
28 UPDATE t_alert_rule
29 <set>
30 <if test="deviceType!=null">device_type=#{deviceType},</if>
31 <if test="metric!=null">metric=#{metric},</if>
32 <if test="operator!=null">operator=#{operator},</if>
33 <if test="threshold!=null">threshold=#{threshold},</if>
34 <if test="level!=null">level=#{level},</if>
35 <if test="enabled!=null">enabled=#{enabled},</if>
36 </set>
37 WHERE id = #{id}
38 </update>
39
40 <update id="deleteById">
41 UPDATE t_alert_rule SET deleted = 1 WHERE id = #{id}
42 </update>
43</mapper>
⏰ 定时任务 — 每分钟自动检查告警规则
⏰ 定时任务:每分钟检查告警规则
@Scheduled(cron = "0 * * * * ?")
public void checkAlertRules() {
    List<AlertRule> rules = alertRuleRepo.findByEnabled(true);
    for (AlertRule rule : rules) {
        Double currentValue = metricService.getLatest(rule.getMetric());
        if (currentValue != null && currentValue > rule.getThreshold()) {
            // 持续超过阈值 duration 秒才告警
            if (alertService.isOverDuration(rule, currentValue)) {
                alertService.sendAlert(rule, currentValue);
            }
        }
    }
}
⏰ 定时任务:每5分钟清理过期告警记录
@Scheduled(cron = "0 */5 * * * ?")
public void cleanExpiredAlertLogs() {
    LocalDateTime expireTime = LocalDateTime.now().minusDays(30);
    int deleted = alertLogMapper.deleteBefore(expireTime);
    log.info("清理过期告警记录: 删除 {} 条", deleted);
}
⚙️ 启用定时任务 — application.yml 配置
spring:
  task:
    scheduling:
      pool:
        size: 3
      thread-name-prefix: alert-scheduler-

后端核心代码写完了。Controller 负责 HTTP 接口,Service 负责业务逻辑(检测告警 + 发钉钉),Mapper 负责 SQL。

定时任务负责每分钟扫描设备数据,匹配告警规则,超阈值就触发告警和钉钉通知。接下来用 Postman 自测一下接口是否正常。

🧪

自测后端接口 11:30

Postman 测试所有 API 接口

Test 1: 创建告警规则 — POST /api/alert/rules
Postman — POST /api/alert/rules
POST http://localhost:8080/api/alert/rules Send
// Request Body (JSON)
{
  "deviceType": "sensor",
  "metric": "temperature",
  "operator": ">",
  "threshold": 80.00,
  "level": 3
}
Status: 200 OK   Time: 45ms

{
  "code": 0,
  "message": "success",
  "data": 1
}
Test 2: 查询规则列表 — GET /api/alert/rules
Postman — GET /api/alert/rules
GET http://localhost:8080/api/alert/rules Send
Status: 200 OK   Time: 23ms

{
  "code": 0,
  "data": [
    {
      "id": 1,
      "deviceType": "sensor",
      "metric": "temperature",
      "operator": ">",
      "threshold": 80.00,
      "level": 3,
      "enabled": 1
    }
  ]
}
Test 3: 删除规则 — DELETE /api/alert/rules/{id}
Postman — DELETE /api/alert/rules/1
DELETE http://localhost:8080/api/alert/rules/1 Send
Status: 200 OK   Time: 18ms

{
  "code": 0,
  "message": "success"
}
POST、GET、DELETE 都通了!后端接口没问题。吃饭去,下午写前端页面。
🎨

写前端页面 13:00

Vue.js + Element Plus 构建告警管理页面

Visual Studio Code — AlertRulePage.vue
AlertRulePage.vue
AlertLogPage.vue
AlertRuleForm.vue
📁 src
📁 views
📁 alert
📄 AlertRulePage.vue
📄 AlertLogPage.vue
📁 components
📄 AlertRuleForm.vue
📁 api
📄 alert.js
📁 router
📄 index.js
1<template>
2 <div class="alert-rule-page">
3 <el-card>
4 <template #header>
5 <div class="card-header">
6 // 告警规则管理
7 <el-button type="primary"
8 @click="showAddForm = true">
9 新增规则
10 </el-button>
11 </div>
12 </template>
13
14 <!-- 新增/编辑表单 -->
15 <AlertRuleForm
16 v-if="showAddForm"
17 :rule="editingRule"
18 @submit="handleSubmit"
19 @cancel="showAddForm = false"
20 />
21
22 <!-- 规则列表 -->
23 <el-table :data="rules" stripe>
24 <el-table-column prop="deviceType"
25 label="设备类型" />
26 <el-table-column prop="metric"
27 label="监控指标" />
28 <el-table-column prop="operator"
29 label="运算符" />
30 <el-table-column prop="threshold"
31 label="阈值" />
32 <el-table-column prop="level"
33 label="告警级别">
34 <!-- level tag -->
35 </el-table-column>
36 <el-table-column label="操作">
37 <el-button size="small"
38 @click="editRule(scope.row)">
39 编辑
40 </el-button>
41 </el-table-column>
42 </el-table>
43 </el-card>
44 </div>
45</template>
46
47<script setup>
48import { ref, onMounted } from 'vue'
49import { getRules, createRule, updateRule,
50 deleteRule } from '@/api/alert'
51
52const rules = ref([])
53const showAddForm = ref(false)
54const editingRule = ref(null)
55
56const loadRules = async () => {
57 rules.value = (await getRules()).data
58}
59
60onMounted(loadRules)
61</script>
前端代码写好了,现在看看浏览器渲染效果。
Chrome — localhost:5173/alert/rules
http://localhost:5173/alert/rules

+ 新增告警规则

设备类型 监控指标 运算符 阈值 告警级别 状态 操作
温度传感器 temperature > 80.00 严重 启用
温度传感器 temperature > 65.00 警告 启用
电表 voltage >= 250.00 警告 启用
温度传感器 temperature > 50.00 提醒 禁用
前端页面渲染正常,表单和表格都能展示。接下来联调前后端,确保数据流通。
🔗

前后端联调 14:30

前后端对接,发现并修复 Bug

打开浏览器 DevTools Network 面板,逐个检查接口请求...
Chrome DevTools — Network
Network Console Elements Application
Name Method Status Time Size
/api/alert/rules POST 200 OK ✓ 45ms 128 B
/api/alert/rules GET 200 OK ✓ 23ms 512 B
/api/alert/rules/1 PUT 400 Bad Request ✗ 12ms 256 B
PUT 接口报 400!检查请求参数...发现问题:前端发的是 JSON,但后端 Controller 的 updateRule 方法漏了 @RequestBody 注解。

找到了!后端 AlertController 第35行的 updateRule 方法,第二个参数忘了加 @RequestBody,导致 Spring 无法解析 JSON 请求体。

AlertController.java — fix: add @RequestBody to updateRule @@ -33,7 +33,7 @@ - public Result<Void> updateRule(@PathVariable Long id, - AlertRule rule) { + public Result<Void> updateRule(@PathVariable Long id, + @RequestBody AlertRule rule) {
修复后重新测试 — PUT 接口正常了
Chrome DevTools — Network (after fix)
Network Console Elements
Name Method Status Time Size
/api/alert/rules POST 200 OK ✓ 42ms 128 B
/api/alert/rules GET 200 OK ✓ 21ms 512 B
/api/alert/rules/1 PUT 200 OK ✓ 35ms 96 B
/api/alert/rules/2 DELETE 200 OK ✓ 15ms 80 B

所有接口都通了!这就是全栈的好处 — 自己的 Bug 自己修,不用等后端同事排查。前后端联调效率极高。

🐳

Docker 部署 15:30

构建前后端,Docker Compose 一键部署全部服务

代码都写完调通了。开始构建和部署 — 前端 build、后端打包、Docker Compose 全部拉起来。
Terminal — zsh
$ cd ~/work/energy-system/frontend && npm run build vite v5.2.0 building for production... ✓ 42 modules transformed. dist/index.html 0.46 kB │ gzip: 0.30 kB dist/assets/index-DiwrgTda.css 48.21 kB │ gzip: 12.83 kB dist/assets/index-Cd9x2f4F.js 128.64 kB │ gzip: 42.37 kB ✓ built in 2.1s
$ cd ~/work/energy-system/backend && mvn clean package -DskipTests [INFO] Scanning for projects... [INFO] Building energy-system 1.0.0 [INFO] --- maven-compiler-plugin:3.11.0:compile --- [INFO] Compiling 23 source files to /target/classes [INFO] --- spring-boot-maven-plugin:3.2.3:repackage --- [INFO] Building jar: target/energy-system-1.0.0.jar [INFO] BUILD SUCCESS [INFO] Total time: 8.432 s
# Docker Compose 部署全部服务 $ cd /opt/energy-system && docker-compose up -d --build Building app... Step 1/6 : FROM openjdk:21-jdk-slim Step 6/6 : ENTRYPOINT ["java", "-jar", "app.jar"] Successfully built a8f3c2d1b4e5 Building nginx... Step 1/5 : FROM nginx:alpine Step 5/5 : COPY nginx.conf /etc/nginx/conf.d/ Successfully built c7e2a1f3d5b8 Creating network "energy-system_default" with driver "bridge" Creating energy-system_redis_1 ... done Creating energy-system_mysql_1 ... done Creating energy-system_app_1 ... done Creating energy-system_nginx_1 ... done
$ docker-compose ps Name Command State Ports -------------------------------------------------------------------- energy-system_app_1 java -jar app.jar Up 0.0.0.0:8080->8080/tcp energy-system_nginx_1 nginx -g daemon off Up 0.0.0.0:80->80/tcp energy-system_mysql_1 docker-entrypoint Up 0.0.0.0:3306->3306/tcp energy-system_redis_1 docker-entrypoint Up 0.0.0.0:6379->6379/tcp
✓ 所有容器启动成功!
📄 docker-compose.yml
version: '3.8' services: app: build: ./backend ports: - "8080:8080" depends_on: - mysql - redis environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/energy nginx: build: ./frontend ports: - "80:80" depends_on: - app mysql: image: mysql:8.0 ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: *** MYSQL_DATABASE: energy_system redis: image: redis:7-alpine ports: - "6379:6379"
🐳 Docker Compose 常用运维命令
Terminal — Docker Compose 运维命令
$ docker-compose up -d [+] Running 5/5 ✔ Network fullstack_default Created ✔ Container mysql Started ✔ Container redis Started ✔ Container app Started ✔ Container nginx Started
$ docker-compose ps NAME STATUS PORTS mysql running 0.0.0.0:3306->3306/tcp redis running 0.0.0.0:6379->6379/tcp app running 0.0.0.0:8080->8080/tcp nginx running 0.0.0.0:80->80/tcp
$ docker-compose logs -f app 2026-03-18 16:00:12 [main] Started EnergySystemApplication in 4.2s 2026-03-18 16:00:15 [main] Connected to MySQL: jdbc:mysql://mysql:3306/energy_system 2026-03-18 16:00:16 [main] Connected to Redis: redis://redis:6379
$ docker-compose restart app [+] Running 1/1 ✔ Container app Restarted
$ docker-compose down [+] Running 4/4 ✔ Container nginx Removed ✔ Container app Removed ✔ Container redis Removed ✔ Container mysql Removed ✔ Network fullstack_default Removed

验证线上环境 16:00

打开生产 URL 验证功能是否正常

Chrome — energy.company.com/alert/rules
http://energy.company.com/alert/rules
✓ 线上环境加载成功!页面正常渲染。
设备类型 监控指标 运算符 阈值 告警级别 状态 操作
温度传感器 temperature > 80.00 严重 启用
温度传感器 temperature > 65.00 警告 启用
电表 voltage >= 250.00 警告 启用
测试创建新规则: ✓ 提交成功 — 新增"温度传感器 > 50°C 提醒"规则,接口返回 200,表格自动刷新显示新数据。
检查后端日志
Terminal — checking logs
$ docker-compose logs app --tail=20 2026-03-18 16:00:12 [main] Started EnergySystemApplication in 4.2s 2026-03-18 16:00:15 [main] Connected to MySQL: jdbc:mysql://mysql:3306/energy_system 2026-03-18 16:00:16 [main] Connected to Redis: redis://redis:6379 2026-03-18 16:05:33 [http-nio-8080-exec-1] GET /api/alert/rules - 200 - 12ms 2026-03-18 16:05:45 [http-nio-8080-exec-2] POST /api/alert/rules - 200 - 23ms 2026-03-18 16:05:46 [http-nio-8080-exec-3] GET /api/alert/rules - 200 - 9ms 2026-03-18 16:06:10 [http-nio-8080-exec-4] GET /api/alert/logs?page=1&size=20 - 200 - 15ms
# 所有接口响应正常,无报错 ✓
🔔

钉钉通知测试 16:30

模拟设备数据触发告警,验证钉钉消息推送

手动触发一次高温告警,模拟 SENSOR-001 温度达到 85.3°C,超过 80°C 的严重告警阈值。
Terminal — trigger test alert
$ curl -X POST http://localhost:8080/api/alert/test-trigger \ -H "Content-Type: application/json" \ -d '{"deviceType":"sensor","deviceId":"SENSOR-001","metric":"temperature","value":85.3}'
{"code":0,"message":"Alert triggered, DingTalk notification sent"}
✓ 告警触发成功,钉钉消息已发送
📱 钉钉群 — 告警通知
🚨 设备告警通知

设备类型 温度传感器
设备编号 SENSOR-001
告警指标 temperature
当前值 85.3°C
阈值 80.0°C
告警级别 ⚠️ 严重

时间 2026-03-18 16:35:00
已通知运维 ✓ 是

钉钉消息推送正常!整个告警链路跑通了:设备数据 → 规则匹配 → 创建告警记录 → 钉钉消息通知。

功能完成,可以收工了!

📊

收工总结 17:00

一天完成从需求到上线的全流程

🤔 全栈 vs 纯前端 vs 纯后端
角色 典型工作流程 需要协作
纯前端 看设计稿 → 搭页面 → 调接口 → 修Bug 需要后端给接口
纯后端 看需求 → 建表 → 写接口 → Debug → 部署 需要前端对接
全栈 全都要做!从需求讨论到部署上线一条龙 基本不需要
⏱️ 今天的工作量分布
需求沟通
30min
30 分钟
表+API设计
30min
30 分钟
后端开发
1.5h
1.5 小时
前端开发
1.5h
1.5 小时
联调+Debug
1h
1 小时
部署+验证
1h
1 小时
总计约 6 小时,从 09:00 到 17:00(含午休)

🛠️ 全栈的核心能力

  • 能独立判断需求的技术可行性
  • 前后端都能写,切换思维要快
  • Docker / CI/CD 必须会(没人帮你部署)
  • 出了问题自己排查(前端+后端+运维)
  • 数据库设计、接口设计、页面设计都得会

⚡ 全栈的核心优势

  • 沟通成本极低 — 自己和自己"联调"
  • 开发效率高 — 不需要等别人接口
  • Bug 修复快 — 知道问题在哪一层
  • 小公司最爱 — 一个人 = 一支队伍
  • 技术视野广 — 懂全局而非单一层

⚠️ 全栈的挑战

  • 技术广度 vs 深度 — 样样通样样松?
  • 精力有限 — 一个人干三人的活
  • 代码质量 — 没人 Review 你的代码
  • 压力大 — 出了线上事故全是你
  • 技术选型 — 全靠自己判断对错

📚 今天用到的技术栈

  • 后端:Java 21 + Spring Boot 3 + MyBatis
  • 前端:Vue 3 + Vite + Element Plus
  • 数据库:MySQL 8.0 + Redis 7
  • 部署:Docker + Docker Compose + Nginx
  • 工具:IntelliJ IDEA + VS Code + Postman
  • 通信:钉钉机器人 Webhook
🎯
一天搞定,从需求到上线
这就是全栈开发者的日常。
不是什么高深的技术,但需要你能独立完成从需求分析到部署上线的每一个环节。

明天继续 — 运维那边还等着告警日志页面呢。
🔀 Git 分支管理 — 团队协作的基础
为什么分支管理很重要?
三个人同时开发三个功能,都往 main 上推代码 → 冲突不断 → 互相覆盖 → 线上出bug。
分支管理的本质:让每个人有独立的工作空间,互不干扰,最后有序合并。
🌿 最常用策略:GitHub Flow(简单实用)
main
生产分支
永远是可部署的
← merge ← feature/login
功能分支
开发完提PR
← merge ← feature/pay
功能分支
独立开发
还可以同时有 feature/search、hotfix/fix-bug-123 等分支,互不干扰
📋 GitHub Flow 完整流程(日常开发最常用)
步骤Git 命令说明
1. 拉取最新代码git checkout main && git pull切到主分支,拉取最新
2. 创建功能分支git checkout -b feature/user-login从 main 拉出新分支
3. 开发+多次提交git add . && git commit -m "feat: 添加登录接口"在自己分支随便提交
4. 推送到远程git push origin feature/user-login推到远程仓库
5. 创建 Pull Request在 GitLab/GitHub 网页上操作请求合并到 main
6. Code Review同事在 PR 里看代码、提意见质量把关
7. 修改意见git add . && git commit -m "fix: 修改Review意见"继续在同一个分支改
8. 合并到 main在网页上点 Merge 按钮合并后自动部署
9. 删除功能分支git branch -d feature/user-login清理已合并的分支
⚠️ 分支命名规范
分支类型命名格式示例
新功能feature/功能名feature/user-login, feature/search
Bug修复hotfix/bug描述hotfix/fix-login-error, hotfix/fix-null-pointer
重构refactor/重构内容refactor/order-service
发布release/版本号release/v2.1.0
📝 commit message 规范
# 格式:: <描述> feat: 添加用户登录接口 # 新功能 fix: 修复登录密码校验bug # 修复bug docs: 更新API文档 # 文档 refactor: 重构订单服务 # 重构(不改变功能) perf: 优化查询性能,加索引 # 性能优化 test: 添加登录接口单元测试 # 测试 chore: 升级Spring Boot版本 # 构建/工具
🔥 冲突解决实战
产生冲突的情况
# 你和同事都改了同一个文件的同一段代码 git pull origin main # 报错:CONFLICT in OrderService.java # 打开文件会看到冲突标记: <<<<<<< HEAD 你的代码 ======= 同事的代码 >>>>>>> feature/other
解决步骤
# 1. 打开冲突文件,手动选择保留哪段代码 # 删掉 <<<< 和 ==== 和 >>>> 标记 # 保留需要的代码(可以两边都保留) # 2. 标记冲突已解决 git add OrderService.java # 3. 继续合并 git commit -m "merge: 解决OrderService冲突" # 💡 小技巧:经常 git pull 减少冲突
🌿 Git Flow(大项目用的策略)
main
生产
release/v2.0
测试
develop
开发主线
feature/xxx
功能分支
还有 hotfix 直接从 main 拉出修复后合回 main + develop
大项目用 Git Flow,中小团队用 GitHub Flow 就够了