fix: 提交标签同步

This commit is contained in:
2026-03-09 16:54:42 +08:00
parent cb18a381f1
commit 0bfd22ccca
8 changed files with 219 additions and 1 deletions

View File

@@ -30,4 +30,10 @@ public interface CustomTagMapper extends BaseMapperX<CustomTagDO> {
.orderByDesc(CustomTagDO::getId)); .orderByDesc(CustomTagDO::getId));
} }
default List<CustomTagDO> selectListByType(String type) {
return selectList(new LambdaQueryWrapperX<CustomTagDO>()
.eq(CustomTagDO::getType, type)
.orderByAsc(CustomTagDO::getId));
}
} }

View File

@@ -29,4 +29,10 @@ public interface TagMapper extends BaseMapperX<TagDO> {
.eqIfPresent(TagDO::getType, type) .eqIfPresent(TagDO::getType, type)
.orderByDesc(TagDO::getId)); .orderByDesc(TagDO::getId));
} }
default TagDO selectByNameAndType(String name, String type) {
return selectOne(new LambdaQueryWrapperX<TagDO>()
.eq(TagDO::getName, name)
.eq(TagDO::getType, type));
}
} }

View File

@@ -24,4 +24,5 @@ public interface ErrorCodeConstants {
ErrorCode TAG_CONFIG_NOT_EXISTS = new ErrorCode(1_001_001_002, "标签同步配置不存在"); 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_SYNC_HISTORY_NOT_EXISTS = new ErrorCode(1_001_001_003, "标签同步历史不存在");
ErrorCode TAG_CONFIG_DATABASE_REQUIRED = new ErrorCode(1_001_001_004, "新增配置时同步数据源不能为空"); ErrorCode TAG_CONFIG_DATABASE_REQUIRED = new ErrorCode(1_001_001_004, "新增配置时同步数据源不能为空");
ErrorCode TAG_NAME_DUPLICATE = new ErrorCode(1_001_001_005, "同类型下已存在相同名称的标签");
} }

View File

@@ -59,4 +59,12 @@ public interface CustomTagService {
*/ */
PageResult<CustomTagDO> getCustomTagPage(CustomTagPageReqVO pageReqVO); PageResult<CustomTagDO> getCustomTagPage(CustomTagPageReqVO pageReqVO);
/**
* 根据类型获取定制标签列表(用于同步)
*
* @param type 标签类型 product/store/supplier/member
* @return 定制标签列表
*/
List<CustomTagDO> getCustomTagListByType(String type);
} }

View File

@@ -82,4 +82,9 @@ public class CustomTagServiceImpl implements CustomTagService {
return customTagMapper.selectPage(pageReqVO); return customTagMapper.selectPage(pageReqVO);
} }
@Override
public List<CustomTagDO> getCustomTagListByType(String type) {
return customTagMapper.selectListByType(type);
}
} }

View File

