docs: 完善前端开发指南与平台支撑文档
- 新增 04-前端开发/04-常见坑点与调试指南.md - 高频踩坑记录(路由、Pinia、表格分页、权限指令) - 调试技巧与性能优化建议 - 开发环境配置指南 - 扩展 06-平台支撑/07-API 文档/01-接口分域与维护原则.md - 接口分域架构(system/infra/ops/iot) - Swagger 注解规范与变更流程 - 接口版本管理与跨域处理 - 扩展 06-平台支撑/08-数据库/01-数据域划分与表关系思路.md - 三大核心数据域(SYSTEM/OPS/IoT) - 核心表结构 SQL(用户、工单、设备、物模型) - 跨域关联原则与索引设计规范 - 扩展 06-平台支撑/09-DevOps 运维/01-部署运行与排障视角.md - 系统分层架构图 - 排障决策树与各层检查清单 - 常用诊断命令速查 - 扩展 06-平台支撑/09-DevOps 运维/02-环境部署指南.md - 四环境规划(Dev/Test/Staging/Prod) - 本地开发环境部署详解 - Jenkins 自动部署流程与 Docker 配置 - 生产部署检查清单与回滚流程
This commit is contained in:
334
开发者文档/04-前端开发/04-常见坑点与调试指南.md
Normal file
334
开发者文档/04-前端开发/04-常见坑点与调试指南.md
Normal file
@@ -0,0 +1,334 @@
|
||||
# 04-常见坑点与调试指南
|
||||
|
||||
本文档收集前端开发过程中高频出现的坑点、错误案例和调试技巧。所有前端开发人员在遇到类似问题时应先查阅本文档。
|
||||
|
||||
---
|
||||
|
||||
## 一、高频踩坑记录
|
||||
|
||||
### 1.1 路由跳转后页面不刷新
|
||||
|
||||
**现象**:从列表页点击进入详情页,URL 已变但页面内容未更新。
|
||||
|
||||
**原因**:Vue Router 复用了同一个组件实例,`onMounted` 不会再次触发。
|
||||
|
||||
**解决方案**:
|
||||
|
||||
```vue
|
||||
<script setup>
|
||||
import { watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// 方案 1:监听 route.params 变化
|
||||
watch(() => route.params.id, (newId) => {
|
||||
fetchDetail(newId)
|
||||
}, { immediate: true })
|
||||
|
||||
// 方案 2:使用 key 强制重新渲染组件
|
||||
// 在 router-view 上添加 :key="route.fullPath"
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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
|
||||
<template>
|
||||
<a-table
|
||||
:data-source="tableData"
|
||||
:pagination="{
|
||||
current: pagination.current,
|
||||
pageSize: pagination.pageSize,
|
||||
total: pagination.total,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true
|
||||
}"
|
||||
@change="handleTableChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const handleTableChange = (pag: any) => {
|
||||
pagination.current = pag.current
|
||||
pagination.pageSize = pag.pageSize
|
||||
pagination.total = pag.total
|
||||
fetchList()
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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
|
||||
<script setup>
|
||||
const loading = ref(false)
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
await submitForm()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-button :loading="loading" @click="handleSubmit">提交</a-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、调试技巧
|
||||
|
||||
### 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
|
||||
<template>
|
||||
<div v-memo="[item.id, item.status]" v-for="item in list" :key="item.id">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
|
||||
// ❌ 错误:每次渲染都重新计算
|
||||
const filteredList = list.value.filter(item => item.status === 'active')
|
||||
|
||||
// ✅ 正确:使用 computed 缓存结果
|
||||
const filteredList = computed(() =>
|
||||
list.value.filter(item => item.status === 'active')
|
||||
)
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、开发环境配置
|
||||
|
||||
### 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)
|
||||
226
开发者文档/06-平台支撑/07-API 文档/01-接口分域与维护原则.md
Normal file
226
开发者文档/06-平台支撑/07-API 文档/01-接口分域与维护原则.md
Normal file
@@ -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<Long> 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)
|
||||
@@ -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<Ticket> tickets = ticketMapper.selectList(query);
|
||||
|
||||
// 2. 批量查询保洁员信息
|
||||
List<Long> cleanerIds = tickets.stream()
|
||||
.map(Ticket::getAssignedCleanerId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Cleaner> cleaners = cleanerMapper.selectBatchIds(cleanerIds);
|
||||
Map<Long, Cleaner> cleanerMap = cleaners.stream()
|
||||
.collect(Collectors.toMap(Cleaner::getId, Function.identity()));
|
||||
|
||||
// 3. 批量查询用户信息(System 域)
|
||||
List<Long> userIds = cleaners.stream()
|
||||
.map(Cleaner::getUserId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<User> 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)
|
||||
|
||||
350
开发者文档/06-平台支撑/09-DevOps 运维/01-部署运行与排障视角.md
Normal file
350
开发者文档/06-平台支撑/09-DevOps 运维/01-部署运行与排障视角.md
Normal file
@@ -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 <pid> 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 <container> --tail 100 -f
|
||||
|
||||
# 进入容器调试
|
||||
docker exec -it <container> bash
|
||||
|
||||
# 重启容器
|
||||
docker restart <container>
|
||||
```
|
||||
|
||||
### 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 <host> 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 <host> <port>
|
||||
nc -zv <host> <port>
|
||||
|
||||
# DNS 解析
|
||||
nslookup <domain>
|
||||
dig <domain>
|
||||
|
||||
# 路由追踪
|
||||
traceroute <host>
|
||||
mtr <host>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、相关文档
|
||||
|
||||
- [00-支撑平台总览.md](../00-支撑平台总览.md)
|
||||
- [01-统一网关入口规范.md](../01-统一网关入口规范.md)
|
||||
- [02-中间件使用规约.md](../02-中间件使用规约.md)
|
||||
- [02-环境部署指南.md](./02-环境部署指南.md)
|
||||
548
开发者文档/06-平台支撑/09-DevOps 运维/02-环境部署指南.md
Normal file
548
开发者文档/06-平台支撑/09-DevOps 运维/02-环境部署指南.md
Normal file
@@ -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 中的 <version>1.2.0</version>
|
||||
# 修改 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)
|
||||
Reference in New Issue
Block a user