|
|
|
|
@@ -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<IReportDataProvider> 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<SysReportTemplateSyncCompareVo> querySyncDiffList() {
|
|
|
|
|
LinkedHashMap<String, LocalTemplateSnapshot> localTemplates = loadLocalTemplateSnapshots();
|
|
|
|
|
Map<String, SysReportTemplateVo> dbTemplates = queryExistingTemplates(null);
|
|
|
|
|
|
|
|
|
|
Set<String> 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<String> templateCodes) {
|
|
|
|
|
LinkedHashMap<String, LocalTemplateSnapshot> localTemplates = loadLocalTemplateSnapshots();
|
|
|
|
|
Set<String> targetCodes = normalizeTemplateCodes(templateCodes);
|
|
|
|
|
if (targetCodes != null && targetCodes.isEmpty()) {
|
|
|
|
|
throw new ServiceException("请选择需要同步的模板");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (targetCodes != null) {
|
|
|
|
|
List<String> missingCodes = targetCodes.stream()
|
|
|
|
|
.filter(code -> !localTemplates.containsKey(code))
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
if (!missingCodes.isEmpty()) {
|
|
|
|
|
throw new ServiceException("以下模板在本地不存在,无法同步: " + String.join(", ", missingCodes));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Map<String, SysReportTemplateVo> 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<String> 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<String> buildDiffTypes(LocalTemplateSnapshot local, SysReportTemplateVo db) {
|
|
|
|
|
List<String> 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<String, SysReportTemplateVo> queryExistingTemplates(Set<String> templateCodes) {
|
|
|
|
|
LambdaQueryWrapper<SysReportTemplate> 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<String, LocalTemplateSnapshot> loadLocalTemplateSnapshots() {
|
|
|
|
|
LinkedHashMap<String, LocalTemplateSnapshot> 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<String, Object> 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<String> normalizeTemplateCodes(Collection<String> 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|