From 37600a85b79387ab6afa7bf342eedbf938331e1d Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期日, 06 九月 2026 17:37:28 +0800
Subject: [PATCH] 实现对比报表快照编辑与历史版本
---
battery-business/src/main/java/com/battery/business/report/CompareReportEditRequest.java | 33 +++++++++++
battery-business/src/main/java/com/battery/business/report/CompareReportRepository.java | 41 +++++++++++--
battery-business/src/main/java/com/battery/business/report/CompareReportRequest.java | 8 ++
battery-business/src/main/java/com/battery/business/report/CompareReportService.java | 48 +++++++++++++++
ruoyi-admin/src/main/resources/db/migration-final/V11__compare_report_edit_permission.sql | 4 +
ruoyi-admin/src/main/java/com/battery/business/web/CompareReportController.java | 28 +++++++++
6 files changed, 153 insertions(+), 9 deletions(-)
diff --git a/battery-business/src/main/java/com/battery/business/report/CompareReportEditRequest.java b/battery-business/src/main/java/com/battery/business/report/CompareReportEditRequest.java
new file mode 100644
index 0000000..c5d9e5b
--- /dev/null
+++ b/battery-business/src/main/java/com/battery/business/report/CompareReportEditRequest.java
@@ -0,0 +1,33 @@
+package com.battery.business.report;
+
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.ruoyi.common.exception.ServiceException;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+import jakarta.validation.constraints.Size;
+
+/** 普通编辑只允许展示内容,不能提交来源身份或测量快照。 */
+public record CompareReportEditRequest(
+ @Positive int baseVersionNo,
+ @NotBlank @Size(max = 100) String name,
+ @Size(max = 2000) String description,
+ @Size(max = 500) String experimentName,
+ @NotNull @Valid CompareReportRequest.Settings settings,
+ @NotBlank @Size(max = 500) String changeSummary)
+{
+ /** 拒绝越过编辑边界的字段,避免将篡改测量请求报告为保存成功。 */
+ @JsonAnySetter
+ public void rejectUnknown(String field, Object value)
+ { throw new ServiceException("报表编辑不允许提交字段:" + field); }
+
+ /** 主动更新只携带已有基准和原因,来源范围由后端快照确定。 */
+ public record Refresh(@Positive int baseVersionNo, @NotBlank @Size(max = 480) String changeSummary)
+ {
+ /** 来源和测量不得由调用方替换。 */
+ @JsonAnySetter
+ public void rejectUnknown(String field, Object value)
+ { throw new ServiceException("更新来源不允许提交字段:" + field); }
+ }
+}
diff --git a/battery-business/src/main/java/com/battery/business/report/CompareReportRepository.java b/battery-business/src/main/java/com/battery/business/report/CompareReportRepository.java
index f7ffd21..6c7df00 100644
--- a/battery-business/src/main/java/com/battery/business/report/CompareReportRepository.java
+++ b/battery-business/src/main/java/com/battery/business/report/CompareReportRepository.java
@@ -17,7 +17,8 @@
public CompareReportRepository(DataSource dataSource) { jdbc = new JdbcTemplate(dataSource); }
public record Summary(long id, String name, String creator, long createdAt) { }
- public record Version(Summary report, int versionNo, String snapshotJson) { }
+ public record Version(Summary report, int versionNo, String snapshotJson, String operatorName, long versionCreatedAt, String changeSummary) { }
+ public record History(int versionNo, String operatorName, long createdAt, String changeSummary) { }
/** 查询所有已保存报表摘要,允许授权用户跨创建人查看。 */
public List<Summary> list()
@@ -26,17 +27,45 @@
(rs, index) -> new Summary(rs.getLong(1), rs.getString(2), rs.getString(3), rs.getTimestamp(4).getTime()));
}
- /** 重读首个完整版本;来源不存在不影响结果。 */
+ /** 重读最新完整版本;来源不存在不影响结果。 */
public Version get(long id)
+ { return get(id, null); }
+
+ /** 读取指定历史版,版本号为空时读取最新版本。 */
+ public Version get(long id, Integer versionNo)
{
- var rows = jdbc.query("select r.report_id, r.report_name, r.creator_name, r.created_at_utc, v.version_no, v.snapshot_json "
- + "from t_compare_report r join t_compare_report_version v on v.report_id=r.report_id and v.version_no=1 where r.report_id=?",
+ var rows = jdbc.query("select r.report_id, r.report_name, r.creator_name, r.created_at_utc, v.version_no, v.snapshot_json, v.operator_name, v.created_at_utc, v.change_summary "
+ + "from t_compare_report r join t_compare_report_version v on v.report_id=r.report_id where r.report_id=? "
+ + (versionNo == null ? "and v.version_no=(select max(v2.version_no) from t_compare_report_version v2 where v2.report_id=r.report_id)" : "and v.version_no=?"),
(rs, index) -> new Version(new Summary(rs.getLong(1), rs.getString(2), rs.getString(3), rs.getTimestamp(4).getTime()),
- rs.getInt(5), rs.getString(6)), id);
- if (rows.isEmpty()) throw new ServiceException("对比报表不存在");
+ rs.getInt(5), rs.getString(6), rs.getString(7), rs.getTimestamp(8).getTime(), rs.getString(9)),
+ versionNo == null ? new Object[] { id } : new Object[] { id, versionNo });
+ if (rows.isEmpty()) throw new ServiceException("对比报表或版本不存在");
return rows.get(0);
}
+ /** 历史摘要只来自正式版本,不返回测量快照。 */
+ public List<History> history(long id)
+ {
+ return jdbc.query("select version_no,operator_name,created_at_utc,change_summary from t_compare_report_version where report_id=? order by version_no desc",
+ (rs, index) -> new History(rs.getInt(1), rs.getString(2), rs.getTimestamp(3).getTime(), rs.getString(4)), id);
+ }
+
+ /** 业务事务内锁定报表,版本号分配与旧基准拒绝共用此锁。 */
+ public void lock(long id)
+ {
+ if (jdbc.queryForList("select report_id from t_compare_report where report_id=? for update", Long.class, id).isEmpty())
+ throw new ServiceException("对比报表不存在");
+ }
+
+ /** 只追加版本并更新列表名称;业务事务保证两步同时成功。 */
+ public void append(long id, int versionNo, String name, String snapshot, long operatorId, String operatorName, String summary)
+ {
+ jdbc.update("insert into t_compare_report_version(report_id,version_no,snapshot_json,operator_id,operator_name,created_at_utc,change_summary) values(?,?,?,?,?,?,?)",
+ id, versionNo, snapshot, operatorId, operatorName, new Timestamp(System.currentTimeMillis()), summary);
+ jdbc.update("update t_compare_report set report_name=? where report_id=?", name, id);
+ }
+
/** 由业务事务调用,身份与首个版本任一写入失败必须一起回滚。 */
public long insert(String name, String snapshot, long operatorId, String operatorName, long createdAt)
{
diff --git a/battery-business/src/main/java/com/battery/business/report/CompareReportRequest.java b/battery-business/src/main/java/com/battery/business/report/CompareReportRequest.java
index 7672837..986d89e 100644
--- a/battery-business/src/main/java/com/battery/business/report/CompareReportRequest.java
+++ b/battery-business/src/main/java/com/battery/business/report/CompareReportRequest.java
@@ -21,5 +21,11 @@
public record Settings(@NotNull List<String> metrics, @NotNull List<String> seriesColors,
@NotNull List<String> batteryTemplateFields, @NotNull List<String> specialFields,
- @NotNull Map<String, Map<String, String>> detailFieldValues) { }
+ @NotNull Map<String, @NotNull Map<String, String>> detailFieldValues)
+ {
+ /** 展示设置不能夹带测量快照或其他未支持的修改。 */
+ @com.fasterxml.jackson.annotation.JsonAnySetter
+ public void rejectUnknown(String field, Object value)
+ { throw new com.ruoyi.common.exception.ServiceException("报表设置不允许提交字段:" + field); }
+ }
}
diff --git a/battery-business/src/main/java/com/battery/business/report/CompareReportService.java b/battery-business/src/main/java/com/battery/business/report/CompareReportService.java
index e953489..f0bdf0b 100644
--- a/battery-business/src/main/java/com/battery/business/report/CompareReportService.java
+++ b/battery-business/src/main/java/com/battery/business/report/CompareReportService.java
@@ -22,7 +22,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-/** 对比来源与首版快照共用同一范围解析,测量始终来自后端当前有效数据。 */
+/** 首次保存和主动更新解析当前来源,普通编辑及历史查看只读取独立快照。 */
@Service
public class CompareReportService
{
@@ -106,14 +106,58 @@
/** 重开只读取保存版本,来源测试、字段或映射已删除也不重新解析。 */
public Map<String, Object> get(long id)
+ { return get(id, null); }
+
+ /** 指定版本按其冻结内容还原,操作信息来自正式版本记录。 */
+ public Map<String, Object> get(long id, Integer versionNo)
{
- var version = reports.get(id);
+ var version = reports.get(id, versionNo);
Map<String, Object> result = JSON.parseObject(version.snapshotJson());
result.put("id", id); result.put("versionNo", version.versionNo());
result.put("creator", version.report().creator()); result.put("createdAt", version.report().createdAt());
+ result.put("operatorName", version.operatorName()); result.put("versionCreatedAt", version.versionCreatedAt());
+ result.put("changeSummary", version.changeSummary());
return result;
}
+ /** 普通编辑追加展示版本,测量、来源身份和单位完全沿用基准快照。 */
+ @Transactional(rollbackFor = Exception.class)
+ public Map<String, Object> edit(long id, CompareReportEditRequest request, long operatorId, String operatorName)
+ {
+ var snapshot = currentSnapshot(id, request.baseVersionNo());
+ validateSettings(request.settings());
+ snapshot.put("name", request.name().trim()); snapshot.put("description", request.description());
+ snapshot.put("experimentName", request.experimentName()); snapshot.put("settings", request.settings());
+ reports.append(id, request.baseVersionNo() + 1, request.name().trim(), JSON.toJSONString(snapshot, JSONWriter.Feature.WriteMapNullValue),
+ operatorId, operatorName, request.changeSummary().trim());
+ return get(id);
+ }
+
+ /** 仅主动更新重读来源;人工表格值仍保留在设置中,不覆盖本次测量快照。 */
+ @Transactional(rollbackFor = Exception.class)
+ public Map<String, Object> refresh(long id, CompareReportEditRequest.Refresh request, long operatorId, String operatorName)
+ {
+ var snapshot = currentSnapshot(id, request.baseVersionNo());
+ var items = JSON.parseArray(JSON.toJSONString(snapshot.get("items")), Item.class);
+ var settings = JSON.parseObject(JSON.toJSONString(snapshot.get("settings")), CompareReportRequest.Settings.class);
+ var updated = preview(items.stream().map(item -> item.source().selection()).toList()).stream()
+ .map(item -> new Item(item.source(), item.zeroTime(), item.records(), item.logicalMapping(),
+ item.businessFields().stream().filter(field -> settings.batteryTemplateFields().contains(field.getFieldName())).toList())).toList();
+ snapshot.put("items", updated);
+ reports.append(id, request.baseVersionNo() + 1, snapshot.getString("name"), JSON.toJSONString(snapshot, JSONWriter.Feature.WriteMapNullValue),
+ operatorId, operatorName, "按最新来源生成:" + request.changeSummary().trim());
+ return get(id);
+ }
+
+ private com.alibaba.fastjson2.JSONObject currentSnapshot(long id, int baseVersionNo)
+ {
+ // 同一报表内串行追加;陈旧页面需重新打开,不从历史版生成回滚版本。
+ reports.lock(id);
+ var current = reports.get(id);
+ if (current.versionNo() != baseVersionNo) throw new ServiceException("报表已有新版本,请重新打开后编辑");
+ return JSON.parseObject(current.snapshotJson());
+ }
+
private void validateSettings(CompareReportRequest.Settings settings)
{
Set<String> metrics = Set.of("voltage", "current", "temperature", "positiveVoltage", "negativeVoltage");
diff --git a/ruoyi-admin/src/main/java/com/battery/business/web/CompareReportController.java b/ruoyi-admin/src/main/java/com/battery/business/web/CompareReportController.java
index 9570eaa..318663c 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/web/CompareReportController.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/web/CompareReportController.java
@@ -3,6 +3,7 @@
import com.battery.business.report.CompareReportRepository;
import com.battery.business.report.CompareReportService;
import com.battery.business.report.CompareReportRequest;
+import com.battery.business.report.CompareReportEditRequest;
import jakarta.validation.Valid;
import java.util.List;
import com.ruoyi.common.core.controller.BaseController;
@@ -51,4 +52,31 @@
@PreAuthorize("@ss.hasPermi('system:compare-report:query')")
@GetMapping("/{id}")
public AjaxResult get(@PathVariable long id) { return success(service.get(id)); }
+
+ /** 授权查看全部历史版本摘要,不限制创建人。 */
+ @PreAuthorize("@ss.hasPermi('system:compare-report:query')")
+ @GetMapping("/{id}/versions")
+ public AjaxResult history(@PathVariable long id) { return success(reports.history(id)); }
+
+ /** 指定历史版本只读,不依赖来源仍存在。 */
+ @PreAuthorize("@ss.hasPermi('system:compare-report:query')")
+ @GetMapping("/{id}/versions/{versionNo}")
+ public AjaxResult version(@PathVariable long id, @PathVariable int versionNo) { return success(service.get(id, versionNo)); }
+
+ /** 编辑权限可读取最新快照作为基准,无需来源测试权限。 */
+ @PreAuthorize("@ss.hasPermi('system:compare-report:edit')")
+ @GetMapping("/{id}/editing")
+ public AjaxResult editing(@PathVariable long id) { return success(service.get(id)); }
+
+ /** 普通编辑只追加新版本,拒绝调用方提供测量事实。 */
+ @PreAuthorize("@ss.hasPermi('system:compare-report:edit')")
+ @PostMapping("/{id}/versions")
+ public AjaxResult edit(@PathVariable long id, @Valid @RequestBody CompareReportEditRequest request)
+ { return success(service.edit(id, request, getUserId(), getUsername())); }
+
+ /** 独立动作重读当前来源并追加快照,失败由业务事务完整回滚。 */
+ @PreAuthorize("@ss.hasPermi('system:compare-report:edit')")
+ @PostMapping("/{id}/refresh-source")
+ public AjaxResult refresh(@PathVariable long id, @Valid @RequestBody CompareReportEditRequest.Refresh request)
+ { return success(service.refresh(id, request, getUserId(), getUsername())); }
}
diff --git a/ruoyi-admin/src/main/resources/db/migration-final/V11__compare_report_edit_permission.sql b/ruoyi-admin/src/main/resources/db/migration-final/V11__compare_report_edit_permission.sql
new file mode 100644
index 0000000..0cd3803
--- /dev/null
+++ b/ruoyi-admin/src/main/resources/db/migration-final/V11__compare_report_edit_permission.sql
@@ -0,0 +1,4 @@
+-- 编辑与主动更新共用编辑权限,历史查看沿用查看权限。
+INSERT INTO sys_menu(menu_name,parent_id,order_num,path,menu_type,visible,status,perms,create_by,create_time)
+SELECT '编辑',parent_id,3,'#','F','0','0','system:compare-report:edit','system',UTC_TIMESTAMP(3)
+FROM sys_menu WHERE perms='system:compare-report:query';
--
Gitblit v1.7.1