diff --git a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/dal/mysql/customtag/CustomTagMapper.java b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/dal/mysql/customtag/CustomTagMapper.java index 4a9f818..03445ed 100644 --- a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/dal/mysql/customtag/CustomTagMapper.java +++ b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/dal/mysql/customtag/CustomTagMapper.java @@ -30,4 +30,10 @@ public interface CustomTagMapper extends BaseMapperX { .orderByDesc(CustomTagDO::getId)); } + default List selectListByType(String type) { + return selectList(new LambdaQueryWrapperX() + .eq(CustomTagDO::getType, type) + .orderByAsc(CustomTagDO::getId)); + } + } \ No newline at end of file diff --git a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/dal/mysql/tag/TagMapper.java b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/dal/mysql/tag/TagMapper.java index 6cd3107..02171b8 100644 --- a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/dal/mysql/tag/TagMapper.java +++ b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/dal/mysql/tag/TagMapper.java @@ -29,4 +29,10 @@ public interface TagMapper extends BaseMapperX { .eqIfPresent(TagDO::getType, type) .orderByDesc(TagDO::getId)); } + + default TagDO selectByNameAndType(String name, String type) { + return selectOne(new LambdaQueryWrapperX() + .eq(TagDO::getName, name) + .eq(TagDO::getType, type)); + } } diff --git a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/enums/ErrorCodeConstants.java b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/enums/ErrorCodeConstants.java index 32201dd..cd65730 100644 --- a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/enums/ErrorCodeConstants.java +++ b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/enums/ErrorCodeConstants.java @@ -24,4 +24,5 @@ public interface ErrorCodeConstants { ErrorCode TAG_CONFIG_NOT_EXISTS = new ErrorCode(1_001_001_002, "标签同步配置不存在"); ErrorCode TAG_SYNC_HISTORY_NOT_EXISTS = new ErrorCode(1_001_001_003, "标签同步历史不存在"); ErrorCode TAG_CONFIG_DATABASE_REQUIRED = new ErrorCode(1_001_001_004, "新增配置时同步数据源不能为空"); + ErrorCode TAG_NAME_DUPLICATE = new ErrorCode(1_001_001_005, "同类型下已存在相同名称的标签"); } diff --git a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/customtag/CustomTagService.java b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/customtag/CustomTagService.java index 885f7bc..5a0ef37 100644 --- a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/customtag/CustomTagService.java +++ b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/customtag/CustomTagService.java @@ -59,4 +59,12 @@ public interface CustomTagService { */ PageResult getCustomTagPage(CustomTagPageReqVO pageReqVO); + /** + * 根据类型获取定制标签列表(用于同步) + * + * @param type 标签类型 product/store/supplier/member + * @return 定制标签列表 + */ + List getCustomTagListByType(String type); + } \ No newline at end of file diff --git a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/customtag/CustomTagServiceImpl.java b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/customtag/CustomTagServiceImpl.java index ffa5c86..a685b2b 100644 --- a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/customtag/CustomTagServiceImpl.java +++ b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/customtag/CustomTagServiceImpl.java @@ -82,4 +82,9 @@ public class CustomTagServiceImpl implements CustomTagService { return customTagMapper.selectPage(pageReqVO); } + @Override + public List getCustomTagListByType(String type) { + return customTagMapper.selectListByType(type); + } + } \ No newline at end of file diff --git a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tag/TagServiceImpl.java b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tag/TagServiceImpl.java index ab95f7c..9af3033 100644 --- a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tag/TagServiceImpl.java +++ b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tag/TagServiceImpl.java @@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ydoyun.dal.mysql.tag.TagMapper; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.ydoyun.enums.ErrorCodeConstants.TAG_NAME_DUPLICATE; import static cn.iocoder.yudao.module.ydoyun.enums.ErrorCodeConstants.TAG_NOT_EXISTS; @Service @@ -21,6 +22,7 @@ public class TagServiceImpl implements TagService { @Override public Long createTag(TagSaveReqVO createReqVO) { + validateTagNameUnique(createReqVO.getName(), createReqVO.getType(), null); TagDO tag = BeanUtils.toBean(createReqVO, TagDO.class); tagMapper.insert(tag); return tag.getId(); @@ -29,10 +31,19 @@ public class TagServiceImpl implements TagService { @Override public void updateTag(TagSaveReqVO updateReqVO) { validateTagExists(updateReqVO.getId()); + validateTagNameUnique(updateReqVO.getName(), updateReqVO.getType(), updateReqVO.getId()); TagDO updateObj = BeanUtils.toBean(updateReqVO, TagDO.class); tagMapper.updateById(updateObj); } + private void validateTagNameUnique(String name, String type, Long excludeId) { + if (name == null || type == null) return; + TagDO existing = tagMapper.selectByNameAndType(name, type); + if (existing != null && (excludeId == null || !existing.getId().equals(excludeId))) { + throw exception(TAG_NAME_DUPLICATE); + } + } + @Override public void deleteTag(Long id) { validateTagExists(id); diff --git a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tagconfig/TagConfigService.java b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tagconfig/TagConfigService.java index fb9084a..67001dc 100644 --- a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tagconfig/TagConfigService.java +++ b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tagconfig/TagConfigService.java @@ -19,4 +19,5 @@ public interface TagConfigService { PageResult getTagConfigPage(TagConfigPageReqVO pageReqVO); void saveAll(@Valid TagConfigSaveAllReqVO reqVO); void manualSync(Long tagConfigId); + void initTagTables(Long databaseId); } diff --git a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tagconfig/TagConfigServiceImpl.java b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tagconfig/TagConfigServiceImpl.java index ae032d7..0dfb7e2 100644 --- a/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tagconfig/TagConfigServiceImpl.java +++ b/yudao-module-ydoyun/src/main/java/cn/iocoder/yudao/module/ydoyun/service/tagconfig/TagConfigServiceImpl.java @@ -1,5 +1,13 @@ package cn.iocoder.yudao.module.ydoyun.service.tagconfig; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.ydoyun.config.ProcedureHttpClient; +import cn.iocoder.yudao.module.ydoyun.dal.dataobject.reportdatabase.ReportDatabaseDO; +import cn.iocoder.yudao.module.ydoyun.dal.dataobject.customtag.CustomTagDO; +import cn.iocoder.yudao.module.ydoyun.dal.dataobject.tag.TagDO; +import cn.iocoder.yudao.module.ydoyun.dal.dataobject.tagsyncdetail.TagSyncDetailDO; +import cn.iocoder.yudao.module.ydoyun.dal.mysql.reportdatabase.ReportDatabaseMapper; +import cn.iocoder.yudao.module.ydoyun.service.tagsyncdetail.TagSyncDetailService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import org.springframework.validation.annotation.Validated; @@ -13,11 +21,14 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.ydoyun.dal.mysql.tagconfig.TagConfigMapper; import cn.iocoder.yudao.module.ydoyun.dal.mysql.tagsynchistory.TagSyncHistoryMapper; +import cn.iocoder.yudao.module.ydoyun.service.customtag.CustomTagService; import cn.iocoder.yudao.module.ydoyun.service.tag.TagService; +import lombok.extern.slf4j.Slf4j; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.ydoyun.enums.ErrorCodeConstants.TAG_CONFIG_DATABASE_REQUIRED; import static cn.iocoder.yudao.module.ydoyun.enums.ErrorCodeConstants.TAG_CONFIG_NOT_EXISTS; +@Slf4j @Service @Validated public class TagConfigServiceImpl implements TagConfigService { @@ -31,6 +42,18 @@ public class TagConfigServiceImpl implements TagConfigService { @Resource private TagService tagService; + @Resource + private ReportDatabaseMapper reportDatabaseMapper; + + @Resource + private ProcedureHttpClient httpClient; + + @Resource + private TagSyncDetailService tagSyncDetailService; + + @Resource + private CustomTagService customTagService; + @Override public Long createTagConfig(TagConfigSaveReqVO createReqVO) { TagConfigDO tagConfig = BeanUtils.toBean(createReqVO, TagConfigDO.class); @@ -120,19 +143,176 @@ public class TagConfigServiceImpl implements TagConfigService { public void manualSync(Long tagConfigId) { validateTagConfigExists(tagConfigId); TagConfigDO config = tagConfigMapper.selectById(tagConfigId); + if (config.getDatabaseId() == null) { + throw exception(TAG_CONFIG_DATABASE_REQUIRED); + } + ReportDatabaseDO reportDatabase = reportDatabaseMapper.selectById(config.getDatabaseId()); + if (reportDatabase == null) { + throw exception(TAG_CONFIG_DATABASE_REQUIRED); + } + + // 0. 先执行初始化标签库(删除并重建四张表) + initTagTables(config.getDatabaseId()); + + // 1. 收集标准标签的拼接好的脚本,过滤掉空的 + List tagsWithScript = new ArrayList<>(); + for (String type : Arrays.asList("product", "store", "supplier", "member")) { + List list = tagService.getTagListByType(type); + for (TagDO tag : list) { + String sql = tag.getSqlScriptResolved(); + if (StrUtil.isNotBlank(sql) && sql.trim().length() > 0) { + tagsWithScript.add(tag); + } + } + } + + // 1.1 收集定制标签(按类型,仅 useParams=false 的参与同步) + List customTagsWithScript = new ArrayList<>(); + for (String type : Arrays.asList("product", "store", "supplier", "member")) { + List customList = customTagService.getCustomTagListByType(type); + for (CustomTagDO ct : customList) { + if (Boolean.TRUE.equals(ct.getUseParams())) { + continue; // 代入参数的定制标签在报表运行时执行,不同步 + } + String sql = ct.getSqlScript(); + if (StrUtil.isNotBlank(sql) && sql.trim().length() > 0) { + customTagsWithScript.add(ct); + } + } + } + + int totalTagCount = tagsWithScript.size() + customTagsWithScript.size(); + LocalDateTime now = LocalDateTime.now(); TagSyncHistoryDO history = TagSyncHistoryDO.builder() .tagConfigId(tagConfigId) .syncType("manual") .syncTime(now) .syncStatus("success") - .totalCount(0) + .totalCount(totalTagCount) .successCount(0) .failCount(0) .recordCount(0L) .build(); tagSyncHistoryMapper.insert(history); + + List detailList = new ArrayList<>(); + int successCount = 0; + int failCount = 0; + long totalRecordCount = 0L; + List errorMessages = new ArrayList<>(); + + // 2. 循环调用 postExecuteSql 执行每个脚本 + for (int i = 0; i < tagsWithScript.size(); i++) { + TagDO tag = tagsWithScript.get(i); + String sql = tag.getSqlScriptResolved().trim(); + TagSyncDetailDO detail = TagSyncDetailDO.builder() + .syncHistoryId(history.getId()) + .tagId(tag.getId()) + .tagName(tag.getName()) + .tagType(tag.getType()) + .sqlExecuted(sql) + .execOrder(i + 1) + .build(); + try { + Map params = new HashMap<>(4); + params.put("reportDatabase", reportDatabase); + params.put("sql", sql); + httpClient.postExecuteSql(params); + detail.setExecStatus("success"); + detail.setRecordCount(0L); + successCount++; + detailList.add(detail); + } catch (Exception e) { + log.error("标签同步执行失败 tagId={} name={}", tag.getId(), tag.getName(), e); + detail.setExecStatus("fail"); + detail.setErrorMessage(e.getMessage()); + failCount++; + errorMessages.add(tag.getName() + ": " + e.getMessage()); + detailList.add(detail); + } + } + + // 3. 执行定制标签 SQL(按类型) + int execOrderOffset = tagsWithScript.size(); + for (int i = 0; i < customTagsWithScript.size(); i++) { + CustomTagDO ct = customTagsWithScript.get(i); + String sql = ct.getSqlScript().trim(); + TagSyncDetailDO detail = TagSyncDetailDO.builder() + .syncHistoryId(history.getId()) + .tagId(ct.getId()) + .tagName("[定制]" + ct.getName()) + .tagType(ct.getType()) + .sqlExecuted(sql) + .execOrder(execOrderOffset + i + 1) + .build(); + try { + Map params = new HashMap<>(4); + params.put("reportDatabase", reportDatabase); + params.put("sql", sql); + httpClient.postExecuteSql(params); + detail.setExecStatus("success"); + detail.setRecordCount(0L); + successCount++; + detailList.add(detail); + } catch (Exception e) { + log.error("定制标签同步执行失败 customTagId={} name={}", ct.getId(), ct.getName(), e); + detail.setExecStatus("fail"); + detail.setErrorMessage(e.getMessage()); + failCount++; + errorMessages.add("[定制]" + ct.getName() + ": " + e.getMessage()); + detailList.add(detail); + } + } + + tagSyncDetailService.insertBatch(detailList); + + history.setSuccessCount(successCount); + history.setFailCount(failCount); + history.setRecordCount(totalRecordCount); + history.setSyncStatus(failCount > 0 ? (successCount > 0 ? "partial" : "fail") : "success"); + if (!errorMessages.isEmpty()) { + history.setErrorMessage(String.join("; ", errorMessages)); + } + tagSyncHistoryMapper.updateById(history); + config.setLastSyncTime(now); tagConfigMapper.updateById(config); } + + @Override + public void initTagTables(Long databaseId) { + if (databaseId == null) { + throw exception(TAG_CONFIG_DATABASE_REQUIRED); + } + ReportDatabaseDO reportDatabase = reportDatabaseMapper.selectById(databaseId); + if (reportDatabase == null) { + throw exception(TAG_CONFIG_DATABASE_REQUIRED); + } + + // SQL Server 语法:先删除再创建(不记录同步历史),color 用于保存标签颜色 + String[][] tableDdl = { + {"ydoyun_tag_product", "CREATE TABLE ydoyun_tag_product (id BIGINT IDENTITY(1,1) PRIMARY KEY, code NVARCHAR(64) NOT NULL, name NVARCHAR(128) NOT NULL, color NVARCHAR(32) NULL, create_time DATETIME NOT NULL DEFAULT GETDATE())"}, + {"ydoyun_tag_supplier", "CREATE TABLE ydoyun_tag_supplier (id BIGINT IDENTITY(1,1) PRIMARY KEY, code NVARCHAR(64) NOT NULL, name NVARCHAR(128) NOT NULL, color NVARCHAR(32) NULL, create_time DATETIME NOT NULL DEFAULT GETDATE())"}, + {"ydoyun_tag_store", "CREATE TABLE ydoyun_tag_store (id BIGINT IDENTITY(1,1) PRIMARY KEY, code NVARCHAR(64) NOT NULL, name NVARCHAR(128) NOT NULL, color NVARCHAR(32) NULL, create_time DATETIME NOT NULL DEFAULT GETDATE())"}, + {"ydoyun_tag_member", "CREATE TABLE ydoyun_tag_member (id BIGINT IDENTITY(1,1) PRIMARY KEY, code NVARCHAR(64) NOT NULL, name NVARCHAR(128) NOT NULL, color NVARCHAR(32) NULL, create_time DATETIME NOT NULL DEFAULT GETDATE())"} + }; + + for (String[] pair : tableDdl) { + String tableName = pair[0]; + String createSql = pair[1]; + try { + Map dropParams = new HashMap<>(4); + dropParams.put("reportDatabase", reportDatabase); + dropParams.put("sql", "DROP TABLE IF EXISTS " + tableName); + httpClient.postExecuteSql(dropParams); + } catch (Exception e) { + log.warn("删除表 {} 时忽略错误(可能表不存在): {}", tableName, e.getMessage()); + } + Map createParams = new HashMap<>(4); + createParams.put("reportDatabase", reportDatabase); + createParams.put("sql", createSql); + httpClient.postExecuteSql(createParams); + } + } }