|
|
@@ -8,15 +8,19 @@ import org.dromara.common.core.domain.R;
|
|
|
import org.dromara.common.core.exception.ServiceException;
|
|
|
import org.dromara.common.core.utils.StringUtils;
|
|
|
import org.dromara.ecs.api.RemoteSectionSyncService;
|
|
|
+import org.dromara.ecs.api.domain.dto.RemoteBatchSyncErrorDto;
|
|
|
import org.dromara.ecs.api.domain.dto.RemoteBatchSyncResultDto;
|
|
|
+import org.dromara.ecs.api.domain.dto.RemoteSectionSyncBatchDto;
|
|
|
import org.dromara.ecs.api.domain.dto.RemoteSectionSyncDto;
|
|
|
import org.dromara.server.sync.business.batch.mapper.SectionRecordMapper;
|
|
|
+import org.dromara.server.sync.domain.SyncBatchErrorRecord;
|
|
|
import org.dromara.server.sync.domain.dto.request.SectionRecordRequest;
|
|
|
+import org.dromara.server.sync.domain.dto.result.BatchSyncResult;
|
|
|
import org.dromara.server.sync.domain.dto.wrapper.BatchSyncRequest;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.List;
|
|
|
-import java.util.UUID;
|
|
|
|
|
|
/**
|
|
|
* @ClassName SectionBatchSyncService
|
|
|
@@ -32,41 +36,149 @@ import java.util.UUID;
|
|
|
@RequiredArgsConstructor
|
|
|
public class SectionBatchSyncService {
|
|
|
|
|
|
+ private static final String API_NAME = "sections";
|
|
|
+ private static final String RESOURCE_TYPE = "SECTION";
|
|
|
+
|
|
|
private final SectionRecordMapper sectionRecordMapper;
|
|
|
+ private final SyncBatchTrackerService syncBatchTrackerService;
|
|
|
|
|
|
@DubboReference
|
|
|
private RemoteSectionSyncService remoteSectionSyncService;
|
|
|
|
|
|
- public void syncSections(BatchSyncRequest<SectionRecordRequest> request) {
|
|
|
+ /**
|
|
|
+ * 批量同步课表节次,完成参数校验、远端调用、错误归集和批次结果汇总。
|
|
|
+ *
|
|
|
+ * @param request 批量课表节次请求
|
|
|
+ * @return 批次同步结果
|
|
|
+ */
|
|
|
+ public BatchSyncResult syncSections(BatchSyncRequest<SectionRecordRequest> request) {
|
|
|
if (request == null || CollUtil.isEmpty(request.getRecords())) {
|
|
|
throw new ServiceException("records 不能为空");
|
|
|
}
|
|
|
|
|
|
- request.getRecords().forEach(this::validateRecord);
|
|
|
- String batchId = UUID.randomUUID().toString().replace("-", "");
|
|
|
- List<RemoteSectionSyncDto> records = request.getRecords().stream().map(sectionRecordMapper::toRemoteDto).toList();
|
|
|
+ List<SectionRecordRequest> requestRecords = request.getRecords();
|
|
|
+ String tenantId = requestRecords.stream().map(SectionRecordRequest::getTenantId).filter(StringUtils::isNotBlank).findFirst().orElse("default");
|
|
|
+ String batchId = syncBatchTrackerService.start(API_NAME, RESOURCE_TYPE, tenantId, request, requestRecords.size());
|
|
|
+
|
|
|
+ List<RemoteSectionSyncBatchDto> records = new ArrayList<>();
|
|
|
+ List<SyncBatchErrorRecord> errors = new ArrayList<>();
|
|
|
+
|
|
|
+ // 逐条校验并转换请求数据,校验失败的记录直接记入错误明细。
|
|
|
+ for (int i = 0; i < requestRecords.size(); i++) {
|
|
|
+ SectionRecordRequest record = requestRecords.get(i);
|
|
|
+ try {
|
|
|
+ validateRecord(record);
|
|
|
+ records.add(sectionRecordMapper.toBatchDto(batchId, i + 1, record));
|
|
|
+ } catch (Exception e) {
|
|
|
+ errors.add(syncBatchTrackerService.error(batchId, RESOURCE_TYPE, i + 1, getBizKey(record),
|
|
|
+ SyncBatchTrackerService.ERROR_CODE_VALIDATION, e.getMessage(), record));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ String sectionIds = records.stream().map(RemoteSectionSyncBatchDto::getRecord).map(RemoteSectionSyncDto::getSectionId).filter(StringUtils::isNotBlank).limit(10).reduce((a, b) -> a + "," + b).orElse("");
|
|
|
+ log.info("[batch-sync-sections-start]-[batchId:{}]-[tenant:{}]-[total:{}]-[valid:{}]-[invalid:{}]-[sampleSectionIds:{}]",
|
|
|
+ batchId, tenantId, requestRecords.size(), records.size(), errors.size(), sectionIds);
|
|
|
+
|
|
|
+ int successCount = 0;
|
|
|
+ if (CollUtil.isNotEmpty(records)) {
|
|
|
+ try {
|
|
|
+ // 调用远端批量同步服务,并根据返回结果汇总成功数与业务失败明细。
|
|
|
+ R<RemoteBatchSyncResultDto> result = remoteSectionSyncService.syncSectionBatch(records);
|
|
|
+ if (R.isError(result)) {
|
|
|
+ String msg = result.getMsg();
|
|
|
+ log.error("[batch-sync-sections-fail]-[batchId:{}]-[msg:{}]", batchId, msg);
|
|
|
+ for (RemoteSectionSyncBatchDto record : records) {
|
|
|
+ errors.add(syncBatchTrackerService.error(batchId, RESOURCE_TYPE, record.getStartIndex(), record.getRecord().getSectionId(),
|
|
|
+ SyncBatchTrackerService.ERROR_CODE_BIZ, msg, record.getRecord()));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ RemoteBatchSyncResultDto data = result.getData();
|
|
|
+ if (data != null) {
|
|
|
+ successCount = data.getSuccess() == null ? 0 : data.getSuccess();
|
|
|
+ addRemoteErrors(batchId, records, errors, data);
|
|
|
+ log.info("[batch-sync-sections-done]-[batchId:{}]-[total:{}]-[成功:{}]-[失败:{}]",
|
|
|
+ batchId, data.getTotal(), data.getSuccess(), data.getFailed());
|
|
|
+ } else {
|
|
|
+ successCount = records.size();
|
|
|
+ log.info("[batch-sync-sections-done]-[batchId:{}]-[total:{}]-[远端未返回统计]", batchId, requestRecords.size());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 远端调用异常时,将本次有效记录全部记为业务失败。
|
|
|
+ log.error("[batch-sync-sections-exception]-[batchId:{}]-[msg:{}]", batchId, e.getMessage(), e);
|
|
|
+ for (RemoteSectionSyncBatchDto record : records) {
|
|
|
+ errors.add(syncBatchTrackerService.error(batchId, RESOURCE_TYPE, record.getStartIndex(), record.getRecord().getSectionId(),
|
|
|
+ SyncBatchTrackerService.ERROR_CODE_BIZ, e.getMessage(), record.getRecord()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- String tenantId = records.stream().map(RemoteSectionSyncDto::getTenantId).filter(StringUtils::isNotBlank).findFirst().orElse("default");
|
|
|
- String sectionIds = records.stream().map(RemoteSectionSyncDto::getSectionId).filter(StringUtils::isNotBlank).limit(10).reduce((a, b) -> a + "," + b).orElse("");
|
|
|
+ // 持久化错误明细并结束批次,最终返回汇总结果。
|
|
|
+ syncBatchTrackerService.saveErrors(errors);
|
|
|
+ int errorCount = errors.size();
|
|
|
+ syncBatchTrackerService.finish(batchId, requestRecords.size(), successCount, errorCount);
|
|
|
|
|
|
- log.info("[batch-sync-sections-start]-[batchId:{}]-[tenant:{}]-[total:{}]-[sampleSectionIds:{}]",
|
|
|
- batchId, tenantId, records.size(), sectionIds);
|
|
|
+ BatchSyncResult batchSyncResult = new BatchSyncResult();
|
|
|
+ batchSyncResult.setBatchId(batchId);
|
|
|
+ batchSyncResult.setTotal(requestRecords.size());
|
|
|
+ batchSyncResult.setSuccess(successCount);
|
|
|
+ batchSyncResult.setError(errorCount);
|
|
|
+ batchSyncResult.setStatus(resolveStatus(requestRecords.size(), successCount, errorCount));
|
|
|
+ return batchSyncResult;
|
|
|
+ }
|
|
|
|
|
|
- R<RemoteBatchSyncResultDto> result = remoteSectionSyncService.syncSections(records);
|
|
|
- if (R.isError(result)) {
|
|
|
- log.error("[batch-sync-sections-fail]-[batchId:{}]-[msg:{}]", batchId, result.getMsg());
|
|
|
- throw new ServiceException(result.getMsg());
|
|
|
+ /**
|
|
|
+ * 将远端返回的失败明细归集到本地批次错误列表。
|
|
|
+ *
|
|
|
+ * @param batchId 批次ID
|
|
|
+ * @param records 有效请求记录
|
|
|
+ * @param errors 错误明细集合
|
|
|
+ * @param result 远端批量同步结果
|
|
|
+ */
|
|
|
+ private void addRemoteErrors(String batchId, List<RemoteSectionSyncBatchDto> records, List<SyncBatchErrorRecord> errors, RemoteBatchSyncResultDto result) {
|
|
|
+ if (CollUtil.isNotEmpty(result.getErrors())) {
|
|
|
+ for (RemoteBatchSyncErrorDto error : result.getErrors()) {
|
|
|
+ RemoteSectionSyncBatchDto record = records.stream().filter(item -> item.getStartIndex().equals(error.getRecordIndex())).findFirst().orElse(null);
|
|
|
+ errors.add(syncBatchTrackerService.error(batchId, RESOURCE_TYPE, error.getRecordIndex(), error.getBizKey(),
|
|
|
+ SyncBatchTrackerService.ERROR_CODE_BIZ, error.getErrorMessage(), record == null ? null : record.getRecord()));
|
|
|
+ }
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- RemoteBatchSyncResultDto data = result.getData();
|
|
|
- if (data != null) {
|
|
|
- log.info("[batch-sync-sections-done]-[batchId:{}]-[total:{}]-[成功:{}]-[失败:{}]",
|
|
|
- batchId, data.getTotal(), data.getSuccess(), data.getFailed());
|
|
|
- } else {
|
|
|
- log.info("[batch-sync-sections-done]-[batchId:{}]-[total:{}]-[远端未返回统计]", batchId, request.getRecords().size());
|
|
|
+ int remoteFailed = result.getFailed() == null ? 0 : result.getFailed();
|
|
|
+ for (int i = 0; i < remoteFailed && i < records.size(); i++) {
|
|
|
+ RemoteSectionSyncBatchDto record = records.get(i);
|
|
|
+ errors.add(syncBatchTrackerService.error(batchId, RESOURCE_TYPE, record.getStartIndex(), record.getRecord().getSectionId(),
|
|
|
+ SyncBatchTrackerService.ERROR_CODE_BIZ, "远端处理失败,未返回具体失败明细", record.getRecord()));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 根据总数、成功数和失败数计算批次状态。
|
|
|
+ *
|
|
|
+ * @param total 请求总数
|
|
|
+ * @param success 成功数
|
|
|
+ * @param error 失败数
|
|
|
+ * @return 批次状态
|
|
|
+ */
|
|
|
+ private String resolveStatus(int total, int success, int error) {
|
|
|
+ if (total != success + error) {
|
|
|
+ return SyncBatchTrackerService.STATUS_COUNT_MISMATCH;
|
|
|
+ }
|
|
|
+ if (error == 0) {
|
|
|
+ return SyncBatchTrackerService.STATUS_SUCCESS;
|
|
|
+ }
|
|
|
+ if (success == 0) {
|
|
|
+ return SyncBatchTrackerService.STATUS_FAIL;
|
|
|
+ }
|
|
|
+ return SyncBatchTrackerService.STATUS_PARTIAL_FAIL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验单条课表节次记录的必填字段。
|
|
|
+ *
|
|
|
+ * @param record 单条课表节次记录
|
|
|
+ */
|
|
|
private void validateRecord(SectionRecordRequest record) {
|
|
|
if (record == null) {
|
|
|
throw new ServiceException("record 不能为空");
|
|
|
@@ -75,4 +187,14 @@ public class SectionBatchSyncService {
|
|
|
throw new ServiceException("sectionId 不能为空");
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 提取记录的业务主键用于错误定位。
|
|
|
+ *
|
|
|
+ * @param record 单条课表节次记录
|
|
|
+ * @return 业务主键
|
|
|
+ */
|
|
|
+ private String getBizKey(SectionRecordRequest record) {
|
|
|
+ return record == null ? null : record.getSectionId();
|
|
|
+ }
|
|
|
}
|