fastbee/docs/fastbee-device-report-engine.md
2026-09-10 11:47:39 +08:00

448 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# FastBee IoT 设备监测报告引擎开发 Skill
> **领域背景**:公司主营业务为桥梁、建筑、边坡、隧道等结构老化与安全监测,核心监测对象为裂缝、深部位移、轴力/应力、沉降、倾角、应变、振动等工程物理量。
>
> **本 Skill 目标**:基于已落库的 IoT 物模型监测数据,为单个监测设备按「设备 + 时间范围」生成专业分析报告(Word `.docx`),支持历史报表下载,**保留期 1 个月**。
>
> **前端入口(2 处)**:① 大屏分析模式;② 数据中心分析页。两处均通过「选设备 + 选时间」即可生成并导出报表;另有历史报表列表可下载。
>
> **存储决策**:**已确认未购买云 OSS 服务 → 统一使用自建 MinIO**(S3 协议兼容,复用现有 `fastbee-oss` 插件,不改上传链路)。
>
> **第一阶段范围**:单设备报告。不在范围:项目综合报告、多设备联合分析、AI 自动工程安全判定、在线 Word 模板编辑器。
---
# 1. 现状盘点(已代码确认,先读后写)
## 1.1 存储结论:OSS 未购买 → MinIO
| 检查项 | 结论 |
|---|---|
| `oss_config` 初始化数据(`oss.sql` / `sql/yack-init.sql`) | 仅有 aliyun/qiniu 两条记录,`access_key`/`secret_key` 均为占位符 `XXXXXXXXXXXXXXX`,**无真实云 OSS 配置** |
| `fastbee-plugs/fastbee-oss` | 基于 `aws-java-sdk-s3`(S3 协议);`OssClient` 显式兼容 MinIO:endpoint 非云服务商关键字时自动启用 path-style,`getUrl()` 有 minio 单独分支 |
| 结论 | 未购买 OSS → 使用自建 **MinIO**。**复用 `OssFactory`/`OssClient`,禁止新写 MinIO/S3 Service**。只需:`PlatformType` 增加 `MINIO`、`oss_config` 插入 minio 默认记录、部署 MinIO 并配置 30 天生命周期规则 |
## 1.2 可复用能力
| 能力 | 位置 | 复用方式 |
|---|---|---|
| 设备/产品/物模型实体 | `Device.java` / `Product.java` / `ThingsModel.java` | 直接复用,禁止新建重复 DTO |
| 时序数据查询 | `DeviceLogMapper.listHistory`(MySQL)/ `TDDeviceLogMapper`(TDengine) | 报告模块通过 Provider 委托,禁止自写 SQL |
| 数据中心分析能力 | `DataCenterController`(`/data/center/deviceStats`、`deviceDataQuality`、`deviceHistogram`) | 报告统计/质量可复用其 Service 层逻辑 |
| 文件上传 | `OssFactory.instance(redis).upload(byte[], path, contentType)` → `UploadResult{url, filename}` | 上传到 MinIO |
| 文件读取/删除 | `OssClient.getObjectContent(path)` / `delete(path)` | 下载代理、过期清理兜底 |
| Word/POI | `poi-ooxml 4.1.2`(已有) | 新增 poi-tl 做模板渲染 |
| Excel | `easyexcel 3.3.1`(已有) | 如需导出原始数据复用 |
| MQ | `fastbee-mq`(RocketMQ) | 未来异步报告任务复用 |
## 1.3 关键缺失(本 Skill 补齐)
1. `fastbee-report` 模块:报告引擎、Analyzer、`report_record` 表(均不存在)。
2. 设备监测分类路由:`DeviceType` 是连接拓扑(直连/网关/摄像头),`iot_category` 是租户商务分类树,均不能作为分析路由键 → 需 `iot_product.monitor_category_code` + `MonitorCategory` 枚举。
3. 报告周期/时间窗口:`ReportPeriod` 枚举 + `ReportPeriodResolver`。
4. 结构化报告阈值:告警阈值藏在 LiteFlow `iot_scene` 脚本里,报告不可直接读 → 新增 `iot_report_threshold`。
5. Java 端图表(JFreeChart)与 Word 模板引擎(poi-tl)。
6. MinIO 默认存储配置。
7. 前端两处「生成并导出报表」按钮 + 历史报表页(列表/下载/过期)。
## 1.4 前端现状
| 入口 | 页面 | 现状 |
|---|---|---|
| 大屏分析模式 | `yack-web/src/views/bigScreen/home.vue`(`activeMode === 'analysis'`) | 已有「设备选择 + 时间范围(预设 5m~7d / 自定义)+ 查询数据和曲线」,**无报表按钮** |
| 数据中心 | `yack-web/src/views/dataCenter/analysis.vue` | 已有「设备 + 时间范围」查询(统计/数据质量/直方图/趋势),**无报表按钮** |
| 历史报表 | 无 | 需新增列表 + 下载 |
---
# 2. 整体逻辑链路
```text
前端(大屏分析模式 / 数据中心):选设备 + 时间范围
↓ POST /iot/report/generate {deviceId, startTime, endTime}
ReportRequest
↓
ReportPeriodResolver → TimeWindow(周期预设或自定义)
MonitorCategoryResolver → MonitorCategory(读 iot_product.monitor_category_code)
↓
ReportContextBuilder → ReportContext(设备/产品/物模型/时序数据/阈值一次性装配)
↓
ReportAnalyzerFactory.get(category) → 专项 Analyzer(V1:CrackMeterAnalyzer)
↓
AnalysisResult(统计/趋势/质量/预警/结论/图表数据)
↓
ReportModel → WordRenderer(poi-tl {{}} + JFreeChart PNG)→ byte[]
↓
OssFactory → MinIO 上传 → fileUrl
↓
report_record 落库(status=SUCCESS,expire_time = create_time + 30 天)
↓
返回 reportNo / fileUrl → 前端直接下载,或进历史报表
```
历史报表:
```text
GET /iot/report/list → 分页返回未过期记录
GET /iot/report/download/{reportNo} → 校验 expire_time → OssClient 流式下载
过期双保险:MinIO 生命周期 30 天删除兜底 + 下载接口返回“报告已过期”
```
---
# 3. 后端实现方案
## 3.1 新增模块 fastbee-report
依赖方向(禁止反向依赖、禁止循环依赖):
```text
fastbee-admin / fastbee-open-api(REST 入口)
↓
fastbee-report(报告引擎核心)
↓
fastbee-iot-service + fastbee-iot-data + fastbee-oss + fastbee-common
↓
MySQL / TDengine / MinIO
```
核心目录:
```text
fastbee-report/
├── api/ ReportEngine, ReportRequest, ReportResult
├── core/ DefaultReportEngine, ReportContext, ReportContextBuilder, ReportAssembler
├── category/ MonitorCategoryResolver(+Default)
├── period/ ReportPeriodResolver(+Default), TimeWindow
├── analyzer/ ReportAnalyzer, ReportAnalyzerFactory(+Default), crack/CrackMeterAnalyzer
├── analysis/ StatisticsCalculator, TrendAnalyzer, RateCalculator, QualityAnalyzer, WarningAnalyzer
├── threshold/ ReportThresholdProvider(+Db 实现), ReportThreshold
├── data/ ReportDataProvider, MetricData
├── model/ ReportModel, AnalysisResult, Statistics, TrendAnalysis, QualityAnalysis, WarningAnalysis, ChartData
├── chart/ ChartGenerator(JFreeChart 生成 PNG)
├── renderer/ ReportRenderer, WordRenderer(poi-tl), WordTemplateResolver
├── record/ ReportRecord, ReportRecordMapper, ReportRecordService
└── exception/ ReportException
```
## 3.2 核心流程(DefaultReportEngine)
```text
1. 参数校验:deviceId 必填;startTime/endTime 与 period 二选一(都传时以自定义为准);endTime > startTime
2. 时间窗口:period != CUSTOM 时按自然日历计算
日报=当日 0-24;周报=周一~周日;半月报=1~15 / 16~月末;月报=1~月末(时区 GMT+8)
3. 分类路由:MonitorCategoryResolver.resolveByDeviceId → MonitorCategory
4. 构建上下文:设备/产品/物模型(isMonitor=1)/时序数据/阈值 一次性装入 ReportContext
5. analyzerFactory.get(category).analyze(context, request) → AnalysisResult
6. reportAssembler.assemble(...) → ReportModel
7. reportRenderer.render(...) → byte[] → MinIO 上传 → ReportResult{reportNo, fileUrl, expireTime}
8. reportRecordService 落库(SUCCESS / FAILED + expire_time)
```
禁止:`ReportEngine` 直接操作 Mapper;Analyzer 自己 `@Autowired` 业务 Service 查询;`WordRenderer` 查库。
## 3.3 设备监测分类(MonitorCategory)
- 现有 `DeviceType`(连接拓扑)与 `iot_category`(商务分类树)均不能作为分析路由键。
- `iot_product` 新增 `monitor_category_code VARCHAR(64)`(可空),值如 `CRACK_METER`、`DEEP_DISPLACEMENT`、`AXIAL_FORCE`、`STATIC_LEVEL`、`VIBRATING_WIRE`;存量产品按产品名 SQL 回填。
- `MonitorCategory` 枚举放 `fastbee-common`;解析不到返回 `UNKNOWN` 并报「未配置监测分类」,**不启发式猜测**。
- 新增设备类型 = 新增一个 Analyzer(实现 `supports(category)`),不改 ReportEngine、不写 `if(category==xxx)` 巨型判断。
## 3.4 时间范围(ReportPeriod)
- 枚举:`DAY / WEEK / HALF_MONTH / MONTH / CUSTOM`,放 `fastbee-common`。
- 前端两入口当前均传**自定义时间范围**(`startTime/endTime`);周期预设(日/周/半月/月)留给后台与后续使用。
- `ReportPeriodResolver` 计算自然日历窗口;必须单测边界:周一/月末/闰年二月/半月报 15 日分界。
## 3.5 阈值(WarningAnalyzer)
- 现状:告警阈值藏在 LiteFlow `iot_scene` 脚本里,报告引擎直接解析成本高、易错。
- V1:新增 `iot_report_threshold` 表(字段:product_id、device_id、monitor_category_code、metric_identifier、warning_value、alarm_value、control_value、unit)。
- 查询优先级:`device_id > product_id > 全局默认(product_id=0)`。Analyzer 只读结构化阈值,**禁止硬编码阈值**。
## 3.6 分析能力(通用 + 专项)
- 通用:统计(当前/初始/极值/均值/中位/变化量/累计变化)、趋势(线性回归 UP/DOWN/STABLE/FLUCTUATION + 斜率 + R²)、数据质量(缺失/重复/越界/完整率)、预警(NORMAL/WARNING/ALARM)。
- 专项:**V1 只实现裂缝计 `CrackMeterAnalyzer`**(当前/初始宽度、变化量、速率、趋势、预警、时程图),跑通后扩展深部位移/轴力/静力水准/振弦。
- 图表:后端 JFreeChart 生成 PNG 嵌入 Word;**禁止前端生成图回传后端**。
- 结论:基于结构化分析结果用规则+模板生成文字,**不用 AI 直接判断结构安全**。
## 3.7 Word 渲染
- 新增依赖:`poi-tl 1.10.4`(兼容 POI 4.1.2)、`jfreechart 1.5.4`;禁止再引入 docx4j。
- 模板:`DEVICE_CRACK_METER_V1.docx` 等,按 `template_code + category` 定位(`report_template` 表)。
- 变量:`{{report.no}}`、`{{report.periodDesc}}`、`{{device.name}}`、`{{device.serialNumber}}`、`{{analysis.statistics.max}}`、`{{analysis.statistics.delta}}`、`{{analysis.trend.description}}`、`{{analysis.warning.status}}`、循环表格 `{{conclusions}}`、图片 `{{charts.crackTrend}}` 等;模板不直接绑定数据库字段。
---
# 4. 存储方案:MinIO(关键决策)
## 4.1 结论与理由
- 仓库内 `oss_config` 无真实云 OSS 密钥(占位符),确认**未购买 OSS 服务**。
- MinIO 自建、S3 协议,现有 `OssClient` 已兼容:endpoint 不含 `aliyun/qcloud/qiniu/obs` 关键字时自动启用 path-style;`getUrl()` 对 minio 单独处理。
- 因此**不改上传代码**,只做:部署 MinIO + `oss_config` 配置 + `PlatformType` 增加枚举 + 过期策略。
## 4.2 部署(Docker Compose 单节点示例)
```yaml
services:
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
ports:
- "9000:9000" # S3 API
- "9001:9001" # 控制台
environment:
MINIO_ROOT_USER: fastbee-minio
MINIO_ROOT_PASSWORD: <强密码>
volumes:
- minio-data:/data
```
启动后:创建 bucket `fastbee`,并在控制台生成 AccessKey/SecretKey。
## 4.3 系统配置(oss_config 插入默认记录)
```sql
-- 先停用云 OSS 配置(存量占位符)
UPDATE `oss_config` SET `status` = '1' WHERE `config_key` IN ('aliyun', 'qiniu');
-- 插入 MinIO 默认配置(endpoint 为云服务器地址,供后端 S3 SDK 连接)
INSERT IGNORE INTO `oss_config`
(tenant_id, tenant_name, config_key, access_key, secret_key, bucket_name, prefix,
endpoint, domain, is_https, region, access_policy, status, del_flag, create_by, create_time, update_by, update_time, remark)
VALUES
(1, 'admin', 'minio', 'supermeminio', 'minio20260909', 'fastbee', 'report',
'http://47.1.28.77:9000', '', 'N', '', '0', '0', '0', 'admin', NOW(), 'admin', NOW(), 'MinIO默认存储');
```
要点:
- `status='0'` 设为默认,启动时 `OssApplicationRunner` 自动装载进 Redis(`sys_oss:default_config`)。
- `endpoint` 为后端连接 MinIO 的 S3 API 地址,需确保端口 9000 在安全组/防火墙放行;若后端与 MinIO 同机部署,可改为 `http://127.0.0.1:9000`。
- `domain` 为空时,`getUrl()` 返回 `http://<endpoint>/<bucketName>`;若配置了 CDN 或公网域名,可填入以生成更友好的 URL。
- `PlatformType` 增加 `MINIO(5, "MinIO")`(仅用于管理页展示;实际路由走 `config_key`)。
- `access_policy=0`(私有),报告下载统一走后端代理接口(`GET /iot/report/download/{reportNo}`),可校验权限和过期状态。
- `bucket_name='fastbee'`:需在 MinIO 中提前创建此 bucket。
- `endpoint` 需根据实际 MinIO 部署地址修改(如 `http://192.168.1.100:9000` 或生产域名)。
- `bucket_name='fastbee'`:需在 MinIO 中提前创建此 bucket。
## 4.4 报告文件路径
```text
/report/yyyy/MM/dd/device/{deviceId}/{reportNo}.docx
例:/report/2026/08/16/device/10001/REPORT-202608160001.docx
```
禁止把 Word 永久存服务器本地。
## 4.5 历史报表过期 1 个月(双保险)
1. **业务层**:`report_record.expire_time = create_time + 30 天`;列表只返回未过期记录;下载接口校验过期并提示「报告已过期」。
2. **存储层兜底**:MinIO 生命周期规则删除 `report/` 前缀下 30 天前的对象:
```sh
mc ilm rule add local/fastbee --prefix report/ --expire-days 30
```
3. 下载实现(推荐后端代理流式下载,可校验权限 + 过期):
```java
// 上传(复用现有插件)
UploadResult r = OssFactory.instance(redis)
.upload(docBytes, path, "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
// 下载:按 reportNo 查 report_record → 校验 expire_time → getObjectContent(path) 流式输出
```
---
# 5. 前端实现方案
## 5.1 入口一:大屏分析模式(`bigScreen/home.vue`)
- 在「分析条件」卡片“查询数据和曲线”旁新增按钮 **「生成并导出报表」**。
- 参数直接取已有 `analysisQuery.deviceId` + `analysisQuery.timeRange`(`[startTime, endTime]`)。
- 交互:点击 → `POST /iot/report/generate`(loading 防重复)→ 成功拿 `reportNo/fileUrl` 直接触发下载,并提示可在历史报表查看。
- 未选设备或时间时按钮禁用。
```js
// 参考实现(新增到 home.vue methods)
async handleGenerateReport() {
const [startTime, endTime] = this.analysisQuery.timeRange || [];
if (!this.analysisQuery.deviceId || !startTime || !endTime) {
this.$message.warning('请先选择设备和时间范围');
return;
}
const res = await generateDeviceReport({
deviceId: this.analysisQuery.deviceId,
startTime,
endTime,
templateCode: 'DEVICE_CRACK_METER_V1',
format: 'WORD',
});
// res.data.fileUrl → window.open 或 a[download] 下载
}
```
## 5.2 入口二:数据中心(`dataCenter/analysis.vue`)
- 在筛选栏“查询/重置”旁新增 **「生成并导出报表」**。
- 参数取已有 `queryParams.deviceId` + `queryParams.dayDaterange`。
- 交互与 5.1 一致,复用同一 API 封装。
## 5.3 历史报表(新页面/面板)
- 入口建议:数据中心新增 tab「历史报表」(或大屏个人中心),接口权限 `iot:report:list`。
- 列表字段:报告编号、设备名称、监测分类、周期、时间范围、生成时间、**过期时间**、状态、操作(下载)。
- 只展示未过期记录;已过期记录不展示(或置灰提示「已过期」)。
- 下载:`GET /iot/report/download/{reportNo}`,走浏览器下载。
---
# 6. API 设计
```text
POST /iot/report/generate 生成报告(同步,V1)
GET /iot/report/list 历史报表(分页,仅未过期)
GET /iot/report/download/{reportNo} 下载(校验过期,代理下载)
```
生成请求(前端两入口的通用格式):
```json
POST /iot/report/generate
{
"deviceId": 10001,
"startTime": "2026-08-01 00:00:00",
"endTime": "2026-08-08 23:59:59",
"templateCode": "DEVICE_CRACK_METER_V1",
"format": "WORD"
}
```
可选周期预设(后台/后续使用):
```json
{
"deviceId": 10001,
"period": "WEEK",
"anchorDate": "2026-08-09",
"templateCode": "DEVICE_CRACK_METER_V1"
}
```
响应:
```json
{
"code": 200,
"data": {
"reportNo": "REPORT-202608160001",
"status": "SUCCESS",
"fileName": "304259363251-周报-监测分析报告.docx",
"fileUrl": "http://minio-host:9000/fastbee/report/2026/08/16/device/10001/REPORT-202608160001.docx",
"expireTime": "2026-09-15 23:59:59"
}
}
```
权限:
```java
@PreAuthorize("@ss.hasPermi('iot:report:generate')") // 生成(写)
@PreAuthorize("@ss.hasAnyPermi('iot:report:list,iot:device:list')") // 列表/下载(读,跨页面复用)
```
---
# 7. 数据库改动
| 对象 | 说明 |
|---|---|
| `iot_product` 加列 | `monitor_category_code VARCHAR(64)` 可空;存量按产品名回填(如 `UPDATE iot_product SET monitor_category_code='CRACK_METER' WHERE product_name LIKE '%裂缝%'`) |
| `iot_report_threshold` | 报告阈值表(product/device 级:预警/报警/控制值) |
| `report_template` | Word 模板表(template_code + version 唯一) |
| `report_record` | 报告记录表(含 `expire_time`) |
| `oss_config` 插入 | minio 默认配置记录(见 4.3) |
`report_record` 核心字段:
```sql
CREATE TABLE report_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
report_no VARCHAR(64) NOT NULL COMMENT 'REPORT-yyyyMMddHHmmss-xxxx',
device_id BIGINT NOT NULL,
monitor_category_code VARCHAR(64),
period VARCHAR(32) COMMENT 'DAY/WEEK/HALF_MONTH/MONTH/CUSTOM',
start_time DATETIME,
end_time DATETIME,
status VARCHAR(16) COMMENT 'CREATING/SUCCESS/FAILED',
file_url VARCHAR(512),
expire_time DATETIME COMMENT '过期时间 = create_time + 30 天',
error_message VARCHAR(1024),
tenant_id BIGINT,
create_by VARCHAR(64),
create_time DATETIME,
KEY idx_device (device_id, create_time),
KEY idx_expire (expire_time)
) COMMENT '报告生成记录';
```
---
# 8. 开发顺序
1. **基础设施**:根 pom 声明 poi-tl/jfreechart 版本;建 3 张新表 + `iot_product` 加列;`MonitorCategory`/`ReportPeriod` 枚举;MinIO 部署 + `oss_config` 默认记录 + `PlatformType.MINIO`。
2. **fastbee-report 骨架**:模块注册进根 pom;`ReportRequest`/`ReportEngine`/`ReportContext`。
3. **分类与周期**:`MonitorCategoryResolver`、`ReportPeriodResolver` + 边界单测。
4. **Analyzer 框架 + 裂缝计**:先只实现 `CrackMeterAnalyzer`。
5. **通用分析**:`StatisticsCalculator`/`TrendAnalyzer`/`RateCalculator`/`QualityAnalyzer`/`WarningAnalyzer`。
6. **Provider**:`ReportDataProvider`(委托 `DeviceLogMapper`)、`ReportThresholdProvider`。
7. **渲染**:`ChartGenerator`(JFreeChart)+ `WordRenderer`(poi-tl)+ 模板 `DEVICE_CRACK_METER_V1.docx`。
8. **落库与下载**:`report_record` + `expire_time`;MinIO 生命周期规则;下载接口(校验过期)。
9. **前端**:大屏分析模式 + 数据中心加「生成并导出报表」;历史报表页 + 下载。
10. **闭环验证**:真实设备 + 时间范围 → 生成 → 下载 → 列表可见 → 过期处理验证。
---
# 9. Definition of Done
```text
[ ] MinIO 部署完成,oss_config 默认 minio 记录生效(OssFactory 可上传/下载)
[ ] fastbee-report 模块创建完成,Maven 无循环依赖
[ ] iot_product.monitor_category_code + 3 张新表建好
[ ] ReportPeriodResolver 边界单测通过
[ ] CrackMeterAnalyzer 跑通(设备 + 时间 → AnalysisResult)
[ ] WordRenderer(poi-tl) + ChartGenerator(JFreeChart) 生成 DOCX
[ ] 生成接口返回 reportNo / fileUrl / expireTime
[ ] report_record 落库,expire_time = +30 天
[ ] 列表接口只返回未过期记录
[ ] 下载接口可下载;过期返回明确提示
[ ] 大屏分析模式与数据中心均可「生成并导出报表」
[ ] 历史报表页面可查看/下载
[ ] 设备 304259363251 完整闭环验证通过
```
---
# 10. 不允许的设计
```text
❌ 新写 MinIO/S3 Service(必须复用 OssFactory/OssClient)
❌ 报告文件永久存服务器本地
❌ ReportEngine / WordRenderer 直接查库
❌ Analyzer 硬编码阈值 / 所有设备写一个 Analyzer / if(category==xxx)
❌ 复用 DeviceType 或 iot_category 作为监测分类路由键
❌ 前端生成图表回传后端
❌ 报告模块反向依赖 fastbee-iot-service 之外、造成循环依赖
❌ 引入 docx4j、新 OSS SDK 等重复依赖
```
---
# 11. 第一阶段范围外(预留)
项目综合报告、多设备联合分析、AI 自动工程安全判定、在线 Word 模板编辑器、异步生成(后续可走 `fastbee-mq` RocketMQ)。