diff --git a/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java b/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java index e4f4b7eb..66c41b2e 100644 --- a/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java +++ b/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysUserSettingController.java @@ -4,6 +4,7 @@ import com.fastbee.common.annotation.Log; import com.fastbee.common.core.controller.BaseController; import com.fastbee.common.core.domain.AjaxResult; import com.fastbee.common.enums.BusinessType; +import com.fastbee.common.enums.ReportTemplate; import com.fastbee.common.utils.SecurityUtils; import com.fastbee.system.domain.LoginRedirectOption; import com.fastbee.system.domain.SysUserSetting; @@ -92,7 +93,8 @@ public class SysUserSettingController extends BaseController { public AjaxResult update(@RequestBody SysUserSetting setting) { if (setting.getLoginRedirect() == null && setting.getQueryGatewayChild() == null - && setting.getShowChild() == null) { + && setting.getShowChild() == null + && setting.getReportTemplate() == null) { return AjaxResult.error("没有需要保存的配置项"); } // 只有在本次确实要改「登录后显示页面」时才做白名单校验; @@ -100,11 +102,33 @@ public class SysUserSettingController extends BaseController { if (setting.getLoginRedirect() != null && !isValidLoginRedirect(setting.getLoginRedirect())) { return AjaxResult.error("登录后跳转页面不合法,请从系统菜单中选择"); } + // 报告模板版式只接受已注册的枚举编码,挡住脏数据/未发布的模板 + if (setting.getReportTemplate() != null && !isValidReportTemplate(setting.getReportTemplate())) { + return AjaxResult.error("报告导出模板不合法,请从可选项中选择"); + } // 审计字段由服务端填充,不接受前端传入,避免越权写入他人操作名 userSettingService.updatePartial(SecurityUtils.getUserId(), setting, SecurityUtils.getUsername()); return AjaxResult.success(); } + /** + * 校验报告模板编码为已注册的模板版式 + * + *
取值即 {@code ReportTemplate.code},未注册的一律拒绝(而非静默回退默认版式), + * 避免用户选到未发布的模板后导出行为与预期不符。
+ */ + private boolean isValidReportTemplate(String code) { + if (code == null || code.trim().isEmpty()) { + return false; + } + for (ReportTemplate template : ReportTemplate.values()) { + if (template.getCode().equalsIgnoreCase(code.trim())) { + return true; + } + } + return false; + } + /** * 校验跳转目标为系统内已注册的一级页签 * diff --git a/fastbee-admin/src/main/resources/application.yml b/fastbee-admin/src/main/resources/application.yml index 4ea2e8cf..569f8a68 100644 --- a/fastbee-admin/src/main/resources/application.yml +++ b/fastbee-admin/src/main/resources/application.yml @@ -233,12 +233,3 @@ map: gaode_secret: d0561ad20142e74f8c59edc5deafdbe4 tianditu_ak: b920e6076a06aa279c974f5f76cf746e -# 设备监测报告导出配置(全局一刀切,test/prod 环境共用) -report: - # 模板编码(取值即 templates/ 下的模板名,V2 为按分类取默认的版本标记): - # 留空 或 V2 → 完整版,按监测分类走 DEVICE_{CATEGORY}_V2(共 10 节) - # DCPReport → DCP 平台专用三节版式(项目概况 / 测点数据 / 时程曲线), - # 测点表只输出 ThingsThreshold.is_primary=1 的主监测指标 - # 默认保持完整版(V2):切换版式是交付格式变更,需现场确认后由运维显式改为 DCPReport - template-code: V2 - diff --git a/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java b/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java index 5bc2c663..f1e74650 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java +++ b/fastbee-common/src/main/java/com/fastbee/common/constant/UserSettingConstants.java @@ -1,5 +1,7 @@ package com.fastbee.common.constant; +import com.fastbee.common.enums.ReportTemplate; + import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -80,6 +82,17 @@ public class UserSettingConstants { */ public static final boolean DEFAULT_SHOW_CHILD = false; + /** + * 报告导出模板版式的默认值 + * + *对应 {@code sys_user_setting.report_template},取值即 {@code ReportTemplate.code}: + * 默认 {@code V2}(完整版,按监测分类取 {@code DEVICE_{CATEGORY}_V2})= 与引入本开关前完全一致。 + * 用户可在「系统设置」页切换为 {@code DCPReport}(DCP 平台三节版式)等其它版式。 + *
维度归属:报告导出由登录用户触发(异步线程已显式透传 SecurityContext,能拿到 userId), + * 故为 per-user 设置;无登录态不允许导出,不做全局兜底。 + */ + public static final String DEFAULT_REPORT_TEMPLATE = ReportTemplate.FULL_VERSION.getCode(); + private UserSettingConstants() { } diff --git a/fastbee-common/src/main/java/com/fastbee/common/enums/ReportTemplate.java b/fastbee-common/src/main/java/com/fastbee/common/enums/ReportTemplate.java new file mode 100644 index 00000000..23164d8b --- /dev/null +++ b/fastbee-common/src/main/java/com/fastbee/common/enums/ReportTemplate.java @@ -0,0 +1,92 @@ +package com.fastbee.common.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 设备监测报告导出模板版式 + * + *
版式是现场交付物,每种版式对应一套固定的渲染策略(是否只出主指标、是否按分类拆分模板文件), + * 因此用枚举把「模板编码 ↔ 渲染行为」内聚在一起,新增版式只需新增枚举项 + 放置模板文件, + * 渲染层无需再散落字符串比较。
+ * + *为什么是枚举而不是自由字符串:早期实现把「是否只出主指标」硬绑在 + * {@code templateCode == "DEVICE_SUMMARY"} 上,一旦模板改名判定会静默失效(不报错, + * 辅助量列又冒出来)。枚举把编码与行为绑定,改名只动枚举,判定入口唯一。
+ * + *模板编码落库语义:{@link #getCode()} 即 {@code sys_user_setting.report_template} + * 的存储值,同时是 {@code templates/} 下模板文件名的决定依据 —— + * {@link #isCategoryScoped()} 为 false 时直接用 code 拼文件名,为 true 时拼分类前缀。
+ * + * @author FastBee + * @date 2026-10-06 + */ +@Getter +@AllArgsConstructor +public enum ReportTemplate { + + /** + * 完整版(V2):按监测分类取 {@code DEVICE_{CATEGORY}_V2},共 10 节 + * + *默认版式。测点表沿用「主指标完整四列 + 辅助量合并列」原口径, + * 辅助物理量(水温、电压、角度…)保留为「本次值(变化量)」一列用于分析影响量。
+ */ + FULL_VERSION("V2", "完整版", false, true), + + /** + * DCP 平台专用三节版式(项目概况 / 测点数据 / 时程曲线) + * + *全分类共用一份 {@code templates/DCPReport.docx}(首行标题用 + * {@code {{device.categoryDesc}}} 占位,故不按分类拆分)。测点表只输出 + * {@code iot_things_threshold.is_primary=1} 的主监测指标,非主监测指标整列不出现。
+ * + *命名对齐 DCP 平台(平台实时数据推送,见 + * {@code com.fastbee.iot.platform.adapter.dcp})要求的交付格式;注意与推送侧的 + * DCP 适配器区分:本枚举属报告导出,彼处属数据推送。
+ */ + DCP_REPORT("DCPReport", "DCP 三节版式", true, false); + + /** + * 模板编码(存库值;非分类拆分时即 templates/ 下模板文件名) + */ + private final String code; + + /** + * 模板版式中文说明(前端下拉展示用) + */ + private final String desc; + + /** + * 测点表是否只输出主监测指标(is_primary=1) + */ + private final boolean primaryOnly; + + /** + * 是否按监测分类拆分模板文件 + * + *true:模板文件名为 {@code DEVICE_{CATEGORY}_V2}(每分类一份); + * false:全分类共用一份 {@code {code}.docx}。
+ */ + private final boolean categoryScoped; + + /** + * 按编码解析模板版式 + * + *未知编码回退 {@link #FULL_VERSION}(完整版)而非抛异常:报告生成不能因为 + * 一个脏配置就整体失败,回退到默认版式至少能产出报告,同时由调用方记录告警。
+ * + * @param code 模板编码(可为 null / 空串) + * @return 匹配的模板版式;空值或未识别时返回 FULL_VERSION + */ + public static ReportTemplate fromCode(String code) { + if (code == null || code.trim().isEmpty()) { + return FULL_VERSION; + } + for (ReportTemplate template : values()) { + if (template.code.equalsIgnoreCase(code.trim())) { + return template; + } + } + return FULL_VERSION; + } +} diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/report/ReportController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/report/ReportController.java index a5e0e64f..48a9e1bc 100644 --- a/fastbee-open-api/src/main/java/com/fastbee/controller/report/ReportController.java +++ b/fastbee-open-api/src/main/java/com/fastbee/controller/report/ReportController.java @@ -6,6 +6,7 @@ import com.fastbee.common.core.domain.AjaxResult; import com.fastbee.common.core.page.TableDataInfo; import com.fastbee.common.core.redis.RedisCache; import com.fastbee.common.enums.MonitorCategory; +import com.fastbee.common.enums.ReportTemplate; import com.fastbee.common.exception.ServiceException; import com.fastbee.common.utils.StringUtils; import com.fastbee.oss.service.OssClient; @@ -160,4 +161,21 @@ public class ReportController extends BaseController { .collect(Collectors.toList()); return AjaxResult.success(list); } + + /** + * 报告导出模板版式列表(枚举驱动,供系统设置页下拉选择 per-user 模板) + */ + @ApiOperation("报告导出模板版式列表") + @GetMapping("/templates") + public AjaxResult templates() { + List