@@ -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.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ydoyun.dal.mysql.tag.TagMapper; 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.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; import static cn.iocoder.yudao.module.ydoyun.enums.ErrorCodeConstants.TAG_NOT_EXISTS;
@Service @Service
@@ -21,6 +22,7 @@ public class TagServiceImpl implements TagService {
@Override @Override
public Long createTag(TagSaveReqVO createReqVO) { public Long createTag(TagSaveReqVO createReqVO) {
validateTagNameUnique(createReqVO.getName(), createReqVO.getType(), null);
TagDO tag = BeanUtils.toBean(createReqVO, TagDO.class); TagDO tag = BeanUtils.toBean(createReqVO, TagDO.class);
tagMapper.insert(tag); tagMapper.insert(tag);
return tag.getId(); return tag.getId();
@@ -29,10 +31,19 @@ public class TagServiceImpl implements TagService {
@Override @Override
public void updateTag(TagSaveReqVO updateReqVO) { public void updateTag(TagSaveReqVO updateReqVO) {
validateTagExists(updateReqVO.getId()); validateTagExists(updateReqVO.getId());
validateTagNameUnique(updateReqVO.getName(), updateReqVO.getType(), updateReqVO.getId());
TagDO updateObj = BeanUtils.toBean(updateReqVO, TagDO.class); TagDO updateObj = BeanUtils.toBean(updateReqVO, TagDO.class);
tagMapper.updateById(updateObj); 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 @Override
public void deleteTag(Long id) { public void deleteTag(Long id) {
validateTagExists(id); validateTagExists(id);

View File

@@ -19,4 +19,5 @@ public interface TagConfigService {
PageResult<TagConfigDO> getTagConfigPage(TagConfigPageReqVO pageReqVO); PageResult<TagConfigDO> getTagConfigPage(TagConfigPageReqVO pageReqVO);
void saveAll(@Valid TagConfigSaveAllReqVO reqVO); void saveAll(@Valid TagConfigSaveAllReqVO reqVO);
void manualSync(Long tagConfigId); void manualSync(Long tagConfigId);
void initTagTables(Long databaseId);
} }

View File

@@ -1,5 +1,13 @@
package cn.iocoder.yudao.module.ydoyun.service.tagconfig; 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 org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated; 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.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.ydoyun.dal.mysql.tagconfig.TagConfigMapper; 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.dal.mysql.tagsynchistory.TagSyncHistoryMapper;
import cn.iocoder.yudao.module.ydoyun.service.customtag.CustomTagService;
import cn.iocoder.yudao.module.ydoyun.service.tag.TagService; 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.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_DATABASE_REQUIRED;
import static cn.iocoder.yudao.module.ydoyun.enums.ErrorCodeConstants.TAG_CONFIG_NOT_EXISTS; import static cn.iocoder.yudao.module.ydoyun.enums.ErrorCodeConstants.TAG_CONFIG_NOT_EXISTS;
@Slf4j
@Service @Service
@Validated @Validated
public class TagConfigServiceImpl implements TagConfigService { public class TagConfigServiceImpl implements TagConfigService {
@@ -31,6 +42,18 @@ public class TagConfigServiceImpl implements TagConfigService {
@Resource @Resource
private TagService tagService; private TagService tagService;
@Resource
private ReportDatabaseMapper reportDatabaseMapper;
@Resource
private ProcedureHttpClient httpClient;
@Resource
private TagSyncDetailService tagSyncDetailService;
@Resource
private CustomTagService customTagService;
@Override @Override
public Long createTagConfig(TagConfigSaveReqVO createReqVO) { public Long createTagConfig(TagConfigSaveReqVO createReqVO) {
TagConfigDO tagConfig = BeanUtils.toBean(createReqVO, TagConfigDO.class); TagConfigDO tagConfig = BeanUtils.toBean(createReqVO, TagConfigDO.class);
@@ -120,19 +143,176 @@ public class TagConfigServiceImpl implements TagConfigService {
public void manualSync(Long tagConfigId) { public void manualSync(Long tagConfigId) {
validateTagConfigExists(tagConfigId); validateTagConfigExists(tagConfigId);
TagConfigDO config = tagConfigMapper.selectById(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<TagDO> tagsWithScript = new ArrayList<>();
for (String type : Arrays.asList("product", "store", "supplier", "member")) {
List<TagDO> 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<CustomTagDO> customTagsWithScript = new ArrayList<>();
for (String type : Arrays.asList("product", "store", "supplier", "member")) {
List<CustomTagDO> 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(); LocalDateTime now = LocalDateTime.now();
TagSyncHistoryDO history = TagSyncHistoryDO.builder() TagSyncHistoryDO history = TagSyncHistoryDO.builder()
.tagConfigId(tagConfigId) .tagConfigId(tagConfigId)
.syncType("manual") .syncType("manual")
.syncTime(now) .syncTime(now)
.syncStatus("success") .syncStatus("success")
.totalCount(0) .totalCount(totalTagCount)
.successCount(0) .successCount(0)
.failCount(0) .failCount(0)
.recordCount(0L) .recordCount(0L)
.build(); .build();
tagSyncHistoryMapper.insert(history); tagSyncHistoryMapper.insert(history);
List<TagSyncDetailDO> detailList = new ArrayList<>();
int successCount = 0;
int failCount = 0;
long totalRecordCount = 0L;
List<String> 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<String, Object> 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<String, Object> 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); config.setLastSyncTime(now);
tagConfigMapper.updateById(config); 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<String, Object> 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<String, Object> createParams = new HashMap<>(4);
createParams.put("reportDatabase", reportDatabase);
createParams.put("sql", createSql);
httpClient.postExecuteSql(createParams);
}
}
} }