From 5b87e2f7cafcbcc7452ee29b993b1ef75698fb5f Mon Sep 17 00:00:00 2001 From: shihongwei <178899525@qq.com> Date: Thu, 4 Jun 2026 09:54:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8A=A5=E8=A1=A8=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SysReportTemplateController.java | 22 ++ .../domain/bo/SysReportTemplateSyncBo.java | 19 + .../vo/SysReportTemplateSyncCompareVo.java | 45 +++ .../service/ISysReportTemplateService.java | 11 + .../impl/SysReportTemplateServiceImpl.java | 326 +++++++++++++----- 5 files changed, 337 insertions(+), 86 deletions(-) create mode 100644 src/main/java/com/hotwj/platform/reportStatistics/domain/bo/SysReportTemplateSyncBo.java create mode 100644 src/main/java/com/hotwj/platform/reportStatistics/domain/vo/SysReportTemplateSyncCompareVo.java diff --git a/src/main/java/com/hotwj/platform/reportStatistics/controller/SysReportTemplateController.java b/src/main/java/com/hotwj/platform/reportStatistics/controller/SysReportTemplateController.java index b4c1a19..e7e9ada 100644 --- a/src/main/java/com/hotwj/platform/reportStatistics/controller/SysReportTemplateController.java +++ b/src/main/java/com/hotwj/platform/reportStatistics/controller/SysReportTemplateController.java @@ -1,7 +1,9 @@ package com.hotwj.platform.reportStatistics.controller; import com.hotwj.platform.reportStatistics.domain.bo.SysReportTemplateBo; +import com.hotwj.platform.reportStatistics.domain.bo.SysReportTemplateSyncBo; import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateVo; +import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateSyncCompareVo; import com.hotwj.platform.reportStatistics.service.ISysReportTemplateService; import com.hotwj.platform.reportStatistics.service.ReportRenderService; import jakarta.servlet.http.HttpServletResponse; @@ -97,6 +99,26 @@ public class SysReportTemplateController extends BaseController { return R.ok(); } + /** + * 查询模板同步差异 + */ + //@SaCheckPermission("report:template:sync") + @GetMapping("/sync/diff") + public R> syncDiff() { + return R.ok(sysReportTemplateService.querySyncDiffList()); + } + + /** + * 按选中项同步模板 + */ + //@SaCheckPermission("report:template:sync") + @Log(title = "按项同步模版", businessType = BusinessType.UPDATE) + @PostMapping("/sync/selective") + public R selectiveSync(@Validated @RequestBody SysReportTemplateSyncBo bo) { + sysReportTemplateService.syncTemplatesFromClasspath(bo.getTemplateCodes()); + return R.ok(); + } + /** * 删除报表模板 */ diff --git a/src/main/java/com/hotwj/platform/reportStatistics/domain/bo/SysReportTemplateSyncBo.java b/src/main/java/com/hotwj/platform/reportStatistics/domain/bo/SysReportTemplateSyncBo.java new file mode 100644 index 0000000..6f339c3 --- /dev/null +++ b/src/main/java/com/hotwj/platform/reportStatistics/domain/bo/SysReportTemplateSyncBo.java @@ -0,0 +1,19 @@ +package com.hotwj.platform.reportStatistics.domain.bo; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +/** + * 报表模板按项同步请求 + */ +@Data +public class SysReportTemplateSyncBo { + + /** + * 需要同步的模板编码集合 + */ + @NotEmpty(message = "请选择需要同步的模板") + private List templateCodes; +} diff --git a/src/main/java/com/hotwj/platform/reportStatistics/domain/vo/SysReportTemplateSyncCompareVo.java b/src/main/java/com/hotwj/platform/reportStatistics/domain/vo/SysReportTemplateSyncCompareVo.java new file mode 100644 index 0000000..659430a --- /dev/null +++ b/src/main/java/com/hotwj/platform/reportStatistics/domain/vo/SysReportTemplateSyncCompareVo.java @@ -0,0 +1,45 @@ +package com.hotwj.platform.reportStatistics.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 报表模板同步差异视图对象 + */ +@Data +public class SysReportTemplateSyncCompareVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String templateCode; + + private Long dbId; + + private String localTemplateName; + + private String dbTemplateName; + + private String localContent; + + private String dbContent; + + private String localMockData; + + private String dbMockData; + + private String localRemark; + + private String dbRemark; + + private Boolean existsInLocal; + + private Boolean existsInDb; + + private Boolean syncable; + + private List diffTypes; +} diff --git a/src/main/java/com/hotwj/platform/reportStatistics/service/ISysReportTemplateService.java b/src/main/java/com/hotwj/platform/reportStatistics/service/ISysReportTemplateService.java index cff20e2..c9615a3 100644 --- a/src/main/java/com/hotwj/platform/reportStatistics/service/ISysReportTemplateService.java +++ b/src/main/java/com/hotwj/platform/reportStatistics/service/ISysReportTemplateService.java @@ -2,6 +2,7 @@ package com.hotwj.platform.reportStatistics.service; import com.hotwj.platform.reportStatistics.domain.bo.SysReportTemplateBo; import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateVo; +import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateSyncCompareVo; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; @@ -56,6 +57,16 @@ public interface ISysReportTemplateService { */ void syncTemplatesFromClasspath(); + /** + * 查询本地模板与数据库模板的差异 + */ + List querySyncDiffList(); + + /** + * 按模板编码同步本地模板到数据库 + */ + void syncTemplatesFromClasspath(Collection templateCodes); + /** * 根据模板编码查找对应的 Provider */ diff --git a/src/main/java/com/hotwj/platform/reportStatistics/service/impl/SysReportTemplateServiceImpl.java b/src/main/java/com/hotwj/platform/reportStatistics/service/impl/SysReportTemplateServiceImpl.java index 172071c..b540520 100644 --- a/src/main/java/com/hotwj/platform/reportStatistics/service/impl/SysReportTemplateServiceImpl.java +++ b/src/main/java/com/hotwj/platform/reportStatistics/service/impl/SysReportTemplateServiceImpl.java @@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.hotwj.platform.reportStatistics.domain.SysReportTemplate; import com.hotwj.platform.reportStatistics.domain.bo.SysReportTemplateBo; +import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateSyncCompareVo; import com.hotwj.platform.reportStatistics.domain.vo.SysReportTemplateVo; import com.hotwj.platform.reportStatistics.mapper.SysReportTemplateMapper; import com.hotwj.platform.reportStatistics.service.IReportDataProvider; @@ -16,6 +17,7 @@ import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.springframework.core.io.ClassPathResource; @@ -27,9 +29,15 @@ import org.springframework.transaction.annotation.Transactional; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; /** * 报表模板Service业务层处理 @@ -42,6 +50,9 @@ import java.util.Map; @Service public class SysReportTemplateServiceImpl implements ISysReportTemplateService { + private static final String CLASSPATH_SCAN_REMARK = "Synced from classpath scan"; + private static final String PROVIDER_REMARK_PREFIX = "Synced from Provider: "; + private final SysReportTemplateMapper baseMapper; private final List reportDataProviders; @@ -153,114 +164,257 @@ public class SysReportTemplateServiceImpl implements ISysReportTemplateService { @Override @Transactional(rollbackFor = Exception.class) public void syncTemplatesFromClasspath() { - // 1. Sync from Providers (Source of truth for Title and MockData) + syncTemplatesFromClasspath(null); + } + + @Override + public List querySyncDiffList() { + LinkedHashMap localTemplates = loadLocalTemplateSnapshots(); + Map dbTemplates = queryExistingTemplates(null); + + Set allCodes = new LinkedHashSet<>(); + allCodes.addAll(localTemplates.keySet()); + allCodes.addAll(dbTemplates.keySet()); + + return allCodes.stream() + .map(code -> buildCompareVo(code, localTemplates.get(code), dbTemplates.get(code))) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncTemplatesFromClasspath(Collection templateCodes) { + LinkedHashMap localTemplates = loadLocalTemplateSnapshots(); + Set targetCodes = normalizeTemplateCodes(templateCodes); + if (targetCodes != null && targetCodes.isEmpty()) { + throw new ServiceException("请选择需要同步的模板"); + } + + if (targetCodes != null) { + List missingCodes = targetCodes.stream() + .filter(code -> !localTemplates.containsKey(code)) + .collect(Collectors.toList()); + if (!missingCodes.isEmpty()) { + throw new ServiceException("以下模板在本地不存在,无法同步: " + String.join(", ", missingCodes)); + } + } + + Map dbTemplates = queryExistingTemplates(targetCodes); + + for (LocalTemplateSnapshot snapshot : localTemplates.values()) { + if (targetCodes != null && !targetCodes.contains(snapshot.getTemplateCode())) { + continue; + } + + SysReportTemplateVo existing = dbTemplates.get(snapshot.getTemplateCode()); + if (existing == null) { + SysReportTemplate insert = new SysReportTemplate(); + insert.setTemplateCode(snapshot.getTemplateCode()); + insert.setTemplateName(snapshot.getTemplateName()); + insert.setContent(snapshot.getContent()); + insert.setMockData(snapshot.getMockData()); + insert.setVersion(1); + insert.setRemark(snapshot.getRemark()); + baseMapper.insert(insert); + log.info("Inserted template from local source: {}", snapshot.getTemplateCode()); + continue; + } + + if (!hasDiff(snapshot, existing)) { + continue; + } + + SysReportTemplate update = new SysReportTemplate(); + update.setId(existing.getId()); + update.setTemplateCode(snapshot.getTemplateCode()); + update.setTemplateName(snapshot.getTemplateName()); + update.setContent(snapshot.getContent()); + update.setMockData(snapshot.getMockData()); + update.setRemark(snapshot.getRemark()); + update.setVersion(existing.getVersion() == null ? 1 : existing.getVersion() + 1); + baseMapper.updateById(update); + log.info("Updated template from local source: {}", snapshot.getTemplateCode()); + } + } + + private SysReportTemplateSyncCompareVo buildCompareVo(String code, LocalTemplateSnapshot local, SysReportTemplateVo db) { + List diffTypes = buildDiffTypes(local, db); + if (diffTypes.isEmpty()) { + return null; + } + + SysReportTemplateSyncCompareVo vo = new SysReportTemplateSyncCompareVo(); + vo.setTemplateCode(code); + vo.setDbId(db == null ? null : db.getId()); + vo.setLocalTemplateName(local == null ? "" : local.getTemplateName()); + vo.setDbTemplateName(db == null ? "" : db.getTemplateName()); + vo.setLocalContent(local == null ? "" : local.getContent()); + vo.setDbContent(db == null ? "" : normalizeText(db.getContent())); + vo.setLocalMockData(toJsonText(local == null ? null : local.getMockData())); + vo.setDbMockData(toJsonText(db == null ? null : db.getMockData())); + vo.setLocalRemark(local == null ? "" : local.getRemark()); + vo.setDbRemark(db == null ? "" : normalizeText(db.getRemark())); + vo.setExistsInLocal(local != null); + vo.setExistsInDb(db != null); + vo.setSyncable(local != null); + vo.setDiffTypes(diffTypes); + return vo; + } + + private boolean hasDiff(LocalTemplateSnapshot local, SysReportTemplateVo db) { + return !buildDiffTypes(local, db).isEmpty(); + } + + private List buildDiffTypes(LocalTemplateSnapshot local, SysReportTemplateVo db) { + List diffTypes = new ArrayList<>(); + if (local == null && db != null) { + diffTypes.add("仅数据库"); + return diffTypes; + } + if (local != null && db == null) { + diffTypes.add("仅本地"); + return diffTypes; + } + if (local == null) { + return diffTypes; + } + + if (!Objects.equals(normalizeText(local.getTemplateName()), normalizeText(db.getTemplateName()))) { + diffTypes.add("名称"); + } + if (!Objects.equals(normalizeText(local.getContent()), normalizeText(db.getContent()))) { + diffTypes.add("内容"); + } + if (!Objects.equals(toJsonText(local.getMockData()), toJsonText(db.getMockData()))) { + diffTypes.add("模拟数据"); + } + if (!Objects.equals(normalizeText(local.getRemark()), normalizeText(db.getRemark()))) { + diffTypes.add("备注"); + } + return diffTypes; + } + + private Map queryExistingTemplates(Set templateCodes) { + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + if (templateCodes != null && !templateCodes.isEmpty()) { + queryWrapper.in(SysReportTemplate::getTemplateCode, templateCodes); + } + return baseMapper.selectVoList(queryWrapper).stream() + .collect(Collectors.toMap(SysReportTemplateVo::getTemplateCode, item -> item, (left, right) -> left, LinkedHashMap::new)); + } + + private LinkedHashMap loadLocalTemplateSnapshots() { + LinkedHashMap templateMap = new LinkedHashMap<>(); + if (reportDataProviders != null) { for (IReportDataProvider provider : reportDataProviders) { try { String code = provider.getTemplateCode(); - if (StringUtils.isBlank(code)) continue; - - SysReportTemplateVo existing = queryByCode(code); - - // Read content - String content = null; - try { - ClassPathResource resource = new ClassPathResource(code); - if (resource.exists()) { - content = IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8); - } - } catch (Exception e) { - log.warn("Template file not found for provider: {}", provider.getClass().getSimpleName()); + if (StringUtils.isBlank(code)) { + continue; } - if (existing == null) { - if (StringUtils.isBlank(content)) { - log.warn("Skipping provider {} because template file {} not found", provider.getClass().getSimpleName(), code); - continue; - } - SysReportTemplate template = new SysReportTemplate(); - template.setTemplateCode(code); - template.setTemplateName(provider.getReportTitle()); - template.setContent(content); - template.setMockData(provider.getMockData()); - template.setVersion(1); - template.setRemark("Synced from Provider: " + provider.getClass().getSimpleName()); - baseMapper.insert(template); - log.info("Inserted template from provider: {}", code); - } else { - // Update metadata - SysReportTemplate update = new SysReportTemplate(); - update.setId(existing.getId()); - boolean changed = false; - - // Sync Title - String title = provider.getReportTitle(); - if (StringUtils.isNotBlank(title) && !title.equals(existing.getTemplateName())) { - update.setTemplateName(title); - changed = true; - } - - // Sync MockData if provided - Map mockData = provider.getMockData(); - if (mockData != null && !mockData.isEmpty()) { - update.setMockData(mockData); - changed = true; - } - - // Optional: Sync content if it was empty in DB (unlikely) - if (StringUtils.isBlank(existing.getContent()) && StringUtils.isNotBlank(content)) { - update.setContent(content); - changed = true; - } - - if (changed) { - baseMapper.updateById(update); - log.info("Updated template metadata from provider: {}", code); - } + String content = readTemplateContent(code); + if (StringUtils.isBlank(content)) { + log.warn("Skipping provider {} because template file {} not found", provider.getClass().getSimpleName(), code); + continue; } + + LocalTemplateSnapshot snapshot = new LocalTemplateSnapshot(); + snapshot.setTemplateCode(code); + snapshot.setTemplateName(provider.getReportTitle()); + snapshot.setContent(content); + snapshot.setMockData(provider.getMockData()); + snapshot.setRemark(PROVIDER_REMARK_PREFIX + provider.getClass().getSimpleName()); + templateMap.put(code, snapshot); } catch (Exception e) { - log.error("Error syncing provider template: " + provider.getClass().getSimpleName(), e); + log.error("Error loading provider template: {}", provider.getClass().getSimpleName(), e); } } } - // 2. Scan remaining files (for layouts and fragments) ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try { - // Find all .vm files in templates directory Resource[] resources = resolver.getResources("classpath*:templates/**/*.vm"); for (Resource resource : resources) { try { - String path = resource.getURL().getPath(); - // Normalize path separators - path = path.replace("\\", "/"); - - int index = path.lastIndexOf("templates/"); - if (index > -1) { - String code = path.substring(index); // Include "templates/" in the code - - // Check if exists - SysReportTemplateVo exist = queryByCode(code); - if (exist == null) { - String content = IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8); - SysReportTemplate template = new SysReportTemplate(); - template.setTemplateCode(code); - // Use filename as name - template.setTemplateName(FileUtil.mainName(resource.getFilename())); - template.setContent(content); - template.setVersion(1); - template.setRemark("Synced from classpath scan"); - baseMapper.insert(template); - log.info("Synced template from scan: {}", code); - } + String code = extractTemplateCode(resource); + if (StringUtils.isBlank(code) || templateMap.containsKey(code)) { + continue; } + + LocalTemplateSnapshot snapshot = new LocalTemplateSnapshot(); + snapshot.setTemplateCode(code); + snapshot.setTemplateName(FileUtil.mainName(resource.getFilename())); + snapshot.setContent(IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8)); + snapshot.setRemark(CLASSPATH_SCAN_REMARK); + templateMap.put(code, snapshot); } catch (Exception e) { - log.error("Error processing resource: " + resource, e); + log.error("Error processing resource: {}", resource, e); } } } catch (IOException e) { - log.error("Failed to sync templates", e); - throw new ServiceException("同步模版失败"); + log.error("Failed to load local templates", e); + throw new ServiceException("读取本地模板失败"); + } + + return templateMap; + } + + private String extractTemplateCode(Resource resource) throws IOException { + String path = resource.getURL().getPath().replace("\\", "/"); + int index = path.lastIndexOf("templates/"); + if (index < 0) { + return null; + } + return path.substring(index); + } + + private String readTemplateContent(String code) { + try { + ClassPathResource resource = new ClassPathResource(code); + if (!resource.exists()) { + return null; + } + return IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8); + } catch (Exception e) { + log.warn("Template file not found: {}", code, e); + return null; } } + + private Set normalizeTemplateCodes(Collection templateCodes) { + if (templateCodes == null) { + return null; + } + return templateCodes.stream() + .filter(StringUtils::isNotBlank) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + private String normalizeText(String value) { + return value == null ? "" : value; + } + + private String toJsonText(Object value) { + if (value == null) { + return ""; + } + return JsonUtils.toJsonString(value); + } + + @lombok.Data + private static class LocalTemplateSnapshot { + + private String templateCode; + + private String templateName; + + private String content; + + private Object mockData; + + private String remark; + } }