reportEngine:完善报告导出,设置模型预警值
This commit is contained in:
parent
a27e99b031
commit
86b85f2327
@ -18,8 +18,8 @@ services:
|
|||||||
- "9000:9000"
|
- "9000:9000"
|
||||||
- "9001:9001"
|
- "9001:9001"
|
||||||
environment:
|
environment:
|
||||||
MINIO_ROOT_USER: yack-superme
|
MINIO_ROOT_USER: supermeminio
|
||||||
MINIO_ROOT_PASSWORD: yack-minio20260816
|
MINIO_ROOT_PASSWORD: minio20260909
|
||||||
MINIO_SERVER_URL: "https://oss.ycdat.cn:9000" # ✅ 指定 API 外部访问地址
|
MINIO_SERVER_URL: "https://oss.ycdat.cn:9000" # ✅ 指定 API 外部访问地址
|
||||||
volumes:
|
volumes:
|
||||||
- ./minio/data:/data
|
- ./minio/data:/data
|
||||||
|
|||||||
27
docker/docker-compose-txy.yaml
Normal file
27
docker/docker-compose-txy.yaml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
networks:
|
||||||
|
network:
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: '176.6.0.0/16'
|
||||||
|
|
||||||
|
services:
|
||||||
|
minio:
|
||||||
|
image: minio/minio:latest
|
||||||
|
container_name: minio
|
||||||
|
restart: always
|
||||||
|
# privileged: true 它几乎拥有与宿主机 root 相同的所有权限;MinIO 是纯用户空间的对象存储服务;无需用到特权模式,故而安全性与最小权限原则
|
||||||
|
networks:
|
||||||
|
network:
|
||||||
|
ipv4_address: 177.7.0.6
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
- "9001:9001"
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: supermeminio
|
||||||
|
MINIO_ROOT_PASSWORD: minio20260909
|
||||||
|
MINIO_SERVER_URL: "https://oss.ycdat.cn:9000" # ✅ 指定 API 外部访问地址
|
||||||
|
volumes:
|
||||||
|
- ./minio/data:/data
|
||||||
|
- ./minio/certs:/root/.minio/certs # ✅:挂载证书目录
|
||||||
|
command: server /data --console-address ":9001"
|
||||||
@ -40,7 +40,7 @@
|
|||||||
1. `fastbee-report` 模块:报告引擎、Analyzer、`report_record` 表(均不存在)。
|
1. `fastbee-report` 模块:报告引擎、Analyzer、`report_record` 表(均不存在)。
|
||||||
2. 设备监测分类路由:`DeviceType` 是连接拓扑(直连/网关/摄像头),`iot_category` 是租户商务分类树,均不能作为分析路由键 → 需 `iot_product.monitor_category_code` + `MonitorCategory` 枚举。
|
2. 设备监测分类路由:`DeviceType` 是连接拓扑(直连/网关/摄像头),`iot_category` 是租户商务分类树,均不能作为分析路由键 → 需 `iot_product.monitor_category_code` + `MonitorCategory` 枚举。
|
||||||
3. 报告周期/时间窗口:`ReportPeriod` 枚举 + `ReportPeriodResolver`。
|
3. 报告周期/时间窗口:`ReportPeriod` 枚举 + `ReportPeriodResolver`。
|
||||||
4. 结构化报告阈值:告警阈值藏在 LiteFlow `iot_scene` 脚本里,报告不可直接读 → 新增 `iot_report_threshold`。
|
4. 结构化报告阈值:告警阈值藏在 LiteFlow `iot_scene` 脚本里,报告不可直接读 → 新增 `iot_things_threshold`。
|
||||||
5. Java 端图表(JFreeChart)与 Word 模板引擎(poi-tl)。
|
5. Java 端图表(JFreeChart)与 Word 模板引擎(poi-tl)。
|
||||||
6. MinIO 默认存储配置。
|
6. MinIO 默认存储配置。
|
||||||
7. 前端两处「生成并导出报表」按钮 + 历史报表页(列表/下载/过期)。
|
7. 前端两处「生成并导出报表」按钮 + 历史报表页(列表/下载/过期)。
|
||||||
@ -116,7 +116,7 @@ fastbee-report/
|
|||||||
├── period/ ReportPeriodResolver(+Default), TimeWindow
|
├── period/ ReportPeriodResolver(+Default), TimeWindow
|
||||||
├── analyzer/ ReportAnalyzer, ReportAnalyzerFactory(+Default), crack/CrackMeterAnalyzer
|
├── analyzer/ ReportAnalyzer, ReportAnalyzerFactory(+Default), crack/CrackMeterAnalyzer
|
||||||
├── analysis/ StatisticsCalculator, TrendAnalyzer, RateCalculator, QualityAnalyzer, WarningAnalyzer
|
├── analysis/ StatisticsCalculator, TrendAnalyzer, RateCalculator, QualityAnalyzer, WarningAnalyzer
|
||||||
├── threshold/ ReportThresholdProvider(+Db 实现), ReportThreshold
|
├── threshold/ ThingsThresholdProvider(+Db 实现), ThingsThreshold
|
||||||
├── data/ ReportDataProvider, MetricData
|
├── data/ ReportDataProvider, MetricData
|
||||||
├── model/ ReportModel, AnalysisResult, Statistics, TrendAnalysis, QualityAnalysis, WarningAnalysis, ChartData
|
├── model/ ReportModel, AnalysisResult, Statistics, TrendAnalysis, QualityAnalysis, WarningAnalysis, ChartData
|
||||||
├── chart/ ChartGenerator(JFreeChart 生成 PNG)
|
├── chart/ ChartGenerator(JFreeChart 生成 PNG)
|
||||||
@ -157,7 +157,7 @@ fastbee-report/
|
|||||||
## 3.5 阈值(WarningAnalyzer)
|
## 3.5 阈值(WarningAnalyzer)
|
||||||
|
|
||||||
- 现状:告警阈值藏在 LiteFlow `iot_scene` 脚本里,报告引擎直接解析成本高、易错。
|
- 现状:告警阈值藏在 LiteFlow `iot_scene` 脚本里,报告引擎直接解析成本高、易错。
|
||||||
- V1:新增 `iot_report_threshold` 表(字段:product_id、device_id、monitor_category_code、metric_identifier、warning_value、alarm_value、control_value、unit)。
|
- V1:新增 `iot_things_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 只读结构化阈值,**禁止硬编码阈值**。
|
- 查询优先级:`device_id > product_id > 全局默认(product_id=0)`。Analyzer 只读结构化阈值,**禁止硬编码阈值**。
|
||||||
|
|
||||||
## 3.6 分析能力(通用 + 专项)
|
## 3.6 分析能力(通用 + 专项)
|
||||||
@ -362,7 +362,7 @@ POST /iot/report/generate
|
|||||||
| 对象 | 说明 |
|
| 对象 | 说明 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `iot_product` 加列 | `monitor_category_code VARCHAR(64)` 可空;存量按产品名回填(如 `UPDATE iot_product SET monitor_category_code='CRACK_METER' WHERE product_name LIKE '%裂缝%'`) |
|
| `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 级:预警/报警/控制值) |
|
| `iot_things_threshold` | 物模型阈值表(product/device 级:预警/报警/控制值 + 速率双控) |
|
||||||
| `report_template` | Word 模板表(template_code + version 唯一) |
|
| `report_template` | Word 模板表(template_code + version 唯一) |
|
||||||
| `report_record` | 报告记录表(含 `expire_time`) |
|
| `report_record` | 报告记录表(含 `expire_time`) |
|
||||||
| `oss_config` 插入 | minio 默认配置记录(见 4.3) |
|
| `oss_config` 插入 | minio 默认配置记录(见 4.3) |
|
||||||
@ -399,7 +399,7 @@ CREATE TABLE report_record (
|
|||||||
3. **分类与周期**:`MonitorCategoryResolver`、`ReportPeriodResolver` + 边界单测。
|
3. **分类与周期**:`MonitorCategoryResolver`、`ReportPeriodResolver` + 边界单测。
|
||||||
4. **Analyzer 框架 + 裂缝计**:先只实现 `CrackMeterAnalyzer`。
|
4. **Analyzer 框架 + 裂缝计**:先只实现 `CrackMeterAnalyzer`。
|
||||||
5. **通用分析**:`StatisticsCalculator`/`TrendAnalyzer`/`RateCalculator`/`QualityAnalyzer`/`WarningAnalyzer`。
|
5. **通用分析**:`StatisticsCalculator`/`TrendAnalyzer`/`RateCalculator`/`QualityAnalyzer`/`WarningAnalyzer`。
|
||||||
6. **Provider**:`ReportDataProvider`(委托 `DeviceLogMapper`)、`ReportThresholdProvider`。
|
6. **Provider**:`ReportDataProvider`(委托 `DeviceLogMapper`)、`ThingsThresholdProvider`。
|
||||||
7. **渲染**:`ChartGenerator`(JFreeChart)+ `WordRenderer`(poi-tl)+ 模板 `DEVICE_CRACK_METER_V1.docx`。
|
7. **渲染**:`ChartGenerator`(JFreeChart)+ `WordRenderer`(poi-tl)+ 模板 `DEVICE_CRACK_METER_V1.docx`。
|
||||||
8. **落库与下载**:`report_record` + `expire_time`;MinIO 生命周期规则;下载接口(校验过期)。
|
8. **落库与下载**:`report_record` + `expire_time`;MinIO 生命周期规则;下载接口(校验过期)。
|
||||||
9. **前端**:大屏分析模式 + 数据中心加「生成并导出报表」;历史报表页 + 下载。
|
9. **前端**:大屏分析模式 + 数据中心加「生成并导出报表」;历史报表页 + 下载。
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import com.fastbee.iot.service.IAlertLogService;
|
|||||||
import com.fastbee.iot.service.IEventLogService;
|
import com.fastbee.iot.service.IEventLogService;
|
||||||
import com.fastbee.notify.service.INotifyLogService;
|
import com.fastbee.notify.service.INotifyLogService;
|
||||||
import com.fastbee.quartz.service.ISysJobLogService;
|
import com.fastbee.quartz.service.ISysJobLogService;
|
||||||
|
import com.fastbee.report.record.ReportRecordService;
|
||||||
import com.fastbee.system.service.ISysLogininforService;
|
import com.fastbee.system.service.ISysLogininforService;
|
||||||
import com.fastbee.system.service.ISysOperLogService;
|
import com.fastbee.system.service.ISysOperLogService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -42,6 +43,9 @@ public class LogCleanTask {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IPlatformPushLogService platformPushLogService;
|
private IPlatformPushLogService platformPushLogService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ReportRecordService reportRecordService;
|
||||||
|
|
||||||
// 之后还有清理的表往下写
|
// 之后还有清理的表往下写
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,12 +87,17 @@ public class LogCleanTask {
|
|||||||
notifyLogService.cleanNotifyLog(days);
|
notifyLogService.cleanNotifyLog(days);
|
||||||
//log.info("已清理 notify_log 表 {} 天前的数据", days);
|
//log.info("已清理 notify_log 表 {} 天前的数据", days);
|
||||||
|
|
||||||
|
// 平台对接推送日志
|
||||||
platformPushLogService.cleanPushLog(days);
|
platformPushLogService.cleanPushLog(days);
|
||||||
|
|
||||||
|
// 清理过期报告记录(report_record 不再保留 expire_time 字段,按 create_time 倒推清理)
|
||||||
|
int reportDeleted = reportRecordService.cleanExpired(days);
|
||||||
|
log.info("已清理 report_record 表 {} 天前的数据,删除 {} 条", days, reportDeleted);
|
||||||
|
|
||||||
log.info("========== 日志清理任务执行完成 ==========");
|
log.info("========== 日志清理任务执行完成 ==========");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("日志清理任务执行失败", e);
|
log.error("日志清理任务执行失败", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,6 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -88,7 +87,6 @@ public class ReportController extends BaseController {
|
|||||||
.status(record.getStatus())
|
.status(record.getStatus())
|
||||||
.fileName(record.getFileName())
|
.fileName(record.getFileName())
|
||||||
.fileUrl(record.getFileUrl())
|
.fileUrl(record.getFileUrl())
|
||||||
.expireTime(record.getExpireTime())
|
|
||||||
.build();
|
.build();
|
||||||
return AjaxResult.success(result);
|
return AjaxResult.success(result);
|
||||||
}
|
}
|
||||||
@ -115,9 +113,6 @@ public class ReportController extends BaseController {
|
|||||||
if (record == null) {
|
if (record == null) {
|
||||||
throw new ServiceException("报告不存在");
|
throw new ServiceException("报告不存在");
|
||||||
}
|
}
|
||||||
if (record.getExpireTime() != null && record.getExpireTime().before(new Date())) {
|
|
||||||
throw new ServiceException("报告已过期");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(record.getFileUrl())) {
|
if (StringUtils.isEmpty(record.getFileUrl())) {
|
||||||
throw new ServiceException("报告文件不存在");
|
throw new ServiceException("报告文件不存在");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,9 +8,9 @@ import com.fastbee.common.enums.BusinessType;
|
|||||||
import com.fastbee.common.utils.poi.ExcelUtil;
|
import com.fastbee.common.utils.poi.ExcelUtil;
|
||||||
import com.fastbee.iot.service.IDeviceService;
|
import com.fastbee.iot.service.IDeviceService;
|
||||||
import com.fastbee.iot.service.IProductService;
|
import com.fastbee.iot.service.IProductService;
|
||||||
import com.fastbee.report.domain.vo.ReportThresholdQuery;
|
import com.fastbee.report.domain.vo.ThingsThresholdQuery;
|
||||||
import com.fastbee.report.service.IReportThresholdService;
|
import com.fastbee.report.service.IThingsThresholdService;
|
||||||
import com.fastbee.report.threshold.ReportThreshold;
|
import com.fastbee.report.threshold.ThingsThreshold;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -32,10 +32,10 @@ import java.util.Map;
|
|||||||
@Api(tags = "监测阈值配置")
|
@Api(tags = "监测阈值配置")
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/iot/threshold")
|
@RequestMapping("/iot/threshold")
|
||||||
public class ReportThresholdController extends BaseController {
|
public class ThingsThresholdController extends BaseController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IReportThresholdService reportThresholdService;
|
private IThingsThresholdService thingsThresholdService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IProductService productService;
|
private IProductService productService;
|
||||||
@ -46,25 +46,25 @@ public class ReportThresholdController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 查询阈值列表
|
* 查询阈值列表
|
||||||
*/
|
*/
|
||||||
@PreAuthorize("@ss.hasPermi('iot:threshold:list')")
|
@PreAuthorize("@ss.hasPermi('iot:device:list')")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
@ApiOperation("阈值分页列表")
|
@ApiOperation("阈值分页列表")
|
||||||
public TableDataInfo list(ReportThresholdQuery query) {
|
public TableDataInfo list(ThingsThresholdQuery query) {
|
||||||
startPage();
|
startPage();
|
||||||
List<ReportThreshold> list = reportThresholdService.selectThresholdList(query);
|
List<ThingsThreshold> list = thingsThresholdService.selectThresholdList(query);
|
||||||
return getDataTable(list);
|
return getDataTable(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出阈值列表
|
* 导出阈值列表
|
||||||
*/
|
*/
|
||||||
@PreAuthorize("@ss.hasPermi('iot:threshold:export')")
|
@PreAuthorize("@ss.hasPermi('iot:device:list')")
|
||||||
@Log(title = "监测阈值配置", businessType = BusinessType.EXPORT)
|
@Log(title = "监测阈值配置", businessType = BusinessType.EXPORT)
|
||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
@ApiOperation("导出阈值")
|
@ApiOperation("导出阈值")
|
||||||
public void export(HttpServletResponse response, ReportThresholdQuery query) {
|
public void export(HttpServletResponse response, ThingsThresholdQuery query) {
|
||||||
List<ReportThreshold> list = reportThresholdService.selectThresholdList(query);
|
List<ThingsThreshold> list = thingsThresholdService.selectThresholdList(query);
|
||||||
ExcelUtil<ReportThreshold> util = new ExcelUtil<>(ReportThreshold.class);
|
ExcelUtil<ThingsThreshold> util = new ExcelUtil<>(ThingsThreshold.class);
|
||||||
util.exportExcel(response, list, "监测阈值数据");
|
util.exportExcel(response, list, "监测阈值数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,43 +72,43 @@ public class ReportThresholdController extends BaseController {
|
|||||||
* 获取阈值详细信息
|
* 获取阈值详细信息
|
||||||
*/
|
*/
|
||||||
@ApiOperation("获取阈值详情")
|
@ApiOperation("获取阈值详情")
|
||||||
@PreAuthorize("@ss.hasPermi('iot:threshold:query')")
|
@PreAuthorize("@ss.hasPermi('iot:device:list')")
|
||||||
@GetMapping(value = "/{id}")
|
@GetMapping(value = "/{id}")
|
||||||
public AjaxResult getInfo(@PathVariable("id") Long id) {
|
public AjaxResult getInfo(@PathVariable("id") Long id) {
|
||||||
return AjaxResult.success(reportThresholdService.selectThresholdById(id));
|
return AjaxResult.success(thingsThresholdService.selectThresholdById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增阈值
|
* 新增阈值
|
||||||
*/
|
*/
|
||||||
@PreAuthorize("@ss.hasPermi('iot:threshold:add')")
|
@PreAuthorize("@ss.hasPermi('iot:device:list')")
|
||||||
@Log(title = "监测阈值配置", businessType = BusinessType.INSERT)
|
@Log(title = "监测阈值配置", businessType = BusinessType.INSERT)
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@ApiOperation("新增阈值")
|
@ApiOperation("新增阈值")
|
||||||
public AjaxResult add(@RequestBody ReportThreshold threshold) {
|
public AjaxResult add(@RequestBody ThingsThreshold threshold) {
|
||||||
return toAjax(reportThresholdService.insertThreshold(threshold));
|
return toAjax(thingsThresholdService.insertThreshold(threshold));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改阈值
|
* 修改阈值
|
||||||
*/
|
*/
|
||||||
@PreAuthorize("@ss.hasPermi('iot:threshold:edit')")
|
@PreAuthorize("@ss.hasPermi('iot:device:list')")
|
||||||
@Log(title = "监测阈值配置", businessType = BusinessType.UPDATE)
|
@Log(title = "监测阈值配置", businessType = BusinessType.UPDATE)
|
||||||
@PutMapping
|
@PutMapping
|
||||||
@ApiOperation("修改阈值")
|
@ApiOperation("修改阈值")
|
||||||
public AjaxResult edit(@RequestBody ReportThreshold threshold) {
|
public AjaxResult edit(@RequestBody ThingsThreshold threshold) {
|
||||||
return toAjax(reportThresholdService.updateThreshold(threshold));
|
return toAjax(thingsThresholdService.updateThreshold(threshold));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除阈值
|
* 删除阈值
|
||||||
*/
|
*/
|
||||||
@PreAuthorize("@ss.hasPermi('iot:threshold:remove')")
|
@PreAuthorize("@ss.hasPermi('iot:device:list')")
|
||||||
@Log(title = "监测阈值配置", businessType = BusinessType.DELETE)
|
@Log(title = "监测阈值配置", businessType = BusinessType.DELETE)
|
||||||
@DeleteMapping("/{ids}")
|
@DeleteMapping("/{ids}")
|
||||||
@ApiOperation("删除阈值")
|
@ApiOperation("删除阈值")
|
||||||
public AjaxResult remove(@PathVariable Long[] ids) {
|
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||||
return toAjax(reportThresholdService.deleteThresholdByIds(ids));
|
return toAjax(thingsThresholdService.deleteThresholdByIds(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,22 +64,25 @@ public class QualityAnalyzer {
|
|||||||
|
|
||||||
// 完整率估算:以相邻点中位间隔为期望上报间隔
|
// 完整率估算:以相邻点中位间隔为期望上报间隔
|
||||||
long expectedMinutes = expectedMinutes(sorted, window);
|
long expectedMinutes = expectedMinutes(sorted, window);
|
||||||
int missing;
|
long intervalMinutes = estimateIntervalMinutes(sorted);
|
||||||
BigDecimal completeness;
|
int missing = 0;
|
||||||
if (expectedMinutes <= 0) {
|
BigDecimal completeness = null;
|
||||||
missing = 0;
|
// 时间窗口无效或数据点不足(无法估算上报间隔)时不做缺失推断,
|
||||||
completeness = BigDecimal.ONE;
|
// 否则单点数据会按"间隔=1分钟"算出上万条缺失、完整率趋近 0
|
||||||
} else {
|
if (expectedMinutes > 0 && intervalMinutes > 0) {
|
||||||
int expected = (int) Math.max(expectedMinutes / Math.max(1, estimateIntervalMinutes(sorted)), 1);
|
int expected = (int) Math.max(expectedMinutes / intervalMinutes, 1);
|
||||||
missing = Math.max(0, expected - sorted.size());
|
missing = Math.max(0, expected - sorted.size());
|
||||||
completeness = BigDecimal.valueOf(Math.min(1.0, sorted.size() / (double) expected))
|
completeness = BigDecimal.valueOf(Math.min(1.0, sorted.size() / (double) expected))
|
||||||
.setScale(4, RoundingMode.HALF_UP);
|
.setScale(4, RoundingMode.HALF_UP);
|
||||||
}
|
}
|
||||||
result.setMissing(missing);
|
result.setMissing(missing);
|
||||||
result.setCompletenessRate(completeness);
|
result.setCompletenessRate(completeness);
|
||||||
|
String rateText = completeness == null
|
||||||
|
? "无法估算(时间窗口无效或数据点不足)"
|
||||||
|
: String.format("%.2f%%", completeness.multiply(BigDecimal.valueOf(100)).doubleValue());
|
||||||
result.setDescription(String.format(
|
result.setDescription(String.format(
|
||||||
"统计期内共 %d 条数据,预估缺数 %d 条,完整率 %.2f%%,重复值 %d 条%s",
|
"统计期内共 %d 条数据,预估缺数 %d 条,完整率 %s,重复值 %d 条%s",
|
||||||
sorted.size(), missing, completeness.multiply(BigDecimal.valueOf(100)).doubleValue(),
|
sorted.size(), missing, rateText,
|
||||||
duplicate, outOfRange > 0 ? ",越界 " + outOfRange + " 条" : ""));
|
duplicate, outOfRange > 0 ? ",越界 " + outOfRange + " 条" : ""));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,7 +44,8 @@ public class TrendAnalyzer {
|
|||||||
if (points.size() < 3) {
|
if (points.size() < 3) {
|
||||||
result.setDirection(TrendAnalysis.DIRECTION_STABLE);
|
result.setDirection(TrendAnalysis.DIRECTION_STABLE);
|
||||||
result.setSlope(BigDecimal.ZERO);
|
result.setSlope(BigDecimal.ZERO);
|
||||||
result.setRSquared(BigDecimal.ONE);
|
// 数据不足时 R² 无统计意义,置空而非 1.0,避免报告出现虚假的"完美拟合"
|
||||||
|
result.setRSquared(null);
|
||||||
result.setDescription("数据点不足(" + points.size() + " 个),暂不判定趋势");
|
result.setDescription("数据点不足(" + points.size() + " 个),暂不判定趋势");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package com.fastbee.report.analysis;
|
package com.fastbee.report.analysis;
|
||||||
|
|
||||||
import com.fastbee.report.model.WarningAnalysis;
|
import com.fastbee.report.model.WarningAnalysis;
|
||||||
import com.fastbee.report.threshold.ReportThreshold;
|
import com.fastbee.report.threshold.ThingsThreshold;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@ -9,13 +9,18 @@ import java.math.BigDecimal;
|
|||||||
/**
|
/**
|
||||||
* 预警分析器:只读结构化阈值(禁止硬编码阈值)
|
* 预警分析器:只读结构化阈值(禁止硬编码阈值)
|
||||||
*
|
*
|
||||||
|
* <p>按 GB 50497-2019 第 8.0.4 条「双控」口径:累计变化量与变化速率分别判定,
|
||||||
|
* 取较严重等级。某一维度未配置阈值时该维度不参与判定;两个维度都不可判定时结果为
|
||||||
|
* {@link WarningAnalysis#STATUS_UNKNOWN},不得回退为「正常」。</p>
|
||||||
|
*
|
||||||
* @author FastBee
|
* @author FastBee
|
||||||
* @date 2026-08-16
|
* @date 2026-08-16
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class WarningAnalyzer {
|
public class WarningAnalyzer {
|
||||||
|
|
||||||
public WarningAnalysis analyze(String identifier, String modelName, BigDecimal currentValue, ReportThreshold threshold) {
|
public WarningAnalysis analyze(String identifier, String modelName, BigDecimal currentValue,
|
||||||
|
BigDecimal changeRate, ThingsThreshold threshold) {
|
||||||
WarningAnalysis result = new WarningAnalysis();
|
WarningAnalysis result = new WarningAnalysis();
|
||||||
result.setIdentifier(identifier);
|
result.setIdentifier(identifier);
|
||||||
result.setModelName(modelName);
|
result.setModelName(modelName);
|
||||||
@ -25,34 +30,117 @@ public class WarningAnalyzer {
|
|||||||
result.setAlarmValue(threshold.getAlarmValue());
|
result.setAlarmValue(threshold.getAlarmValue());
|
||||||
result.setControlValue(threshold.getControlValue());
|
result.setControlValue(threshold.getControlValue());
|
||||||
}
|
}
|
||||||
if (currentValue == null) {
|
result.setStatus(WarningAnalysis.STATUS_UNKNOWN);
|
||||||
result.setStatus(WarningAnalysis.STATUS_NORMAL);
|
|
||||||
result.setDescription("统计期内无有效当前值");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (threshold == null) {
|
if (threshold == null) {
|
||||||
result.setStatus(WarningAnalysis.STATUS_UNKNOWN);
|
|
||||||
result.setDescription("未配置预警阈值,无法评估安全等级");
|
result.setDescription("未配置预警阈值,无法评估安全等级");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
BigDecimal abs = currentValue.abs();
|
boolean noCumulative = isAllNull(threshold.getControlValue(), threshold.getAlarmValue(), threshold.getWarningValue());
|
||||||
if (threshold.getControlValue() != null && abs.compareTo(threshold.getControlValue()) >= 0) {
|
boolean noRate = isAllNull(threshold.getControlRate(), threshold.getAlarmRate(), threshold.getWarningRate());
|
||||||
result.setStatus(WarningAnalysis.STATUS_ALARM);
|
if (noCumulative && noRate) {
|
||||||
result.setDescription(String.format("当前值 %s 达到/超过控制值 %s,处于报警状态",
|
result.setDescription("阈值记录未配置累计值与速率阈值,无法评估安全等级");
|
||||||
currentValue.toPlainString(), threshold.getControlValue().toPlainString()));
|
return result;
|
||||||
} else if (threshold.getAlarmValue() != null && abs.compareTo(threshold.getAlarmValue()) >= 0) {
|
|
||||||
result.setStatus(WarningAnalysis.STATUS_ALARM);
|
|
||||||
result.setDescription(String.format("当前值 %s 达到/超过报警值 %s,处于报警状态",
|
|
||||||
currentValue.toPlainString(), threshold.getAlarmValue().toPlainString()));
|
|
||||||
} else if (threshold.getWarningValue() != null && abs.compareTo(threshold.getWarningValue()) >= 0) {
|
|
||||||
result.setStatus(WarningAnalysis.STATUS_WARNING);
|
|
||||||
result.setDescription(String.format("当前值 %s 达到/超过预警值 %s,处于预警状态",
|
|
||||||
currentValue.toPlainString(), threshold.getWarningValue().toPlainString()));
|
|
||||||
} else {
|
|
||||||
result.setStatus(WarningAnalysis.STATUS_NORMAL);
|
|
||||||
result.setDescription("当前值处于正常范围");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 维度一:累计变化量(相对量设备传相对基准点的累计变化,绝对量设备传当前值)
|
||||||
|
String cumulativeStatus = null;
|
||||||
|
String cumulativeDesc = null;
|
||||||
|
if (!noCumulative && currentValue != null) {
|
||||||
|
BigDecimal abs = currentValue.abs();
|
||||||
|
if (threshold.getControlValue() != null && abs.compareTo(threshold.getControlValue()) >= 0) {
|
||||||
|
cumulativeStatus = WarningAnalysis.STATUS_ALARM;
|
||||||
|
cumulativeDesc = String.format("当前值 %s 达到/超过控制值 %s,处于报警状态",
|
||||||
|
currentValue.toPlainString(), threshold.getControlValue().toPlainString());
|
||||||
|
} else if (threshold.getAlarmValue() != null && abs.compareTo(threshold.getAlarmValue()) >= 0) {
|
||||||
|
cumulativeStatus = WarningAnalysis.STATUS_ALARM;
|
||||||
|
cumulativeDesc = String.format("当前值 %s 达到/超过报警值 %s,处于报警状态",
|
||||||
|
currentValue.toPlainString(), threshold.getAlarmValue().toPlainString());
|
||||||
|
} else if (threshold.getWarningValue() != null && abs.compareTo(threshold.getWarningValue()) >= 0) {
|
||||||
|
cumulativeStatus = WarningAnalysis.STATUS_WARNING;
|
||||||
|
cumulativeDesc = String.format("当前值 %s 达到/超过预警值 %s,处于预警状态",
|
||||||
|
currentValue.toPlainString(), threshold.getWarningValue().toPlainString());
|
||||||
|
} else {
|
||||||
|
cumulativeStatus = WarningAnalysis.STATUS_NORMAL;
|
||||||
|
cumulativeDesc = "累计值处于正常范围";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 维度二:变化速率(单位/天)
|
||||||
|
String rateStatus = null;
|
||||||
|
String rateDesc = null;
|
||||||
|
if (!noRate && changeRate != null) {
|
||||||
|
BigDecimal absRate = changeRate.abs();
|
||||||
|
if (threshold.getControlRate() != null && absRate.compareTo(threshold.getControlRate()) >= 0) {
|
||||||
|
rateStatus = WarningAnalysis.STATUS_ALARM;
|
||||||
|
rateDesc = String.format("变化速率 %s 达到/超过速率控制值 %s,处于报警状态",
|
||||||
|
absRate.toPlainString(), threshold.getControlRate().toPlainString());
|
||||||
|
} else if (threshold.getAlarmRate() != null && absRate.compareTo(threshold.getAlarmRate()) >= 0) {
|
||||||
|
rateStatus = WarningAnalysis.STATUS_ALARM;
|
||||||
|
rateDesc = String.format("变化速率 %s 达到/超过速率报警值 %s,处于报警状态",
|
||||||
|
absRate.toPlainString(), threshold.getAlarmRate().toPlainString());
|
||||||
|
} else if (threshold.getWarningRate() != null && absRate.compareTo(threshold.getWarningRate()) >= 0) {
|
||||||
|
rateStatus = WarningAnalysis.STATUS_WARNING;
|
||||||
|
rateDesc = String.format("变化速率 %s 达到/超过速率预警值 %s,处于预警状态",
|
||||||
|
absRate.toPlainString(), threshold.getWarningRate().toPlainString());
|
||||||
|
} else {
|
||||||
|
rateStatus = WarningAnalysis.STATUS_NORMAL;
|
||||||
|
rateDesc = "变化速率处于正常范围";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String finalStatus = worse(cumulativeStatus, rateStatus);
|
||||||
|
if (finalStatus == null) {
|
||||||
|
result.setDescription("缺少可判定的累计变化量或变化速率,无法评估安全等级");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.setStatus(finalStatus);
|
||||||
|
|
||||||
|
StringBuilder desc = new StringBuilder();
|
||||||
|
if (cumulativeDesc != null) {
|
||||||
|
desc.append(cumulativeDesc);
|
||||||
|
}
|
||||||
|
if (rateDesc != null) {
|
||||||
|
if (desc.length() > 0) {
|
||||||
|
desc.append(";");
|
||||||
|
}
|
||||||
|
desc.append(rateDesc);
|
||||||
|
}
|
||||||
|
// 维度缺数据时明确说明,避免"只判了一半"被当成已完整评估
|
||||||
|
if (!noCumulative && currentValue == null) {
|
||||||
|
desc.append(";累计值缺失,累计维度未参与判定");
|
||||||
|
}
|
||||||
|
if (!noRate && changeRate == null) {
|
||||||
|
desc.append(";变化速率缺失,速率维度未参与判定");
|
||||||
|
}
|
||||||
|
result.setDescription(desc.toString());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 取更严重等级:ALARM > WARNING > NORMAL;null 表示该维度未参与判定 */
|
||||||
|
private String worse(String a, String b) {
|
||||||
|
if (a == null) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
if (b == null) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
if (WarningAnalysis.STATUS_ALARM.equals(a) || WarningAnalysis.STATUS_ALARM.equals(b)) {
|
||||||
|
return WarningAnalysis.STATUS_ALARM;
|
||||||
|
}
|
||||||
|
if (WarningAnalysis.STATUS_WARNING.equals(a) || WarningAnalysis.STATUS_WARNING.equals(b)) {
|
||||||
|
return WarningAnalysis.STATUS_WARNING;
|
||||||
|
}
|
||||||
|
return WarningAnalysis.STATUS_NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAllNull(BigDecimal... values) {
|
||||||
|
for (BigDecimal v : values) {
|
||||||
|
if (v != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import com.fastbee.report.model.Statistics;
|
|||||||
import com.fastbee.report.model.TrendAnalysis;
|
import com.fastbee.report.model.TrendAnalysis;
|
||||||
import com.fastbee.report.model.WarningAnalysis;
|
import com.fastbee.report.model.WarningAnalysis;
|
||||||
import com.fastbee.report.period.TimeWindow;
|
import com.fastbee.report.period.TimeWindow;
|
||||||
import com.fastbee.report.threshold.ReportThreshold;
|
import com.fastbee.report.threshold.ThingsThreshold;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@ -30,6 +30,7 @@ import java.math.BigDecimal;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -54,6 +55,15 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
public abstract class AbstractDeviceAnalyzer implements ReportAnalyzer {
|
public abstract class AbstractDeviceAnalyzer implements ReportAnalyzer {
|
||||||
|
|
||||||
|
/** 安全等级:安全 */
|
||||||
|
public static final String SAFETY_SAFE = "SAFE";
|
||||||
|
/** 安全等级:预警 */
|
||||||
|
public static final String SAFETY_WARNING = "WARNING";
|
||||||
|
/** 安全等级:危险 */
|
||||||
|
public static final String SAFETY_DANGER = "DANGER";
|
||||||
|
/** 安全等级:未配置阈值,无法评估(区别于 SAFE,避免"未评估"被当成"安全") */
|
||||||
|
public static final String SAFETY_NOT_ASSESSED = "NOT_ASSESSED";
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
protected StatisticsCalculator statisticsCalculator;
|
protected StatisticsCalculator statisticsCalculator;
|
||||||
|
|
||||||
@ -210,6 +220,15 @@ public abstract class AbstractDeviceAnalyzer implements ReportAnalyzer {
|
|||||||
.filter(d -> d.getIdentity() != null)
|
.filter(d -> d.getIdentity() != null)
|
||||||
.collect(Collectors.groupingBy(MetricData::getIdentity, LinkedHashMap::new, Collectors.toList()));
|
.collect(Collectors.groupingBy(MetricData::getIdentity, LinkedHashMap::new, Collectors.toList()));
|
||||||
|
|
||||||
|
// 1.1 基准点数据分组(相对测量换算用)
|
||||||
|
Map<String, List<MetricData>> baselineByIdentity = Collections.emptyMap();
|
||||||
|
List<MetricData> baselineList = context.getBaselineMetricDataList();
|
||||||
|
if (baselineList != null && !baselineList.isEmpty()) {
|
||||||
|
baselineByIdentity = baselineList.stream()
|
||||||
|
.filter(d -> d.getIdentity() != null)
|
||||||
|
.collect(Collectors.groupingBy(MetricData::getIdentity, LinkedHashMap::new, Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
// 2. 获取所有监测物模型
|
// 2. 获取所有监测物模型
|
||||||
Map<String, ThingsModel> allModels = getAllModels(context);
|
Map<String, ThingsModel> allModels = getAllModels(context);
|
||||||
|
|
||||||
@ -223,8 +242,12 @@ public abstract class AbstractDeviceAnalyzer implements ReportAnalyzer {
|
|||||||
String identity = entry.getKey();
|
String identity = entry.getKey();
|
||||||
ThingsModel model = entry.getValue();
|
ThingsModel model = entry.getValue();
|
||||||
List<MetricData> data = byIdentity.getOrDefault(identity, Collections.emptyList());
|
List<MetricData> data = byIdentity.getOrDefault(identity, Collections.emptyList());
|
||||||
|
// 相对测量换算:监测点读数 − 基准点读数(按时间就近对齐)
|
||||||
|
if (!baselineByIdentity.isEmpty()) {
|
||||||
|
data = toRelativeSeries(data, baselineByIdentity.get(identity));
|
||||||
|
}
|
||||||
|
|
||||||
ReportThreshold threshold = context.getThresholds().get(identity);
|
ThingsThreshold threshold = context.getThresholds().get(identity);
|
||||||
BigDecimal controlValue = threshold == null ? null : threshold.getControlValue();
|
BigDecimal controlValue = threshold == null ? null : threshold.getControlValue();
|
||||||
|
|
||||||
Statistics statistics = statisticsCalculator.calculate(data);
|
Statistics statistics = statisticsCalculator.calculate(data);
|
||||||
@ -232,8 +255,15 @@ public abstract class AbstractDeviceAnalyzer implements ReportAnalyzer {
|
|||||||
|
|
||||||
TrendAnalysis trend = trendAnalyzer.analyze(data);
|
TrendAnalysis trend = trendAnalyzer.analyze(data);
|
||||||
QualityAnalysis quality = qualityAnalyzer.analyze(data, context.getTimeWindow(), controlValue);
|
QualityAnalysis quality = qualityAnalyzer.analyze(data, context.getTimeWindow(), controlValue);
|
||||||
|
|
||||||
|
// 双控:变化速率(单位/天)与累计量一并参与安全判定
|
||||||
|
TimeWindow window = context.getTimeWindow();
|
||||||
|
long durationSeconds = (window != null && window.getStartTime() != null && window.getEndTime() != null)
|
||||||
|
? Duration.between(window.getStartTime(), window.getEndTime()).getSeconds()
|
||||||
|
: 0L;
|
||||||
|
BigDecimal changeRate = rateCalculator.ratePerDay(statistics, durationSeconds);
|
||||||
WarningAnalysis warning = warningAnalyzer.analyze(
|
WarningAnalysis warning = warningAnalyzer.analyze(
|
||||||
identity, statistics.getModelName(), statistics.getCurrent(), threshold);
|
identity, statistics.getModelName(), statistics.getCurrent(), changeRate, threshold);
|
||||||
|
|
||||||
result.getStatistics().put(identity, statistics);
|
result.getStatistics().put(identity, statistics);
|
||||||
result.getTrends().put(identity, trend);
|
result.getTrends().put(identity, trend);
|
||||||
@ -261,6 +291,76 @@ public abstract class AbstractDeviceAnalyzer implements ReportAnalyzer {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 相对测量换算:监测点读数 − 基准点读数(按时间就近对齐)
|
||||||
|
*
|
||||||
|
* <p>静力水准等设备的原始读数是相对基准点的,累计变化量应按
|
||||||
|
* 「(当前读数 − 基准点当前读数) − (初始读数 − 基准点初始读数)」计算,
|
||||||
|
* 直接拿原始读数的首尾差会混入基准点自身的漂移。</p>
|
||||||
|
*
|
||||||
|
* <p>对齐不上的点直接剔除而非补零,避免制造虚假数据点。</p>
|
||||||
|
*/
|
||||||
|
protected List<MetricData> toRelativeSeries(List<MetricData> monitor, List<MetricData> baseline) {
|
||||||
|
if (monitor == null || monitor.isEmpty() || baseline == null || baseline.isEmpty()) {
|
||||||
|
return monitor;
|
||||||
|
}
|
||||||
|
List<MetricData> sortedBase = baseline.stream()
|
||||||
|
.filter(d -> d.getTime() != null && d.getNumericValue() != null)
|
||||||
|
.sorted(Comparator.comparing(MetricData::getTime))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (sortedBase.isEmpty()) {
|
||||||
|
return monitor;
|
||||||
|
}
|
||||||
|
long toleranceMs = alignmentToleranceMs(sortedBase);
|
||||||
|
List<MetricData> sortedMonitor = monitor.stream()
|
||||||
|
.filter(d -> d.getTime() != null && d.getNumericValue() != null)
|
||||||
|
.sorted(Comparator.comparing(MetricData::getTime))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<MetricData> result = new ArrayList<>();
|
||||||
|
int j = 0;
|
||||||
|
for (MetricData m : sortedMonitor) {
|
||||||
|
long target = m.getTime().getTime();
|
||||||
|
while (j + 1 < sortedBase.size()
|
||||||
|
&& Math.abs(sortedBase.get(j + 1).getTime().getTime() - target)
|
||||||
|
<= Math.abs(sortedBase.get(j).getTime().getTime() - target)) {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
MetricData base = sortedBase.get(j);
|
||||||
|
if (Math.abs(base.getTime().getTime() - target) > toleranceMs) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
MetricData rel = new MetricData();
|
||||||
|
rel.setIdentity(m.getIdentity());
|
||||||
|
rel.setModelName(m.getModelName());
|
||||||
|
rel.setUnit(m.getUnit());
|
||||||
|
rel.setTime(m.getTime());
|
||||||
|
rel.setNumericValue(m.getNumericValue().subtract(base.getNumericValue()));
|
||||||
|
rel.setValue(rel.getNumericValue().toPlainString());
|
||||||
|
result.add(rel);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 对齐容差:基准点上报间隔中位数的 2 倍,至少 1 分钟 */
|
||||||
|
private long alignmentToleranceMs(List<MetricData> sortedBase) {
|
||||||
|
List<Long> gaps = new ArrayList<>();
|
||||||
|
for (int i = 1; i < sortedBase.size(); i++) {
|
||||||
|
long gap = sortedBase.get(i).getTime().getTime() - sortedBase.get(i - 1).getTime().getTime();
|
||||||
|
if (gap > 0) {
|
||||||
|
gaps.add(gap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long median;
|
||||||
|
if (gaps.isEmpty()) {
|
||||||
|
median = 30 * 60 * 1000L;
|
||||||
|
} else {
|
||||||
|
Collections.sort(gaps);
|
||||||
|
median = gaps.get(gaps.size() / 2);
|
||||||
|
}
|
||||||
|
return Math.max(median * 2, 60 * 1000L);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有监测物模型
|
* 获取所有监测物模型
|
||||||
*/
|
*/
|
||||||
@ -397,7 +497,7 @@ public abstract class AbstractDeviceAnalyzer implements ReportAnalyzer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 设置控制值
|
// 设置控制值
|
||||||
ReportThreshold threshold = context.getThresholds().get(primaryIdentity);
|
ThingsThreshold threshold = context.getThresholds().get(primaryIdentity);
|
||||||
if (threshold != null && threshold.getControlValue() != null) {
|
if (threshold != null && threshold.getControlValue() != null) {
|
||||||
result.setControlValue(threshold.getControlValue());
|
result.setControlValue(threshold.getControlValue());
|
||||||
result.setControlValueUnit(threshold.getUnit());
|
result.setControlValueUnit(threshold.getUnit());
|
||||||
@ -491,11 +591,16 @@ public abstract class AbstractDeviceAnalyzer implements ReportAnalyzer {
|
|||||||
*/
|
*/
|
||||||
protected String convertWarningToSafetyLevel(String warningStatus) {
|
protected String convertWarningToSafetyLevel(String warningStatus) {
|
||||||
if (WarningAnalysis.STATUS_ALARM.equals(warningStatus)) {
|
if (WarningAnalysis.STATUS_ALARM.equals(warningStatus)) {
|
||||||
return "DANGER";
|
return SAFETY_DANGER;
|
||||||
} else if (WarningAnalysis.STATUS_WARNING.equals(warningStatus)) {
|
|
||||||
return "WARNING";
|
|
||||||
}
|
}
|
||||||
return "SAFE";
|
if (WarningAnalysis.STATUS_WARNING.equals(warningStatus)) {
|
||||||
|
return SAFETY_WARNING;
|
||||||
|
}
|
||||||
|
// 未配置阈值/阈值不可用:不可等同于安全,否则报告会给出虚假的安全结论
|
||||||
|
if (WarningAnalysis.STATUS_UNKNOWN.equals(warningStatus)) {
|
||||||
|
return SAFETY_NOT_ASSESSED;
|
||||||
|
}
|
||||||
|
return SAFETY_SAFE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
package com.fastbee.report.api;
|
package com.fastbee.report.api;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
|
||||||
import io.swagger.annotations.ApiModel;
|
import io.swagger.annotations.ApiModel;
|
||||||
import io.swagger.annotations.ApiModelProperty;
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 报告生成结果
|
* 报告生成结果
|
||||||
@ -33,8 +31,4 @@ public class ReportResult implements Serializable {
|
|||||||
|
|
||||||
@ApiModelProperty("文件地址(MinIO)")
|
@ApiModelProperty("文件地址(MinIO)")
|
||||||
private String fileUrl;
|
private String fileUrl;
|
||||||
|
|
||||||
@ApiModelProperty("过期时间")
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
||||||
private Date expireTime;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import com.fastbee.iot.domain.ThingsModel;
|
|||||||
import com.fastbee.report.api.ReportRequest;
|
import com.fastbee.report.api.ReportRequest;
|
||||||
import com.fastbee.report.data.MetricData;
|
import com.fastbee.report.data.MetricData;
|
||||||
import com.fastbee.report.period.TimeWindow;
|
import com.fastbee.report.period.TimeWindow;
|
||||||
import com.fastbee.report.threshold.ReportThreshold;
|
import com.fastbee.report.threshold.ThingsThreshold;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -38,8 +38,16 @@ public class ReportContext {
|
|||||||
/** 时序数据 */
|
/** 时序数据 */
|
||||||
private List<MetricData> metricDataList;
|
private List<MetricData> metricDataList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基准点设备的时序数据(device.baselineDeviceId 非空时加载)
|
||||||
|
*
|
||||||
|
* <p>相对测量类设备(静力水准等)的读数是相对基准点的,需先按时间对齐做差值
|
||||||
|
* 换算为相对序列,再统计累计变化量。为 null 表示该设备为绝对量测量。</p>
|
||||||
|
*/
|
||||||
|
private List<MetricData> baselineMetricDataList;
|
||||||
|
|
||||||
/** 阈值(key = 物模型标识符) */
|
/** 阈值(key = 物模型标识符) */
|
||||||
private Map<String, ReportThreshold> thresholds;
|
private Map<String, ThingsThreshold> thresholds;
|
||||||
|
|
||||||
private String reportNo;
|
private String reportNo;
|
||||||
|
|
||||||
|
|||||||
@ -15,8 +15,8 @@ import com.fastbee.report.data.MetricData;
|
|||||||
import com.fastbee.report.data.ReportDataProvider;
|
import com.fastbee.report.data.ReportDataProvider;
|
||||||
import com.fastbee.report.exception.ReportException;
|
import com.fastbee.report.exception.ReportException;
|
||||||
import com.fastbee.report.period.TimeWindow;
|
import com.fastbee.report.period.TimeWindow;
|
||||||
import com.fastbee.report.threshold.ReportThreshold;
|
import com.fastbee.report.threshold.ThingsThreshold;
|
||||||
import com.fastbee.report.threshold.ReportThresholdProvider;
|
import com.fastbee.report.threshold.ThingsThresholdProvider;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@ -53,7 +53,7 @@ public class ReportContextBuilder {
|
|||||||
private ReportDataProvider dataProvider;
|
private ReportDataProvider dataProvider;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ReportThresholdProvider thresholdProvider;
|
private ThingsThresholdProvider thingsThresholdProvider;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SubGatewayMapper subGatewayMapper;
|
private SubGatewayMapper subGatewayMapper;
|
||||||
@ -90,12 +90,23 @@ public class ReportContextBuilder {
|
|||||||
|
|
||||||
List<MetricData> metricDataList = dataProvider.loadHistory(device, models, window);
|
List<MetricData> metricDataList = dataProvider.loadHistory(device, models, window);
|
||||||
|
|
||||||
Map<String, ReportThreshold> thresholds = new LinkedHashMap<>();
|
// 基准点设备数据:相对测量类设备(静力水准)需按时间对齐做差值换算
|
||||||
|
// deviceId 全局唯一,因此基准点可以属于另一个网关(跨网关参考点)
|
||||||
|
List<MetricData> baselineMetricDataList = null;
|
||||||
|
Long baselineDeviceId = device.getBaselineDeviceId();
|
||||||
|
if (baselineDeviceId != null && baselineDeviceId > 0 && !baselineDeviceId.equals(device.getDeviceId())) {
|
||||||
|
Device baselineDevice = deviceService.selectDeviceByDeviceId(baselineDeviceId);
|
||||||
|
if (baselineDevice != null) {
|
||||||
|
baselineMetricDataList = dataProvider.loadHistory(baselineDevice, models, window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, ThingsThreshold> thresholds = new LinkedHashMap<>();
|
||||||
for (ThingsModel model : models) {
|
for (ThingsModel model : models) {
|
||||||
if (model.getIdentifier() == null) {
|
if (model.getIdentifier() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ReportThreshold threshold = thresholdProvider.find(
|
ThingsThreshold threshold = thingsThresholdProvider.find(
|
||||||
device.getDeviceId(), product.getProductId(), category.getCode(), model.getIdentifier());
|
device.getDeviceId(), product.getProductId(), category.getCode(), model.getIdentifier());
|
||||||
if (threshold != null) {
|
if (threshold != null) {
|
||||||
thresholds.put(model.getIdentifier(), threshold);
|
thresholds.put(model.getIdentifier(), threshold);
|
||||||
@ -110,6 +121,7 @@ public class ReportContextBuilder {
|
|||||||
context.setProduct(product);
|
context.setProduct(product);
|
||||||
context.setThingsModels(models);
|
context.setThingsModels(models);
|
||||||
context.setMetricDataList(metricDataList);
|
context.setMetricDataList(metricDataList);
|
||||||
|
context.setBaselineMetricDataList(baselineMetricDataList);
|
||||||
context.setThresholds(thresholds);
|
context.setThresholds(thresholds);
|
||||||
context.setReportNo(reportNo);
|
context.setReportNo(reportNo);
|
||||||
|
|
||||||
|
|||||||
@ -3,13 +3,13 @@ package com.fastbee.report.domain.vo;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 报告阈值查询 VO
|
* 物模型阈值查询 VO
|
||||||
*
|
*
|
||||||
* @author FastBee
|
* @author FastBee
|
||||||
* @date 2026-09-10
|
* @date 2026-09-10
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class ReportThresholdQuery {
|
public class ThingsThresholdQuery {
|
||||||
|
|
||||||
/** 产品ID */
|
/** 产品ID */
|
||||||
private Long productId;
|
private Long productId;
|
||||||
@ -29,7 +29,6 @@ import java.time.LocalDate;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,7 +43,6 @@ public class ReportAsyncExecutor {
|
|||||||
|
|
||||||
private static final String WORD_CONTENT_TYPE =
|
private static final String WORD_CONTENT_TYPE =
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
||||||
private static final int RETENTION_DAYS = 30;
|
|
||||||
private static final DateTimeFormatter REPORT_NO_TIME = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
private static final DateTimeFormatter REPORT_NO_TIME = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||||
private static final DateTimeFormatter PATH_DATE = DateTimeFormatter.ofPattern("yyyy/MM/dd");
|
private static final DateTimeFormatter PATH_DATE = DateTimeFormatter.ofPattern("yyyy/MM/dd");
|
||||||
|
|
||||||
@ -119,9 +117,8 @@ public class ReportAsyncExecutor {
|
|||||||
String path = buildObjectPath(request.getDeviceId(), reportNo);
|
String path = buildObjectPath(request.getDeviceId(), reportNo);
|
||||||
UploadResult upload = OssFactory.instance(redisCache)
|
UploadResult upload = OssFactory.instance(redisCache)
|
||||||
.upload(docBytes, path, WORD_CONTENT_TYPE);
|
.upload(docBytes, path, WORD_CONTENT_TYPE);
|
||||||
Date expireTime = new Date(System.currentTimeMillis() + RETENTION_DAYS * 24L * 3600 * 1000);
|
|
||||||
|
|
||||||
reportRecordService.markSuccess(record, model.getFileName(), upload.getUrl(), expireTime);
|
reportRecordService.markSuccess(record, model.getFileName(), upload.getUrl());
|
||||||
log.info("报告生成成功 reportNo={} fileUrl={}", reportNo, upload.getUrl());
|
log.info("报告生成成功 reportNo={} fileUrl={}", reportNo, upload.getUrl());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@ -15,7 +15,15 @@ import java.util.List;
|
|||||||
public interface ReportRecordMapper extends BaseMapper<ReportRecord> {
|
public interface ReportRecordMapper extends BaseMapper<ReportRecord> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询未过期报告记录(expire_time >= now)
|
* 查询报告记录(过期清理由 LogCleanTask#cleanLogs 统一负责)
|
||||||
*/
|
*/
|
||||||
List<ReportRecord> selectUnexpiredList(ReportRecordQuery query);
|
List<ReportRecord> selectUnexpiredList(ReportRecordQuery query);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理过期报告记录:按 create_time 倒推,保留最近 days 天
|
||||||
|
*
|
||||||
|
* @param days 保留天数
|
||||||
|
* @return 删除记录数
|
||||||
|
*/
|
||||||
|
int cleanExpired(int days);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
package com.fastbee.report.mapper;
|
package com.fastbee.report.mapper;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.fastbee.report.threshold.ReportThreshold;
|
import com.fastbee.report.threshold.ThingsThreshold;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 报告阈值 Mapper
|
* 物模型阈值 Mapper
|
||||||
*
|
*
|
||||||
* @author FastBee
|
* @author FastBee
|
||||||
* @date 2026-08-16
|
* @date 2026-08-16
|
||||||
*/
|
*/
|
||||||
public interface ReportThresholdMapper extends BaseMapper<ReportThreshold> {
|
public interface ThingsThresholdMapper extends BaseMapper<ThingsThreshold> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按优先级查询阈值:device_id > product_id > 全局默认(product_id=0)
|
* 按优先级查询阈值:device_id > product_id > 全局默认(product_id=0)
|
||||||
*/
|
*/
|
||||||
ReportThreshold selectByPriority(@Param("deviceId") Long deviceId,
|
ThingsThreshold selectByPriority(@Param("deviceId") Long deviceId,
|
||||||
@Param("productId") Long productId,
|
@Param("productId") Long productId,
|
||||||
@Param("categoryCode") String categoryCode,
|
@Param("categoryCode") String categoryCode,
|
||||||
@Param("metricIdentifier") String metricIdentifier);
|
@Param("metricIdentifier") String metricIdentifier);
|
||||||
@ -49,9 +49,6 @@ public class ReportRecord extends BaseEntity {
|
|||||||
|
|
||||||
private String fileUrl;
|
private String fileUrl;
|
||||||
|
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
|
||||||
private Date expireTime;
|
|
||||||
|
|
||||||
private String errorMessage;
|
private String errorMessage;
|
||||||
|
|
||||||
private Long tenantId;
|
private Long tenantId;
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package com.fastbee.report.record;
|
|||||||
|
|
||||||
import com.fastbee.report.core.ReportContext;
|
import com.fastbee.report.core.ReportContext;
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,14 +13,14 @@ import java.util.List;
|
|||||||
public interface ReportRecordService {
|
public interface ReportRecordService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成前创建 CREATING 记录(expire_time = create_time + 30 天)
|
* 生成前创建 CREATING 记录
|
||||||
*/
|
*/
|
||||||
ReportRecord createCreating(ReportContext context);
|
ReportRecord createCreating(ReportContext context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成成功:更新 SUCCESS + 文件信息
|
* 生成成功:更新 SUCCESS + 文件信息
|
||||||
*/
|
*/
|
||||||
void markSuccess(ReportRecord record, String fileName, String fileUrl, Date expireTime);
|
void markSuccess(ReportRecord record, String fileName, String fileUrl);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成失败:更新 FAILED + 失败原因
|
* 生成失败:更新 FAILED + 失败原因
|
||||||
@ -29,7 +28,7 @@ public interface ReportRecordService {
|
|||||||
void markFailed(ReportRecord record, String errorMessage);
|
void markFailed(ReportRecord record, String errorMessage);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询未过期记录
|
* 分页查询报告记录
|
||||||
*/
|
*/
|
||||||
List<ReportRecord> listUnexpired(ReportRecordQuery query);
|
List<ReportRecord> listUnexpired(ReportRecordQuery query);
|
||||||
|
|
||||||
@ -37,4 +36,12 @@ public interface ReportRecordService {
|
|||||||
* 按报告编号查询
|
* 按报告编号查询
|
||||||
*/
|
*/
|
||||||
ReportRecord findByReportNo(String reportNo);
|
ReportRecord findByReportNo(String reportNo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理过期报告记录:保留最近 days 天的记录
|
||||||
|
*
|
||||||
|
* @param days 保留天数
|
||||||
|
* @return 删除记录数
|
||||||
|
*/
|
||||||
|
int cleanExpired(int days);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,6 @@ import java.time.ZoneOffset;
|
|||||||
@Service
|
@Service
|
||||||
public class ReportRecordServiceImpl implements ReportRecordService {
|
public class ReportRecordServiceImpl implements ReportRecordService {
|
||||||
|
|
||||||
private static final int RETENTION_DAYS = 30;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ReportRecordMapper reportRecordMapper;
|
private ReportRecordMapper reportRecordMapper;
|
||||||
|
|
||||||
@ -39,7 +37,6 @@ public class ReportRecordServiceImpl implements ReportRecordService {
|
|||||||
record.setTenantId(context.getTenantId());
|
record.setTenantId(context.getTenantId());
|
||||||
record.setCreateBy(context.getCreateBy());
|
record.setCreateBy(context.getCreateBy());
|
||||||
record.setCreateTime(now);
|
record.setCreateTime(now);
|
||||||
record.setExpireTime(new Date(now.getTime() + RETENTION_DAYS * 24L * 3600 * 1000));
|
|
||||||
reportRecordMapper.insert(record);
|
reportRecordMapper.insert(record);
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
@ -49,13 +46,12 @@ public class ReportRecordServiceImpl implements ReportRecordService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void markSuccess(ReportRecord record, String fileName, String fileUrl, Date expireTime) {
|
public void markSuccess(ReportRecord record, String fileName, String fileUrl) {
|
||||||
ReportRecord update = new ReportRecord();
|
ReportRecord update = new ReportRecord();
|
||||||
update.setId(record.getId());
|
update.setId(record.getId());
|
||||||
update.setStatus(ReportRecord.STATUS_SUCCESS);
|
update.setStatus(ReportRecord.STATUS_SUCCESS);
|
||||||
update.setFileName(fileName);
|
update.setFileName(fileName);
|
||||||
update.setFileUrl(fileUrl);
|
update.setFileUrl(fileUrl);
|
||||||
update.setExpireTime(expireTime);
|
|
||||||
// 异步线程中无法访问 SecurityContext,使用创建时的用户名
|
// 异步线程中无法访问 SecurityContext,使用创建时的用户名
|
||||||
update.setUpdateBy(record.getCreateBy());
|
update.setUpdateBy(record.getCreateBy());
|
||||||
update.setUpdateTime(DateUtils.getNowDate());
|
update.setUpdateTime(DateUtils.getNowDate());
|
||||||
@ -86,4 +82,9 @@ public class ReportRecordServiceImpl implements ReportRecordService {
|
|||||||
.eq(ReportRecord::getReportNo, reportNo)
|
.eq(ReportRecord::getReportNo, reportNo)
|
||||||
.last("limit 1"));
|
.last("limit 1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int cleanExpired(int days) {
|
||||||
|
return reportRecordMapper.cleanExpired(days);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -178,9 +178,9 @@ public class WordRenderer implements ReportRenderer {
|
|||||||
data.put("professionalAdvice", StringUtils.isNotEmpty(analysis.getProfessionalAdvice())
|
data.put("professionalAdvice", StringUtils.isNotEmpty(analysis.getProfessionalAdvice())
|
||||||
? analysis.getProfessionalAdvice() : "暂无专业分析建议。");
|
? analysis.getProfessionalAdvice() : "暂无专业分析建议。");
|
||||||
|
|
||||||
// 添加安全评估(始终 put,空值兜底)
|
// 添加安全评估(始终 put,空值兜底直接给"待评估",不可再经 safetyLevelText 回退成"安全")
|
||||||
data.put("safetyLevel", safetyLevelText(StringUtils.isNotEmpty(analysis.getSafetyLevel())
|
data.put("safetyLevel", StringUtils.isNotEmpty(analysis.getSafetyLevel())
|
||||||
? analysis.getSafetyLevel() : "待评估"));
|
? safetyLevelText(analysis.getSafetyLevel()) : "待评估");
|
||||||
data.put("safetyDescription", StringUtils.isNotEmpty(analysis.getSafetyDescription())
|
data.put("safetyDescription", StringUtils.isNotEmpty(analysis.getSafetyDescription())
|
||||||
? analysis.getSafetyDescription() : "暂无评估说明。");
|
? analysis.getSafetyDescription() : "暂无评估说明。");
|
||||||
|
|
||||||
@ -310,6 +310,10 @@ public class WordRenderer implements ReportRenderer {
|
|||||||
if ("CRITICAL".equals(level)) {
|
if ("CRITICAL".equals(level)) {
|
||||||
return "临界";
|
return "临界";
|
||||||
}
|
}
|
||||||
|
// 未配置阈值,无法评估——不得回退为"安全"
|
||||||
|
if ("NOT_ASSESSED".equals(level) || "UNKNOWN".equals(level)) {
|
||||||
|
return "待评估";
|
||||||
|
}
|
||||||
return "安全";
|
return "安全";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
package com.fastbee.report.service;
|
package com.fastbee.report.service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.fastbee.report.domain.vo.ReportThresholdQuery;
|
import com.fastbee.report.domain.vo.ThingsThresholdQuery;
|
||||||
import com.fastbee.report.threshold.ReportThreshold;
|
import com.fastbee.report.threshold.ThingsThreshold;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 报告阈值 Service 接口
|
* 物模型阈值 Service 接口
|
||||||
*
|
*
|
||||||
* @author FastBee
|
* @author FastBee
|
||||||
* @date 2026-09-10
|
* @date 2026-09-10
|
||||||
*/
|
*/
|
||||||
public interface IReportThresholdService extends IService<ReportThreshold> {
|
public interface IThingsThresholdService extends IService<ThingsThreshold> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询阈值列表(分页)
|
* 查询阈值列表(分页)
|
||||||
@ -20,7 +20,7 @@ public interface IReportThresholdService extends IService<ReportThreshold> {
|
|||||||
* @param query 查询条件
|
* @param query 查询条件
|
||||||
* @return 阈值列表
|
* @return 阈值列表
|
||||||
*/
|
*/
|
||||||
List<ReportThreshold> selectThresholdList(ReportThresholdQuery query);
|
List<ThingsThreshold> selectThresholdList(ThingsThresholdQuery query);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据ID查询阈值
|
* 根据ID查询阈值
|
||||||
@ -28,7 +28,7 @@ public interface IReportThresholdService extends IService<ReportThreshold> {
|
|||||||
* @param id 阈值ID
|
* @param id 阈值ID
|
||||||
* @return 阈值对象
|
* @return 阈值对象
|
||||||
*/
|
*/
|
||||||
ReportThreshold selectThresholdById(Long id);
|
ThingsThreshold selectThresholdById(Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增阈值
|
* 新增阈值
|
||||||
@ -36,7 +36,7 @@ public interface IReportThresholdService extends IService<ReportThreshold> {
|
|||||||
* @param threshold 阈值对象
|
* @param threshold 阈值对象
|
||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int insertThreshold(ReportThreshold threshold);
|
int insertThreshold(ThingsThreshold threshold);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改阈值
|
* 修改阈值
|
||||||
@ -44,7 +44,7 @@ public interface IReportThresholdService extends IService<ReportThreshold> {
|
|||||||
* @param threshold 阈值对象
|
* @param threshold 阈值对象
|
||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int updateThreshold(ReportThreshold threshold);
|
int updateThreshold(ThingsThreshold threshold);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除阈值
|
* 批量删除阈值
|
||||||
@ -3,59 +3,59 @@ package com.fastbee.report.service.impl;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.fastbee.common.utils.StringUtils;
|
import com.fastbee.common.utils.StringUtils;
|
||||||
import com.fastbee.report.domain.vo.ReportThresholdQuery;
|
import com.fastbee.report.domain.vo.ThingsThresholdQuery;
|
||||||
import com.fastbee.report.mapper.ReportThresholdMapper;
|
import com.fastbee.report.mapper.ThingsThresholdMapper;
|
||||||
import com.fastbee.report.service.IReportThresholdService;
|
import com.fastbee.report.service.IThingsThresholdService;
|
||||||
import com.fastbee.report.threshold.ReportThreshold;
|
import com.fastbee.report.threshold.ThingsThreshold;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 报告阈值 Service 实现
|
* 物模型阈值 Service 实现
|
||||||
*
|
*
|
||||||
* @author FastBee
|
* @author FastBee
|
||||||
* @date 2026-09-10
|
* @date 2026-09-10
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class ReportThresholdServiceImpl extends ServiceImpl<ReportThresholdMapper, ReportThreshold>
|
public class ThingsThresholdServiceImpl extends ServiceImpl<ThingsThresholdMapper, ThingsThreshold>
|
||||||
implements IReportThresholdService {
|
implements IThingsThresholdService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ReportThreshold> selectThresholdList(ReportThresholdQuery query) {
|
public List<ThingsThreshold> selectThresholdList(ThingsThresholdQuery query) {
|
||||||
LambdaQueryWrapper<ReportThreshold> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<ThingsThreshold> wrapper = new LambdaQueryWrapper<>();
|
||||||
if (query.getProductId() != null) {
|
if (query.getProductId() != null) {
|
||||||
wrapper.eq(ReportThreshold::getProductId, query.getProductId());
|
wrapper.eq(ThingsThreshold::getProductId, query.getProductId());
|
||||||
}
|
}
|
||||||
if (query.getDeviceId() != null) {
|
if (query.getDeviceId() != null) {
|
||||||
wrapper.eq(ReportThreshold::getDeviceId, query.getDeviceId());
|
wrapper.eq(ThingsThreshold::getDeviceId, query.getDeviceId());
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(query.getMonitorCategoryCode())) {
|
if (StringUtils.isNotEmpty(query.getMonitorCategoryCode())) {
|
||||||
wrapper.eq(ReportThreshold::getMonitorCategoryCode, query.getMonitorCategoryCode());
|
wrapper.eq(ThingsThreshold::getMonitorCategoryCode, query.getMonitorCategoryCode());
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(query.getMetricIdentifier())) {
|
if (StringUtils.isNotEmpty(query.getMetricIdentifier())) {
|
||||||
wrapper.like(ReportThreshold::getMetricIdentifier, query.getMetricIdentifier());
|
wrapper.like(ThingsThreshold::getMetricIdentifier, query.getMetricIdentifier());
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(query.getUnit())) {
|
if (StringUtils.isNotEmpty(query.getUnit())) {
|
||||||
wrapper.eq(ReportThreshold::getUnit, query.getUnit());
|
wrapper.eq(ThingsThreshold::getUnit, query.getUnit());
|
||||||
}
|
}
|
||||||
wrapper.orderByDesc(ReportThreshold::getCreateTime);
|
wrapper.orderByDesc(ThingsThreshold::getCreateTime);
|
||||||
return baseMapper.selectList(wrapper);
|
return baseMapper.selectList(wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReportThreshold selectThresholdById(Long id) {
|
public ThingsThreshold selectThresholdById(Long id) {
|
||||||
return baseMapper.selectById(id);
|
return baseMapper.selectById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int insertThreshold(ReportThreshold threshold) {
|
public int insertThreshold(ThingsThreshold threshold) {
|
||||||
return baseMapper.insert(threshold);
|
return baseMapper.insert(threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int updateThreshold(ReportThreshold threshold) {
|
public int updateThreshold(ThingsThreshold threshold) {
|
||||||
return baseMapper.updateById(threshold);
|
return baseMapper.updateById(threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,25 +1,25 @@
|
|||||||
package com.fastbee.report.threshold;
|
package com.fastbee.report.threshold;
|
||||||
|
|
||||||
import com.fastbee.common.utils.StringUtils;
|
import com.fastbee.common.utils.StringUtils;
|
||||||
import com.fastbee.report.mapper.ReportThresholdMapper;
|
import com.fastbee.report.mapper.ThingsThresholdMapper;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于 iot_report_threshold 表的阈值 Provider
|
* 基于 iot_things_threshold 表的阈值 Provider
|
||||||
*
|
*
|
||||||
* @author FastBee
|
* @author FastBee
|
||||||
* @date 2026-08-16
|
* @date 2026-08-16
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class DbReportThresholdProvider implements ReportThresholdProvider {
|
public class DbThingsThresholdProvider implements ThingsThresholdProvider {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ReportThresholdMapper reportThresholdMapper;
|
private ThingsThresholdMapper thingsThresholdMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ReportThreshold find(Long deviceId, Long productId, String categoryCode, String metricIdentifier) {
|
public ThingsThreshold find(Long deviceId, Long productId, String categoryCode, String metricIdentifier) {
|
||||||
return reportThresholdMapper.selectByPriority(deviceId, productId, categoryCode, metricIdentifier);
|
return thingsThresholdMapper.selectByPriority(deviceId, productId, categoryCode, metricIdentifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -8,23 +8,23 @@ import java.math.BigDecimal;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 报告结构化阈值(iot_report_threshold)
|
* 物模型监测阈值(iot_things_threshold)
|
||||||
*
|
*
|
||||||
* <p>查询优先级:device_id > product_id > 全局默认(product_id=0)。</p>
|
* <p>查询优先级:设备级(device_id) > 产品级(device_id=0)。设备阈值覆盖产品阈值,无全局默认层。</p>
|
||||||
*
|
*
|
||||||
* @author FastBee
|
* @author FastBee
|
||||||
* @date 2026-08-16
|
* @date 2026-08-16
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@TableName("iot_report_threshold")
|
@TableName("iot_things_threshold")
|
||||||
public class ReportThreshold {
|
public class ThingsThreshold {
|
||||||
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
/** 产品ID(0=全局默认) */
|
/** 产品ID */
|
||||||
private Long productId;
|
private Long productId;
|
||||||
|
|
||||||
/** 设备ID(0=不限定设备) */
|
/** 设备ID(0=产品级阈值,作用于该产品下所有设备;非0=设备级阈值,覆盖产品级) */
|
||||||
private Long deviceId;
|
private Long deviceId;
|
||||||
|
|
||||||
private String monitorCategoryCode;
|
private String monitorCategoryCode;
|
||||||
@ -37,6 +37,15 @@ public class ReportThreshold {
|
|||||||
|
|
||||||
private BigDecimal controlValue;
|
private BigDecimal controlValue;
|
||||||
|
|
||||||
|
/** 变化速率预警值(单位/天),GB 50497-2019 双控口径 */
|
||||||
|
private BigDecimal warningRate;
|
||||||
|
|
||||||
|
/** 变化速率报警值(单位/天) */
|
||||||
|
private BigDecimal alarmRate;
|
||||||
|
|
||||||
|
/** 变化速率控制值(单位/天) */
|
||||||
|
private BigDecimal controlRate;
|
||||||
|
|
||||||
private String unit;
|
private String unit;
|
||||||
|
|
||||||
private String createBy;
|
private String createBy;
|
||||||
@ -1,15 +1,15 @@
|
|||||||
package com.fastbee.report.threshold;
|
package com.fastbee.report.threshold;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 报告阈值 Provider(Analyzer 只读结构化阈值,禁止硬编码阈值)
|
* 物模型阈值 Provider(Analyzer 只读结构化阈值,禁止硬编码阈值)
|
||||||
*
|
*
|
||||||
* @author FastBee
|
* @author FastBee
|
||||||
* @date 2026-08-16
|
* @date 2026-08-16
|
||||||
*/
|
*/
|
||||||
public interface ReportThresholdProvider {
|
public interface ThingsThresholdProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询指定设备 + 物模型标识符的结构化阈值(device > product > 全局)
|
* 查询指定设备 + 物模型标识符的结构化阈值(device > product > 全局)
|
||||||
*/
|
*/
|
||||||
ReportThreshold find(Long deviceId, Long productId, String categoryCode, String metricIdentifier);
|
ThingsThreshold find(Long deviceId, Long productId, String categoryCode, String metricIdentifier);
|
||||||
}
|
}
|
||||||
@ -7,25 +7,31 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<select id="selectUnexpiredList" parameterType="com.fastbee.report.record.ReportRecordQuery"
|
<select id="selectUnexpiredList" parameterType="com.fastbee.report.record.ReportRecordQuery"
|
||||||
resultType="com.fastbee.report.record.ReportRecord">
|
resultType="com.fastbee.report.record.ReportRecord">
|
||||||
select id, report_no, device_id, device_name, monitor_category_code, period,
|
select id, report_no, device_id, device_name, monitor_category_code, period,
|
||||||
start_time, end_time, status, file_name, file_url, expire_time, error_message,
|
start_time, end_time, status, file_name, file_url, error_message,
|
||||||
tenant_id, create_by, create_time, update_by, update_time
|
tenant_id, create_by, create_time, update_by, update_time
|
||||||
from report_record
|
from report_record
|
||||||
where expire_time is not null
|
<where>
|
||||||
and expire_time >= now()
|
<if test="deviceId != null">
|
||||||
<if test="deviceId != null">
|
and device_id = #{deviceId}
|
||||||
and device_id = #{deviceId}
|
</if>
|
||||||
</if>
|
<if test="period != null and period != ''">
|
||||||
<if test="period != null and period != ''">
|
and period = #{period}
|
||||||
and period = #{period}
|
</if>
|
||||||
</if>
|
<if test="status != null and status != ''">
|
||||||
<if test="status != null and status != ''">
|
and status = #{status}
|
||||||
and status = #{status}
|
</if>
|
||||||
</if>
|
<if test="keyword != null and keyword != ''">
|
||||||
<if test="keyword != null and keyword != ''">
|
and (report_no like concat('%', #{keyword}, '%')
|
||||||
and (report_no like concat('%', #{keyword}, '%')
|
or device_name like concat('%', #{keyword}, '%'))
|
||||||
or device_name like concat('%', #{keyword}, '%'))
|
</if>
|
||||||
</if>
|
</where>
|
||||||
order by create_time desc
|
order by create_time desc
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 清理过期报告记录:按 create_time 倒推,保留最近 N 天 -->
|
||||||
|
<delete id="cleanExpired" parameterType="int">
|
||||||
|
delete from report_record
|
||||||
|
where create_time < date_sub(now(), interval #{days} day)
|
||||||
|
</delete>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@ -2,13 +2,15 @@
|
|||||||
<!DOCTYPE mapper
|
<!DOCTYPE mapper
|
||||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="com.fastbee.report.mapper.ReportThresholdMapper">
|
<mapper namespace="com.fastbee.report.mapper.ThingsThresholdMapper">
|
||||||
|
|
||||||
<select id="selectByPriority" resultType="com.fastbee.report.threshold.ReportThreshold">
|
<!-- 阈值优先级:设备级(device_id) > 产品级(product_id)。无全局默认层。 -->
|
||||||
|
<select id="selectByPriority" resultType="com.fastbee.report.threshold.ThingsThreshold">
|
||||||
select id, product_id, device_id, monitor_category_code, metric_identifier,
|
select id, product_id, device_id, monitor_category_code, metric_identifier,
|
||||||
warning_value, alarm_value, control_value, unit,
|
warning_value, alarm_value, control_value,
|
||||||
|
warning_rate, alarm_rate, control_rate, unit,
|
||||||
create_by, create_time, update_by, update_time
|
create_by, create_time, update_by, update_time
|
||||||
from iot_report_threshold
|
from iot_things_threshold
|
||||||
<where>
|
<where>
|
||||||
metric_identifier = #{metricIdentifier}
|
metric_identifier = #{metricIdentifier}
|
||||||
<if test="categoryCode != null and categoryCode != ''">
|
<if test="categoryCode != null and categoryCode != ''">
|
||||||
@ -17,8 +19,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
or monitor_category_code = '')
|
or monitor_category_code = '')
|
||||||
</if>
|
</if>
|
||||||
and (device_id = #{deviceId}
|
and (device_id = #{deviceId}
|
||||||
or (device_id = 0 and product_id = #{productId})
|
or (device_id = 0 and product_id = #{productId}))
|
||||||
or (device_id = 0 and product_id = 0))
|
|
||||||
</where>
|
</where>
|
||||||
order by
|
order by
|
||||||
case when device_id = #{deviceId} then 0
|
case when device_id = #{deviceId} then 0
|
||||||
@ -79,6 +79,19 @@ public class Device extends BaseEntity {
|
|||||||
@Excel(name = "设备编号")
|
@Excel(name = "设备编号")
|
||||||
private String serialNumber;
|
private String serialNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基准点设备ID(结构安全监测用)
|
||||||
|
*
|
||||||
|
* <p>相对测量类设备(如静力水准)的读数是相对基准点的,累计变化量需按
|
||||||
|
* 「当前相对值 − 初始相对值」计算,其中相对值 = 本设备读数 − 基准点设备读数。</p>
|
||||||
|
*
|
||||||
|
* <p>deviceId 全局唯一,因此天然支持跨网关引用(如 GW2 的监测点引用 GW1 下的参考点)。
|
||||||
|
* 留空表示该设备为绝对量测量,不参与基准点换算。</p>
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("基准点设备ID(留空表示无需基准点换算)")
|
||||||
|
@Excel(name = "基准点设备ID")
|
||||||
|
private Long baselineDeviceId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 固件版本
|
* 固件版本
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -40,6 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="iccid" column="iccid" />
|
<result property="iccid" column="iccid" />
|
||||||
<result property="projectId" column="project_id" />
|
<result property="projectId" column="project_id" />
|
||||||
<result property="projectName" column="project_name" />
|
<result property="projectName" column="project_name" />
|
||||||
|
<result property="baselineDeviceId" column="baseline_device_id" />
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@ -543,7 +544,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
select d.device_id, d.device_name, d.product_id, p.product_name,p.device_type, d.tenant_id, d.tenant_name,
|
select d.device_id, d.device_name, d.product_id, p.product_name,p.device_type, d.tenant_id, d.tenant_name,
|
||||||
d.serial_number, d.firmware_version,d.wireless_version, d.status, d.rssi,d.is_shadow,d.is_simulate ,d.location_way,d.things_model_value,
|
d.serial_number, d.firmware_version,d.wireless_version, d.status, d.rssi,d.is_shadow,d.is_simulate ,d.location_way,d.things_model_value,
|
||||||
d.network_address, d.network_ip, d.longitude, d.latitude, d.active_time, d.create_time, d.update_time,
|
d.network_address, d.network_ip, d.longitude, d.latitude, d.active_time, d.create_time, d.update_time,
|
||||||
d.img_url,d.summary,d.imei,d.iccid,d.remark,d.project_id,d.project_name,p.guid, p.transport, p.protocol_code from iot_device d
|
d.img_url,d.summary,d.imei,d.iccid,d.remark,d.project_id,d.project_name,p.guid, p.transport, p.protocol_code,
|
||||||
|
d.baseline_device_id from iot_device d
|
||||||
left join iot_product p on p.product_id=d.product_id
|
left join iot_product p on p.product_id=d.product_id
|
||||||
where device_id = #{deviceId}
|
where device_id = #{deviceId}
|
||||||
</select>
|
</select>
|
||||||
@ -784,6 +786,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<if test="imei != null">imei = #{imei},</if>
|
<if test="imei != null">imei = #{imei},</if>
|
||||||
<if test="iccid != null">iccid = #{iccid},</if>
|
<if test="iccid != null">iccid = #{iccid},</if>
|
||||||
<if test="slaveId != null">slave_id = #{slaveId},</if>
|
<if test="slaveId != null">slave_id = #{slaveId},</if>
|
||||||
|
<if test="baselineDeviceId != null">baseline_device_id = #{baselineDeviceId},</if>
|
||||||
<if test="projectId != null">project_id = #{projectId},</if>
|
<if test="projectId != null">project_id = #{projectId},</if>
|
||||||
</trim>
|
</trim>
|
||||||
where device_id = #{deviceId}
|
where device_id = #{deviceId}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
-- 创建日期: 2026-08-16
|
-- 创建日期: 2026-08-16
|
||||||
-- 说明:
|
-- 说明:
|
||||||
-- 1. iot_product 增加监测分类编码列
|
-- 1. iot_product 增加监测分类编码列
|
||||||
-- 2. 新增 iot_report_threshold / report_template / report_record 三张表
|
-- 2. 新增 iot_things_threshold / report_template / report_record 三张表
|
||||||
-- 3. oss_config 插入 MinIO 默认配置(存储决策:未购买云 OSS,统一使用自建 MinIO)
|
-- 3. oss_config 插入 MinIO 默认配置(存储决策:未购买云 OSS,统一使用自建 MinIO)
|
||||||
-- 4. 历史报表菜单与按钮权限
|
-- 4. 历史报表菜单与按钮权限
|
||||||
-- 注意: 存量库执行前请先备份;增量脚本可重复执行(使用 / INSERT IGNORE)
|
-- 注意: 存量库执行前请先备份;增量脚本可重复执行(使用 / INSERT IGNORE)
|
||||||
@ -62,7 +62,7 @@ WHERE (`monitor_category_code` IS NULL OR `monitor_category_code` = '')
|
|||||||
-- ------------------------------------------------------------
|
-- ------------------------------------------------------------
|
||||||
-- 2.1 报告阈值表(查询优先级:device_id > product_id > 全局默认 product_id=0)
|
-- 2.1 报告阈值表(查询优先级:device_id > product_id > 全局默认 product_id=0)
|
||||||
-- ------------------------------------------------------------
|
-- ------------------------------------------------------------
|
||||||
CREATE TABLE `iot_report_threshold` (
|
CREATE TABLE `iot_things_threshold` (
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
`product_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '产品ID(0=全局默认)',
|
`product_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '产品ID(0=全局默认)',
|
||||||
`device_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '设备ID(0=不限定设备)',
|
`device_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '设备ID(0=不限定设备)',
|
||||||
@ -71,6 +71,9 @@ CREATE TABLE `iot_report_threshold` (
|
|||||||
`warning_value` decimal(20,6) DEFAULT NULL COMMENT '预警值',
|
`warning_value` decimal(20,6) DEFAULT NULL COMMENT '预警值',
|
||||||
`alarm_value` decimal(20,6) DEFAULT NULL COMMENT '报警值',
|
`alarm_value` decimal(20,6) DEFAULT NULL COMMENT '报警值',
|
||||||
`control_value` decimal(20,6) DEFAULT NULL COMMENT '控制值',
|
`control_value` decimal(20,6) DEFAULT NULL COMMENT '控制值',
|
||||||
|
`warning_rate` DECIMAL(18, 6) DEFAULT NULL COMMENT '变化速率预警值(单位/天)',
|
||||||
|
`alarm_rate` DECIMAL(18, 6) DEFAULT NULL COMMENT '变化速率报警值(单位/天)' ,
|
||||||
|
`control_rate` DECIMAL(18, 6) DEFAULT NULL COMMENT '变化速率控制值(单位/天)',
|
||||||
`unit` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '单位',
|
`unit` varchar(32) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '单位',
|
||||||
`create_by` varchar(64) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '创建者',
|
`create_by` varchar(64) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
@ -117,7 +120,7 @@ VALUES
|
|||||||
('DEVICE_RAIN_GAUGE_V1', '雨量计监测分析报告V1', 'RAIN_GAUGE', 1, 'templates/DEVICE_RAIN_GAUGE_V1.docx', '0', '0', 'admin', NOW());
|
('DEVICE_RAIN_GAUGE_V1', '雨量计监测分析报告V1', 'RAIN_GAUGE', 1, 'templates/DEVICE_RAIN_GAUGE_V1.docx', '0', '0', 'admin', NOW());
|
||||||
|
|
||||||
-- ------------------------------------------------------------
|
-- ------------------------------------------------------------
|
||||||
-- 2.3 报告生成记录表(保留期 1 个月:expire_time = create_time + 30 天)
|
-- 2.3 报告生成记录表(保留期由 LogCleanTask#cleanLogs(days) 统一管理,按 create_time 倒推清理)
|
||||||
-- ------------------------------------------------------------
|
-- ------------------------------------------------------------
|
||||||
CREATE TABLE `report_record` (
|
CREATE TABLE `report_record` (
|
||||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
@ -131,7 +134,6 @@ CREATE TABLE `report_record` (
|
|||||||
`status` varchar(16) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '状态(CREATING/SUCCESS/FAILED)',
|
`status` varchar(16) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '状态(CREATING/SUCCESS/FAILED)',
|
||||||
`file_name` varchar(256) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '文件名称',
|
`file_name` varchar(256) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '文件名称',
|
||||||
`file_url` varchar(512) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '文件地址',
|
`file_url` varchar(512) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '文件地址',
|
||||||
`expire_time` datetime DEFAULT NULL COMMENT '过期时间(= create_time + 30 天)',
|
|
||||||
`error_message` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '失败原因',
|
`error_message` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '失败原因',
|
||||||
`tenant_id` bigint(20) DEFAULT NULL COMMENT '租户ID',
|
`tenant_id` bigint(20) DEFAULT NULL COMMENT '租户ID',
|
||||||
`create_by` varchar(64) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '创建者',
|
`create_by` varchar(64) COLLATE utf8_unicode_ci DEFAULT '' COMMENT '创建者',
|
||||||
@ -140,7 +142,6 @@ CREATE TABLE `report_record` (
|
|||||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
KEY `idx_device_time` (`device_id`, `create_time`) USING BTREE,
|
KEY `idx_device_time` (`device_id`, `create_time`) USING BTREE,
|
||||||
KEY `idx_expire` (`expire_time`) USING BTREE,
|
|
||||||
UNIQUE KEY `uk_report_no` (`report_no`) USING BTREE
|
UNIQUE KEY `uk_report_no` (`report_no`) USING BTREE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='报告生成记录';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='报告生成记录';
|
||||||
|
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
-- 报告阈值初始化脚本(基于 JGJ 8-2016、GB 50497-2019 等行业标准)
|
|
||||||
-- 使用方式:在 fastbee 数据库中执行此脚本
|
|
||||||
-- 说明:product_id=0 表示全局默认,可被产品级/设备级配置覆盖
|
|
||||||
|
|
||||||
-- 1. 深部位移(累计位移控制值通常 30-100mm,速率预警值 2mm/d)
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'DEEP_DISPLACEMENT', 'displacement', 20.00, 30.00, 50.00, 'mm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 2. 静力水准(累计沉降控制值通常 20-60mm,速率预警值 2mm/d)
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'STATIC_LEVEL', 'settlement', 15.00, 25.00, 40.00, 'mm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 3. 裂缝计(裂缝宽度控制值通常 0.3-1.0mm,变化量预警值 0.2mm)
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'CRACK_METER', 'crack_width', 0.30, 0.50, 1.00, 'mm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 4. 轴力计(轴力控制值按设计值的 60%-80%,这里设为相对值)
|
|
||||||
-- 注:轴力阈值通常需根据设计值动态计算,此处仅作示例
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'AXIAL_FORCE', 'axial_force', NULL, NULL, NULL, 'kN', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 5. 测距仪(收敛变形控制值通常 20-50mm)
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'RANGING_METER', 'distance', 15.00, 25.00, 40.00, 'mm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 6. 倾角计(倾斜角度控制值通常 1°-3°)
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'INCLINOMETER', 'inclination', 1.00, 2.00, 3.00, '°', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 7. 水位计(水位变化控制值通常 0.5-2.0m)
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'WATER_LEVEL', 'water_level', 0.50, 1.00, 2.00, 'm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 8. 应变计(应变控制值通常 ±1000με)
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'STRAIN_GAUGE', 'strain', 500.00, 800.00, 1000.00, 'με', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 9. 振弦式传感器(频率变化控制值通常 ±50Hz)
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'VIBRATING_WIRE', 'frequency', 30.00, 40.00, 50.00, 'Hz', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 10. 温度计(温度变化控制值通常 ±15℃)
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'THERMOMETER', 'temperature', 10.00, 15.00, 20.00, '℃', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 11. 雨量计(累计降雨量预警值通常 50mm/d)
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'RAIN_GAUGE', 'rainfall', 25.00, 50.00, 100.00, 'mm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 说明:
|
|
||||||
-- 1. 以上阈值基于 JGJ 8-2016《建筑变形测量规范》、GB 50497-2019《建筑基坑工程监测标准》
|
|
||||||
-- 等行业标准,适用于一般建筑工程。实际项目应根据设计要求调整。
|
|
||||||
-- 2. product_id=0 表示全局默认,如需针对特定产品,请复制以上记录并修改 product_id。
|
|
||||||
-- 3. metric_identifier 需与物模型标识符匹配,如不匹配则阈值不生效。
|
|
||||||
-- 4. 预警值 < 报警值 < 控制值,系统会按当前值与阈值的比较自动判断安全等级。
|
|
||||||
110
sql/init_things_threshold.sql
Normal file
110
sql/init_things_threshold.sql
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
-- 报告阈值初始化脚本(基于 JGJ 8-2016、GB 50497-2019 等行业标准)
|
||||||
|
-- 使用方式:在 fastbee 数据库中执行此脚本
|
||||||
|
-- 说明:product_id=0 表示全局默认,可被产品级/设备级配置覆盖
|
||||||
|
|
||||||
|
-- 1. 深部位移(累计位移控制值通常 30-100mm,速率预警值 2mm/d)
|
||||||
|
INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
||||||
|
VALUES (0, 0, 'DEEP_DISPLACEMENT', 'displacement', 20.00, 30.00, 50.00, 'mm', 'admin', NOW());
|
||||||
|
|
||||||
|
-- 2. 静力水准(累计沉降控制值通常 20-60mm,速率预警值 2mm/d)
|
||||||
|
INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
||||||
|
VALUES (0, 0, 'STATIC_LEVEL', 'settlement', 15.00, 25.00, 40.00, 'mm', 'admin', NOW());
|
||||||
|
|
||||||
|
-- 3. 裂缝计(裂缝宽度控制值通常 0.3-1.0mm,变化量预警值 0.2mm)
|
||||||
|
INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
||||||
|
VALUES (0, 0, 'CRACK_METER', 'crack_width', 0.30, 0.50, 1.00, 'mm', 'admin', NOW());
|
||||||
|
|
||||||
|
-- 4. 轴力计(轴力控制值按设计值的 60%-80%,这里设为相对值)
|
||||||
|
-- 注:轴力阈值通常需根据设计值动态计算,此处仅作示例
|
||||||
|
INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
||||||
|
VALUES (0, 0, 'AXIAL_FORCE', 'axial_force', NULL, NULL, NULL, 'kN', 'admin', NOW());
|
||||||
|
|
||||||
|
-- 5. 测距仪(收敛变形控制值通常 20-50mm)
|
||||||
|
INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
||||||
|
VALUES (0, 0, 'RANGING_METER', 'distance', 15.00, 25.00, 40.00, 'mm', 'admin', NOW());
|
||||||
|
|
||||||
|
-- 6. 倾角计(倾斜角度控制值通常 1°-3°)
|
||||||
|
INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
||||||
|
VALUES (0, 0, 'INCLINOMETER', 'inclination', 1.00, 2.00, 3.00, '°', 'admin', NOW());
|
||||||
|
|
||||||
|
-- 7. 水位计(水位变化控制值通常 0.5-2.0m)
|
||||||
|
INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
||||||
|
VALUES (0, 0, 'WATER_LEVEL', 'water_level', 0.50, 1.00, 2.00, 'm', 'admin', NOW());
|
||||||
|
|
||||||
|
-- 8. 应变计(应变控制值通常 ±1000με)
|
||||||
|
INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
||||||
|
VALUES (0, 0, 'STRAIN_GAUGE', 'strain', 500.00, 800.00, 1000.00, 'με', 'admin', NOW());
|
||||||
|
|
||||||
|
-- 9. 振弦式传感器(频率变化控制值通常 ±50Hz)
|
||||||
|
INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
||||||
|
VALUES (0, 0, 'VIBRATING_WIRE', 'frequency', 30.00, 40.00, 50.00, 'Hz', 'admin', NOW());
|
||||||
|
|
||||||
|
-- 10. 温度计(温度变化控制值通常 ±15℃)
|
||||||
|
INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
||||||
|
VALUES (0, 0, 'THERMOMETER', 'temperature', 10.00, 15.00, 20.00, '℃', 'admin', NOW());
|
||||||
|
|
||||||
|
-- 11. 雨量计(累计降雨量预警值通常 50mm/d)
|
||||||
|
INSERT INTO iot_things_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
||||||
|
VALUES (0, 0, 'RAIN_GAUGE', 'rainfall', 25.00, 50.00, 100.00, 'mm', 'admin', NOW());
|
||||||
|
|
||||||
|
-- 说明:
|
||||||
|
-- 1. 以上阈值基于 JGJ 8-2016《建筑变形测量规范》、GB 50497-2019《建筑基坑工程监测标准》
|
||||||
|
-- 等行业标准,适用于一般建筑工程。实际项目应根据设计要求调整。
|
||||||
|
-- 2. product_id=0 表示全局默认,如需针对特定产品,请复制以上记录并修改 product_id。
|
||||||
|
-- 3. metric_identifier 需与物模型标识符匹配,如不匹配则阈值不生效。
|
||||||
|
-- 4. 预警值 < 报警值 < 控制值,系统会按当前值与阈值的比较自动判断安全等级。
|
||||||
|
|
||||||
|
|
||||||
|
-- 结构安全监测改造:设备级基准点 + 阈值速率维度(双控)
|
||||||
|
-- 执行前请确认已备份,且字段尚未存在(MySQL 不支持 ADD COLUMN IF NOT EXISTS,重复执行会报错)
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 1. 设备级基准点(相对测量用)
|
||||||
|
-- 静力水准等设备的读数是相对基准点的,累计变化量需按
|
||||||
|
-- 「(当前读数 − 基准点当前读数) − (初始读数 − 基准点初始读数)」计算。
|
||||||
|
-- deviceId 全局唯一,因此天然支持跨网关引用
|
||||||
|
-- (如 GW2 的监测点引用 GW1 下的参考点)。
|
||||||
|
-- 留空表示该设备为绝对量测量,不参与基准点换算。
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE iot_device
|
||||||
|
ADD COLUMN baseline_device_id BIGINT NULL COMMENT '基准点设备ID(相对测量用,留空=绝对量)' AFTER serial_number;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 2. 阈值表:变化速率维度
|
||||||
|
-- 依据 GB 50497-2019 第 8.0.4 条(强制性条文):
|
||||||
|
-- 监测报警按「累计变化量」与「变化速率」双控,任一超限即取较严重等级。
|
||||||
|
-- 单位统一为「单位/天」,例如 mm/d、kN/d。
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE iot_things_threshold
|
||||||
|
ADD COLUMN warning_rate DECIMAL(18, 6) NULL COMMENT '变化速率预警值(单位/天)' AFTER control_value,
|
||||||
|
ADD COLUMN alarm_rate DECIMAL(18, 6) NULL COMMENT '变化速率报警值(单位/天)' AFTER warning_rate,
|
||||||
|
ADD COLUMN control_rate DECIMAL(18, 6) NULL COMMENT '变化速率控制值(单位/天)' AFTER alarm_rate;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 附:各行业规范推荐取值(供配置产品阈值时参考,不写入全局默认值)
|
||||||
|
-- 未配置阈值的产品将输出「待评估」,不会伪造为「安全」。
|
||||||
|
--
|
||||||
|
-- 监测项目 累计控制值 速率预警值 依据
|
||||||
|
-- 深部位移(测斜) 40~50mm (0.4%~0.5%H) 3~5 mm/d GB 50497-2019
|
||||||
|
-- 支护结构顶部位移 25~30mm (0.2%~0.3%H) 3~5 mm/d GB 50497-2019
|
||||||
|
-- 周边地表沉降 25~35mm 3~5 mm/d GB 50497-2019
|
||||||
|
-- 管片结构沉降 硬土 10~20 / 软土 20~30mm 2~3 mm/d GB 50911-2013
|
||||||
|
-- 管片结构隆起 10mm 1 mm/d GB 50911-2013
|
||||||
|
-- 净空收敛(测距仪) 0.2%D 或 10mm 3 mm/d GB 50911-2013
|
||||||
|
-- 差异沉降 0.04%La 3 mm/d GB 50911-2013
|
||||||
|
-- 地下水位变化 1000~2000mm(常年变幅外) 500 mm/d GB 50497-2019 表8.0.5
|
||||||
|
-- 裂缝(结构性/既有) 1.5~3mm 持续发展 GB 50497-2019 表8.0.5
|
||||||
|
-- 裂缝(地表/新增) 1~3mm 持续发展 GB 50497-2019 表8.0.5
|
||||||
|
-- 锚索/支撑轴力 构件承载力设计值的 80% ≥80% 且持续增长 GB 50497-2019 8.0.6
|
||||||
|
-- 管线沉降(刚性/压力) 10~20mm 2 mm/d GB 50497-2019 表8.0.5
|
||||||
|
-- 构筑物及无压管线 20~25mm 2 mm/d GB 50157-2013
|
||||||
|
-- 建筑物倾斜 — 2°/d GB 50157-2013
|
||||||
|
--
|
||||||
|
-- 三级取值比例(设计允许值为基准):
|
||||||
|
-- 预警值 ≈ 60%~70% 报警值 ≈ 80% 控制值 = 100%
|
||||||
|
-- 速率细则:
|
||||||
|
-- 达到规定值、或连续 3 次超过该值的 70%,即应预警;
|
||||||
|
-- 底板浇筑完成后,速率不宜超过速率预警值的 70%。
|
||||||
|
-- 注:雨量计不是安全指标,规范中是工况触发因素(雨季/暴雨后须加测),
|
||||||
|
-- 不应参与预警判定。
|
||||||
|
-- ============================================================
|
||||||
@ -1,77 +0,0 @@
|
|||||||
-- ============================================================
|
|
||||||
-- 报告阈值表创建 + 行业标准默认值初始化
|
|
||||||
-- 数据库:fastbee
|
|
||||||
-- 执行方式:mysql -u root -p fastbee < sql/init_threshold_with_table.sql
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
-- 1. 创建阈值表
|
|
||||||
CREATE TABLE IF NOT EXISTS iot_report_threshold (
|
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键',
|
|
||||||
product_id BIGINT NOT NULL DEFAULT 0 COMMENT '产品ID(0=全局默认)',
|
|
||||||
device_id BIGINT NOT NULL DEFAULT 0 COMMENT '设备ID(0=不限定设备)',
|
|
||||||
monitor_category_code VARCHAR(50) COMMENT '监测分类编码',
|
|
||||||
metric_identifier VARCHAR(100) NOT NULL COMMENT '物模型标识符',
|
|
||||||
warning_value DECIMAL(10,4) COMMENT '预警值',
|
|
||||||
alarm_value DECIMAL(10,4) COMMENT '报警值',
|
|
||||||
control_value DECIMAL(10,4) COMMENT '控制值',
|
|
||||||
unit VARCHAR(20) COMMENT '单位',
|
|
||||||
create_by VARCHAR(64) COMMENT '创建者',
|
|
||||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
|
||||||
update_by VARCHAR(64) COMMENT '更新者',
|
|
||||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
|
||||||
INDEX idx_product_device (product_id, device_id),
|
|
||||||
INDEX idx_metric_identifier (metric_identifier)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告结构化阈值表';
|
|
||||||
|
|
||||||
-- 2. 初始化行业标准默认阈值(product_id=0 全局默认)
|
|
||||||
-- 来源:JGJ 8-2016《建筑变形测量规范》、GB 50497-2019《建筑基坑工程监测标准》
|
|
||||||
|
|
||||||
-- 深部位移:累计位移控制值 30-100mm,速率预警值 2mm/d
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'DEEP_DISPLACEMENT', 'displacement', 20.00, 30.00, 50.00, 'mm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 静力水准:累计沉降控制值 20-60mm,速率预警值 2mm/d
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'STATIC_LEVEL', 'settlement', 15.00, 25.00, 40.00, 'mm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 裂缝计:裂缝宽度控制值 0.3-1.0mm,变化量预警值 0.2mm
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'CRACK_METER', 'crack_width', 0.30, 0.50, 1.00, 'mm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 测距仪:收敛变形控制值 20-50mm
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'RANGING_METER', 'distance', 15.00, 25.00, 40.00, 'mm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 倾角计:倾斜角度控制值 1°-3°
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'INCLINOMETER', 'inclination', 1.00, 2.00, 3.00, '°', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 水位计:水位变化控制值 0.5-2.0m
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'WATER_LEVEL', 'water_level', 0.50, 1.00, 2.00, 'm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 应变计:应变控制值 ±1000με
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'STRAIN_GAUGE', 'strain', 500.00, 800.00, 1000.00, 'με', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 振弦式传感器:频率变化控制值 ±50Hz
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'VIBRATING_WIRE', 'frequency', 30.00, 40.00, 50.00, 'Hz', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 温度计:温度变化控制值 ±15℃
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'THERMOMETER', 'temperature', 10.00, 15.00, 20.00, '℃', 'admin', NOW());
|
|
||||||
|
|
||||||
-- 雨量计:累计降雨量预警值 50mm/d
|
|
||||||
INSERT INTO iot_report_threshold (product_id, device_id, monitor_category_code, metric_identifier, warning_value, alarm_value, control_value, unit, create_by, create_time)
|
|
||||||
VALUES (0, 0, 'RAIN_GAUGE', 'rainfall', 25.00, 50.00, 100.00, 'mm', 'admin', NOW());
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- 重要说明:
|
|
||||||
-- 1. metric_identifier 需与产品物模型的标识符匹配
|
|
||||||
-- 如果物模型标识符不同,请修改 UPDATE 语句更新对应记录
|
|
||||||
-- 示例:UPDATE iot_report_threshold SET metric_identifier='X_axis_displacement' WHERE monitor_category_code='DEEP_DISPLACEMENT';
|
|
||||||
-- 2. 预警值 < 报警值 < 控制值,系统会按当前值与阈值的比较自动判断安全等级
|
|
||||||
-- 3. 如需针对特定产品配置阈值,复制以上记录并修改 product_id
|
|
||||||
-- 4. 如需针对特定设备配置阈值,复制以上记录并修改 device_id
|
|
||||||
-- ============================================================
|
|
||||||
@ -26,7 +26,7 @@ INSERT INTO `sys_job` (
|
|||||||
'0', -- 状态:正常
|
'0', -- 状态:正常
|
||||||
'admin', -- 创建者
|
'admin', -- 创建者
|
||||||
NOW(), -- 创建时间
|
NOW(), -- 创建时间
|
||||||
'每天凌晨0点自动清理15天前的日志数据(iot_event_log、sys_oper_log、sys_logininfor、iot_alert_log、sys_job_log、notify_log)'
|
'每天凌晨0点自动清理30天前的日志与报告数据(iot_event_log、sys_oper_log、sys_logininfor、iot_alert_log、sys_job_log、notify_log、iot_platform_push_log、report_record)'
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ============================================
|
-- ============================================
|
||||||
@ -38,6 +38,8 @@ INSERT INTO `sys_job` (
|
|||||||
-- 4. iot_alert_log - 设备告警日志
|
-- 4. iot_alert_log - 设备告警日志
|
||||||
-- 5. sys_job_log - 定时任务日志
|
-- 5. sys_job_log - 定时任务日志
|
||||||
-- 6. notify_log - 通知日志
|
-- 6. notify_log - 通知日志
|
||||||
|
-- 7. iot_platform_push_log - 平台对接推送日志
|
||||||
|
-- 8. report_record - 报告生成记录(按 create_time 倒推清理,不再使用 expire_time 字段)
|
||||||
--
|
--
|
||||||
-- 注意:iot_device_log 表(设备监测日志)不参与清理
|
-- 注意:iot_device_log 表(设备监测日志)不参与清理
|
||||||
-- 该表为核心数据,目前有 214 万条记录,需要数据迁移方案
|
-- 该表为核心数据,目前有 214 万条记录,需要数据迁移方案
|
||||||
|
|||||||
@ -1,37 +0,0 @@
|
|||||||
-- ============================================================
|
|
||||||
-- 监测阈值配置菜单权限初始化
|
|
||||||
-- 说明:在 fastbee 数据库中执行此脚本
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
-- 查询 IoT 模块父菜单 ID(假设菜单名称为"设备管理"或"监测管理")
|
|
||||||
-- 如果不存在,则创建一个"监测管理"父菜单
|
|
||||||
SET @iot_parent_id = (SELECT menu_id FROM sys_menu WHERE menu_name = '设备管理' AND parent_id = 0 LIMIT 1);
|
|
||||||
|
|
||||||
-- 如果找不到"设备管理",则使用系统工具作为父菜单
|
|
||||||
SET @iot_parent_id = IFNULL(@iot_parent_id, 1);
|
|
||||||
|
|
||||||
-- 插入阈值配置菜单
|
|
||||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
|
|
||||||
VALUES ('阈值配置', @iot_parent_id, 10, 'threshold', 'iot/threshold/index', 1, 0, 'C', '0', '0', 'iot:threshold:list', 'tool', 'admin', NOW(), '监测阈值配置菜单');
|
|
||||||
|
|
||||||
-- 获取刚插入的菜单 ID
|
|
||||||
SET @threshold_menu_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
-- 插入按钮权限
|
|
||||||
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) VALUES
|
|
||||||
('阈值查询', @threshold_menu_id, 1, '', '', 1, 0, 'F', '0', '0', 'iot:threshold:query', '#', 'admin', NOW(), ''),
|
|
||||||
('阈值新增', @threshold_menu_id, 2, '', '', 1, 0, 'F', '0', '0', 'iot:threshold:add', '#', 'admin', NOW(), ''),
|
|
||||||
('阈值修改', @threshold_menu_id, 3, '', '', 1, 0, 'F', '0', '0', 'iot:threshold:edit', '#', 'admin', NOW(), ''),
|
|
||||||
('阈值删除', @threshold_menu_id, 4, '', '', 1, 0, 'F', '0', '0', 'iot:threshold:remove', '#', 'admin', NOW(), ''),
|
|
||||||
('阈值导出', @threshold_menu_id, 5, '', '', 1, 0, 'F', '0', '0', 'iot:threshold:export', '#', 'admin', NOW(), '');
|
|
||||||
|
|
||||||
-- 提示
|
|
||||||
SELECT '阈值配置菜单已创建,菜单ID为:' AS message, @threshold_menu_id AS menu_id;
|
|
||||||
SELECT '请在前端重新登录或刷新页面以查看新菜单' AS hint;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- 说明:
|
|
||||||
-- 1. 执行此脚本后,管理员角色将自动拥有阈值配置菜单权限
|
|
||||||
-- 2. 如需分配给其他角色,请在"系统管理 -> 角色管理"中配置
|
|
||||||
-- 3. 菜单路径为:/iot/threshold,组件路径为:iot/threshold/index
|
|
||||||
-- ============================================================
|
|
||||||
@ -1,460 +0,0 @@
|
|||||||
-- =============================================
|
|
||||||
-- DCP 平台内置接口和流程初始化数据
|
|
||||||
-- 使用方法:
|
|
||||||
-- 1. 先在平台管理页面新增 DCP 平台实例(code='dcp')
|
|
||||||
-- 2. 执行本脚本前,将下方 @dcp_platform_id 替换为实际的平台 ID
|
|
||||||
-- 或直接执行:SET @dcp_platform_id = (SELECT id FROM platform_instance WHERE code='dcp' LIMIT 1);
|
|
||||||
-- =============================================
|
|
||||||
|
|
||||||
SET @dcp_platform_id = (SELECT id FROM platform_instance WHERE code = 'dcp' LIMIT 1);
|
|
||||||
|
|
||||||
-- 重新执行时先清理本脚本维护的流程和接口,避免旧配置被 LIMIT 1 命中。
|
|
||||||
DELETE s FROM platform_flow_step s
|
|
||||||
INNER JOIN platform_flow f ON f.id = s.flow_id
|
|
||||||
WHERE f.platform_id = @dcp_platform_id
|
|
||||||
AND f.code IN (
|
|
||||||
'dcp_init', 'dcp_token_refresh', 'dcp_onboard_inclino',
|
|
||||||
'dcp_onboard_stress', 'dcp_onboard_hyeto',
|
|
||||||
'dcp_onboard_static_level', 'dcp_onboard_deep_inclino'
|
|
||||||
);
|
|
||||||
|
|
||||||
DELETE FROM platform_flow
|
|
||||||
WHERE platform_id = @dcp_platform_id
|
|
||||||
AND code IN (
|
|
||||||
'dcp_init', 'dcp_token_refresh', 'dcp_onboard_inclino',
|
|
||||||
'dcp_onboard_stress', 'dcp_onboard_hyeto',
|
|
||||||
'dcp_onboard_static_level', 'dcp_onboard_deep_inclino'
|
|
||||||
);
|
|
||||||
|
|
||||||
DELETE FROM platform_interface
|
|
||||||
WHERE platform_id = @dcp_platform_id
|
|
||||||
AND code IN (
|
|
||||||
'dcp_auth', 'dcp_get_user', 'dcp_get_projects', 'dcp_create_point',
|
|
||||||
'dcp_bind_inclino', 'dcp_bind_stress', 'dcp_bind_hyeto',
|
|
||||||
'dcp_bind_static_level', 'dcp_bind_deep_inclino',
|
|
||||||
'dcp_push_inclino', 'dcp_push_stress', 'dcp_push_hyeto',
|
|
||||||
'dcp_push_static_level'
|
|
||||||
);
|
|
||||||
|
|
||||||
-- =============================================
|
|
||||||
-- 一、接口定义(13个)
|
|
||||||
-- =============================================
|
|
||||||
|
|
||||||
-- 1. 认证接口
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '获取Token', 'dcp_auth', 'POST', '/connect/token', 'application/x-www-form-urlencoded',
|
|
||||||
'[
|
|
||||||
{"name":"grant_type","type":"string","source":"fixed","value":"password","required":true,"desc":"授权类型"},
|
|
||||||
{"name":"scope", "type":"string","source":"auth", "field":"scope", "required":true,"desc":"scope"},
|
|
||||||
{"name":"client_id", "type":"string","source":"auth", "field":"clientId", "required":true,"desc":"客户端ID"},
|
|
||||||
{"name":"userName", "type":"string","source":"auth", "field":"userName", "required":true,"desc":"用户名"},
|
|
||||||
{"name":"password", "type":"string","source":"auth", "field":"password", "required":true,"desc":"密码"}
|
|
||||||
]',
|
|
||||||
'[
|
|
||||||
{"name":"access_token","type":"string","path":"access_token","desc":"访问Token"},
|
|
||||||
{"name":"expires_in", "type":"number","path":"expires_in", "desc":"过期秒数"},
|
|
||||||
{"name":"token_type", "type":"string","path":"token_type", "desc":"Token类型"}
|
|
||||||
]',
|
|
||||||
1, 'DCP平台认证,获取access_token', 'system', NOW());
|
|
||||||
|
|
||||||
-- 2. 获取用户ID
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '获取用户ID', 'dcp_get_user', 'GET', '/api/app/user/user-id', 'application/json',
|
|
||||||
'[]',
|
|
||||||
'[
|
|
||||||
{"name":"id", "type":"string","path":"id", "desc":"用户ID"},
|
|
||||||
{"name":"userName", "type":"string","path":"userName", "desc":"用户名"},
|
|
||||||
{"name":"phoneNumber", "type":"string","path":"phoneNumber", "desc":"手机号"}
|
|
||||||
]',
|
|
||||||
2, '获取当前登录用户信息', 'system', NOW());
|
|
||||||
|
|
||||||
-- 3. 获取项目列表
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '获取项目列表', 'dcp_get_projects', 'GET', '/api/app/project/by-user-id/{userId}', 'application/json',
|
|
||||||
'[
|
|
||||||
{"name":"userId","type":"string","source":"context","contextKey":"step2.id","required":true,"desc":"用户ID,来自上一步"}
|
|
||||||
]',
|
|
||||||
'[
|
|
||||||
{"name":"id", "type":"string","path":"id", "desc":"项目ID"},
|
|
||||||
{"name":"name", "type":"string","path":"name", "desc":"项目名称"}
|
|
||||||
]',
|
|
||||||
3, '获取用户下的完整项目列表', 'system', NOW());
|
|
||||||
|
|
||||||
-- 4. 创建测点
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '创建测点', 'dcp_create_point', 'POST', '/api/app/point', 'application/json',
|
|
||||||
'[
|
|
||||||
{"name":"projectId","type":"string","source":"device","field":"platform_project_id","required":true,"desc":"项目ID"},
|
|
||||||
{"name":"name", "type":"string","source":"device","field":"serial_number","required":true,"desc":"测点名称,用设备序列号"},
|
|
||||||
{"name":"type", "type":"number","source":"fixed","value":3,"required":true,"desc":"测点类型,固定为3"}
|
|
||||||
]',
|
|
||||||
'[
|
|
||||||
{"name":"id","type":"string","path":"id","desc":"测点ID(pointId)"}
|
|
||||||
]',
|
|
||||||
4, '在DCP平台创建监测测点', 'system', NOW());
|
|
||||||
|
|
||||||
-- 5. 绑定倾角传感器
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '绑定倾角传感器', 'dcp_bind_inclino', 'POST', '/api/app/inclino-sensor', 'application/json',
|
|
||||||
'[
|
|
||||||
{"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true,"desc":"项目ID"},
|
|
||||||
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID,来自创建测点步骤"},
|
|
||||||
{"name":"deviceType", "type":"number","source":"fixed", "value":5, "required":true,"desc":"设备类型,倾角=5"},
|
|
||||||
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"},
|
|
||||||
{"name":"moduleId", "type":"string","source":"uuid", "required":true,"desc":"模块ID,系统自动生成UUID"},
|
|
||||||
{"name":"virtualId", "type":"string","source":"uuid", "required":true,"desc":"虚拟ID,系统自动生成UUID"},
|
|
||||||
{"name":"dH", "type":"number","source":"fixed", "value":0, "required":false,"desc":"高程"}
|
|
||||||
]',
|
|
||||||
'[
|
|
||||||
{"name":"id","type":"string","path":"id","desc":"DCP设备ID(deviceId)"}
|
|
||||||
]',
|
|
||||||
5, '将倾角传感器绑定到测点', 'system', NOW());
|
|
||||||
|
|
||||||
-- 6. 绑定轴力计
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '绑定轴力计', 'dcp_bind_stress', 'POST', '/api/app/stress-sensor', 'application/json',
|
|
||||||
'[
|
|
||||||
{"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true,"desc":"项目ID"},
|
|
||||||
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID"},
|
|
||||||
{"name":"deviceType", "type":"number","source":"fixed", "value":10, "required":true,"desc":"设备类型,轴力计=10"},
|
|
||||||
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"},
|
|
||||||
{"name":"factor", "type":"number","source":"fixed", "value":1, "required":false,"desc":"系数"},
|
|
||||||
{"name":"moduleId", "type":"string","source":"uuid", "required":true},
|
|
||||||
{"name":"virtualId", "type":"string","source":"uuid", "required":true}
|
|
||||||
]',
|
|
||||||
'[{"name":"id","type":"string","path":"id","desc":"DCP设备ID"}]',
|
|
||||||
6, '将轴力计绑定到测点', 'system', NOW());
|
|
||||||
|
|
||||||
-- 7. 绑定雨量计
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '绑定雨量计', 'dcp_bind_hyeto', 'POST', '/api/app/hyeto-sensor', 'application/json',
|
|
||||||
'[
|
|
||||||
{"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true},
|
|
||||||
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true},
|
|
||||||
{"name":"deviceType", "type":"number","source":"fixed", "value":3, "required":true,"desc":"雨量计=3"},
|
|
||||||
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true},
|
|
||||||
{"name":"moduleId", "type":"string","source":"uuid", "required":true},
|
|
||||||
{"name":"virtualId", "type":"string","source":"uuid", "required":true}
|
|
||||||
]',
|
|
||||||
'[{"name":"id","type":"string","path":"id","desc":"DCP设备ID"}]',
|
|
||||||
7, '将雨量计绑定到测点', 'system', NOW());
|
|
||||||
|
|
||||||
-- 8. 绑定静力水准
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '绑定静力水准', 'dcp_bind_static_level', 'POST', '/api/app/static-level-device', 'application/json',
|
|
||||||
'[
|
|
||||||
{"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true},
|
|
||||||
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true},
|
|
||||||
{"name":"deviceType", "type":"number","source":"fixed", "value":1002, "required":true,"desc":"静力水准=1002"},
|
|
||||||
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true},
|
|
||||||
{"name":"moduleId", "type":"string","source":"uuid", "required":true},
|
|
||||||
{"name":"virtualId", "type":"string","source":"uuid", "required":true},
|
|
||||||
{"name":"sensorIds", "type":"array", "source":"referenceSubDeviceSensorIdPairs","required":true,"desc":"基准点与每个监测点成对绑定"}
|
|
||||||
]',
|
|
||||||
'[{"name":"id","type":"string","path":"id","desc":"DCP设备ID"}]',
|
|
||||||
8, '将静力水准传感器绑定到测点', 'system', NOW());
|
|
||||||
|
|
||||||
-- 9. 绑定深部位移
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '绑定深部位移', 'dcp_bind_deep_inclino', 'POST', '/api/app/deep-inclino-device', 'application/json',
|
|
||||||
'[
|
|
||||||
{"name":"projectId", "type":"string","source":"device", "field":"platform_project_id","required":true},
|
|
||||||
{"name":"pointId", "type":"string","source":"context", "contextKey":"step1.id", "required":true},
|
|
||||||
{"name":"deviceType", "type":"number","source":"fixed", "value":1001, "required":true},
|
|
||||||
{"name":"name", "type":"string","source":"device", "field":"serial_number", "required":true},
|
|
||||||
{"name":"moduleId", "type":"string","source":"uuid", "required":true},
|
|
||||||
{"name":"virtualId", "type":"string","source":"uuid", "required":true},
|
|
||||||
{"name":"sensorIds", "type":"array", "source":"cascadedSubDeviceSensorIds", "required":true},
|
|
||||||
{"name":"sensorDepths", "type":"array", "source":"cascadedSubDeviceDepths", "required":true},
|
|
||||||
{"name":"holeDepth", "type":"number","source":"onboard_config", "field":"holeDepth", "required":true},
|
|
||||||
{"name":"holeElevation","type":"number","source":"onboard_config", "field":"holeElevation", "required":true}
|
|
||||||
]',
|
|
||||||
'[{"name":"id","type":"string","path":"id","desc":"DCP设备ID"}]',
|
|
||||||
9, '将深部位移设备绑定到测点', 'system', NOW());
|
|
||||||
|
|
||||||
|
|
||||||
-- =============================================
|
|
||||||
-- 1.5 推送数据接口(4个,设备绑定时选择推送接口用)
|
|
||||||
-- =============================================
|
|
||||||
|
|
||||||
-- 9. 推送倾角数据
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '推送倾角数据', 'dcp_push_inclino', 'POST', '/api/app/inclino-data/many', 'application/json',
|
|
||||||
'[
|
|
||||||
{"name":"deviceId", "type":"string","source":"device","field":"platform_device_id","required":true,"desc":"DCP设备ID"},
|
|
||||||
{"name":"epoch", "type":"string","source":"ts","required":true,"desc":"采集时间,ISO8601格式"},
|
|
||||||
{"name":"xDeg", "type":"number","source":"thingsModel","required":true,"desc":"X轴倾角(度)"},
|
|
||||||
{"name":"yDeg", "type":"number","source":"thingsModel","required":true,"desc":"Y轴倾角(度)"},
|
|
||||||
{"name":"temperature","type":"number","source":"thingsModel","required":false,"desc":"温度"}
|
|
||||||
]',
|
|
||||||
'[]',
|
|
||||||
9, '推送倾角传感器实时数据到DCP', 'system', NOW());
|
|
||||||
|
|
||||||
-- 10. 推送轴力计数据
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '推送轴力计数据', 'dcp_push_stress', 'POST', '/api/app/stress-data/many', 'application/json',
|
|
||||||
'[
|
|
||||||
{"name":"deviceId","type":"string","source":"device","field":"platform_device_id","required":true,"desc":"DCP设备ID"},
|
|
||||||
{"name":"epoch", "type":"string","source":"ts","required":true,"desc":"采集时间,ISO8601格式"},
|
|
||||||
{"name":"e", "type":"number","source":"thingsModel","required":true,"desc":"累计值(必须递增)"}
|
|
||||||
]',
|
|
||||||
'[]',
|
|
||||||
10, '推送轴力计实时数据到DCP', 'system', NOW());
|
|
||||||
|
|
||||||
-- 11. 推送雨量计数据
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '推送雨量计数据', 'dcp_push_hyeto', 'POST', '/api/app/hyeto-data/many', 'application/json',
|
|
||||||
'[
|
|
||||||
{"name":"deviceId","type":"string","source":"device","field":"platform_device_id","required":true,"desc":"DCP设备ID"},
|
|
||||||
{"name":"epoch", "type":"string","source":"ts","required":true,"desc":"采集时间,ISO8601格式"},
|
|
||||||
{"name":"value", "type":"number","source":"thingsModel","required":true,"desc":"累计雨量(必须递增)"}
|
|
||||||
]',
|
|
||||||
'[]',
|
|
||||||
11, '推送雨量计实时数据到DCP', 'system', NOW());
|
|
||||||
|
|
||||||
-- 12. 推送静力水准数据
|
|
||||||
INSERT INTO `platform_interface` (`platform_id`,`name`,`code`,`method`,`path`,`content_type`,`request_schema`,`response_schema`,`sort_order`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, '推送静力水准数据', 'dcp_push_static_level', 'POST', '/api/app/static-level-data/many', 'application/json',
|
|
||||||
'[
|
|
||||||
{"name":"deviceId", "type":"string","source":"device","field":"platform_device_id","required":true,"desc":"DCP设备ID"},
|
|
||||||
{"name":"sensorData","type":"array","source":"context","contextKey":"sensorData","required":true,"desc":"静力水准数组","itemSchema":[
|
|
||||||
{"name":"epoch","type":"string","source":"ts","desc":"采集时间"},
|
|
||||||
{"name":"height","type":"number","source":"thingsModel","desc":"高程"},
|
|
||||||
{"name":"temperature","type":"number","source":"thingsModel","desc":"温度"},
|
|
||||||
{"name":"sensorId","type":"string","source":"subDeviceId","desc":"DCP侧sensorId"}
|
|
||||||
]},
|
|
||||||
{"name":"ct","type":"number","source":"fixed","value":1,"required":true,"desc":"采集类型"}
|
|
||||||
]',
|
|
||||||
'[]',
|
|
||||||
12, '推送静力水准实时数据到DCP', 'system', NOW());
|
|
||||||
|
|
||||||
|
|
||||||
-- =============================================
|
|
||||||
-- 二、流程定义(7个)
|
|
||||||
-- =============================================
|
|
||||||
|
|
||||||
-- 流程1:平台初始化(flow_type=1)
|
|
||||||
INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, 'DCP平台初始化', 'dcp_init', 1, NULL, '1', '获取Token→userId,一次性执行', 'system', NOW());
|
|
||||||
|
|
||||||
SET @flow_init_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
-- 初始化流程步骤
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_init_id, 1,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_auth' LIMIT 1),
|
|
||||||
'step1',
|
|
||||||
'[{"field":"access_token","saveTo":"platform_instance.access_token"},{"field":"expires_in","saveTo":"platform_instance.token_expires_at"}]',
|
|
||||||
'获取access_token并持久化');
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_init_id, 2,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_get_user' LIMIT 1),
|
|
||||||
'step2',
|
|
||||||
'[{"field":"id","saveTo":"extra_config.userId"}]',
|
|
||||||
'获取userId并保存');
|
|
||||||
|
|
||||||
|
|
||||||
-- 流程2:Token刷新(flow_type=3)
|
|
||||||
INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, 'DCP Token刷新', 'dcp_token_refresh', 3, NULL, '1', 'Token过期时自动触发', 'system', NOW());
|
|
||||||
|
|
||||||
SET @flow_token_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_token_id, 1,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_auth' LIMIT 1),
|
|
||||||
'tokenStep',
|
|
||||||
'[{"field":"access_token","saveTo":"platform_instance.access_token"},{"field":"expires_in","saveTo":"platform_instance.token_expires_at"}]',
|
|
||||||
'获取新Token并持久化');
|
|
||||||
|
|
||||||
|
|
||||||
-- 流程3:倾角设备入网(flow_type=2)
|
|
||||||
INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, 'DCP倾角入网', 'dcp_onboard_inclino', 2, 'inclino', '1', '创建测点→绑定倾角传感器', 'system', NOW());
|
|
||||||
|
|
||||||
SET @flow_inclino_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_inclino_id, 1,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_create_point' LIMIT 1),
|
|
||||||
'step1',
|
|
||||||
'[{"field":"id","saveTo":"device_mapping.platform_point_id"}]',
|
|
||||||
'创建测点,获取pointId并回填');
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_inclino_id, 2,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_bind_inclino' LIMIT 1),
|
|
||||||
'step2',
|
|
||||||
'[{"field":"id","saveTo":"device_mapping.platform_device_id"}]',
|
|
||||||
'绑定传感器,获取DCP deviceId');
|
|
||||||
|
|
||||||
|
|
||||||
-- 流程4:轴力计设备入网(flow_type=2)
|
|
||||||
INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, 'DCP轴力计入网', 'dcp_onboard_stress', 2, 'stress', '1', '创建测点→绑定轴力计', 'system', NOW());
|
|
||||||
|
|
||||||
SET @flow_stress_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_stress_id, 1,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_create_point' LIMIT 1),
|
|
||||||
'step1',
|
|
||||||
'[{"field":"id","saveTo":"device_mapping.platform_point_id"}]',
|
|
||||||
'创建测点');
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_stress_id, 2,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_bind_stress' LIMIT 1),
|
|
||||||
'step2',
|
|
||||||
'[{"field":"id","saveTo":"device_mapping.platform_device_id"}]',
|
|
||||||
'绑定轴力计');
|
|
||||||
|
|
||||||
|
|
||||||
-- 流程5:雨量计设备入网(flow_type=2)
|
|
||||||
INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, 'DCP雨量计入网', 'dcp_onboard_hyeto', 2, 'hyeto', '1', '创建测点→绑定雨量计', 'system', NOW());
|
|
||||||
|
|
||||||
SET @flow_hyeto_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_hyeto_id, 1,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_create_point' LIMIT 1),
|
|
||||||
'step1',
|
|
||||||
'[{"field":"id","saveTo":"device_mapping.platform_point_id"}]',
|
|
||||||
'创建测点');
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_hyeto_id, 2,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_bind_hyeto' LIMIT 1),
|
|
||||||
'step2',
|
|
||||||
'[{"field":"id","saveTo":"device_mapping.platform_device_id"}]',
|
|
||||||
'绑定雨量计');
|
|
||||||
|
|
||||||
|
|
||||||
-- 流程6:静力水准设备入网(flow_type=2)
|
|
||||||
INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, 'DCP静力水准入网', 'dcp_onboard_static_level', 2, 'static_level', '1', '创建测点→绑定静力水准', 'system', NOW());
|
|
||||||
|
|
||||||
SET @flow_static_level_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_static_level_id, 1,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_create_point' LIMIT 1),
|
|
||||||
'step1',
|
|
||||||
'[{"field":"id","saveTo":"device_mapping.platform_point_id"}]',
|
|
||||||
'创建测点');
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_static_level_id, 2,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_bind_static_level' LIMIT 1),
|
|
||||||
'step2',
|
|
||||||
'[{"field":"id","saveTo":"device_mapping.platform_device_id"}]',
|
|
||||||
'绑定静力水准');
|
|
||||||
|
|
||||||
-- 流程7:深部位移设备入网(flow_type=2)
|
|
||||||
INSERT INTO `platform_flow` (`platform_id`,`name`,`code`,`flow_type`,`sensor_type`,`enable`,`remark`,`create_by`,`create_time`)
|
|
||||||
VALUES (@dcp_platform_id, 'DCP深部位移入网', 'dcp_onboard_deep_inclino', 2, 'deep_inclino', '1', '创建测点→绑定深部位移', 'system', NOW());
|
|
||||||
|
|
||||||
SET @flow_deep_inclino_id = LAST_INSERT_ID();
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_deep_inclino_id, 1,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_create_point' LIMIT 1),
|
|
||||||
'step1',
|
|
||||||
'[{"field":"id","saveTo":"device_mapping.platform_point_id"}]',
|
|
||||||
'创建测点');
|
|
||||||
|
|
||||||
INSERT INTO `platform_flow_step` (`flow_id`,`step_order`,`interface_id`,`result_key`,`result_save`,`remark`)
|
|
||||||
VALUES (@flow_deep_inclino_id, 2,
|
|
||||||
(SELECT id FROM platform_interface WHERE platform_id=@dcp_platform_id AND code='dcp_bind_deep_inclino' LIMIT 1),
|
|
||||||
'step2',
|
|
||||||
'[{"field":"id","saveTo":"device_mapping.platform_device_id"}]',
|
|
||||||
'绑定深部位移');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- 更新已落库的平台接口配置,去掉 fixed:auto 和静力水准扁平 sensorIds 配置。
|
|
||||||
-- 执行前确认平台实例 code,如不是 dcp,请手动替换 @platform_id。
|
|
||||||
|
|
||||||
SET @platform_id = (SELECT id FROM platform_instance WHERE code = 'dcp' LIMIT 1);
|
|
||||||
|
|
||||||
UPDATE platform_interface
|
|
||||||
SET request_schema = '[
|
|
||||||
{"name":"projectId","type":"string","source":"device","field":"platform_project_id","required":true,"desc":"项目ID"},
|
|
||||||
{"name":"name", "type":"string","source":"device","field":"serial_number","required":true,"desc":"测点名称,用设备序列号"},
|
|
||||||
{"name":"type", "type":"number","source":"fixed","value":3,"required":true,"desc":"测点类型,固定为3"}
|
|
||||||
]', update_by = 'system', update_time = NOW()
|
|
||||||
WHERE platform_id = @platform_id AND code = 'dcp_create_point';
|
|
||||||
|
|
||||||
DELETE s FROM platform_flow_step s
|
|
||||||
INNER JOIN platform_flow f ON f.id = s.flow_id
|
|
||||||
WHERE f.platform_id = @platform_id AND f.code = 'dcp_init' AND s.step_order > 2;
|
|
||||||
|
|
||||||
UPDATE platform_flow
|
|
||||||
SET remark = '获取Token→userId,一次性执行', update_by = 'system', update_time = NOW()
|
|
||||||
WHERE platform_id = @platform_id AND code = 'dcp_init';
|
|
||||||
|
|
||||||
UPDATE platform_interface
|
|
||||||
SET request_schema = '[
|
|
||||||
{"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true,"desc":"项目ID"},
|
|
||||||
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID,来自创建测点步骤"},
|
|
||||||
{"name":"deviceType", "type":"number","source":"fixed", "value":5, "required":true,"desc":"设备类型,倾角=5"},
|
|
||||||
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"},
|
|
||||||
{"name":"moduleId", "type":"string","source":"uuid", "required":true,"desc":"模块ID,系统自动生成UUID"},
|
|
||||||
{"name":"virtualId", "type":"string","source":"uuid", "required":true,"desc":"虚拟ID,系统自动生成UUID"},
|
|
||||||
{"name":"dH", "type":"number","source":"fixed", "value":0, "required":false,"desc":"高程"}
|
|
||||||
]',
|
|
||||||
update_by = 'system',
|
|
||||||
update_time = NOW()
|
|
||||||
WHERE platform_id = @platform_id
|
|
||||||
AND code = 'dcp_bind_inclino';
|
|
||||||
|
|
||||||
UPDATE platform_interface
|
|
||||||
SET request_schema = '[
|
|
||||||
{"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true,"desc":"项目ID"},
|
|
||||||
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true,"desc":"测点ID"},
|
|
||||||
{"name":"deviceType", "type":"number","source":"fixed", "value":10, "required":true,"desc":"设备类型,轴力计=10"},
|
|
||||||
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true,"desc":"传感器名称"},
|
|
||||||
{"name":"factor", "type":"number","source":"fixed", "value":1, "required":false,"desc":"系数"},
|
|
||||||
{"name":"moduleId", "type":"string","source":"uuid", "required":true},
|
|
||||||
{"name":"virtualId", "type":"string","source":"uuid", "required":true}
|
|
||||||
]',
|
|
||||||
update_by = 'system',
|
|
||||||
update_time = NOW()
|
|
||||||
WHERE platform_id = @platform_id
|
|
||||||
AND code = 'dcp_bind_stress';
|
|
||||||
|
|
||||||
UPDATE platform_interface
|
|
||||||
SET request_schema = '[
|
|
||||||
{"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true},
|
|
||||||
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true},
|
|
||||||
{"name":"deviceType", "type":"number","source":"fixed", "value":3, "required":true,"desc":"雨量计=3"},
|
|
||||||
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true},
|
|
||||||
{"name":"moduleId", "type":"string","source":"uuid", "required":true},
|
|
||||||
{"name":"virtualId", "type":"string","source":"uuid", "required":true}
|
|
||||||
]',
|
|
||||||
update_by = 'system',
|
|
||||||
update_time = NOW()
|
|
||||||
WHERE platform_id = @platform_id
|
|
||||||
AND code = 'dcp_bind_hyeto';
|
|
||||||
|
|
||||||
UPDATE platform_interface
|
|
||||||
SET request_schema = '[
|
|
||||||
{"name":"projectId", "type":"string","source":"device", "field":"platform_project_id", "required":true},
|
|
||||||
{"name":"pointId", "type":"string","source":"context","contextKey":"step1.id","required":true},
|
|
||||||
{"name":"deviceType", "type":"number","source":"fixed", "value":1002, "required":true,"desc":"静力水准=1002"},
|
|
||||||
{"name":"name", "type":"string","source":"device", "field":"serial_number","required":true},
|
|
||||||
{"name":"moduleId", "type":"string","source":"uuid", "required":true},
|
|
||||||
{"name":"virtualId", "type":"string","source":"uuid", "required":true},
|
|
||||||
{"name":"sensorIds", "type":"array", "source":"referenceSubDeviceSensorIdPairs","required":true,"desc":"基准点与每个监测点成对绑定"}
|
|
||||||
]',
|
|
||||||
update_by = 'system',
|
|
||||||
update_time = NOW()
|
|
||||||
WHERE platform_id = @platform_id
|
|
||||||
AND code = 'dcp_bind_static_level';
|
|
||||||
|
|
||||||
SELECT code, request_schema
|
|
||||||
FROM platform_interface
|
|
||||||
WHERE platform_id = @platform_id
|
|
||||||
AND code IN ('dcp_bind_inclino', 'dcp_bind_stress', 'dcp_bind_hyeto', 'dcp_bind_static_level');
|
|
||||||
@ -113,6 +113,7 @@
|
|||||||
"device.device-edit.148398-82": "Voice intercom",
|
"device.device-edit.148398-82": "Voice intercom",
|
||||||
"device.device-edit.148398-83": "Calculation Formula",
|
"device.device-edit.148398-83": "Calculation Formula",
|
||||||
"device.device-edit.148398-84": "History Records",
|
"device.device-edit.148398-84": "History Records",
|
||||||
|
"device.device-edit.148398-85": "Device Threshold",
|
||||||
"device.device-functionlog.399522-0": "Please Select A Device Slave:",
|
"device.device-functionlog.399522-0": "Please Select A Device Slave:",
|
||||||
"device.device-functionlog.399522-1": "Please Select A Device Slave",
|
"device.device-functionlog.399522-1": "Please Select A Device Slave",
|
||||||
"device.device-functionlog.399522-2": "Slave Address:$",
|
"device.device-functionlog.399522-2": "Slave Address:$",
|
||||||
|
|||||||
@ -200,6 +200,7 @@
|
|||||||
"product.product-edit.473153-82": "Please select the protocol",
|
"product.product-edit.473153-82": "Please select the protocol",
|
||||||
"product.product-edit.473153-83": "Sub products",
|
"product.product-edit.473153-83": "Sub products",
|
||||||
"product.product-edit.473153-84": "Polling task",
|
"product.product-edit.473153-84": "Polling task",
|
||||||
|
"product.product-edit.473153-85": "Product Threshold",
|
||||||
"product.product-firmware.420545-0": "Add",
|
"product.product-firmware.420545-0": "Add",
|
||||||
"product.product-firmware.420545-1": "Refresh",
|
"product.product-firmware.420545-1": "Refresh",
|
||||||
"product.product-firmware.420545-2": "Firmware Name",
|
"product.product-firmware.420545-2": "Firmware Name",
|
||||||
|
|||||||
@ -113,6 +113,7 @@
|
|||||||
"device.device-edit.148398-82": "语音对讲",
|
"device.device-edit.148398-82": "语音对讲",
|
||||||
"device.device-edit.148398-83": "计算公式",
|
"device.device-edit.148398-83": "计算公式",
|
||||||
"device.device-edit.148398-84": "历史记录",
|
"device.device-edit.148398-84": "历史记录",
|
||||||
|
"device.device-edit.148398-85": "设备阈值",
|
||||||
"device.device-functionlog.399522-0": "请选择设备从机:",
|
"device.device-functionlog.399522-0": "请选择设备从机:",
|
||||||
"device.device-functionlog.399522-1": "请选择设备从机",
|
"device.device-functionlog.399522-1": "请选择设备从机",
|
||||||
"device.device-functionlog.399522-2": "从机地址:$",
|
"device.device-functionlog.399522-2": "从机地址:$",
|
||||||
|
|||||||
@ -200,6 +200,7 @@
|
|||||||
"product.product-edit.473153-82": "请选择协议",
|
"product.product-edit.473153-82": "请选择协议",
|
||||||
"product.product-edit.473153-83": "子产品",
|
"product.product-edit.473153-83": "子产品",
|
||||||
"product.product-edit.473153-84": "轮询任务",
|
"product.product-edit.473153-84": "轮询任务",
|
||||||
|
"product.product-edit.473153-85": "产品阈值",
|
||||||
"product.product-firmware.420545-0": "新增",
|
"product.product-firmware.420545-0": "新增",
|
||||||
"product.product-firmware.420545-1": "刷新",
|
"product.product-firmware.420545-1": "刷新",
|
||||||
"product.product-firmware.420545-2": "固件名称",
|
"product.product-firmware.420545-2": "固件名称",
|
||||||
@ -551,4 +552,4 @@
|
|||||||
"product.product-things-model.142341-201": "引用产品物模型标识",
|
"product.product-things-model.142341-201": "引用产品物模型标识",
|
||||||
"product.product-things-model.142341-202": "暂无可用标识",
|
"product.product-things-model.142341-202": "暂无可用标识",
|
||||||
"product.product-things-model.142341-203": "新增计算模型"
|
"product.product-things-model.142341-203": "新增计算模型"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,6 @@
|
|||||||
<template slot-scope="scope">{{ scope.row.startTime }} ~ {{ scope.row.endTime }}</template>
|
<template slot-scope="scope">{{ scope.row.startTime }} ~ {{ scope.row.endTime }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="生成时间" prop="createTime" width="160" align="center" />
|
<el-table-column label="生成时间" prop="createTime" width="160" align="center" />
|
||||||
<el-table-column label="过期时间" prop="expireTime" width="160" align="center" />
|
|
||||||
<el-table-column label="状态" min-width="90" align="center">
|
<el-table-column label="状态" min-width="90" align="center">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-tag :type="statusType(scope.row.status)" size="mini">{{ statusText(scope.row.status) }}</el-tag>
|
<el-tag :type="statusType(scope.row.status)" size="mini">{{ statusText(scope.row.status) }}</el-tag>
|
||||||
@ -54,7 +53,7 @@
|
|||||||
type="success"
|
type="success"
|
||||||
icon="el-icon-download"
|
icon="el-icon-download"
|
||||||
size="mini"
|
size="mini"
|
||||||
:disabled="scope.row.status !== 'SUCCESS' || isExpired(scope.row)"
|
:disabled="scope.row.status !== 'SUCCESS'"
|
||||||
@click="handleDownload(scope.row)"
|
@click="handleDownload(scope.row)"
|
||||||
>下载</el-button>
|
>下载</el-button>
|
||||||
</template>
|
</template>
|
||||||
@ -149,9 +148,6 @@ export default {
|
|||||||
this.$message.error('下载失败,请稍后重试');
|
this.$message.error('下载失败,请稍后重试');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isExpired(row) {
|
|
||||||
return !!row.expireTime && new Date(row.expireTime.replace(/-/g, '/')).getTime() < Date.now();
|
|
||||||
},
|
|
||||||
categoryText(code) {
|
categoryText(code) {
|
||||||
const map = {
|
const map = {
|
||||||
CRACK_METER: '裂缝计',
|
CRACK_METER: '裂缝计',
|
||||||
|
|||||||
301
yack-web/src/views/iot/components/threshold-config.vue
Normal file
301
yack-web/src/views/iot/components/threshold-config.vue
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
<template>
|
||||||
|
<div style="padding-left: 10px">
|
||||||
|
<el-row :gutter="10" class="mb8">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" plain icon="el-icon-check" size="mini" :loading="saving" @click="save">
|
||||||
|
保存阈值
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="warning" plain icon="el-icon-refresh" size="mini" @click="load">刷新</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="14">
|
||||||
|
<el-link type="info" :underline="false" style="padding-top: 5px">
|
||||||
|
{{ tipText }}
|
||||||
|
</el-link>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="rows" size="mini" border>
|
||||||
|
<el-table-column label="物模型名称" align="center" prop="modelName" min-width="140" />
|
||||||
|
<el-table-column label="标识符" align="center" prop="identifier" min-width="120" />
|
||||||
|
<el-table-column label="数据类型" align="center" width="90">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<dict-tag :options="dict.type.iot_data_type" :value="scope.row.datatype" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="单位" align="center" width="80" prop="unit" />
|
||||||
|
<el-table-column label="预警值" align="center" width="140">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input v-model="scope.row.warningValue" size="mini" type="number" placeholder="未设置" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="报警值" align="center" width="140">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input v-model="scope.row.alarmValue" size="mini" type="number" placeholder="未设置" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="控制值" align="center" width="140">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input v-model="scope.row.controlValue" size="mini" type="number" placeholder="未设置" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="速率预警值" align="center" width="140">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input v-model="scope.row.warningRate" size="mini" type="number" placeholder="单位/天" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="速率报警值" align="center" width="140">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input v-model="scope.row.alarmRate" size="mini" type="number" placeholder="单位/天" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="速率控制值" align="center" width="140">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-input v-model="scope.row.controlRate" size="mini" type="number" placeholder="单位/天" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="scopeType === 'device'" label="产品阈值(继承)" align="center" min-width="160">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span v-if="parentMap[scope.row.identifier]">{{ formatThreshold(parentMap[scope.row.identifier]) }}</span>
|
||||||
|
<span v-else style="color: #c0c4cc">未配置</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { listModel } from '@/api/iot/model';
|
||||||
|
import { getProduct } from '@/api/iot/product';
|
||||||
|
import { listThreshold, addThreshold, updateThreshold, delThreshold } from '@/api/iot/threshold';
|
||||||
|
|
||||||
|
/** 仅数值型物模型可配置阈值 */
|
||||||
|
const NUMERIC_TYPES = ['integer', 'decimal'];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ThresholdConfig',
|
||||||
|
dicts: ['iot_data_type'],
|
||||||
|
props: {
|
||||||
|
/** 产品ID */
|
||||||
|
productId: {
|
||||||
|
type: [Number, String],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/** 设备ID,产品级传 0 */
|
||||||
|
deviceId: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
/** 作用域:product=产品级,device=设备级 */
|
||||||
|
scopeType: {
|
||||||
|
type: String,
|
||||||
|
default: 'product',
|
||||||
|
validator: (v) => ['product', 'device'].indexOf(v) !== -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
saving: false,
|
||||||
|
rows: [],
|
||||||
|
/** 上级(产品级)阈值,仅设备级展示 */
|
||||||
|
parentMap: {},
|
||||||
|
monitorCategoryCode: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
tipText() {
|
||||||
|
return this.scopeType === 'device'
|
||||||
|
? '设备阈值优先级高于产品阈值;留空则继承所属产品的阈值'
|
||||||
|
: '产品阈值对该产品下所有设备生效,可被单个设备的设备阈值覆盖';
|
||||||
|
},
|
||||||
|
/** 保存阈值时落库的 deviceId:产品级为 0 */
|
||||||
|
targetDeviceId() {
|
||||||
|
return this.scopeType === 'device' ? this.toNum(this.deviceId) : 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
productId() {
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
deviceId() {
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.load();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/** 入参可能为字符串(路由/表单来源),统一转数字 */
|
||||||
|
toNum(v) {
|
||||||
|
const n = Number(v);
|
||||||
|
return Number.isNaN(n) ? 0 : n;
|
||||||
|
},
|
||||||
|
/** 解析 specs 中的单位 */
|
||||||
|
parseUnit(specs) {
|
||||||
|
if (!specs) return '';
|
||||||
|
try {
|
||||||
|
const obj = typeof specs === 'string' ? JSON.parse(specs) : specs;
|
||||||
|
return obj && obj.unit ? obj.unit : '';
|
||||||
|
} catch (e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 加载:产品监测类别码 -> 物模型 -> 本级阈值(设备级额外加载产品级阈值) */
|
||||||
|
load() {
|
||||||
|
if (this.toNum(this.productId) <= 0) {
|
||||||
|
this.rows = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
this.loadCategoryCode()
|
||||||
|
.then(() => this.loadModels())
|
||||||
|
.then(() => this.loadThresholds())
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 监测类别码从产品继承 */
|
||||||
|
loadCategoryCode() {
|
||||||
|
return getProduct(this.productId)
|
||||||
|
.then((res) => {
|
||||||
|
this.monitorCategoryCode = (res.data && res.data.monitorCategoryCode) || '';
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.monitorCategoryCode = '';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 物模型:产品级取产品物模型;设备级额外并入设备级物模型 */
|
||||||
|
loadModels() {
|
||||||
|
const base = { productId: this.productId, deviceId: 0, pageNum: 1, pageSize: 500 };
|
||||||
|
const tasks = [listModel(base)];
|
||||||
|
if (this.scopeType === 'device' && this.toNum(this.deviceId) > 0) {
|
||||||
|
tasks.push(listModel({ deviceId: this.deviceId, pageNum: 1, pageSize: 500 }));
|
||||||
|
}
|
||||||
|
return Promise.all(tasks).then((responses) => {
|
||||||
|
const merged = new Map();
|
||||||
|
responses.forEach((res) => {
|
||||||
|
const list = (res && res.rows) || [];
|
||||||
|
list.forEach((m) => {
|
||||||
|
if (!m || !m.identifier) return;
|
||||||
|
if (NUMERIC_TYPES.indexOf(m.datatype) === -1) return;
|
||||||
|
if (!merged.has(m.identifier)) {
|
||||||
|
merged.set(m.identifier, {
|
||||||
|
modelName: m.modelName,
|
||||||
|
identifier: m.identifier,
|
||||||
|
datatype: m.datatype,
|
||||||
|
unit: this.parseUnit(m.specs),
|
||||||
|
id: undefined,
|
||||||
|
warningValue: undefined,
|
||||||
|
alarmValue: undefined,
|
||||||
|
controlValue: undefined,
|
||||||
|
warningRate: undefined,
|
||||||
|
alarmRate: undefined,
|
||||||
|
controlRate: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.rows = Array.from(merged.values());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 阈值回填 */
|
||||||
|
loadThresholds() {
|
||||||
|
const tasks = [listThreshold({ productId: this.productId, deviceId: this.targetDeviceId, pageSize: 500 })];
|
||||||
|
if (this.scopeType === 'device') {
|
||||||
|
tasks.push(listThreshold({ productId: this.productId, deviceId: 0, pageSize: 500 }));
|
||||||
|
}
|
||||||
|
return Promise.all(tasks).then((responses) => {
|
||||||
|
const own = (responses[0] && (responses[0].rows || responses[0].data)) || [];
|
||||||
|
const ownMap = {};
|
||||||
|
own.forEach((t) => {
|
||||||
|
if (t && t.metricIdentifier) ownMap[t.metricIdentifier] = t;
|
||||||
|
});
|
||||||
|
this.rows.forEach((row) => {
|
||||||
|
const t = ownMap[row.identifier];
|
||||||
|
row.id = t ? t.id : undefined;
|
||||||
|
row.warningValue = t && t.warningValue !== null ? t.warningValue : undefined;
|
||||||
|
row.alarmValue = t && t.alarmValue !== null ? t.alarmValue : undefined;
|
||||||
|
row.controlValue = t && t.controlValue !== null ? t.controlValue : undefined;
|
||||||
|
row.warningRate = t && t.warningRate !== null ? t.warningRate : undefined;
|
||||||
|
row.alarmRate = t && t.alarmRate !== null ? t.alarmRate : undefined;
|
||||||
|
row.controlRate = t && t.controlRate !== null ? t.controlRate : undefined;
|
||||||
|
});
|
||||||
|
if (this.scopeType === 'device') {
|
||||||
|
const parent = (responses[1] && (responses[1].rows || responses[1].data)) || [];
|
||||||
|
const map = {};
|
||||||
|
parent.forEach((t) => {
|
||||||
|
if (t && t.metricIdentifier) map[t.metricIdentifier] = t;
|
||||||
|
});
|
||||||
|
this.parentMap = map;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatThreshold(t) {
|
||||||
|
if (!t) return '';
|
||||||
|
const parts = [];
|
||||||
|
if (t.warningValue !== null && t.warningValue !== undefined) parts.push('预警 ' + t.warningValue);
|
||||||
|
if (t.alarmValue !== null && t.alarmValue !== undefined) parts.push('报警 ' + t.alarmValue);
|
||||||
|
if (t.controlValue !== null && t.controlValue !== undefined) parts.push('控制 ' + t.controlValue);
|
||||||
|
return parts.length ? parts.join(' / ') : '未配置';
|
||||||
|
},
|
||||||
|
/** 是否有任一阈值值 */
|
||||||
|
hasAnyValue(row) {
|
||||||
|
return [
|
||||||
|
row.warningValue,
|
||||||
|
row.alarmValue,
|
||||||
|
row.controlValue,
|
||||||
|
row.warningRate,
|
||||||
|
row.alarmRate,
|
||||||
|
row.controlRate,
|
||||||
|
].some((v) => v !== undefined && v !== null && v !== '');
|
||||||
|
},
|
||||||
|
/** 批量保存:有值新增/更新,清空则删除已有记录 */
|
||||||
|
save() {
|
||||||
|
if (this.toNum(this.productId) <= 0) {
|
||||||
|
this.$modal.msgError('请先保存产品信息');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tasks = [];
|
||||||
|
this.rows.forEach((row) => {
|
||||||
|
const payload = {
|
||||||
|
productId: this.toNum(this.productId),
|
||||||
|
deviceId: this.targetDeviceId,
|
||||||
|
monitorCategoryCode: this.monitorCategoryCode,
|
||||||
|
metricIdentifier: row.identifier,
|
||||||
|
unit: row.unit,
|
||||||
|
warningValue: row.warningValue === '' ? undefined : row.warningValue,
|
||||||
|
alarmValue: row.alarmValue === '' ? undefined : row.alarmValue,
|
||||||
|
controlValue: row.controlValue === '' ? undefined : row.controlValue,
|
||||||
|
warningRate: row.warningRate === '' ? undefined : row.warningRate,
|
||||||
|
alarmRate: row.alarmRate === '' ? undefined : row.alarmRate,
|
||||||
|
controlRate: row.controlRate === '' ? undefined : row.controlRate,
|
||||||
|
};
|
||||||
|
if (this.hasAnyValue(row)) {
|
||||||
|
if (row.id) {
|
||||||
|
tasks.push(updateThreshold(Object.assign({ id: row.id }, payload)));
|
||||||
|
} else {
|
||||||
|
tasks.push(addThreshold(payload));
|
||||||
|
}
|
||||||
|
} else if (row.id) {
|
||||||
|
tasks.push(delThreshold(row.id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!tasks.length) {
|
||||||
|
this.$modal.msgWarning('没有需要保存的阈值');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.saving = true;
|
||||||
|
Promise.all(tasks)
|
||||||
|
.then(() => {
|
||||||
|
this.$modal.msgSuccess('阈值保存成功');
|
||||||
|
this.load();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.saving = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@ -54,6 +54,25 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<!-- 基准点设备:相对测量类设备(静力水准等)需指定基准点,留空表示绝对量测量 -->
|
||||||
|
<el-form-item v-if="form.deviceType != 2" prop="baselineDeviceId">
|
||||||
|
<template slot="label">
|
||||||
|
<el-tooltip placement="top">
|
||||||
|
<div slot="content">
|
||||||
|
相对测量类设备(如静力水准)需指定基准点。<br/>
|
||||||
|
累计变化量按「(当前读数−基准点读数)−(初始读数−基准点初始读数)」换算。<br/>
|
||||||
|
留空表示绝对量测量,不参与基准点换算。<br/>
|
||||||
|
deviceId 全局唯一,支持跨网关引用(如 GW2 监测点引用 GW1 参考点)。
|
||||||
|
</div>
|
||||||
|
<span>基准点设备 <i class="el-icon-question"></i></span>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
<el-select v-model="form.baselineDeviceId" placeholder="留空=绝对量测量,不参与基准点换算" clearable
|
||||||
|
filterable size="small" style="width: 100%">
|
||||||
|
<el-option v-for="item in baselineDeviceOptions" :key="item.deviceId"
|
||||||
|
:label="item.deviceName + '(' + item.serialNumber + ')'" :value="Number(item.deviceId)"/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item v-if="openServerTip">
|
<el-form-item v-if="openServerTip">
|
||||||
<template>
|
<template>
|
||||||
<el-alert type="info" show-icon :description="$t('device.device-edit.148398-10')"></el-alert>
|
<el-alert type="info" show-icon :description="$t('device.device-edit.148398-10')"></el-alert>
|
||||||
@ -234,6 +253,17 @@
|
|||||||
<device-log ref="deviceLog" :device="form"/>
|
<device-log ref="deviceLog" :device="form"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!-- 设备阈值(优先级高于产品阈值,留空则继承产品阈值) -->
|
||||||
|
<el-tab-pane name="deviceThreshold" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3" lazy>
|
||||||
|
<span slot="label">{{ $t('device.device-edit.148398-85') }}</span>
|
||||||
|
<threshold-config
|
||||||
|
ref="deviceThreshold"
|
||||||
|
:product-id="form.productId"
|
||||||
|
:device-id="form.deviceId"
|
||||||
|
scope-type="device"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-tab-pane name="alertUser" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3">
|
<el-tab-pane name="alertUser" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3">
|
||||||
<span slot="label" v-hasPermi="['iot:device:alert:user:list']">{{ $t('device.device-edit.148398-80') }}</span>
|
<span slot="label" v-hasPermi="['iot:device:alert:user:list']">{{ $t('device.device-edit.148398-80') }}</span>
|
||||||
<alert-user ref="alertUser" :device="form"/>
|
<alert-user ref="alertUser" :device="form"/>
|
||||||
@ -243,20 +273,20 @@
|
|||||||
<device-alert ref="deviceAlert" :device="form"/>
|
<device-alert ref="deviceAlert" :device="form"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-tab-pane name="deviceFuncLog" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3" lazy>
|
<!-- <el-tab-pane name="deviceFuncLog" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3" lazy>
|
||||||
<span slot="label">{{ $t('device.device-edit.148398-50') }}</span>
|
<span slot="label">{{ $t('device.device-edit.148398-50') }}</span>
|
||||||
<device-func ref="deviceFuncLog" :device="form"/>
|
<device-func ref="deviceFuncLog" :device="form"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane> -->
|
||||||
|
|
||||||
<el-tab-pane name="deviceMonitor" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3">
|
<!-- <el-tab-pane name="deviceMonitor" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3">
|
||||||
<span slot="label">{{ $t('device.device-edit.148398-51') }}</span>
|
<span slot="label">{{ $t('device.device-edit.148398-51') }}</span>
|
||||||
<device-monitor ref="deviceMonitor" :device="form"/>
|
<device-monitor ref="deviceMonitor" :device="form"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane> -->
|
||||||
|
|
||||||
<el-tab-pane name="deviceStastic" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3">
|
<!-- <el-tab-pane name="deviceStastic" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3">
|
||||||
<span slot="label">{{ $t('device.device-edit.148398-52') }}</span>
|
<span slot="label">{{ $t('device.device-edit.148398-52') }}</span>
|
||||||
<device-statistic ref="deviceStatistic" :device="form"/>
|
<device-statistic ref="deviceStatistic" :device="form"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane> -->
|
||||||
|
|
||||||
<!-- 轮询任务 -->
|
<!-- 轮询任务 -->
|
||||||
<el-tab-pane name="deviceModbusTask" :disabled="form.deviceId == 0" v-if="form.canConfigPoll === true" lazy>
|
<el-tab-pane name="deviceModbusTask" :disabled="form.deviceId == 0" v-if="form.canConfigPoll === true" lazy>
|
||||||
@ -279,10 +309,10 @@
|
|||||||
<device-variable ref="deviceVariable" :device="form"/>
|
<device-variable ref="deviceVariable" :device="form"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-tab-pane name="inlineVideo" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3" lazy>
|
<!-- <el-tab-pane name="inlineVideo" :disabled="form.deviceId == 0" v-if="form.deviceType !== 3" lazy>
|
||||||
<span slot="label">{{ $t('device.device-edit.148398-75') }}</span>
|
<span slot="label">{{ $t('device.device-edit.148398-75') }}</span>
|
||||||
<device-inline-video ref="deviceInlineVideo" :sipRelationList="form.sipRelationList"/>
|
<device-inline-video ref="deviceInlineVideo" :sipRelationList="form.sipRelationList"/>
|
||||||
</el-tab-pane>
|
</el-tab-pane> -->
|
||||||
|
|
||||||
<!-- 用于设置间距 -->
|
<!-- 用于设置间距 -->
|
||||||
<el-tab-pane disabled>
|
<el-tab-pane disabled>
|
||||||
@ -436,7 +466,8 @@ import {
|
|||||||
updateDevice,
|
updateDevice,
|
||||||
generatorDeviceNum,
|
generatorDeviceNum,
|
||||||
getMqttConnect,
|
getMqttConnect,
|
||||||
assignDeviceUser
|
assignDeviceUser,
|
||||||
|
listDeviceShort
|
||||||
} from '@/api/iot/device';
|
} from '@/api/iot/device';
|
||||||
import {getDeviceRunningStatus} from '@/api/iot/device';
|
import {getDeviceRunningStatus} from '@/api/iot/device';
|
||||||
import {listUser} from '@/api/system/user';
|
import {listUser} from '@/api/system/user';
|
||||||
@ -446,6 +477,7 @@ import DeviceFunc from '@/views/iot/device/device-functionlog';
|
|||||||
import RealTimeStatus from '@/views/iot/device/realTime-status';
|
import RealTimeStatus from '@/views/iot/device/realTime-status';
|
||||||
import {clientOut} from '@/api/iot/netty';
|
import {clientOut} from '@/api/iot/netty';
|
||||||
import defaultSettings from '@/settings';
|
import defaultSettings from '@/settings';
|
||||||
|
import thresholdConfig from '@/views/iot/components/threshold-config';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DeviceEdit',
|
name: 'DeviceEdit',
|
||||||
@ -480,6 +512,7 @@ export default {
|
|||||||
deviceInlineVideo,
|
deviceInlineVideo,
|
||||||
deviceThingsModel,
|
deviceThingsModel,
|
||||||
DeviceHistoryTablePanel,
|
DeviceHistoryTablePanel,
|
||||||
|
thresholdConfig,
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
activeName(val) {
|
activeName(val) {
|
||||||
@ -489,6 +522,12 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 产品变更(含编辑态回显、新增态选产品)时刷新基准点候选设备
|
||||||
|
'form.productId': {
|
||||||
|
handler(val) {
|
||||||
|
this.loadBaselineDeviceOptions();
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
deviceStatus: {
|
deviceStatus: {
|
||||||
@ -542,6 +581,8 @@ export default {
|
|||||||
phonenumber: undefined,
|
phonenumber: undefined,
|
||||||
status: 0,
|
status: 0,
|
||||||
},
|
},
|
||||||
|
// 基准点候选设备列表(同产品下的其他设备,支持跨网关)
|
||||||
|
baselineDeviceOptions: [],
|
||||||
// 设备开始状态
|
// 设备开始状态
|
||||||
oldDeviceStatus: null,
|
oldDeviceStatus: null,
|
||||||
deviceId: '',
|
deviceId: '',
|
||||||
@ -555,6 +596,8 @@ export default {
|
|||||||
serialNumber: '',
|
serialNumber: '',
|
||||||
deviceType: 1,
|
deviceType: 1,
|
||||||
isSimulate: 0,
|
isSimulate: 0,
|
||||||
|
// 基准点设备ID,null/0 表示绝对量测量,不参与基准点换算
|
||||||
|
baselineDeviceId: null,
|
||||||
},
|
},
|
||||||
//mqtt参数查看
|
//mqtt参数查看
|
||||||
listQuery: {
|
listQuery: {
|
||||||
@ -627,6 +670,22 @@ export default {
|
|||||||
this.mqttUnSubscribe(this.form);
|
this.mqttUnSubscribe(this.form);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/** 加载基准点候选设备:同产品下的其他设备(排除自身,支持跨网关引用) **/
|
||||||
|
loadBaselineDeviceOptions() {
|
||||||
|
if (!this.form.productId) {
|
||||||
|
this.baselineDeviceOptions = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listDeviceShort({productId: this.form.productId, pageSize: 1000}).then((response) => {
|
||||||
|
const rows = (response && response.rows) || [];
|
||||||
|
// 排除自身:基准点不能指向自己
|
||||||
|
this.baselineDeviceOptions = rows.filter(
|
||||||
|
(item) => Number(item.deviceId) !== Number(this.form.deviceId)
|
||||||
|
);
|
||||||
|
}).catch(() => {
|
||||||
|
this.baselineDeviceOptions = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
openAssignUserDialog() {
|
openAssignUserDialog() {
|
||||||
this.assignUserOpen = true;
|
this.assignUserOpen = true;
|
||||||
this.selectedAssignUser = null;
|
this.selectedAssignUser = null;
|
||||||
@ -1088,6 +1147,9 @@ export default {
|
|||||||
|
|
||||||
this.$refs['form'].validate((valid) => {
|
this.$refs['form'].validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
|
// 基准点未选择时归一化为 0:后端 <if test="baselineDeviceId != null"> 才能落库覆盖旧值
|
||||||
|
// 0 与 null 在 ReportContextBuilder 中均表示「绝对量测量,不参与基准点换算」
|
||||||
|
this.form.baselineDeviceId = this.form.baselineDeviceId || 0;
|
||||||
if (this.form.deviceId != 0) {
|
if (this.form.deviceId != 0) {
|
||||||
updateDevice(this.form).then((response) => {
|
updateDevice(this.form).then((response) => {
|
||||||
if (response.data == 0) {
|
if (response.data == 0) {
|
||||||
|
|||||||
@ -233,6 +233,17 @@
|
|||||||
<product-modbus ref="productModbus" :product="form" @sendPollType="sendPollType" />
|
<product-modbus ref="productModbus" :product="form" @sendPollType="sendPollType" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!-- 产品阈值(对该产品下所有设备生效,可被单个设备的设备阈值覆盖) -->
|
||||||
|
<el-tab-pane name="productThreshold" :disabled="form.productId == 0" v-if="form.deviceType !== 3" lazy>
|
||||||
|
<span slot="label">{{ $t('product.product-edit.473153-85') }}</span>
|
||||||
|
<threshold-config
|
||||||
|
ref="productThreshold"
|
||||||
|
:product-id="form.productId"
|
||||||
|
:device-id="0"
|
||||||
|
scope-type="product"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
<!-- 轮询任务(已隐藏) -->
|
<!-- 轮询任务(已隐藏) -->
|
||||||
<!-- <el-tab-pane
|
<!-- <el-tab-pane
|
||||||
name="productModbusTask"
|
name="productModbusTask"
|
||||||
@ -309,6 +320,7 @@ import { getAllPoints } from '@/api/iot/template';
|
|||||||
import { listMonitorCategories } from '@/api/iot/report';
|
import { listMonitorCategories } from '@/api/iot/report';
|
||||||
import ProductModbus from '@/views/iot/product/product-modbus';
|
import ProductModbus from '@/views/iot/product/product-modbus';
|
||||||
import defaultSettings from '@/settings';
|
import defaultSettings from '@/settings';
|
||||||
|
import thresholdConfig from '@/views/iot/components/threshold-config';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ProductEdit',
|
name: 'ProductEdit',
|
||||||
@ -324,6 +336,7 @@ export default {
|
|||||||
productScada,
|
productScada,
|
||||||
productSub,
|
productSub,
|
||||||
productModbusTask,
|
productModbusTask,
|
||||||
|
thresholdConfig,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user