|
@@ -0,0 +1,552 @@
|
|
|
|
|
+package com.fs.tenant.dict.service.impl;
|
|
|
|
|
+
|
|
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
|
|
+import com.fs.common.core.domain.entity.SysDictData;
|
|
|
|
|
+import com.fs.common.core.domain.entity.SysDictType;
|
|
|
|
|
+import com.fs.common.enums.DataSourceType;
|
|
|
|
|
+import com.fs.common.exception.CustomException;
|
|
|
|
|
+import com.fs.common.utils.StringUtils;
|
|
|
|
|
+import com.fs.framework.datasource.DynamicDataSourceContextHolder;
|
|
|
|
|
+import com.fs.system.mapper.SysDictDataMapper;
|
|
|
|
|
+import com.fs.system.mapper.SysDictTypeMapper;
|
|
|
|
|
+import com.fs.system.service.ISysDictTypeService;
|
|
|
|
|
+import com.fs.tenant.dict.constant.TenantDictConstants;
|
|
|
|
|
+import com.fs.tenant.dict.domain.TenantDictTemplateData;
|
|
|
|
|
+import com.fs.tenant.dict.domain.TenantDictTemplateType;
|
|
|
|
|
+import com.fs.tenant.dict.dto.TenantDictSyncRunReq;
|
|
|
|
|
+import com.fs.tenant.dict.helper.TenantDictContextHelper;
|
|
|
|
|
+import com.fs.tenant.dict.service.TenantDictSyncService;
|
|
|
|
|
+import com.fs.tenant.dict.service.TenantDictTemplateService;
|
|
|
|
|
+import com.fs.tenant.dict.vo.TenantDictSyncResultVo;
|
|
|
|
|
+import com.fs.tenant.dict.vo.TenantDictSyncTaskDetailVo;
|
|
|
|
|
+import com.fs.tenant.dict.vo.TenantDictSyncTaskVo;
|
|
|
|
|
+import com.fs.tenant.domain.TenantInfo;
|
|
|
|
|
+import com.fs.tenant.service.TenantInfoService;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.concurrent.*;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 租户字典同步引擎:模板 → 租户库,支持 APPEND / MERGE / OVERWRITE。
|
|
|
|
|
+ */
|
|
|
|
|
+@Service
|
|
|
|
|
+public class TenantDictSyncServiceImpl implements TenantDictSyncService {
|
|
|
|
|
+
|
|
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(TenantDictSyncServiceImpl.class);
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private TenantDictTemplateService templateService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private TenantDictContextHelper contextHelper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private TenantInfoService tenantInfoService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private SysDictTypeMapper dictTypeMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private SysDictDataMapper dictDataMapper;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private ISysDictTypeService dictTypeService;
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private JdbcTemplate jdbcTemplate;
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Map<String, Object> runSync(TenantDictSyncRunReq req, String triggerBy) {
|
|
|
|
|
+ validateSyncReq(req);
|
|
|
|
|
+ List<TenantDictTemplateType> templateTypes = resolveTemplateTypes(req.getDictTypes());
|
|
|
|
|
+ if (templateTypes.isEmpty()) {
|
|
|
|
|
+ throw new CustomException("没有可同步的模板字典类型,请先在平台模板中维护");
|
|
|
|
|
+ }
|
|
|
|
|
+ List<TenantInfo> tenants = resolveTenants(req);
|
|
|
|
|
+ if (tenants.isEmpty()) {
|
|
|
|
|
+ throw new CustomException("未匹配到可同步租户");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (Boolean.TRUE.equals(req.getDryRun())) {
|
|
|
|
|
+ List<TenantDictSyncResultVo> preview = new ArrayList<>();
|
|
|
|
|
+ for (TenantInfo tenant : tenants) {
|
|
|
|
|
+ preview.add(previewOneTenant(tenant, templateTypes, req.getSyncMode()));
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<String, Object> resp = new HashMap<>();
|
|
|
|
|
+ resp.put("dryRun", true);
|
|
|
|
|
+ resp.put("preview", preview);
|
|
|
|
|
+ return resp;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String taskNo = buildTaskNo();
|
|
|
|
|
+ contextHelper.runInMaster(() -> {
|
|
|
|
|
+ initMasterTaskTables();
|
|
|
|
|
+ insertTask(taskNo, req, tenants.size(), triggerBy);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ CompletableFuture.runAsync(() -> executeSyncTask(taskNo, req, tenants, templateTypes));
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> resp = new HashMap<>();
|
|
|
|
|
+ resp.put("taskNo", taskNo);
|
|
|
|
|
+ resp.put("status", TenantDictConstants.TASK_RUNNING);
|
|
|
|
|
+ resp.put("totalTenants", tenants.size());
|
|
|
|
|
+ resp.put("syncMode", req.getSyncMode());
|
|
|
|
|
+ return resp;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public TenantDictSyncTaskVo getTask(String taskNo) {
|
|
|
|
|
+ return contextHelper.executeInMaster(() -> {
|
|
|
|
|
+ initMasterTaskTables();
|
|
|
|
|
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(
|
|
|
|
|
+ "SELECT task_no,sync_mode,scope_type,dict_types,total_tenants,success_tenants,failed_tenants,status,trigger_by,started_at,finished_at " +
|
|
|
|
|
+ "FROM tenant_dict_sync_task WHERE task_no=?", taskNo);
|
|
|
|
|
+ if (rows.isEmpty()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<String, Object> row = rows.get(0);
|
|
|
|
|
+ TenantDictSyncTaskVo vo = new TenantDictSyncTaskVo();
|
|
|
|
|
+ vo.setTaskNo(str(row.get("task_no")));
|
|
|
|
|
+ vo.setSyncMode(str(row.get("sync_mode")));
|
|
|
|
|
+ vo.setScopeType(str(row.get("scope_type")));
|
|
|
|
|
+ vo.setDictTypes(str(row.get("dict_types")));
|
|
|
|
|
+ vo.setTotalTenants(intVal(row.get("total_tenants")));
|
|
|
|
|
+ vo.setSuccessTenants(intVal(row.get("success_tenants")));
|
|
|
|
|
+ vo.setFailedTenants(intVal(row.get("failed_tenants")));
|
|
|
|
|
+ vo.setStatus(str(row.get("status")));
|
|
|
|
|
+ vo.setTriggerBy(str(row.get("trigger_by")));
|
|
|
|
|
+ vo.setStartedAt(str(row.get("started_at")));
|
|
|
|
|
+ vo.setFinishedAt(str(row.get("finished_at")));
|
|
|
|
|
+ vo.setDetails(queryTaskDetails(taskNo));
|
|
|
|
|
+ return vo;
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<TenantDictSyncResultVo> previewDiff(Long tenantId, List<String> dictTypes) {
|
|
|
|
|
+ TenantInfo tenant = contextHelper.loadActiveTenant(tenantId);
|
|
|
|
|
+ List<TenantDictTemplateType> templateTypes = resolveTemplateTypes(dictTypes);
|
|
|
|
|
+ TenantDictSyncResultVo result = previewOneTenant(tenant, templateTypes, TenantDictConstants.SYNC_MERGE);
|
|
|
|
|
+ return Collections.singletonList(result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void executeSyncTask(String taskNo, TenantDictSyncRunReq req, List<TenantInfo> tenants,
|
|
|
|
|
+ List<TenantDictTemplateType> templateTypes) {
|
|
|
|
|
+ int success = 0;
|
|
|
|
|
+ int failed = 0;
|
|
|
|
|
+ Map<String, List<TenantDictTemplateData>> dataCache = loadTemplateDataCache(templateTypes);
|
|
|
|
|
+
|
|
|
|
|
+ if (Boolean.TRUE.equals(req.getParallel())) {
|
|
|
|
|
+ int threads = req.getThreads() == null || req.getThreads() <= 0 ? 4 : req.getThreads();
|
|
|
|
|
+ ExecutorService pool = Executors.newFixedThreadPool(threads);
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<Future<Boolean>> futures = new ArrayList<>();
|
|
|
|
|
+ for (TenantInfo tenant : tenants) {
|
|
|
|
|
+ futures.add(pool.submit(() -> syncOneTenant(taskNo, tenant, templateTypes, dataCache, req.getSyncMode())));
|
|
|
|
|
+ }
|
|
|
|
|
+ for (Future<Boolean> f : futures) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (Boolean.TRUE.equals(f.get())) success++;
|
|
|
|
|
+ else failed++;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ failed++;
|
|
|
|
|
+ log.error("[TenantDictSync] 并行任务异常", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ pool.shutdown();
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ for (TenantInfo tenant : tenants) {
|
|
|
|
|
+ if (syncOneTenant(taskNo, tenant, templateTypes, dataCache, req.getSyncMode())) {
|
|
|
|
|
+ success++;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ failed++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ finalizeTask(taskNo, success, failed);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean syncOneTenant(String taskNo, TenantInfo tenant, List<TenantDictTemplateType> templateTypes,
|
|
|
|
|
+ Map<String, List<TenantDictTemplateData>> dataCache, String syncMode) {
|
|
|
|
|
+ Date start = new Date();
|
|
|
|
|
+ TenantDictSyncResultVo stats = new TenantDictSyncResultVo();
|
|
|
|
|
+ stats.setTenantId(tenant.getId());
|
|
|
|
|
+ stats.setTenantCode(tenant.getTenantCode());
|
|
|
|
|
+ stats.setTenantName(tenant.getTenantName());
|
|
|
|
|
+ String status = TenantDictConstants.DETAIL_SUCCESS;
|
|
|
|
|
+ String errorMsg = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ contextHelper.executeInTenant(tenant.getId(), () -> {
|
|
|
|
|
+ for (TenantDictTemplateType templateType : templateTypes) {
|
|
|
|
|
+ String mode = resolveMode(syncMode, templateType);
|
|
|
|
|
+ syncType(templateType, mode, stats);
|
|
|
|
|
+ List<TenantDictTemplateData> templateDataList = dataCache.getOrDefault(templateType.getDictType(), Collections.emptyList());
|
|
|
|
|
+ if (TenantDictConstants.SYNC_OVERWRITE.equals(mode)) {
|
|
|
|
|
+ overwriteData(templateType.getDictType(), templateDataList, stats);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ for (TenantDictTemplateData templateData : templateDataList) {
|
|
|
|
|
+ syncData(templateData, mode, stats);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ dictTypeService.resetDictCache();
|
|
|
|
|
+ return null;
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ status = TenantDictConstants.DETAIL_FAILED;
|
|
|
|
|
+ errorMsg = e.getMessage();
|
|
|
|
|
+ log.error("[TenantDictSync] tenantId={} 同步失败", tenant.getId(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ insertTaskDetail(taskNo, tenant, stats, status, errorMsg, start, new Date());
|
|
|
|
|
+ return TenantDictConstants.DETAIL_SUCCESS.equals(status);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private TenantDictSyncResultVo previewOneTenant(TenantInfo tenant, List<TenantDictTemplateType> templateTypes, String syncMode) {
|
|
|
|
|
+ Map<String, List<TenantDictTemplateData>> dataCache = loadTemplateDataCache(templateTypes);
|
|
|
|
|
+ TenantDictSyncResultVo stats = new TenantDictSyncResultVo();
|
|
|
|
|
+ stats.setTenantId(tenant.getId());
|
|
|
|
|
+ stats.setTenantCode(tenant.getTenantCode());
|
|
|
|
|
+ stats.setTenantName(tenant.getTenantName());
|
|
|
|
|
+ contextHelper.executeInTenant(tenant.getId(), () -> {
|
|
|
|
|
+ for (TenantDictTemplateType templateType : templateTypes) {
|
|
|
|
|
+ String mode = resolveMode(syncMode, templateType);
|
|
|
|
|
+ previewType(templateType, mode, stats);
|
|
|
|
|
+ List<TenantDictTemplateData> templateDataList = dataCache.getOrDefault(templateType.getDictType(), Collections.emptyList());
|
|
|
|
|
+ if (TenantDictConstants.SYNC_OVERWRITE.equals(mode)) {
|
|
|
|
|
+ previewOverwriteData(templateType.getDictType(), templateDataList, stats);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ for (TenantDictTemplateData templateData : templateDataList) {
|
|
|
|
|
+ previewData(templateData, mode, stats);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ });
|
|
|
|
|
+ stats.setMessage("预览完成(未写入)");
|
|
|
|
|
+ return stats;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void syncType(TenantDictTemplateType template, String mode, TenantDictSyncResultVo stats) {
|
|
|
|
|
+ SysDictType existing = dictTypeMapper.selectDictTypeByType(template.getDictType());
|
|
|
|
|
+ if (existing == null) {
|
|
|
|
|
+ SysDictType row = buildTypeFromTemplate(template);
|
|
|
|
|
+ dictTypeMapper.insertDictType(row);
|
|
|
|
|
+ stats.setTypeAdded(stats.getTypeAdded() + 1);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (TenantDictConstants.SYNC_APPEND.equals(mode)) {
|
|
|
|
|
+ stats.setDataSkipped(stats.getDataSkipped() + 1);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (canUpdateType(existing, mode)) {
|
|
|
|
|
+ applyTypeFromTemplate(existing, template);
|
|
|
|
|
+ dictTypeMapper.updateDictType(existing);
|
|
|
|
|
+ stats.setTypeUpdated(stats.getTypeUpdated() + 1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ stats.setDataSkipped(stats.getDataSkipped() + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void previewType(TenantDictTemplateType template, String mode, TenantDictSyncResultVo stats) {
|
|
|
|
|
+ SysDictType existing = dictTypeMapper.selectDictTypeByType(template.getDictType());
|
|
|
|
|
+ if (existing == null) {
|
|
|
|
|
+ stats.setTypeAdded(stats.getTypeAdded() + 1);
|
|
|
|
|
+ } else if (!TenantDictConstants.SYNC_APPEND.equals(mode) && canUpdateType(existing, mode)) {
|
|
|
|
|
+ stats.setTypeUpdated(stats.getTypeUpdated() + 1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ stats.setDataSkipped(stats.getDataSkipped() + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void syncData(TenantDictTemplateData template, String mode, TenantDictSyncResultVo stats) {
|
|
|
|
|
+ SysDictData existing = dictDataMapper.selectDictDataByTypeAndValue(template.getDictType(), template.getDictValue());
|
|
|
|
|
+ if (existing == null) {
|
|
|
|
|
+ dictDataMapper.insertDictData(buildDataFromTemplate(template));
|
|
|
|
|
+ stats.setDataAdded(stats.getDataAdded() + 1);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (TenantDictConstants.SYNC_APPEND.equals(mode)) {
|
|
|
|
|
+ stats.setDataSkipped(stats.getDataSkipped() + 1);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (canUpdateData(existing, template, mode)) {
|
|
|
|
|
+ applyDataFromTemplate(existing, template);
|
|
|
|
|
+ dictDataMapper.updateDictData(existing);
|
|
|
|
|
+ stats.setDataUpdated(stats.getDataUpdated() + 1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ stats.setDataSkipped(stats.getDataSkipped() + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void previewData(TenantDictTemplateData template, String mode, TenantDictSyncResultVo stats) {
|
|
|
|
|
+ SysDictData existing = dictDataMapper.selectDictDataByTypeAndValue(template.getDictType(), template.getDictValue());
|
|
|
|
|
+ if (existing == null) {
|
|
|
|
|
+ stats.setDataAdded(stats.getDataAdded() + 1);
|
|
|
|
|
+ } else if (!TenantDictConstants.SYNC_APPEND.equals(mode) && canUpdateData(existing, template, mode)) {
|
|
|
|
|
+ stats.setDataUpdated(stats.getDataUpdated() + 1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ stats.setDataSkipped(stats.getDataSkipped() + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void overwriteData(String dictType, List<TenantDictTemplateData> templateDataList, TenantDictSyncResultVo stats) {
|
|
|
|
|
+ List<SysDictData> tenantDataList = dictDataMapper.selectDictDataList(buildDataQuery(dictType));
|
|
|
|
|
+ Set<String> templateValues = templateDataList.stream().map(TenantDictTemplateData::getDictValue).collect(Collectors.toSet());
|
|
|
|
|
+ for (SysDictData row : tenantDataList) {
|
|
|
|
|
+ boolean platformItem = TenantDictConstants.SOURCE_PLATFORM.equals(row.getDictSource())
|
|
|
|
|
+ || (row.getIsPlatformManaged() != null && row.getIsPlatformManaged() == 1);
|
|
|
|
|
+ if (platformItem && !templateValues.contains(row.getDictValue())) {
|
|
|
|
|
+ dictDataMapper.deleteDictDataById(row.getDictCode());
|
|
|
|
|
+ stats.setDataRemoved(stats.getDataRemoved() + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for (TenantDictTemplateData template : templateDataList) {
|
|
|
|
|
+ SysDictData existing = dictDataMapper.selectDictDataByTypeAndValue(dictType, template.getDictValue());
|
|
|
|
|
+ if (existing == null) {
|
|
|
|
|
+ dictDataMapper.insertDictData(buildDataFromTemplate(template));
|
|
|
|
|
+ stats.setDataAdded(stats.getDataAdded() + 1);
|
|
|
|
|
+ } else if (canUpdateData(existing, template, TenantDictConstants.SYNC_OVERWRITE)
|
|
|
|
|
+ || TenantDictConstants.SOURCE_PLATFORM.equals(existing.getDictSource())) {
|
|
|
|
|
+ applyDataFromTemplate(existing, template);
|
|
|
|
|
+ dictDataMapper.updateDictData(existing);
|
|
|
|
|
+ stats.setDataUpdated(stats.getDataUpdated() + 1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ stats.setDataSkipped(stats.getDataSkipped() + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void previewOverwriteData(String dictType, List<TenantDictTemplateData> templateDataList, TenantDictSyncResultVo stats) {
|
|
|
|
|
+ List<SysDictData> tenantDataList = dictDataMapper.selectDictDataList(buildDataQuery(dictType));
|
|
|
|
|
+ Set<String> templateValues = templateDataList.stream().map(TenantDictTemplateData::getDictValue).collect(Collectors.toSet());
|
|
|
|
|
+ for (SysDictData row : tenantDataList) {
|
|
|
|
|
+ boolean platformItem = TenantDictConstants.SOURCE_PLATFORM.equals(row.getDictSource())
|
|
|
|
|
+ || (row.getIsPlatformManaged() != null && row.getIsPlatformManaged() == 1);
|
|
|
|
|
+ if (platformItem && !templateValues.contains(row.getDictValue())) {
|
|
|
|
|
+ stats.setDataRemoved(stats.getDataRemoved() + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ for (TenantDictTemplateData template : templateDataList) {
|
|
|
|
|
+ SysDictData existing = dictDataMapper.selectDictDataByTypeAndValue(dictType, template.getDictValue());
|
|
|
|
|
+ if (existing == null) {
|
|
|
|
|
+ stats.setDataAdded(stats.getDataAdded() + 1);
|
|
|
|
|
+ } else if (canUpdateData(existing, template, TenantDictConstants.SYNC_OVERWRITE)
|
|
|
|
|
+ || TenantDictConstants.SOURCE_PLATFORM.equals(existing.getDictSource())) {
|
|
|
|
|
+ stats.setDataUpdated(stats.getDataUpdated() + 1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ stats.setDataSkipped(stats.getDataSkipped() + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean canUpdateType(SysDictType existing, String mode) {
|
|
|
|
|
+ if (TenantDictConstants.SYNC_APPEND.equals(mode)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // MERGE / OVERWRITE:模板中的类型均写入平台溯源字段,便于租户侧锁定
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private boolean canUpdateData(SysDictData existing, TenantDictTemplateData template, String mode) {
|
|
|
|
|
+ if (TenantDictConstants.SYNC_OVERWRITE.equals(mode)) {
|
|
|
|
|
+ return TenantDictConstants.SOURCE_PLATFORM.equals(existing.getDictSource())
|
|
|
|
|
+ || (existing.getIsPlatformManaged() != null && existing.getIsPlatformManaged() == 1)
|
|
|
|
|
+ || (template.getIsManaged() != null && template.getIsManaged() == 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (TenantDictConstants.SYNC_MERGE.equals(mode)) {
|
|
|
|
|
+ return TenantDictConstants.SOURCE_PLATFORM.equals(existing.getDictSource())
|
|
|
|
|
+ || (existing.getIsPlatformManaged() != null && existing.getIsPlatformManaged() == 1)
|
|
|
|
|
+ || (template.getIsManaged() != null && template.getIsManaged() == 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private SysDictType buildTypeFromTemplate(TenantDictTemplateType template) {
|
|
|
|
|
+ SysDictType row = new SysDictType();
|
|
|
|
|
+ applyTypeFromTemplate(row, template);
|
|
|
|
|
+ row.setCreateBy("platform-sync");
|
|
|
|
|
+ return row;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void applyTypeFromTemplate(SysDictType row, TenantDictTemplateType template) {
|
|
|
|
|
+ row.setDictName(template.getDictName());
|
|
|
|
|
+ row.setDictType(template.getDictType());
|
|
|
|
|
+ row.setStatus(template.getStatus());
|
|
|
|
|
+ row.setRemark(template.getRemark());
|
|
|
|
|
+ row.setDictSource(TenantDictConstants.SOURCE_PLATFORM);
|
|
|
|
|
+ row.setIsPlatformManaged(template.getIsManaged() == null ? 1 : template.getIsManaged());
|
|
|
|
|
+ row.setUpdateBy("platform-sync");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private SysDictData buildDataFromTemplate(TenantDictTemplateData template) {
|
|
|
|
|
+ SysDictData row = new SysDictData();
|
|
|
|
|
+ applyDataFromTemplate(row, template);
|
|
|
|
|
+ row.setCreateBy("platform-sync");
|
|
|
|
|
+ return row;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void applyDataFromTemplate(SysDictData row, TenantDictTemplateData template) {
|
|
|
|
|
+ row.setDictSort(template.getDictSort());
|
|
|
|
|
+ row.setDictLabel(template.getDictLabel());
|
|
|
|
|
+ row.setDictValue(template.getDictValue());
|
|
|
|
|
+ row.setDictType(template.getDictType());
|
|
|
|
|
+ row.setCssClass(template.getCssClass());
|
|
|
|
|
+ row.setListClass(template.getListClass());
|
|
|
|
|
+ row.setIsDefault(template.getIsDefault());
|
|
|
|
|
+ row.setStatus(template.getStatus());
|
|
|
|
|
+ row.setRemark(template.getRemark());
|
|
|
|
|
+ row.setDictSource(TenantDictConstants.SOURCE_PLATFORM);
|
|
|
|
|
+ row.setIsPlatformManaged(template.getIsManaged() == null ? 1 : template.getIsManaged());
|
|
|
|
|
+ row.setUpdateBy("platform-sync");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private SysDictData buildDataQuery(String dictType) {
|
|
|
|
|
+ SysDictData query = new SysDictData();
|
|
|
|
|
+ query.setDictType(dictType);
|
|
|
|
|
+ return query;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String resolveMode(String globalMode, TenantDictTemplateType template) {
|
|
|
|
|
+ if (StringUtils.isNotEmpty(globalMode)) {
|
|
|
|
|
+ return globalMode.toUpperCase();
|
|
|
|
|
+ }
|
|
|
|
|
+ return template.getSyncMode() == null ? TenantDictConstants.SYNC_MERGE : template.getSyncMode().toUpperCase();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, List<TenantDictTemplateData>> loadTemplateDataCache(List<TenantDictTemplateType> templateTypes) {
|
|
|
|
|
+ Map<String, List<TenantDictTemplateData>> cache = new HashMap<>();
|
|
|
|
|
+ for (TenantDictTemplateType type : templateTypes) {
|
|
|
|
|
+ cache.put(type.getDictType(), templateService.selectTemplateDataByType(type.getDictType()));
|
|
|
|
|
+ }
|
|
|
|
|
+ return cache;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<TenantDictTemplateType> resolveTemplateTypes(List<String> dictTypes) {
|
|
|
|
|
+ List<TenantDictTemplateType> all = templateService.selectAllTemplateTypes();
|
|
|
|
|
+ if (dictTypes == null || dictTypes.isEmpty()) {
|
|
|
|
|
+ return all;
|
|
|
|
|
+ }
|
|
|
|
|
+ Set<String> filter = dictTypes.stream().map(String::trim).filter(StringUtils::isNotEmpty).collect(Collectors.toSet());
|
|
|
|
|
+ return all.stream().filter(t -> filter.contains(t.getDictType())).collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<TenantInfo> resolveTenants(TenantDictSyncRunReq req) {
|
|
|
|
|
+ DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<TenantInfo> all = tenantInfoService.selectTenantInfoList(new TenantInfo());
|
|
|
|
|
+ all.removeIf(t -> t.getStatus() == null || t.getStatus() != 1);
|
|
|
|
|
+ String scope = req.getScopeType() == null ? TenantDictConstants.SCOPE_ALL : req.getScopeType().toUpperCase();
|
|
|
|
|
+ List<Long> ids = req.getTenantIds() == null ? Collections.emptyList() : req.getTenantIds();
|
|
|
|
|
+ if (TenantDictConstants.SCOPE_INCLUDE.equals(scope)) {
|
|
|
|
|
+ return all.stream().filter(t -> ids.contains(t.getId())).collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (TenantDictConstants.SCOPE_EXCLUDE.equals(scope)) {
|
|
|
|
|
+ return all.stream().filter(t -> !ids.contains(t.getId())).collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+ return all;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void validateSyncReq(TenantDictSyncRunReq req) {
|
|
|
|
|
+ if (req == null) {
|
|
|
|
|
+ throw new CustomException("同步参数不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ String mode = req.getSyncMode() == null ? TenantDictConstants.SYNC_MERGE : req.getSyncMode().toUpperCase();
|
|
|
|
|
+ req.setSyncMode(mode);
|
|
|
|
|
+ if (!TenantDictConstants.SYNC_APPEND.equals(mode)
|
|
|
|
|
+ && !TenantDictConstants.SYNC_MERGE.equals(mode)
|
|
|
|
|
+ && !TenantDictConstants.SYNC_OVERWRITE.equals(mode)) {
|
|
|
|
|
+ throw new CustomException("不支持的同步模式: " + mode);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (TenantDictConstants.SYNC_OVERWRITE.equals(mode) && !Boolean.TRUE.equals(req.getOverwriteConfirm())) {
|
|
|
|
|
+ throw new CustomException("OVERWRITE 模式风险较高,请设置 overwriteConfirm=true 后重试");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String buildTaskNo() {
|
|
|
|
|
+ return "DSYNC" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())
|
|
|
|
|
+ + String.format("%04d", new Random().nextInt(10000));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void initMasterTaskTables() {
|
|
|
|
|
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS tenant_dict_sync_task (" +
|
|
|
|
|
+ "id bigint NOT NULL AUTO_INCREMENT, task_no varchar(32) NOT NULL, sync_mode varchar(20) NOT NULL, " +
|
|
|
|
|
+ "scope_type varchar(20) NOT NULL, dict_types text, total_tenants int NOT NULL DEFAULT 0, " +
|
|
|
|
|
+ "success_tenants int NOT NULL DEFAULT 0, failed_tenants int NOT NULL DEFAULT 0, status varchar(20) NOT NULL DEFAULT 'RUNNING', " +
|
|
|
|
|
+ "trigger_by varchar(64) DEFAULT NULL, started_at datetime DEFAULT NULL, finished_at datetime DEFAULT NULL, remark varchar(500) DEFAULT NULL, " +
|
|
|
|
|
+ "PRIMARY KEY (id), UNIQUE KEY uk_task_no (task_no)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
|
|
|
|
+ jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS tenant_dict_sync_task_detail (" +
|
|
|
|
|
+ "id bigint NOT NULL AUTO_INCREMENT, task_no varchar(32) NOT NULL, tenant_id bigint NOT NULL, tenant_code varchar(64) DEFAULT NULL, " +
|
|
|
|
|
+ "tenant_name varchar(128) DEFAULT NULL, status varchar(20) NOT NULL, type_added int DEFAULT 0, type_updated int DEFAULT 0, " +
|
|
|
|
|
+ "data_added int DEFAULT 0, data_updated int DEFAULT 0, data_skipped int DEFAULT 0, error_msg text, " +
|
|
|
|
|
+ "started_at datetime DEFAULT NULL, finished_at datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_task_no (task_no)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void insertTask(String taskNo, TenantDictSyncRunReq req, int total, String triggerBy) {
|
|
|
|
|
+ contextHelper.runInMaster(() -> jdbcTemplate.update(
|
|
|
|
|
+ "INSERT INTO tenant_dict_sync_task(task_no,sync_mode,scope_type,dict_types,total_tenants,status,trigger_by,started_at) VALUES(?,?,?,?,?,?,?,NOW())",
|
|
|
|
|
+ taskNo, req.getSyncMode(), req.getScopeType(), JSON.toJSONString(req.getDictTypes()), total,
|
|
|
|
|
+ TenantDictConstants.TASK_RUNNING, triggerBy));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void insertTaskDetail(String taskNo, TenantInfo tenant, TenantDictSyncResultVo stats,
|
|
|
|
|
+ String status, String errorMsg, Date start, Date end) {
|
|
|
|
|
+ contextHelper.runInMaster(() -> jdbcTemplate.update(
|
|
|
|
|
+ "INSERT INTO tenant_dict_sync_task_detail(task_no,tenant_id,tenant_code,tenant_name,status,type_added,type_updated,data_added,data_updated,data_skipped,error_msg,started_at,finished_at) " +
|
|
|
|
|
+ "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
|
|
|
+ taskNo, tenant.getId(), tenant.getTenantCode(), tenant.getTenantName(), status,
|
|
|
|
|
+ stats.getTypeAdded(), stats.getTypeUpdated(), stats.getDataAdded(), stats.getDataUpdated(),
|
|
|
|
|
+ stats.getDataSkipped(), errorMsg, start, end));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void finalizeTask(String taskNo, int success, int failed) {
|
|
|
|
|
+ contextHelper.runInMaster(() -> {
|
|
|
|
|
+ String status = failed == 0 ? TenantDictConstants.TASK_SUCCESS
|
|
|
|
|
+ : (success == 0 ? TenantDictConstants.TASK_FAILED : TenantDictConstants.TASK_PARTIAL);
|
|
|
|
|
+ jdbcTemplate.update(
|
|
|
|
|
+ "UPDATE tenant_dict_sync_task SET success_tenants=?, failed_tenants=?, status=?, finished_at=NOW() WHERE task_no=?",
|
|
|
|
|
+ success, failed, status, taskNo);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<TenantDictSyncTaskDetailVo> queryTaskDetails(String taskNo) {
|
|
|
|
|
+ List<Map<String, Object>> rows = jdbcTemplate.queryForList(
|
|
|
|
|
+ "SELECT tenant_id,tenant_code,tenant_name,status,type_added,type_updated,data_added,data_updated,data_skipped,error_msg,started_at,finished_at " +
|
|
|
|
|
+ "FROM tenant_dict_sync_task_detail WHERE task_no=? ORDER BY id ASC", taskNo);
|
|
|
|
|
+ List<TenantDictSyncTaskDetailVo> list = new ArrayList<>();
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ TenantDictSyncTaskDetailVo vo = new TenantDictSyncTaskDetailVo();
|
|
|
|
|
+ vo.setTenantId(longVal(row.get("tenant_id")));
|
|
|
|
|
+ vo.setTenantCode(str(row.get("tenant_code")));
|
|
|
|
|
+ vo.setTenantName(str(row.get("tenant_name")));
|
|
|
|
|
+ vo.setStatus(str(row.get("status")));
|
|
|
|
|
+ vo.setTypeAdded(intVal(row.get("type_added")));
|
|
|
|
|
+ vo.setTypeUpdated(intVal(row.get("type_updated")));
|
|
|
|
|
+ vo.setDataAdded(intVal(row.get("data_added")));
|
|
|
|
|
+ vo.setDataUpdated(intVal(row.get("data_updated")));
|
|
|
|
|
+ vo.setDataSkipped(intVal(row.get("data_skipped")));
|
|
|
|
|
+ vo.setErrorMsg(str(row.get("error_msg")));
|
|
|
|
|
+ vo.setStartedAt(str(row.get("started_at")));
|
|
|
|
|
+ vo.setFinishedAt(str(row.get("finished_at")));
|
|
|
|
|
+ list.add(vo);
|
|
|
|
|
+ }
|
|
|
|
|
+ return list;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String str(Object o) {
|
|
|
|
|
+ return o == null ? null : String.valueOf(o);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int intVal(Object o) {
|
|
|
|
|
+ return o == null ? 0 : Integer.parseInt(String.valueOf(o));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private long longVal(Object o) {
|
|
|
|
|
+ return o == null ? 0L : Long.parseLong(String.valueOf(o));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|