diff --git a/开发者文档/04-前端开发/04-常见坑点与调试指南.md b/开发者文档/04-前端开发/04-常见坑点与调试指南.md new file mode 100644 index 0000000..57c31dc --- /dev/null +++ b/开发者文档/04-前端开发/04-常见坑点与调试指南.md @@ -0,0 +1,334 @@ +# 04-常见坑点与调试指南 + +本文档收集前端开发过程中高频出现的坑点、错误案例和调试技巧。所有前端开发人员在遇到类似问题时应先查阅本文档。 + +--- + +## 一、高频踩坑记录 + +### 1.1 路由跳转后页面不刷新 + +**现象**:从列表页点击进入详情页,URL 已变但页面内容未更新。 + +**原因**:Vue Router 复用了同一个组件实例,`onMounted` 不会再次触发。 + +**解决方案**: + +```vue + +``` + +--- + +### 1.2 Pinia Store 数据响应式丢失 + +**现象**:修改 Store 中的数据后,页面未更新。 + +**错误写法**: + +```typescript +// ❌ 错误:直接替换整个对象,丢失响应式 +const userStore = useUserStore() +userStore.userInfo = { name: 'new', age: 25 } +``` + +**正确写法**: + +```typescript +// ✅ 正确:使用 Object.assign 保持响应式 +const userStore = useUserStore() +Object.assign(userStore.userInfo, { name: 'new', age: 25 }) + +// 或者在定义 Store 时使用 ref +export const useUserStore = defineStore('user', () => { + const userInfo = ref({ name: '', age: 0 }) + + const updateInfo = (newInfo: any) => { + userInfo.value = { ...userInfo.value, ...newInfo } + } + + return { userInfo, updateInfo } +}) +``` + +--- + +### 1.3 表格分页参数不生效 + +**现象**:切换页码后,表格数据未更新或总是返回第一页。 + +**原因**:Pagination 组件未正确绑定 `v-model:current` 和 `v-model:pageSize`。 + +**正确写法**: + +```vue + + + +``` + +--- + +### 1.4 权限指令不生效 + +**现象**:添加了 `v-hasPermi` 但按钮仍然显示。 + +**排查步骤**: + +1. 检查后端返回的权限标识是否正确(`/system/user/get-permission`) +2. 检查权限标识字符串是否与后端 `@PreAuthorize` 注解完全一致 +3. 检查是否在登录完成后才注册指令(确保 `permissionStore` 已加载) + +**调试命令**: + +```typescript +// 在浏览器控制台执行 +window.$permission = usePermission() +window.$permission.hasPermission('ops:ticket:update') +// 返回 true/false +``` + +--- + +### 1.5 Axios 请求重复发送 + +**现象**:点击一次按钮,接口被调用多次。 + +**常见原因**: + +1. 按钮未添加 `loading` 状态防抖 +2. 表单验证触发多次提交 +3. Vue 3 的 `watch` 未正确配置 `immediate` 导致初始化时多触发一次 + +**解决方案**: + +```vue + + + +``` + +--- + +## 二、调试技巧 + +### 2.1 快速定位组件来源 + +在浏览器 DevTools 中: + +```javascript +// 在控制台执行,点击页面元素后自动定位到源码 +import { inspect } from 'vue' +inspect() +``` + +--- + +### 2.2 查看 Pinia Store 状态 + +```javascript +// 浏览器控制台 +window.$pinia = usePinia() +Object.keys(window.$pinia.state.value).forEach(key => { + console.log(key, window.$pinia.state.value[key]) +}) +``` + +--- + +### 2.3 网络请求调试 + +在 `src/utils/http/axios/index.ts` 中临时开启详细日志: + +```typescript +axios.interceptors.request.use(config => { + console.log('[AXIOS REQUEST]', config.url, config.params, config.data) + return config +}) + +axios.interceptors.response.use(response => { + console.log('[AXIOS RESPONSE]', response.config.url, response.data) + return response +}, error => { + console.error('[AXIOS ERROR]', error.config?.url, error.response?.data) + return Promise.reject(error) +}) +``` + +--- + +### 2.4 路由调试 + +```javascript +// 查看当前路由信息 +console.log($route) + +// 查看所有已注册路由 +console.log($router.getRoutes()) + +// 动态添加路由后检查是否生效 +console.log($router.hasRoute('ops-ticket-list')) +``` + +--- + +## 三、性能优化建议 + +### 3.1 大列表渲染优化 + +**问题**:一次性渲染 1000+ 条数据导致页面卡顿。 + +**解决方案**: + +1. 使用虚拟滚动(`vue-virtual-scroller`) +2. 开启分页(推荐) +3. 使用 `v-memo` 缓存静态内容(Vue 3.2+) + +```vue + +``` + +--- + +### 3.2 组件懒加载 + +```typescript +// 路由懒加载 +const routes = [ + { + path: '/ops', + component: () => import('@/views/ops/index.vue'), + children: [ + { + path: 'ticket', + component: () => import('@/views/ops/ticket/index.vue') + } + ] + } +] +``` + +--- + +### 3.3 避免不必要的计算 + +```vue + +``` + +--- + +## 四、开发环境配置 + +### 4.1 推荐 VS Code 插件 + +- Volar(Vue 3 官方插件) +- ESLint +- Prettier +- TypeScript Vue Plugin + +### 4.2 本地调试配置 + +```bash +# 安装依赖 +pnpm install + +# 启动开发服务器 +pnpm dev + +# 构建生产版本 +pnpm build + +# 类型检查 +pnpm type-check + +# Lint 检查 +pnpm lint +``` + +### 4.3 环境变量配置 + +```bash +# .env.development +VITE_API_BASE_URL=http://localhost:48080 +VITE_APP_TITLE=AIOT 管理后台 - 开发环境 + +# .env.production +VITE_API_BASE_URL=https://api.example.com +VITE_APP_TITLE=AIOT 管理后台 +``` + +--- + +## 五、相关文档 + +- [01-前端工程结构与协作边界.md](./01-前端工程结构与协作边界.md) +- [02-API 交互与状态管理规范.md](./02-API 交互与状态管理规范.md) +- [03-RBAC 权限控制与开发规范.md](./03-RBAC 权限控制与开发规范.md) diff --git a/开发者文档/06-平台支撑/07-API 文档/01-接口分域与维护原则.md b/开发者文档/06-平台支撑/07-API 文档/01-接口分域与维护原则.md new file mode 100644 index 0000000..df0320d --- /dev/null +++ b/开发者文档/06-平台支撑/07-API 文档/01-接口分域与维护原则.md @@ -0,0 +1,226 @@ +# 01-接口分域与维护原则 + +本文档定义 AIOT 系统 API 接口的组织原则、文档维护责任边界,以及接口变更的协作流程。 + +**核心原则**:接口文档优先按**业务域**维护,而非按页面或客户端维护。因为页面会变、客户端会增,但领域边界相对稳定。 + +--- + +## 一、接口分域架构 + +### 1.1 四大核心域 + +| 域标识 | 对应微服务 | 职责范围 | 负责人 | +|--------|-----------|---------|--------| +| `system` | `module-system` | 用户、角色、菜单、部门、租户、字典 | 后端架构组 | +| `infra` | `module-infra` | 文件管理、定时任务、代码生成、消息推送 | 后端架构组 | +| `ops` | `module-ops` | 工单、巡检、保洁、安保、排班、考勤 | Ops 业务组 | +| `iot` | `module-iot` | 设备、物模型、规则引擎、告警、MQTT 桥接 | IoT 业务组 | + +### 1.2 接口 URL 规范 + +所有接口必须遵循 RESTful 风格,并按域组织路径: + +``` +GET /api/system/users # 用户列表 +POST /api/system/users # 创建用户 +GET /api/system/users/{id} # 用户详情 +PUT /api/system/users/{id} # 更新用户 +DELETE /api/system/users/{id} # 删除用户 + +GET /api/ops/tickets # 工单列表 +POST /api/ops/tickets/{id}/dispatch # 派单(业务操作) +POST /api/ops/tickets/{id}/confirm # 确认到岗(业务操作) + +GET /api/iot/devices # 设备列表 +POST /api/iot/devices/{id}/reboot # 重启设备(业务操作) +``` + +**禁止事项**: +- ❌ `/api/getUserList` - 非 RESTful +- ❌ `/api/ticket/updateStatus` - 动词在 URL 中,应使用 `/api/tickets/{id}/status` +- ❌ `/api/opsAndIot/xxx` - 跨域接口应拆分或通过事件驱动 + +--- + +## 二、接口文档维护责任 + +### 2.1 Swagger 注解是唯一的真理源 + +**所有接口必须在 Controller 方法上完整标注 Swagger 注解**: + +```java +@RestController +@RequestMapping("/ops/tickets") +@Tag(name = "工单管理", description = "工单 CRUD 及状态流转") +public class TicketController { + + @PostMapping("/dispatch") + @Operation(summary = "派单", description = "将工单派发给指定保洁员") + @PreAuthorize("@ss.hasPermi('ops:ticket:dispatch')") + public CommonResult dispatchTicket(@RequestBody @Valid TicketDispatchReqVO reqVO) { + // ... + } +} +``` + +**必填注解**: +- `@Tag` - 接口分组(对应域) +- `@Operation(summary = ..., description = ...)` - 接口用途 +- `@Parameter` / `@Schema` - 参数说明 +- `@ApiResponse` - 返回码说明(特别是业务错误码) + +### 2.2 接口变更流程 + +``` +开发者修改接口 + ↓ +更新 Swagger 注解 + ↓ +提交 MR → 自动触发 Swagger 文档生成 + ↓ +前端/移动端负责人 Review 文档 + ↓ +确认无破坏性变更后合并 +``` + +**破坏性变更定义**(需提前通知所有调用方): +- 删除或重命名已有字段 +- 修改字段类型(如 `String` → `Integer`) +- 增加必填参数 +- 修改接口路径或 HTTP 方法 + +**非破坏性变更**(可直接发布): +- 新增可选参数 +- 新增接口 +- 增加返回字段 + +--- + +## 三、为什么不按页面维护接口文档 + +### 3.1 错误示例 + +``` +❌ 按页面组织: +- 保洁管理页面接口 + - 获取保洁员列表 + - 创建保洁员 + - 编辑保洁员 +- 工单列表页面接口 + - 获取工单列表 + - 工单详情 +``` + +**问题**: +1. 同一个 `/api/ops/cleaners` 接口可能被保洁管理页面、工单派发页面、排班页面同时使用 +2. 新增一个移动端页面时,接口文档需要重复维护 +3. 页面重构或合并时,接口文档需要大量调整 + +### 3.2 正确示例 + +``` +✅ 按业务域组织: +- ops 域 + - 保洁员管理 + - GET /api/ops/cleaners - 保洁员列表 + - POST /api/ops/cleaners - 创建保洁员 + - PUT /api/ops/cleaners/{id} - 更新保洁员 + - 工单管理 + - GET /api/ops/tickets - 工单列表 + - POST /api/ops/tickets/{id}/dispatch - 派单 +``` + +**优势**: +1. 接口与页面解耦,一个接口服务多个客户端 +2. 领域边界清晰,便于微服务拆分 +3. 新增客户端(如小程序)时,直接引用已有域文档 + +--- + +## 四、接口版本管理 + +### 4.1 版本号位置 + +当需要发布不兼容的接口变更时,使用 URL 路径版本号: + +``` +GET /api/v1/ops/tickets # 旧版本 +GET /api/v2/ops/tickets # 新版本(字段结构变化) +``` + +**禁止使用**: +- ❌ Query 参数版本:`/api/ops/tickets?version=2` +- ❌ Header 版本:`X-API-Version: 2`(不利于缓存和调试) + +### 4.2 版本共存策略 + +- 新版本发布后,旧版本至少保留 **3 个月** 过渡期 +- 在网关层监控旧版本接口的调用量,提前通知调用方迁移 +- 过渡期结束后,在网关层返回 `410 Gone` 并引导升级 + +--- + +## 五、跨域接口处理 + +### 5.1 禁止跨域直接调用 + +```java +// ❌ 错误:ops 服务直接调用 iot 服务的 Feign Client +@Autowired +private IotDeviceClient deviceClient; + +public void dispatchTicket() { + // 直接调用 IoT 服务 + deviceClient.getDeviceStatus(deviceId); +} +``` + +**问题**: +- 微服务之间产生强耦合 +- 无法独立部署和扩展 +- 故障传播(IoT 服务宕机拖垮 Ops 服务) + +### 5.2 正确做法:事件驱动 + +```java +// ✅ 正确:通过消息队列解耦 +// Ops 服务发布事件 +applicationEventPublisher.publishEvent(new TicketDispatchedEvent(ticketId, deviceId)); + +// IoT 服务监听事件并处理 +@EventListener +public void onTicketDispatched(TicketDispatchedEvent event) { + // 处理设备相关逻辑 +} +``` + +--- + +## 六、接口文档访问 + +### 6.1 Swagger UI 地址 + +| 环境 | 地址 | +|------|------| +| 开发环境 | `http://localhost:48080/swagger-ui.html` | +| 测试环境 | `http://test-api.example.com/swagger-ui.html` | +| 生产环境 | **不开放**(通过内部文档平台查看) | + +### 6.2 导出 OpenAPI 规范 + +```bash +# 导出 YAML 格式 +curl http://localhost:48080/v3/api-docs -o openapi.yaml + +# 导出 JSON 格式 +curl http://localhost:48080/v3/api-docs -o openapi.json +``` + +--- + +## 七、相关文档 + +- [00-支撑平台总览.md](../00-支撑平台总览.md) +- [01-统一网关入口规范.md](../01-统一网关入口规范.md) +- [08-数据库/01-数据域划分与表关系思路.md](../08-数据库/01-数据域划分与表关系思路.md) diff --git a/开发者文档/06-平台支撑/08-数据库/01-数据域划分与表关系思路.md b/开发者文档/06-平台支撑/08-数据库/01-数据域划分与表关系思路.md index 7b2055a..dc78278 100644 --- a/开发者文档/06-平台支撑/08-数据库/01-数据域划分与表关系思路.md +++ b/开发者文档/06-平台支撑/08-数据库/01-数据域划分与表关系思路.md @@ -1,31 +1,415 @@ -# 🗄️ 数据域划分与表关系思路 +# 01-数据域划分与表关系思路 -当前数据库文档最重要的不是表清单,而是先划分数据域。 +本文档定义 AIOT 系统 MySQL 数据库的逻辑分域原则、表命名规范,以及跨域关联查询的底线。 -## 1. 系统主数据 +**核心原则**:数据库表按业务域划分,域内可自由关联,跨域关联必须通过冗余字段或事件驱动,禁止跨域 JOIN。 -- 用户 -- 角色 -- 菜单 -- 部门 -- 租户 +--- -## 2. IoT 主数据与过程数据 +## 一、数据库分域架构 -- 产品 -- 设备 -- 设备分组 -- 物模型 -- 告警配置 -- 告警记录 -- 设备消息 +### 1.1 三大核心数据域 -## 3. Ops 主数据与过程数据 +``` +┌─────────────────────────────────────────────────────────────┐ +│ AIOT Database │ +├─────────────────┬─────────────────┬─────────────────────────┤ +│ SYSTEM 域 │ OPS 域 │ IoT 域 │ +│ (系统主数据) │ (业务过程数据) │ (设备与物联数据) │ +├─────────────────┼─────────────────┼─────────────────────────┤ +│ - sys_user │ - ops_ticket │ - iot_product │ +│ - sys_role │ - ops_order │ - iot_device │ +│ - sys_menu │ - ops_cleaner │ - iot_thing_model │ +│ - sys_dept │ - ops_inspection│ - iot_rule │ +│ - sys_dict │ - ops_security │ - iot_alarm │ +│ - sys_tenant │ - ops_shift │ - iot_message_log │ +└─────────────────┴─────────────────┴─────────────────────────┘ +``` -- 工单主记录 -- 工单业务日志 -- 工单事件 -- 队列记录 -- 执行人状态 -- 统计结果 +### 1.2 域职责边界 +| 域 | 职责 | 数据特点 | 读写比例 | +|----|------|---------|---------| +| **SYSTEM** | 用户、角色、权限、组织架构、字典 | 低频变更、高一致性要求 | 读 95% / 写 5% | +| **OPS** | 工单、排班、考勤、巡检记录 | 高频写入、状态流转复杂 | 读 60% / 写 40% | +| **IoT** | 设备、物模型、消息、告警 | 超高频写入、时序性强 | 读 20% / 写 80% | + +--- + +## 二、表命名规范 + +### 2.1 强制前缀规则 + +所有表名必须带域前缀,格式:`{域标识}_{模块名}_{实体名}` + +```sql +-- ✅ 正确 +sys_user -- 系统域 - 用户 +sys_role -- 系统域 - 角色 +ops_ticket -- Ops 域 - 工单 +ops_order_queue -- Ops 域 - 工单队列 +iot_device -- IoT 域 - 设备 +iot_alarm_record -- IoT 域 - 告警记录 + +-- ❌ 错误 +user -- 缺少域前缀 +ticket -- 缺少域前缀 +iot_device_info -- 冗余后缀(device 本身就表示信息) +``` + +### 2.2 关联表命名 + +多对多关联表使用 `_{域}_关联实体 A_关联实体 B` 格式: + +```sql +sys_user_role -- 用户 - 角色关联 +sys_role_menu -- 角色 - 菜单关联 +ops_ticket_cleaner -- 工单 - 保洁员关联(历史派单记录) +``` + +### 2.3 扩展表命名 + +当主表字段过多需要拆分时: + +```sql +ops_ticket -- 主表(核心字段) +ops_ticket_ext -- 扩展表(低频访问的大字段) +ops_ticket_trace -- 追踪表(状态流转日志) +``` + +--- + +## 三、核心表结构概览 + +### 3.1 SYSTEM 域 + +```sql +-- 用户表 +CREATE TABLE sys_user ( + id BIGINT PRIMARY KEY, + tenant_id BIGINT NOT NULL COMMENT '租户 ID', + username VARCHAR(50) NOT NULL COMMENT '用户名', + password VARCHAR(100) NOT NULL COMMENT '密码', + nickname VARCHAR(50) COMMENT '昵称', + email VARCHAR(255) COMMENT '邮箱', + phone VARCHAR(20) COMMENT '手机号', + status TINYINT DEFAULT 1 COMMENT '状态 (0-禁用 1-正常)', + dept_id BIGINT COMMENT '部门 ID', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + INDEX idx_tenant_username (tenant_id, username), + INDEX idx_phone (phone) +); + +-- 角色表 +CREATE TABLE sys_role ( + id BIGINT PRIMARY KEY, + tenant_id BIGINT NOT NULL, + name VARCHAR(50) NOT NULL COMMENT '角色名称', + code VARCHAR(50) NOT NULL COMMENT '角色标识', + status TINYINT DEFAULT 1, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + + UNIQUE KEY uk_tenant_code (tenant_id, code) +); + +-- 用户角色关联表 +CREATE TABLE sys_user_role ( + user_id BIGINT NOT NULL, + role_id BIGINT NOT NULL, + PRIMARY KEY (user_id, role_id) +); +``` + +### 3.2 OPS 域 + +```sql +-- 工单表 +CREATE TABLE ops_ticket ( + id BIGINT PRIMARY KEY, + tenant_id BIGINT NOT NULL, + ticket_no VARCHAR(32) NOT NULL COMMENT '工单号', + type TINYINT NOT NULL COMMENT '工单类型 (1-保洁 2-安保 3-巡检)', + status TINYINT NOT NULL COMMENT '状态 (见 WorkOrderStatusEnum)', + priority TINYINT DEFAULT 1 COMMENT '优先级 (1-P0 紧急 2-P1 高 3-P2 中 4-P3 低)', + + -- 位置信息 + location_name VARCHAR(100) COMMENT '位置名称', + location_address VARCHAR(255) COMMENT '详细地址', + beacon_id VARCHAR(50) COMMENT '关联信标 ID', + + -- 派单信息 + assigned_cleaner_id BIGINT COMMENT '指派保洁员 ID', + assigned_at DATETIME COMMENT '派单时间', + confirmed_at DATETIME COMMENT '确认时间', + arrived_at DATETIME COMMENT '到岗时间 (信标感应)', + completed_at DATETIME COMMENT '完成时间', + + -- 队列评分 (用于智能派单排序) + queue_score DECIMAL(10,2) COMMENT '队列评分', + + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + + UNIQUE KEY uk_ticket_no (tenant_id, ticket_no), + INDEX idx_status (status), + INDEX idx_assigned_cleaner (assigned_cleaner_id), + INDEX idx_beacon (beacon_id) +); + +-- 工单状态流转日志表 +CREATE TABLE ops_ticket_log ( + id BIGINT PRIMARY KEY, + ticket_id BIGINT NOT NULL, + from_status TINYINT NOT NULL, + to_status TINYINT NOT NULL, + operator_id BIGINT COMMENT '操作人 ID', + operator_type TINYINT COMMENT '操作人类型 (1-人 2-系统 3-信标)', + remark VARCHAR(500) COMMENT '备注', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + + INDEX idx_ticket (ticket_id), + INDEX idx_created (created_at) +); + +-- 保洁员表 +CREATE TABLE ops_cleaner ( + id BIGINT PRIMARY KEY, + tenant_id BIGINT NOT NULL, + user_id BIGINT NOT NULL COMMENT '关联系统用户 ID', + name VARCHAR(50) NOT NULL, + phone VARCHAR(20) NOT NULL, + status TINYINT DEFAULT 1 COMMENT '状态 (1-空闲 2-工作中 3-离线)', + current_ticket_id BIGINT COMMENT '当前工单 ID', + badge_no VARCHAR(50) COMMENT '工牌编号', + + -- 统计字段 (冗余,避免实时 COUNT) + total_tickets INT DEFAULT 0 COMMENT '累计完成工单数', + today_tickets INT DEFAULT 0 COMMENT '今日完成工单数', + + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + + UNIQUE KEY uk_user (tenant_id, user_id), + INDEX idx_status (status), + INDEX idx_badge (badge_no) +); +``` + +### 3.3 IoT 域 + +```sql +-- 产品表 (设备模板) +CREATE TABLE iot_product ( + id BIGINT PRIMARY KEY, + tenant_id BIGINT NOT NULL, + name VARCHAR(100) NOT NULL, + product_key VARCHAR(50) NOT NULL COMMENT '产品唯一标识', + product_secret VARCHAR(100) COMMENT '产品密钥', + + -- 物模型 + thing_model_id BIGINT COMMENT '关联物模型 ID', + + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + + UNIQUE KEY uk_product_key (tenant_id, product_key) +); + +-- 设备表 +CREATE TABLE iot_device ( + id BIGINT PRIMARY KEY, + tenant_id BIGINT NOT NULL, + product_id BIGINT NOT NULL, + device_name VARCHAR(100) NOT NULL, + device_key VARCHAR(50) NOT NULL COMMENT '设备唯一标识', + device_secret VARCHAR(100) COMMENT '设备密钥', + + -- 状态 + status TINYINT DEFAULT 0 COMMENT '状态 (0-未激活 1-在线 2-离线 3-禁用)', + last_heartbeat_at DATETIME COMMENT '最后心跳时间', + + -- 关联信息 + beacon_id VARCHAR(50) COMMENT '绑定信标 ID', + cleaner_id BIGINT COMMENT '绑定保洁员 ID (工牌设备)', + + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + + UNIQUE KEY uk_device_key (tenant_id, device_key), + INDEX idx_product (product_id), + INDEX idx_status (status), + INDEX idx_cleaner (cleaner_id) +); + +-- 物模型定义表 +CREATE TABLE iot_thing_model ( + id BIGINT PRIMARY KEY, + tenant_id BIGINT NOT NULL, + name VARCHAR(100) NOT NULL, + + -- 属性定义 (JSON 存储) + properties JSON COMMENT '属性列表', + events JSON COMMENT '事件列表', + services JSON COMMENT '服务列表', + + created_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +-- 告警记录表 +CREATE TABLE iot_alarm ( + id BIGINT PRIMARY KEY, + tenant_id BIGINT NOT NULL, + device_id BIGINT NOT NULL, + alarm_type VARCHAR(50) NOT NULL COMMENT '告警类型', + alarm_level TINYINT NOT NULL COMMENT '告警级别 (1-紧急 2-重要 3-一般)', + content VARCHAR(500) COMMENT '告警内容', + status TINYINT DEFAULT 0 COMMENT '状态 (0-未处理 1-已处理)', + + triggered_at DATETIME NOT NULL, + handled_at DATETIME COMMENT '处理时间', + handler_id BIGINT COMMENT '处理人 ID', + + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + + INDEX idx_device (device_id), + INDEX idx_status (status), + INDEX idx_triggered (triggered_at) +); +``` + +--- + +## 四、跨域关联原则 + +### 4.1 禁止跨域 JOIN + +```sql +-- ❌ 错误:跨域 JOIN(Ops 域直接 JOIN System 域) +SELECT t.*, u.nickname +FROM ops_ticket t +JOIN sys_user u ON t.assigned_cleaner_id = u.id +WHERE t.status = 1; + +-- ❌ 错误:跨域 JOIN(IoT 域直接 JOIN Ops 域) +SELECT d.*, c.name +FROM iot_device d +JOIN ops_cleaner c ON d.cleaner_id = c.id; +``` + +**问题**: +- 违反微服务边界,未来无法拆分数据库 +- 跨域查询性能不可控 +- 事务边界模糊 + +### 4.2 正确做法:冗余字段 + 应用层组装 + +```sql +-- ✅ 正确:在 Ops 域冗余 System 域的必要字段 +CREATE TABLE ops_cleaner ( + id BIGINT PRIMARY KEY, + user_id BIGINT NOT NULL, + name VARCHAR(50) NOT NULL, -- 冗余 sys_user.nickname + phone VARCHAR(20) NOT NULL, -- 冗余 sys_user.phone + -- ... +); + +-- 应用层查询时: +-- 1. 先查 ops_ticket 获取 assigned_cleaner_id +-- 2. 批量查询 ops_cleaner 获取保洁员信息(含冗余的 name/phone) +-- 3. 在内存中组装结果 +``` + +### 4.3 必须跨域查询时的方案 + +**场景**:后台管理页面需要展示工单列表,包含保洁员姓名、部门等信息。 + +**方案 A:应用层批量查询(推荐)** + +```java +// 1. 查询工单列表 +List tickets = ticketMapper.selectList(query); + +// 2. 批量查询保洁员信息 +List cleanerIds = tickets.stream() + .map(Ticket::getAssignedCleanerId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + +List cleaners = cleanerMapper.selectBatchIds(cleanerIds); +Map cleanerMap = cleaners.stream() + .collect(Collectors.toMap(Cleaner::getId, Function.identity())); + +// 3. 批量查询用户信息(System 域) +List userIds = cleaners.stream() + .map(Cleaner::getUserId) + .collect(Collectors.toList()); + +List users = userMapper.selectBatchIds(userIds); + +// 4. 内存组装 +tickets.forEach(ticket -> { + Cleaner cleaner = cleanerMap.get(ticket.getAssignedCleanerId()); + if (cleaner != null) { + ticket.setCleanerName(cleaner.getName()); + // ... + } +}); +``` + +**方案 B:数据同步(读多写少场景)** + +```java +// System 域的用户信息变更时,发布事件 +applicationEventPublisher.publishEvent(new UserInfoChangedEvent(userId)); + +// Ops 域监听事件,更新冗余字段 +@EventListener +public void onUserInfoChanged(UserInfoChangedEvent event) { + User user = userService.getById(event.getUserId()); + cleanerMapper.updateByUserId(user.getId(), + CleanerUpdateParams.builder() + .name(user.getNickname()) + .phone(user.getPhone()) + .build() + ); +} +``` + +--- + +## 五、索引设计规范 + +### 5.1 必建索引场景 + +| 场景 | 索引类型 | 示例 | +|------|---------|------| +| 主键 | PRIMARY KEY | `id` | +| 唯一业务键 | UNIQUE KEY | `tenant_id + ticket_no` | +| 外键关联 | INDEX | `assigned_cleaner_id` | +| 状态筛选 | INDEX | `status` | +| 时间范围查询 | INDEX | `created_at` | +| 组合查询 | 复合索引 | `tenant_id + status + created_at` | + +### 5.2 复合索引顺序原则 + +```sql +-- ✅ 正确:高选择性字段在前 +INDEX idx_tenant_status_created (tenant_id, status, created_at) + +-- 查询时: +WHERE tenant_id = ? AND status = ? AND created_at > ? +WHERE tenant_id = ? AND status = ? +WHERE tenant_id = ? + +-- ❌ 错误:低选择性字段在前 +INDEX idx_status_tenant (status, tenant_id) +-- 当 status 只有 4 个值时,区分度极低 +``` + +--- + +## 六、相关文档 + +- [00-支撑平台总览.md](../00-支撑平台总览.md) +- [02-中间件使用规约.md](../02-中间件使用规约.md) +- [07-API 文档/01-接口分域与维护原则.md](../07-API 文档/01-接口分域与维护原则.md) diff --git a/开发者文档/06-平台支撑/09-DevOps 运维/01-部署运行与排障视角.md b/开发者文档/06-平台支撑/09-DevOps 运维/01-部署运行与排障视角.md new file mode 100644 index 0000000..f25abc0 --- /dev/null +++ b/开发者文档/06-平台支撑/09-DevOps 运维/01-部署运行与排障视角.md @@ -0,0 +1,350 @@ +# 01-部署运行与排障视角 + +本文档建立 AIOT 系统的排障方法论,帮助开发和运维人员快速定位问题所属层次,而非堆砌命令。 + +**核心原则**:排障第一刀先判断问题属于哪一层,再逐层深入。禁止一上来就 `kubectl logs` 或 `docker exec` 盲目翻日志。 + +--- + +## 一、系统分层架构 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 客户端层 (Client) │ +│ Web 管理后台 │ 移动端 App │ 小程序 │ 工牌/信标 │ +└─────────────────────────────────────────────────────────────┘ + ↓ HTTPS / MQTT +┌─────────────────────────────────────────────────────────────┐ +│ 网关入口层 (Gateway) │ +│ viewsh-gateway (Spring Cloud Gateway + Sentinel) │ +│ - 路由分发 - JWT 鉴权 - 限流熔断 - 黑白名单 │ +└─────────────────────────────────────────────────────────────┘ + ↓ 内部 HTTP / Feign +┌─────────────────────────────────────────────────────────────┐ +│ 主服务装配层 (Microservices) │ +│ ┌─────────────┬─────────────┬─────────────┬─────────────┐ │ +│ │module-system│ module-infra│ module-ops │ module-iot │ │ +│ │ 用户角色权限 │ 文件任务消息│ 工单巡检保洁│ 设备物模型 │ │ +│ └─────────────┴─────────────┴─────────────┴─────────────┘ │ +│ Nacos (注册发现/配置中心) │ +│ Redis (缓存/分布式锁) │ +│ MQ (RabbitMQ/Kafka 消息队列) │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ IoT 规则与设备层 (IoT Edge) │ +│ MQTT Broker (EMQX) │ 规则引擎 │ 设备影子 │ 边缘网关 │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ Ops 状态与执行层 (Physical) │ +│ 智能工牌 (Badge) │ 蓝牙信标 (Beacon) │ 现场工作人员 │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 二、排障决策树 + +### 2.1 第一刀:问题现象归类 + +``` +用户报告问题 + ↓ +┌──────────────────────────────────────────┐ +│ 1. 所有用户都访问不了? │ +│ → 网关入口层 / 基础设施层 │ +│ → 检查网关健康度、Nacos、数据库连接池 │ +└──────────────────────────────────────────┘ + ↓ 否 +┌──────────────────────────────────────────┐ +│ 2. 特定功能访问不了? │ +│ → 主服务装配层 │ +│ → 检查对应微服务状态、日志、依赖中间件 │ +└──────────────────────────────────────────┘ + ↓ 否 +┌──────────────────────────────────────────┐ +│ 3. 设备数据不上报/不响应? │ +│ → IoT 规则与设备层 │ +│ → 检查 MQTT Broker、设备在线状态、规则引擎│ +└──────────────────────────────────────────┘ + ↓ 否 +┌──────────────────────────────────────────┐ +│ 4. 工单状态不更新/信标无感应? │ +│ → Ops 状态与执行层 │ +│ → 检查工牌电量、信标广播、蓝牙连接日志 │ +└──────────────────────────────────────────┘ +``` + +--- + +## 三、各层排障清单 + +### 3.1 网关入口层 + +**典型症状**: +- 所有接口返回 `502 Bad Gateway` 或 `503 Service Unavailable` +- 登录接口正常,业务接口全部 `401 Unauthorized` +- 部分用户访问正常,部分用户报错 + +**检查步骤**: + +```bash +# 1. 检查网关容器状态 +docker ps | grep gateway +docker logs viewsh-gateway --tail 100 + +# 2. 检查网关健康端点 +curl http://gateway-host:18080/actuator/health + +# 3. 检查 Nacos 服务注册 +curl http://nacos-host:8848/nacos/v1/ns/instance/list?serviceName=module-ops + +# 4. 检查网关路由配置 (Nacos 配置中心) +# Data ID: viewsh-gateway.yaml +# 检查 route 配置是否指向正确的服务名 + +# 5. 检查 JWT 密钥配置 +# 确认 gateway 和 各微服务使用相同的 jwt.secret +``` + +**常见问题**: +| 问题 | 原因 | 解决方案 | +|------|------|---------| +| 全部 502 | 下游服务全部未注册 | 检查 Nacos 是否正常,微服务是否启动 | +| 全部 401 | JWT 密钥不一致 | 统一 Nacos 中的 `jwt.secret` 配置 | +| 部分路由 404 | 路由配置遗漏 | 在 Nacos 网关配置中补充 route | +| 限流报错 429 | 触发 Sentinel 限流 | 检查限流阈值,临时调高或扩容 | + +--- + +### 3.2 主服务装配层 + +**典型症状**: +- 特定功能报错(如工单列表打不开,但用户管理正常) +- 接口响应极慢(>5s) +- 间歇性报错,重试后正常 + +**检查步骤**: + +```bash +# 1. 定位问题服务 +# 根据功能确定所属微服务: +# - 工单/保洁/巡检 → module-ops +# - 设备/物模型/告警 → module-iot +# - 用户/角色/权限 → module-system + +# 2. 检查服务容器状态 +docker ps | grep module-ops +docker stats module-ops # 查看 CPU/内存使用率 + +# 3. 查看应用日志 +docker logs module-ops --tail 200 | grep -E "ERROR|WARN" + +# 4. 检查 JVM 状态 +docker exec module-ops jstat -gcutil 1000 5 + +# 5. 检查数据库连接池 +# 登录 MySQL 查看连接数 +SHOW PROCESSLIST; +SHOW STATUS LIKE 'Threads_connected'; + +# 6. 检查 Redis 连接 +redis-cli -h redis-host ping +redis-cli -h redis-host KEYS "aiot:ops:*" | head 20 + +# 7. 检查 MQ 队列积压 +# RabbitMQ 管理界面:http://mq-host:15672 +# 查看队列消息数,确认是否有消费失败 +``` + +**常见问题**: +| 问题 | 原因 | 解决方案 | +|------|------|---------| +| 接口超时 | 数据库慢查询 | 检查慢查询日志,添加索引 | +| 内存溢出 OOM | 堆内存不足 | 调大 `-Xmx`,检查内存泄漏 | +| 数据库连接耗尽 | 连接池配置过小 | 调大 `maximum-pool-size` | +| Redis 连接失败 | Redis 宕机或密码错误 | 检查 Redis 状态和 Nacos 配置 | +| MQ 消息积压 | 消费者处理慢或宕机 | 检查消费者日志,增加并发数 | + +--- + +### 3.3 IoT 规则与设备层 + +**典型症状**: +- 设备显示离线,但实际已通电 +- 设备上报数据,但系统未收到 +- 规则引擎未触发预期动作 + +**检查步骤**: + +```bash +# 1. 检查 MQTT Broker 状态 +docker ps | grep emqx +docker logs emqx --tail 100 + +# 2. 检查设备在线状态 +# EMQX 管理界面:http://emqx-host:18083 +# 查看设备连接数、订阅主题 + +# 3. 检查设备认证日志 +docker logs emqx | grep "device-xxx" + +# 4. 检查规则引擎 +# EMQX 规则界面:查看规则执行日志 +# 确认规则 SQL 是否正确,动作是否配置 + +# 5. 检查设备消息日志 +# 查询 iot_message_log 表 +SELECT * FROM iot_message_log +WHERE device_id = xxx +ORDER BY created_at DESC +LIMIT 10; + +# 6. 模拟设备上报测试 +# 使用 MQTT.fx 或命令行工具 +mosquitto_pub -h emqx-host -t "/sys/xxx/xxx/post" -m '{"temp": 25}' +``` + +**常见问题**: +| 问题 | 原因 | 解决方案 | +|------|------|---------| +| 设备连不上 | 设备密钥错误或 Broker 宕机 | 检查设备三元组,重启 EMQX | +| 数据不上报 | 网络问题或主题错误 | 检查设备网络,确认发布主题 | +| 规则不触发 | 规则 SQL 语法错误 | 在 EMQX 控制台测试规则 SQL | +| 消息丢失 | QoS 级别过低 | 设备端使用 QoS 1 或 2 | + +--- + +### 3.4 Ops 状态与执行层 + +**典型症状**: +- 保洁员已到岗,但系统显示未到达 +- 工单派发了,但工牌未收到通知 +- 信标感应失败,无法自动完工 + +**检查步骤**: + +```bash +# 1. 检查工牌状态 +# 查询 ops_cleaner 表 +SELECT id, name, badge_no, status, current_ticket_id +FROM ops_cleaner +WHERE id = xxx; + +# 2. 检查信标绑定关系 +# 查询 iot_device 或 ops_location 表 +SELECT beacon_id, location_name +FROM ops_location +WHERE id = xxx; + +# 3. 检查工单状态流转日志 +SELECT * FROM ops_ticket_log +WHERE ticket_id = xxx +ORDER BY created_at DESC; + +# 4. 检查蓝牙信标广播 +# 使用手机 App 或 nRF Connect 扫描信标 +# 确认信标 UUID、Major、Minor 是否正确广播 + +# 5. 检查工牌电量 +# 工牌管理界面查看电量,或询问现场人员 +# 电量低于 20% 可能导致蓝牙断开 + +# 6. 检查信标感应日志 +# 查看 IoT 服务日志中信标事件上报记录 +docker logs module-iot | grep "beacon:xxx" +``` + +**常见问题**: +| 问题 | 原因 | 解决方案 | +|------|------|---------| +| 未自动到岗 | 信标未广播或工牌未感应 | 检查信标电量,重新绑定 | +| 工牌未收到派单 | 工牌离线或 MQTT 断开 | 检查工牌网络,重启工牌 | +| 状态不更新 | 工牌按键损坏或固件 bug | 更换工牌,升级固件 | +| 误感应 | 信标位置过近或信号穿透 | 调整信标位置,降低发射功率 | + +--- + +## 四、常用诊断命令速查 + +### 4.1 容器诊断 + +```bash +# 查看所有容器状态 +docker ps -a + +# 查看容器资源使用 +docker stats + +# 查看容器日志 +docker logs --tail 100 -f + +# 进入容器调试 +docker exec -it bash + +# 重启容器 +docker restart +``` + +### 4.2 数据库诊断 + +```bash +# 查看当前连接 +SHOW PROCESSLIST; + +# 查看慢查询 +SHOW VARIABLES LIKE 'slow_query_log'; +SHOW VARIABLES LIKE 'long_query_time'; + +# 查看表锁 +SHOW OPEN TABLES WHERE In_use > 0; + +# 查看连接数 +SHOW STATUS LIKE 'Threads_connected'; +SHOW VARIABLES LIKE 'max_connections'; +``` + +### 4.3 Redis 诊断 + +```bash +# 连接测试 +redis-cli -h ping + +# 查看内存使用 +redis-cli info memory + +# 查看慢查询 +redis-cli slowlog get 10 + +# 查看 Key 数量 +redis-cli dbsize + +# 查看特定 Key +redis-cli get "aiot:ops:ticket:1001" +``` + +### 4.4 网络诊断 + +```bash +# 测试端口连通性 +telnet +nc -zv + +# DNS 解析 +nslookup +dig + +# 路由追踪 +traceroute +mtr +``` + +--- + +## 五、相关文档 + +- [00-支撑平台总览.md](../00-支撑平台总览.md) +- [01-统一网关入口规范.md](../01-统一网关入口规范.md) +- [02-中间件使用规约.md](../02-中间件使用规约.md) +- [02-环境部署指南.md](./02-环境部署指南.md) diff --git a/开发者文档/06-平台支撑/09-DevOps 运维/02-环境部署指南.md b/开发者文档/06-平台支撑/09-DevOps 运维/02-环境部署指南.md new file mode 100644 index 0000000..76e078d --- /dev/null +++ b/开发者文档/06-平台支撑/09-DevOps 运维/02-环境部署指南.md @@ -0,0 +1,548 @@ +# 02-环境部署指南 + +本文档描述 AIOT 项目从开发环境到生产环境的完整部署流程、配置规范和运维操作。 + +--- + +## 一、环境规划 + +### 1.1 环境清单 + +| 环境 | 用途 | 域名 | 数据库 | 更新频率 | 负责人 | +|------|------|------|--------|---------|--------| +| **开发 (Dev)** | 本地开发联调 | `dev-api.example.com` | 共享 Dev DB | 随时 | 开发者 | +| **测试 (Test)** | QA 功能测试 | `test-api.example.com` | 独立 Test DB | 每日 | QA | +| **预生产 (Staging)** | 上线前验证 | `staging-api.example.com` | 生产库只读副本 | 每周 | DevOps | +| **生产 (Prod)** | 正式用户 | `api.example.com` | 生产主库 | 计划性 | DevOps+PM | + +### 1.2 环境隔离原则 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 开发环境 (Dev) │ +│ - 开发者本地 Docker Compose 或 WSL2 运行 │ +│ - 连接共享 Dev 数据库(多人共用) │ +│ - Nacos 配置:dev Profile │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ 测试环境 (Test) │ +│ - 独立服务器/容器集群 │ +│ - 独立 Test 数据库(每日从生产脱敏同步) │ +│ - Nacos 配置:test Profile │ +│ - Jenkins 自动部署(test 分支触发) │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ 预生产环境 (Staging) │ +│ - 独立服务器/容器集群(配置同生产) │ +│ - 生产库只读副本(用于验证 SQL) │ +│ - Nacos 配置:prod Profile(但指向测试 MQ/Redis) │ +│ - 手动触发部署(Release 分支) │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ 生产环境 (Prod) │ +│ - 高可用集群(多副本 + 负载均衡) │ +│ - 生产主数据库(主从复制) │ +│ - Nacos 配置:prod Profile │ +│ - 严格审批流程(PM+Tech Lead 签字) │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 二、开发环境部署(本地) + +### 2.1 前置条件 + +```bash +# 必需软件 +- Docker 20.10+ +- Docker Compose 2.0+ +- JDK 11/17 +- Maven 3.8+ +- Node.js 18+ +- pnpm 8+ + +# 可选工具 +- MySQL Workbench / DBeaver +- Redis Desktop Manager +- Postman / Apifox +``` + +### 2.2 启动基础设施 + +```bash +# 进入项目根目录 +cd /path/to/aiot-platform-cloud + +# 启动核心中间件(Nacos + MySQL + Redis + EMQX) +docker-compose -f docker-compose.core.yml up -d + +# 检查容器状态 +docker-compose -f docker-compose.core.yml ps + +# 查看 Nacos 日志(确认启动成功) +docker logs aiot-nacos --tail 50 +``` + +**核心服务端口**: +| 服务 | 端口 | 访问地址 | +|------|------|---------| +| Nacos | 8848 | `http://localhost:8848/nacos` | +| MySQL | 3306 | `localhost:3306` | +| Redis | 6379 | `localhost:6379` | +| EMQX | 1883/18083 | `localhost:18083` | +| RabbitMQ | 5672/15672 | `localhost:15672` | + +**默认账号**: +- Nacos: `nacos / nacos` +- MySQL: `root / aiot123456` +- Redis: 无密码 +- RabbitMQ: `guest / guest` +- EMQX: `admin / public` + +### 2.3 初始化数据库 + +```bash +# 1. 创建数据库 +mysql -h localhost -u root -p << EOF +CREATE DATABASE IF NOT EXISTS aiot_platform + DEFAULT CHARACTER SET utf8mb4 + DEFAULT COLLATE utf8mb4_general_ci; +USE aiot_platform; +EOF + +# 2. 导入表结构 +mysql -h localhost -u root -p aiot_platform < sql/aiot_schema.sql + +# 3. 导入初始数据(租户、管理员账号、菜单权限) +mysql -h localhost -u root -p aiot_platform < sql/aiot_data.sql +``` + +### 2.4 配置 Nacos + +1. 访问 `http://localhost:8848/nacos` +2. 登录(`nacos / nacos`) +3. 导入配置(`导入` → 选择 `config/` 目录下的配置文件) + - `viewsh-gateway.yaml` + - `module-system.yaml` + - `module-ops.yaml` + - `module-iot.yaml` + - `application-common.yaml` + +**关键配置项检查**: +```yaml +# application-common.yaml +spring: + datasource: + url: jdbc:mysql://host.docker.internal:3306/aiot_platform?useSSL=false + username: root + password: aiot123456 + redis: + host: host.docker.internal + port: 6379 + +# 开发环境特殊配置 +aiot: + dev-mode: true + swagger-enable: true + mock-iot-device: true # 启用模拟设备上报 +``` + +> **注意**:Docker 容器内访问宿主机服务需使用 `host.docker.internal` 而非 `localhost`。 + +### 2.5 启动后端服务 + +```bash +# 1. 编译网关 +cd viewsh-gateway +mvn clean package -DskipTests -P dev +java -jar target/viewsh-gateway.jar --spring.profiles.active=dev + +# 2. 编译主服务(module-system / module-ops / module-iot) +cd module-system +mvn clean package -DskipTests -P dev +java -jar target/module-system.jar --spring.profiles.active=dev + +# 3. 检查服务注册 +# 访问 Nacos 控制台,确认服务状态为「健康」 +``` + +### 2.6 启动前端 + +```bash +# 1. 安装依赖 +cd yudao-ui-admin-vben +pnpm install + +# 2. 配置环境变量 +# 复制 .env.development 并修改 API 地址 +cp .env.development .env.development.local +echo "VITE_API_BASE_URL=http://localhost:18080" >> .env.development.local + +# 3. 启动开发服务器 +pnpm dev + +# 4. 访问管理后台 +# http://localhost:5173 +# 默认管理员账号:admin / admin123 +``` + +--- + +## 三、测试环境部署(Jenkins 自动) + +### 3.1 部署流程 + +``` +开发者推送代码到 test 分支 + ↓ +GitLab Webhook 触发 Jenkins Job + ↓ +Jenkins 拉取代码 → Maven 编译 → 运行单元测试 + ↓ +构建 Docker 镜像(Tag: test-{commit_hash}) + ↓ +推送到私有镜像仓库 + ↓ +SSH 登录测试服务器 → docker pull → docker stop → docker run + ↓ +健康检查(/actuator/health) + ↓ +发送通知到飞书/钉钉群 +``` + +### 3.2 Jenkinsfile 示例 + +```groovy +pipeline { + agent any + + environment { + DOCKER_IMAGE = "registry.example.com/aiot/module-ops" + DOCKER_TAG = "test-${env.GIT_COMMIT.take(7)}" + } + + stages { + stage('Checkout') { + steps { + git branch: 'test', + url: 'git@gitlab.example.com:aiot/aiot-platform-cloud.git' + } + } + + stage('Build') { + steps { + sh 'cd module-ops && mvn clean package -DskipTests -P test' + } + } + + stage('Unit Test') { + steps { + sh 'cd module-ops && mvn test' + } + } + + stage('Docker Build') { + steps { + sh ''' + cd module-ops + docker build -t ${DOCKER_IMAGE}:${DOCKER_TAG} . + docker push ${DOCKER_IMAGE}:${DOCKER_TAG} + ''' + } + } + + stage('Deploy') { + steps { + sh ''' + ssh deploy@test-server " + docker pull ${DOCKER_IMAGE}:${DOCKER_TAG} && + docker stop module-ops || true && + docker rm module-ops || true && + docker run -d --name module-ops \\ + -p 18081:18080 \\ + -e SPRING_PROFILES_ACTIVE=test \\ + ${DOCKER_IMAGE}:${DOCKER_TAG} + " + ''' + } + } + + stage('Health Check') { + steps { + sh ''' + for i in {1..30}; do + if curl -s http://test-server:18081/actuator/health | grep -q UP; then + echo "Deploy successful!" + exit 0 + fi + sleep 2 + done + echo "Health check failed!" + exit 1 + ''' + } + } + } + + post { + success { + // 发送飞书通知 + sh ''' + curl -X POST https://open.feishu.cn/open-apis/bot/v2/hook/xxx \\ + -H "Content-Type: application/json" \\ + -d '{"msg_type":"text","content":{"text":"✅ module-ops 测试环境部署成功\\n版本:${DOCKER_TAG}"}}' + ''' + } + failure { + // 发送失败通知 + sh ''' + curl -X POST https://open.feishu.cn/open-apis/bot/v2/hook/xxx \\ + -H "Content-Type: application/json" \\ + -d '{"msg_type":"text","content":{"text":"❌ module-ops 测试环境部署失败\\n请检查 Jenkins 日志"}}' + ''' + } + } +} +``` + +--- + +## 四、生产环境部署(严格审批) + +### 4.1 部署前检查清单 + +**必须全部勾选才能执行部署**: + +- [ ] **代码审查**:MR 已获 2 人以上 Approve +- [ ] **测试报告**:QA 已签署测试通过报告 +- [ ] **SQL 审查**:涉及数据库变更的 SQL 已获 DBA 审核 +- [ ] **回滚方案**:已制定明确回滚步骤并验证 +- [ ] **备份确认**:生产数据库已完成全量备份 +- [ ] **通知到位**:相关干系人(客服、运营)已收到部署通知 +- [ ] **时间窗口**:部署时间在低峰期(凌晨 2:00-4:00) +- [ ] **值班人员**:DevOps 和业务负责人在线待命 + +### 4.2 部署流程 + +```bash +# 1. 创建 Release 分支(从 master 最新提交) +git checkout master +git pull +git checkout -b release/v1.2.0 + +# 2. 更新版本号 +# 修改各模块 pom.xml 中的 1.2.0 +# 修改 CHANGELOG.md + +# 3. 提交并打 Tag +git add . +git commit -m "release: v1.2.0" +git tag -a v1.2.0 -m "Release version 1.2.0" + +# 4. 推送 Release 分支和 Tag +git push origin release/v1.2.0 +git push origin v1.2.0 + +# 5. 在 Jenkins 触发生产部署 Job +# 选择 Release 分支,确认版本号,点击「部署到生产」 + +# 6. 部署过程中实时监控 +# - Grafana 仪表盘(CPU、内存、QPS、错误率) +# - 日志平台(ELK) +# - 业务监控(工单处理量、设备在线数) + +# 7. 部署完成后验证 +# - 冒烟测试(核心功能快速验证) +# - 确认无异常日志 +# - 通知干系人部署完成 +``` + +### 4.3 回滚流程 + +**当生产部署后出现严重 Bug 时**: + +```bash +# 1. 立即通知 +# 飞书群通知:「生产环境出现异常,准备回滚到 v1.1.0」 + +# 2. 执行回滚 +# Jenkins 选择「回滚」Job,选择上一个稳定版本 v1.1.0 + +# 3. 验证回滚 +# - 确认服务恢复正常 +# - 检查数据一致性(是否有脏数据) + +# 4. 事后复盘 +# - 记录事故时间线 +# - 分析根本原因 +# - 制定改进措施 +``` + +--- + +## 五、Docker 部署详解 + +### 5.1 Dockerfile 示例(后端) + +```dockerfile +# 构建阶段 +FROM maven:3.8-openjdk-17 AS builder + +WORKDIR /build +COPY pom.xml . +COPY module-ops/pom.xml module-ops/ +RUN mvn -f module-ops/pom.xml dependency:go-offline -B + +COPY module-ops/src module-ops/src +RUN mvn -f module-ops/pom.xml clean package -DskipTests + +# 运行阶段 +FROM openjdk:17-slim + +WORKDIR /app + +# 创建非 root 用户 +RUN useradd -m -u 1000 appuser + +# 复制 JAR +COPY --from=builder /build/module-ops/target/*.jar app.jar + +# 设置时区 +ENV TZ=Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \\ + CMD curl -f http://localhost:18080/actuator/health || exit 1 + +# 切换用户 +USER appuser + +EXPOSE 18080 + +ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"] +``` + +### 5.2 Docker Compose 示例(生产) + +```yaml +version: '3.8' + +services: + gateway: + image: registry.example.com/aiot/viewsh-gateway:v1.2.0 + ports: + - "80:18080" + - "443:18443" + environment: + - SPRING_PROFILES_ACTIVE=prod + - NACOS_SERVER_ADDR=nacos:8848 + depends_on: + - nacos + restart: always + deploy: + replicas: 2 + resources: + limits: + cpus: '2' + memory: 2G + + module-ops: + image: registry.example.com/aiot/module-ops:v1.2.0 + environment: + - SPRING_PROFILES_ACTIVE=prod + - NACOS_SERVER_ADDR=nacos:8848 + depends_on: + - nacos + - mysql + - redis + restart: always + deploy: + replicas: 3 + resources: + limits: + cpus: '4' + memory: 4G + + nacos: + image: nacos/nacos-server:2.2.0 + environment: + - MODE=cluster + - NACOS_SERVERS=nacos1:8848 nacos2:8848 nacos3:8848 + volumes: + - ./nacos/cluster-logs:/home/nacos/logs + restart: always + + mysql: + image: mysql:8.0 + environment: + - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} + volumes: + - mysql-data:/var/lib/mysql + restart: always + + redis: + image: redis:7-alpine + command: redis-server --appendonly yes + volumes: + - redis-data:/data + restart: always + +volumes: + mysql-data: + redis-data: +``` + +--- + +## 六、监控与告警 + +### 6.1 关键监控指标 + +| 指标类别 | 指标名称 | 告警阈值 | 告警级别 | +|---------|---------|---------|---------| +| **应用层** | HTTP 错误率 | > 1% | P1 | +| | API 响应时间 P99 | > 2000ms | P2 | +| | JVM 堆内存使用率 | > 85% | P2 | +| **中间件** | MySQL 连接数 | > 80% | P2 | +| | Redis 内存使用率 | > 80% | P2 | +| | MQ 消息积压 | > 10000 | P2 | +| **业务层** | 工单派发失败率 | > 5% | P1 | +| | 设备离线率 | > 10% | P2 | +| | 信标感应成功率 | < 95% | P2 | + +### 6.2 告警通知渠道 + +```yaml +# Prometheus Alertmanager 配置 +receivers: + - name: 'feishu-p1' + webhook_configs: + - url: 'https://open.feishu.cn/open-apis/bot/v2/hook/xxx' + send_resolved: true + # P1 级别:电话 + 飞书 + + - name: 'feishu-p2' + webhook_configs: + - url: 'https://open.feishu.cn/open-apis/bot/v2/hook/yyy' + send_resolved: true + # P2 级别:飞书 + + - name: 'email' + email_configs: + - to: 'devops@example.com' + send_resolved: true + # P3 级别:邮件 +``` + +--- + +## 七、相关文档 + +- [03-CICD 与部署流水线规范.md](../03-CICD 与部署流水线规范.md) +- [01-部署运行与排障视角.md](./01-部署运行与排障视角.md) +- [07-协作规范/02-开发工作流与-Git-规约.md](../../07-协作规范/02-开发工作流与-Git-规约.md)