reportEngine:导出报告支持指定物模型
This commit is contained in:
parent
426cdb2567
commit
7039f43964
@ -11,6 +11,8 @@ import java.util.List;
|
||||
* 报告生成请求
|
||||
*
|
||||
* <p>deviceId 必填;startTime/endTime 与 period 二选一(都传时以自定义时间为准)。</p>
|
||||
* <p>identifiers 选填:指定后只分析所选物模型(物理量),为空表示分析产品下全部监测物模型;
|
||||
* 多测点(deviceIds / groupId / 网关展开)时该列表对每个测点生效。</p>
|
||||
*
|
||||
* @author FastBee
|
||||
* @date 2026-08-16
|
||||
@ -30,6 +32,9 @@ public class ReportRequest implements Serializable {
|
||||
@ApiModelProperty("设备分组ID(按 iot_group 导出,报告表格展示断面列,与 deviceId/deviceIds 三选一)")
|
||||
private Long groupId;
|
||||
|
||||
@ApiModelProperty("物模型标识符列表(选填;为空表示分析产品下全部监测物模型)")
|
||||
private List<String> identifiers;
|
||||
|
||||
@ApiModelProperty("统计开始时间 yyyy-MM-dd HH:mm:ss(与 period 二选一)")
|
||||
private String startTime;
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ import com.fastbee.iot.domain.Product;
|
||||
import com.fastbee.iot.domain.ThingsModel;
|
||||
import com.fastbee.common.utils.SecurityUtils;
|
||||
import com.fastbee.common.core.domain.model.LoginUser;
|
||||
import com.fastbee.iot.mapper.SubGatewayMapper;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import com.fastbee.iot.service.IProductService;
|
||||
import com.fastbee.iot.service.IThingsModelService;
|
||||
@ -17,6 +16,7 @@ import com.fastbee.report.exception.ReportException;
|
||||
import com.fastbee.report.period.TimeWindow;
|
||||
import com.fastbee.report.threshold.ThingsThreshold;
|
||||
import com.fastbee.report.threshold.ThingsThresholdProvider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@ -24,16 +24,20 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 报告上下文构建器:设备/产品/物模型/时序数据/阈值一次性装配
|
||||
*
|
||||
* <p>网关设备特殊处理:使用网关下第一个有监测分类的子设备构建上下文。</p>
|
||||
* <p>调用方按测点(iot_device)逐个调用:物模型、时序数据、阈值都以设备维度装配,
|
||||
* 分析器按设备分析,再由 {@link MultiDeviceAnalysisMerger} 归并。
|
||||
* 网关设备在 {@code ReportTargetResolver} 阶段就已展开为子设备,这里不感知网关概念。</p>
|
||||
*
|
||||
* @author FastBee
|
||||
* @date 2026-08-16
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ReportContextBuilder {
|
||||
|
||||
@ -55,9 +59,6 @@ public class ReportContextBuilder {
|
||||
@Resource
|
||||
private ThingsThresholdProvider thingsThresholdProvider;
|
||||
|
||||
@Resource
|
||||
private SubGatewayMapper subGatewayMapper;
|
||||
|
||||
/**
|
||||
* 构建报告上下文(按请求中的设备)
|
||||
*/
|
||||
@ -90,6 +91,8 @@ public class ReportContextBuilder {
|
||||
modelQuery.setType(1);
|
||||
modelQuery.setIsMonitor(1);
|
||||
List<ThingsModel> models = thingsModelService.selectThingsModelList(modelQuery);
|
||||
// 前端勾选的物模型:只分析所选物理量;未勾选(空)表示分析产品下全部监测物模型
|
||||
models = filterByIdentifiers(models, request.getIdentifiers(), deviceId);
|
||||
|
||||
List<MetricData> metricDataList = dataProvider.loadHistory(device, models, window);
|
||||
|
||||
@ -149,30 +152,37 @@ public class ReportContextBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析网关下第一个有监测分类的子设备
|
||||
* 按请求指定的物模型标识符过滤监测物模型
|
||||
*
|
||||
* @param gatewayDeviceId 网关设备ID
|
||||
* @return 第一个有监测分类的子设备,如果没有则返回null
|
||||
* <p>语义:<b>未指定(空列表)= 全量分析</b>,与「未勾选子设备即全部子设备」保持一致的默认口径;
|
||||
* 指定后只分析所选物理量,避免报告被无关变量(如设备电压、轴角度)稀释。</p>
|
||||
*
|
||||
* <p>一个都匹配不上时返回空列表而非抛异常:多测点(网关子设备 / 分组 / 多选)场景下
|
||||
* 允许个别测点未配置该物模型,交由 {@code ReportAsyncExecutor} 按「该测点无有效数据」跳过,
|
||||
* 避免一台子设备漏配就把整份报告打失败。</p>
|
||||
*/
|
||||
private Device resolveFirstMonitorSubDevice(Long gatewayDeviceId) {
|
||||
List<Long> subDeviceIds = subGatewayMapper.selectSubDeviceIdsByGwDeviceId(gatewayDeviceId);
|
||||
if (subDeviceIds == null || subDeviceIds.isEmpty()) {
|
||||
return null;
|
||||
private List<ThingsModel> filterByIdentifiers(List<ThingsModel> models, List<String> identifiers, Long deviceId) {
|
||||
if (models == null || models.isEmpty()) {
|
||||
return models;
|
||||
}
|
||||
for (Long subDeviceId : subDeviceIds) {
|
||||
Device subDevice = deviceService.selectDeviceByDeviceId(subDeviceId);
|
||||
if (subDevice == null || subDevice.getProductId() == null) {
|
||||
continue;
|
||||
}
|
||||
Product subProduct = productService.selectProductByProductId(subDevice.getProductId());
|
||||
if (subProduct == null) {
|
||||
continue;
|
||||
}
|
||||
MonitorCategory cat = MonitorCategory.resolve(subProduct.getMonitorCategoryCode());
|
||||
if (cat != MonitorCategory.UNKNOWN) {
|
||||
return subDevice;
|
||||
}
|
||||
if (identifiers == null || identifiers.isEmpty()) {
|
||||
return models;
|
||||
}
|
||||
return null;
|
||||
Set<String> wanted = identifiers.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.collect(Collectors.toSet());
|
||||
if (wanted.isEmpty()) {
|
||||
return models;
|
||||
}
|
||||
List<ThingsModel> filtered = models.stream()
|
||||
.filter(m -> m.getIdentifier() != null && wanted.contains(m.getIdentifier()))
|
||||
.collect(Collectors.toList());
|
||||
if (filtered.isEmpty()) {
|
||||
log.warn("所选物模型在设备监测物模型中没有匹配项,该测点将按无数据跳过 deviceId={} identifiers={}",
|
||||
deviceId, wanted);
|
||||
}
|
||||
return filtered;
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,10 +225,16 @@ public class ReportAsyncExecutor {
|
||||
}
|
||||
|
||||
if (analyses.isEmpty()) {
|
||||
log.warn("报告生成中止:全部测点在时间范围内无有效监测数据 reportNo={} 测点={}",
|
||||
reportNo, String.join("、", skippedPoints));
|
||||
reportRecordService.markFailed(record, "所选时间范围内无有效监测数据,无法生成报告(测点:"
|
||||
+ String.join("、", skippedPoints) + ")");
|
||||
log.warn("报告生成中止:全部测点在时间范围内无有效监测数据 reportNo={} 测点={} 指定物模型={}",
|
||||
reportNo, String.join("、", skippedPoints), request.getIdentifiers());
|
||||
boolean hasIdentifiers = request.getIdentifiers() != null && !request.getIdentifiers().isEmpty();
|
||||
String reason = hasIdentifiers
|
||||
? "所选物模型在统计期内无有效监测数据,无法生成报告(测点:"
|
||||
+ String.join("、", skippedPoints)
|
||||
+ ");请确认所选物模型在产品上已开启监测,且设备在该时段有上报记录"
|
||||
: "所选时间范围内无有效监测数据,无法生成报告(测点:"
|
||||
+ String.join("、", skippedPoints) + ")";
|
||||
reportRecordService.markFailed(record, reason);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -488,10 +488,22 @@ export default {
|
||||
if (Array.isArray(subIds) && subIds.length > 0) {
|
||||
payload.deviceIds = subIds;
|
||||
} else {
|
||||
// 未勾选子设备 = 全量子设备:传网关 deviceId,由后端展开其下同产品子设备
|
||||
payload.deviceId = this.device.deviceId;
|
||||
}
|
||||
// 物模型:只传「子设备变量」(计算量是网关纯计算,不参与多测点物模型分析);
|
||||
// 未勾选时不传该字段,后端按产品全部监测物模型分析
|
||||
const subIdentifiers = this.gwSelectedSubIdentifiers || [];
|
||||
if (subIdentifiers.length > 0) {
|
||||
payload.identifiers = [...subIdentifiers];
|
||||
}
|
||||
} else {
|
||||
payload.deviceId = reportDeviceId;
|
||||
// 物模型:未勾选时不传该字段,后端按全部监测物模型分析
|
||||
const picked = (this.queryParams.identifiers || []).filter((id) => id !== '__selectAll__');
|
||||
if (picked.length > 0) {
|
||||
payload.identifiers = picked;
|
||||
}
|
||||
}
|
||||
this.reportLoading = true;
|
||||
generateDeviceReport(payload)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user