feat: 添加采购交易异步导入Service实现类

This commit is contained in:
wkc
2026-02-06 15:51:41 +08:00
parent a2764fd3eb
commit a4c21b83e9

View File

@@ -0,0 +1,284 @@
package com.ruoyi.ccdi.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.ccdi.domain.CcdiPurchaseTransaction;
import com.ruoyi.ccdi.domain.dto.CcdiPurchaseTransactionAddDTO;
import com.ruoyi.ccdi.domain.excel.CcdiPurchaseTransactionExcel;
import com.ruoyi.ccdi.domain.vo.ImportFailureVO;
import com.ruoyi.ccdi.domain.vo.ImportResult;
import com.ruoyi.ccdi.domain.vo.ImportStatusVO;
import com.ruoyi.ccdi.mapper.CcdiPurchaseTransactionMapper;
import com.ruoyi.ccdi.service.ICcdiPurchaseTransactionImportService;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 采购交易信息异步导入服务层处理
*
* @author ruoyi
* @date 2026-02-06
*/
@Service
@EnableAsync
public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTransactionImportService {
@Resource
private CcdiPurchaseTransactionMapper transactionMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
@Async
@Transactional
public void importTransactionAsync(List<CcdiPurchaseTransactionExcel> excelList, Boolean isUpdateSupport, String taskId, String userName) {
List<CcdiPurchaseTransaction> newRecords = new ArrayList<>();
List<CcdiPurchaseTransaction> updateRecords = new ArrayList<>();
List<ImportFailureVO> failures = new ArrayList<>();
// 批量查询已存在的采购事项ID
Set<String> existingIds = getExistingPurchaseIds(excelList);
// 分类数据
for (int i = 0; i < excelList.size(); i++) {
CcdiPurchaseTransactionExcel excel = excelList.get(i);
try {
// 转换为AddDTO进行验证
CcdiPurchaseTransactionAddDTO addDTO = new CcdiPurchaseTransactionAddDTO();
BeanUtils.copyProperties(excel, addDTO);
// 验证数据(支持更新模式)
validateTransactionData(addDTO, isUpdateSupport, existingIds);
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
BeanUtils.copyProperties(excel, transaction);
if (existingIds.contains(excel.getPurchaseId())) {
if (isUpdateSupport) {
transaction.setUpdatedBy(userName);
updateRecords.add(transaction);
} else {
throw new RuntimeException("采购事项ID已存在且未启用更新支持");
}
} else {
transaction.setCreatedBy(userName);
newRecords.add(transaction);
}
} catch (Exception e) {
ImportFailureVO failure = new ImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(e.getMessage());
failures.add(failure);
}
}
// 批量插入新数据
if (!newRecords.isEmpty()) {
saveBatch(newRecords, 500);
}
// 批量更新已有数据(先删除再插入)
if (!updateRecords.isEmpty() && isUpdateSupport) {
transactionMapper.insertOrUpdateBatch(updateRecords);
}
// 保存失败记录到Redis
if (!failures.isEmpty()) {
String failuresKey = "import:purchaseTransaction:" + taskId + ":failures";
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(newRecords.size() + updateRecords.size());
result.setFailureCount(failures.size());
// 更新最终状态
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
updateImportStatus(taskId, finalStatus, result);
}
/**
* 获取导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
@Override
public List<ImportFailureVO> getImportFailures(String taskId) {
String key = "import:purchaseTransaction:" + taskId + ":failures";
Object failuresObj = redisTemplate.opsForValue().get(key);
if (failuresObj == null) {
return Collections.emptyList();
}
return JSON.parseArray(JSON.toJSONString(failuresObj), ImportFailureVO.class);
}
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态信息
*/
@Override
public ImportStatusVO getImportStatus(String taskId) {
String key = "import:purchaseTransaction:" + taskId;
Boolean hasKey = redisTemplate.hasKey(key);
if (Boolean.FALSE.equals(hasKey)) {
throw new RuntimeException("任务不存在或已过期");
}
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId((String) statusMap.get("taskId"));
statusVO.setStatus((String) statusMap.get("status"));
statusVO.setTotalCount((Integer) statusMap.get("totalCount"));
statusVO.setSuccessCount((Integer) statusMap.get("successCount"));
statusVO.setFailureCount((Integer) statusMap.get("failureCount"));
statusVO.setProgress((Integer) statusMap.get("progress"));
statusVO.setStartTime((Long) statusMap.get("startTime"));
statusVO.setEndTime((Long) statusMap.get("endTime"));
statusVO.setMessage((String) statusMap.get("message"));
return statusVO;
}
/**
* 更新导入状态
*/
private void updateImportStatus(String taskId, String status, ImportResult result) {
String key = "import:purchaseTransaction:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("status", status);
statusData.put("successCount", result.getSuccessCount());
statusData.put("failureCount", result.getFailureCount());
statusData.put("progress", 100);
statusData.put("endTime", System.currentTimeMillis());
if ("SUCCESS".equals(status)) {
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据");
} else {
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
}
redisTemplate.opsForHash().putAll(key, statusData);
}
/**
* 批量查询已存在的采购事项ID
*/
private Set<String> getExistingPurchaseIds(List<CcdiPurchaseTransactionExcel> excelList) {
List<String> purchaseIds = excelList.stream()
.map(CcdiPurchaseTransactionExcel::getPurchaseId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (purchaseIds.isEmpty()) {
return Collections.emptySet();
}
List<CcdiPurchaseTransaction> existingTransactions = transactionMapper.selectBatchIds(purchaseIds);
return existingTransactions.stream()
.map(CcdiPurchaseTransaction::getPurchaseId)
.collect(Collectors.toSet());
}
/**
* 批量保存
*/
private void saveBatch(List<CcdiPurchaseTransaction> list, int batchSize) {
// 使用真正的批量插入,分批次执行以提高性能
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiPurchaseTransaction> subList = list.subList(i, end);
transactionMapper.insertBatch(subList);
}
}
/**
* 验证采购交易数据
*
* @param addDTO 新增DTO
* @param isUpdateSupport 是否支持更新
* @param existingIds 已存在的采购事项ID集合
*/
private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO, Boolean isUpdateSupport, Set<String> existingIds) {
// 验证必填字段
if (StringUtils.isEmpty(addDTO.getPurchaseId())) {
throw new RuntimeException("采购事项ID不能为空");
}
if (StringUtils.isEmpty(addDTO.getPurchaseCategory())) {
throw new RuntimeException("采购类别不能为空");
}
if (StringUtils.isEmpty(addDTO.getSubjectName())) {
throw new RuntimeException("标的物名称不能为空");
}
if (addDTO.getPurchaseQty() == null) {
throw new RuntimeException("采购数量不能为空");
}
if (addDTO.getBudgetAmount() == null) {
throw new RuntimeException("预算金额不能为空");
}
if (StringUtils.isEmpty(addDTO.getPurchaseMethod())) {
throw new RuntimeException("采购方式不能为空");
}
if (addDTO.getApplyDate() == null) {
throw new RuntimeException("采购申请日期不能为空");
}
if (StringUtils.isEmpty(addDTO.getApplicantId())) {
throw new RuntimeException("申请人工号不能为空");
}
if (StringUtils.isEmpty(addDTO.getApplicantName())) {
throw new RuntimeException("申请人姓名不能为空");
}
if (StringUtils.isEmpty(addDTO.getApplyDepartment())) {
throw new RuntimeException("申请部门不能为空");
}
// 验证工号格式7位数字
if (!addDTO.getApplicantId().matches("^\\d{7}$")) {
throw new RuntimeException("申请人工号必须为7位数字");
}
if (StringUtils.isNotEmpty(addDTO.getPurchaseLeaderId()) && !addDTO.getPurchaseLeaderId().matches("^\\d{7}$")) {
throw new RuntimeException("采购负责人工号必须为7位数字");
}
// 验证金额非负
if (addDTO.getPurchaseQty().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("采购数量必须大于0");
}
if (addDTO.getBudgetAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("预算金额必须大于0");
}
if (addDTO.getBidAmount() != null && addDTO.getBidAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("中标金额必须大于0");
}
if (addDTO.getActualAmount() != null && addDTO.getActualAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("实际采购金额必须大于0");
}
if (addDTO.getContractAmount() != null && addDTO.getContractAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("合同金额必须大于0");
}
if (addDTO.getSettlementAmount() != null && addDTO.getSettlementAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("结算金额必须大于0");
}
}
}