From a4c21b83e9227033a7d52c8519d16c0b12c1780a Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Fri, 6 Feb 2026 15:51:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=87=87=E8=B4=AD?= =?UTF-8?q?=E4=BA=A4=E6=98=93=E5=BC=82=E6=AD=A5=E5=AF=BC=E5=85=A5Service?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...iPurchaseTransactionImportServiceImpl.java | 284 ++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiPurchaseTransactionImportServiceImpl.java diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiPurchaseTransactionImportServiceImpl.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiPurchaseTransactionImportServiceImpl.java new file mode 100644 index 0000000..74b585a --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiPurchaseTransactionImportServiceImpl.java @@ -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 redisTemplate; + + @Override + @Async + @Transactional + public void importTransactionAsync(List excelList, Boolean isUpdateSupport, String taskId, String userName) { + List newRecords = new ArrayList<>(); + List updateRecords = new ArrayList<>(); + List failures = new ArrayList<>(); + + // 批量查询已存在的采购事项ID + Set 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 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 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 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 getExistingPurchaseIds(List excelList) { + List purchaseIds = excelList.stream() + .map(CcdiPurchaseTransactionExcel::getPurchaseId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (purchaseIds.isEmpty()) { + return Collections.emptySet(); + } + + List existingTransactions = transactionMapper.selectBatchIds(purchaseIds); + return existingTransactions.stream() + .map(CcdiPurchaseTransaction::getPurchaseId) + .collect(Collectors.toSet()); + } + + /** + * 批量保存 + */ + private void saveBatch(List list, int batchSize) { + // 使用真正的批量插入,分批次执行以提高性能 + for (int i = 0; i < list.size(); i += batchSize) { + int end = Math.min(i + batchSize, list.size()); + List subList = list.subList(i, end); + transactionMapper.insertBatch(subList); + } + } + + /** + * 验证采购交易数据 + * + * @param addDTO 新增DTO + * @param isUpdateSupport 是否支持更新 + * @param existingIds 已存在的采购事项ID集合 + */ + private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO, Boolean isUpdateSupport, Set 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"); + } + } +}