合并拉取本行信息后端实现
This commit is contained in:
@@ -2,8 +2,10 @@ package com.ruoyi.ccdi.project.controller;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiFileUploadQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiFileUploadQueryDTO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiPullBankInfoSubmitDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiIdCardParseVO;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiFileUploadService;
|
import com.ruoyi.ccdi.project.service.ICcdiFileUploadService;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
@@ -15,9 +17,12 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,6 +90,48 @@ public class CcdiFileUploadController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析身份证文件
|
||||||
|
*/
|
||||||
|
@PostMapping("/parse-id-card-file")
|
||||||
|
@Operation(summary = "解析身份证文件", description = "解析首个sheet第一列的身份证号")
|
||||||
|
public AjaxResult parseIdCardFile(@RequestParam MultipartFile file) {
|
||||||
|
if (file == null || file.isEmpty()) {
|
||||||
|
return AjaxResult.error("身份证文件不能为空");
|
||||||
|
}
|
||||||
|
List<String> idCards = fileUploadService.parseIdCardFile(file);
|
||||||
|
return AjaxResult.success("解析成功", new CcdiIdCardParseVO(idCards, idCards.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交拉取本行信息任务
|
||||||
|
*/
|
||||||
|
@PostMapping("/pull-bank-info")
|
||||||
|
@Operation(summary = "拉取本行信息", description = "按身份证号批量提交拉取本行信息任务")
|
||||||
|
public AjaxResult pullBankInfo(@RequestBody CcdiPullBankInfoSubmitDTO dto) {
|
||||||
|
if (dto == null || dto.getProjectId() == null) {
|
||||||
|
return AjaxResult.error("项目ID不能为空");
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isEmpty(dto.getIdCards())) {
|
||||||
|
return AjaxResult.error("身份证号不能为空");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(dto.getStartDate()) || !StringUtils.hasText(dto.getEndDate())) {
|
||||||
|
return AjaxResult.error("开始日期和结束日期不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long userId = SecurityUtils.getUserId();
|
||||||
|
String username = SecurityUtils.getUsername();
|
||||||
|
String batchId = fileUploadService.submitPullBankInfo(
|
||||||
|
dto.getProjectId(),
|
||||||
|
dto.getIdCards(),
|
||||||
|
dto.getStartDate(),
|
||||||
|
dto.getEndDate(),
|
||||||
|
userId,
|
||||||
|
username
|
||||||
|
);
|
||||||
|
return AjaxResult.success("拉取任务已提交", batchId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询上传记录列表
|
* 查询上传记录列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉取本行信息提交参数
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiPullBankInfoSubmitDTO {
|
||||||
|
|
||||||
|
/** 项目ID */
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/** 身份证号列表 */
|
||||||
|
private List<String> idCards;
|
||||||
|
|
||||||
|
/** 开始日期 */
|
||||||
|
private String startDate;
|
||||||
|
|
||||||
|
/** 结束日期 */
|
||||||
|
private String endDate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.excel;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 身份证导入行
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiIdCardExcelRow {
|
||||||
|
|
||||||
|
@ExcelProperty(index = 0)
|
||||||
|
private String idCard;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 身份证文件解析结果
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CcdiIdCardParseVO {
|
||||||
|
|
||||||
|
/** 解析到的身份证列表 */
|
||||||
|
private List<String> idCards;
|
||||||
|
|
||||||
|
/** 数量 */
|
||||||
|
private Integer count;
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
|||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件上传服务接口
|
* 文件上传服务接口
|
||||||
*
|
*
|
||||||
@@ -24,6 +26,32 @@ public interface ICcdiFileUploadService {
|
|||||||
*/
|
*/
|
||||||
String batchUploadFiles(Long projectId, MultipartFile[] files, String username);
|
String batchUploadFiles(Long projectId, MultipartFile[] files, String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析身份证文件
|
||||||
|
*
|
||||||
|
* @param file Excel 文件
|
||||||
|
* @return 身份证号列表
|
||||||
|
*/
|
||||||
|
List<String> parseIdCardFile(MultipartFile file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交拉取本行信息任务
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param idCards 身份证号列表
|
||||||
|
* @param startDate 开始日期
|
||||||
|
* @param endDate 结束日期
|
||||||
|
* @param userId 当前登录用户ID
|
||||||
|
* @param username 当前登录用户名
|
||||||
|
* @return 批次ID
|
||||||
|
*/
|
||||||
|
String submitPullBankInfo(Long projectId,
|
||||||
|
List<String> idCards,
|
||||||
|
String startDate,
|
||||||
|
String endDate,
|
||||||
|
Long userId,
|
||||||
|
String username);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询上传记录列表
|
* 查询上传记录列表
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
package com.ruoyi.ccdi.project.service.impl;
|
package com.ruoyi.ccdi.project.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.excel.EasyExcel;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
import com.ruoyi.ccdi.project.domain.dto.CcdiFileUploadQueryDTO;
|
import com.ruoyi.ccdi.project.domain.dto.CcdiFileUploadQueryDTO;
|
||||||
import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement;
|
import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement;
|
||||||
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
||||||
|
import com.ruoyi.ccdi.project.domain.excel.CcdiIdCardExcelRow;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiFileUploadService;
|
import com.ruoyi.ccdi.project.service.ICcdiFileUploadService;
|
||||||
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
||||||
|
import com.ruoyi.lsfx.domain.request.FetchInnerFlowRequest;
|
||||||
import com.ruoyi.lsfx.domain.request.GetBankStatementRequest;
|
import com.ruoyi.lsfx.domain.request.GetBankStatementRequest;
|
||||||
import com.ruoyi.lsfx.domain.request.GetFileUploadStatusRequest;
|
import com.ruoyi.lsfx.domain.request.GetFileUploadStatusRequest;
|
||||||
import com.ruoyi.lsfx.domain.response.*;
|
import com.ruoyi.lsfx.domain.response.*;
|
||||||
@@ -34,10 +37,13 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件上传服务实现
|
* 文件上传服务实现
|
||||||
@@ -50,6 +56,8 @@ import java.util.concurrent.RejectedExecutionException;
|
|||||||
public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||||
|
|
||||||
private static final int MAX_ERROR_MESSAGE_LENGTH = 2000;
|
private static final int MAX_ERROR_MESSAGE_LENGTH = 2000;
|
||||||
|
private static final Pattern ID_CARD_PATTERN =
|
||||||
|
Pattern.compile("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])([0-2]\\d|3[01])\\d{3}[0-9Xx]$");
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
private static class FetchBankStatementResult {
|
private static class FetchBankStatementResult {
|
||||||
@@ -88,6 +96,116 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
return uploadPath + File.separator + "temp";
|
return uploadPath + File.separator + "temp";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> parseIdCardFile(MultipartFile file) {
|
||||||
|
if (file == null || file.isEmpty()) {
|
||||||
|
throw new RuntimeException("身份证文件不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<CcdiIdCardExcelRow> rows = EasyExcel.read(file.getInputStream())
|
||||||
|
.head(CcdiIdCardExcelRow.class)
|
||||||
|
.sheet(0)
|
||||||
|
.headRowNumber(1)
|
||||||
|
.doReadSync();
|
||||||
|
|
||||||
|
LinkedHashSet<String> idCards = new LinkedHashSet<>();
|
||||||
|
for (CcdiIdCardExcelRow row : rows) {
|
||||||
|
if (row == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String idCard = row.getIdCard();
|
||||||
|
if (!StringUtils.hasText(idCard)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String normalized = idCard.trim();
|
||||||
|
if (!ID_CARD_PATTERN.matcher(normalized).matches()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
idCards.add(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idCards.isEmpty()) {
|
||||||
|
throw new RuntimeException("首个sheet第一列未解析到有效身份证号");
|
||||||
|
}
|
||||||
|
return new ArrayList<>(idCards);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("读取身份证文件失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public String submitPullBankInfo(Long projectId,
|
||||||
|
List<String> idCards,
|
||||||
|
String startDate,
|
||||||
|
String endDate,
|
||||||
|
Long userId,
|
||||||
|
String username) {
|
||||||
|
if (projectId == null) {
|
||||||
|
throw new IllegalArgumentException("项目ID不能为空");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(startDate) || !StringUtils.hasText(endDate)) {
|
||||||
|
throw new IllegalArgumentException("开始日期和结束日期不能为空");
|
||||||
|
}
|
||||||
|
if (idCards == null || idCards.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("身份证号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDate start = LocalDate.parse(startDate);
|
||||||
|
LocalDate end = LocalDate.parse(endDate);
|
||||||
|
if (start.isAfter(end)) {
|
||||||
|
throw new IllegalArgumentException("开始日期不能晚于结束日期");
|
||||||
|
}
|
||||||
|
|
||||||
|
CcdiProject project = projectMapper.selectById(projectId);
|
||||||
|
if (project == null) {
|
||||||
|
throw new IllegalArgumentException("项目不存在: projectId=" + projectId);
|
||||||
|
}
|
||||||
|
Integer lsfxProjectId = project.getLsfxProjectId();
|
||||||
|
if (lsfxProjectId == null) {
|
||||||
|
throw new IllegalStateException("项目未关联流水分析平台: projectId=" + projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
String batchId = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
Date now = new Date();
|
||||||
|
List<CcdiFileUploadRecord> records = new ArrayList<>();
|
||||||
|
List<String> normalizedIdCards = new ArrayList<>();
|
||||||
|
for (String idCard : idCards) {
|
||||||
|
if (!StringUtils.hasText(idCard)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String normalized = idCard.trim();
|
||||||
|
normalizedIdCards.add(normalized);
|
||||||
|
|
||||||
|
CcdiFileUploadRecord record = new CcdiFileUploadRecord();
|
||||||
|
record.setProjectId(projectId);
|
||||||
|
record.setLsfxProjectId(lsfxProjectId);
|
||||||
|
record.setFileName(normalized);
|
||||||
|
record.setFileSize(0L);
|
||||||
|
record.setFileStatus("uploading");
|
||||||
|
record.setAccountNos(normalized);
|
||||||
|
record.setUploadTime(now);
|
||||||
|
record.setUploadUser(username);
|
||||||
|
records.add(record);
|
||||||
|
}
|
||||||
|
if (records.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("身份证号不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
recordMapper.insertBatch(records);
|
||||||
|
|
||||||
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||||
|
@Override
|
||||||
|
public void afterCommit() {
|
||||||
|
CompletableFuture.runAsync(() -> submitPullBankInfoTasks(
|
||||||
|
projectId, lsfxProjectId, records, normalizedIdCards, startDate, endDate, userId, batchId
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return batchId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Page<CcdiFileUploadRecord> selectPage(Page<CcdiFileUploadRecord> page,
|
public Page<CcdiFileUploadRecord> selectPage(Page<CcdiFileUploadRecord> page,
|
||||||
CcdiFileUploadQueryDTO queryDTO) {
|
CcdiFileUploadQueryDTO queryDTO) {
|
||||||
@@ -351,6 +469,90 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
statement.setLeAccountNo(trimAccountNo(statement.getLeAccountNo()));
|
statement.setLeAccountNo(trimAccountNo(statement.getLeAccountNo()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void submitPullBankInfoTasks(Long projectId,
|
||||||
|
Integer lsfxProjectId,
|
||||||
|
List<CcdiFileUploadRecord> records,
|
||||||
|
List<String> idCards,
|
||||||
|
String startDate,
|
||||||
|
String endDate,
|
||||||
|
Long userId,
|
||||||
|
String batchId) {
|
||||||
|
log.info("【拉取本行信息】调度线程启动: projectId={}, batchId={}", projectId, batchId);
|
||||||
|
|
||||||
|
for (int i = 0; i < records.size(); i++) {
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
log.warn("【拉取本行信息】调度线程被中断,停止提交剩余任务");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
CcdiFileUploadRecord record = records.get(i);
|
||||||
|
String idCard = idCards.get(i);
|
||||||
|
boolean submitted = false;
|
||||||
|
int retryCount = 0;
|
||||||
|
|
||||||
|
while (!submitted && retryCount < 2) {
|
||||||
|
try {
|
||||||
|
CompletableFuture.runAsync(
|
||||||
|
() -> processPullBankInfoAsync(projectId, lsfxProjectId, record, idCard, startDate, endDate, userId),
|
||||||
|
fileUploadExecutor
|
||||||
|
);
|
||||||
|
submitted = true;
|
||||||
|
log.info("【拉取本行信息】任务提交成功: idCard={}, recordId={}", idCard, record.getId());
|
||||||
|
} catch (RejectedExecutionException e) {
|
||||||
|
retryCount++;
|
||||||
|
if (retryCount == 1) {
|
||||||
|
log.warn("【拉取本行信息】线程池已满,等待30秒后重试: idCard={}", idCard);
|
||||||
|
try {
|
||||||
|
Thread.sleep(30000);
|
||||||
|
} catch (InterruptedException interruptedException) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.error("【拉取本行信息】等待被中断: idCard={}", idCard);
|
||||||
|
updateRecordStatus(record.getId(), "parsed_failed", "任务提交被中断");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.error("【拉取本行信息】重试失败,放弃任务: idCard={}", idCard);
|
||||||
|
updateRecordStatus(record.getId(), "parsed_failed", "系统繁忙,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processPullBankInfoAsync(Long projectId,
|
||||||
|
Integer lsfxProjectId,
|
||||||
|
CcdiFileUploadRecord record,
|
||||||
|
String idCard,
|
||||||
|
String startDate,
|
||||||
|
String endDate,
|
||||||
|
Long userId) {
|
||||||
|
try {
|
||||||
|
FetchInnerFlowRequest request = new FetchInnerFlowRequest();
|
||||||
|
request.setGroupId(lsfxProjectId);
|
||||||
|
request.setCustomerNo(idCard);
|
||||||
|
request.setDataChannelCode("ZJRCU");
|
||||||
|
request.setRequestDateId(Integer.parseInt(LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE)));
|
||||||
|
request.setDataStartDateId(Integer.parseInt(startDate.replace("-", "")));
|
||||||
|
request.setDataEndDateId(Integer.parseInt(endDate.replace("-", "")));
|
||||||
|
request.setUploadUserId(toUploadUserId(userId));
|
||||||
|
|
||||||
|
FetchInnerFlowResponse response = lsfxClient.fetchInnerFlow(request);
|
||||||
|
if (response == null || response.getData() == null || response.getData().isEmpty()) {
|
||||||
|
throw new RuntimeException("拉取本行信息失败: 未返回logId");
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer logId = response.getData().get(0);
|
||||||
|
if (logId == null) {
|
||||||
|
throw new RuntimeException("拉取本行信息失败: 未返回logId");
|
||||||
|
}
|
||||||
|
|
||||||
|
processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("【拉取本行信息】处理失败: idCard={}, recordId={}", idCard, record.getId(), e);
|
||||||
|
updateFailedRecord(record, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异步处理单个文件的完整流程
|
* 异步处理单个文件的完整流程
|
||||||
* 包含:上传 → 轮询解析状态 → 获取结果 → 保存流水数据
|
* 包含:上传 → 轮询解析状态 → 获取结果 → 保存流水数据
|
||||||
@@ -399,84 +601,7 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.info("【文件上传】文件上传成功: logId={}", logId);
|
log.info("【文件上传】文件上传成功: logId={}", logId);
|
||||||
|
processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId);
|
||||||
// 步骤3:更新状态为 parsing
|
|
||||||
log.info("【文件上传】步骤3: 更新状态为解析中, logId={}", logId);
|
|
||||||
record.setLogId(logId);
|
|
||||||
record.setFileStatus("parsing");
|
|
||||||
recordMapper.updateById(record);
|
|
||||||
|
|
||||||
// 步骤4:轮询解析状态(最多300次,间隔2秒)
|
|
||||||
log.info("【文件上传】步骤4: 开始轮询解析状态");
|
|
||||||
boolean parsingComplete = waitForParsingComplete(lsfxProjectId, logId.toString());
|
|
||||||
|
|
||||||
if (!parsingComplete) {
|
|
||||||
throw new RuntimeException("解析超时(超过10分钟),请检查文件格式是否正确");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 步骤5:获取文件上传状态
|
|
||||||
log.info("【文件上传】步骤5: 获取文件上传状态: logId={}", logId);
|
|
||||||
|
|
||||||
GetFileUploadStatusRequest statusRequest = new GetFileUploadStatusRequest();
|
|
||||||
statusRequest.setGroupId(lsfxProjectId);
|
|
||||||
statusRequest.setLogId(logId);
|
|
||||||
|
|
||||||
GetFileUploadStatusResponse statusResponse = lsfxClient.getFileUploadStatus(statusRequest);
|
|
||||||
|
|
||||||
if (statusResponse == null || statusResponse.getData() == null
|
|
||||||
|| statusResponse.getData().getLogs() == null
|
|
||||||
|| statusResponse.getData().getLogs().isEmpty()) {
|
|
||||||
throw new RuntimeException("获取文件上传状态失败: 响应数据为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取第一个log项(因为我们传了logId,应该只返回一个)
|
|
||||||
GetFileUploadStatusResponse.LogItem logItem = statusResponse.getData().getLogs().get(0);
|
|
||||||
Integer status = logItem.getStatus();
|
|
||||||
String uploadStatusDesc = logItem.getUploadStatusDesc();
|
|
||||||
|
|
||||||
log.info("【文件上传】文件状态: status={}, uploadStatusDesc={}", status, uploadStatusDesc);
|
|
||||||
|
|
||||||
// 步骤6:判断解析结果
|
|
||||||
// status=-5 且 uploadStatusDesc="data.wait.confirm.newaccount" 表示解析成功
|
|
||||||
boolean parseSuccess = status != null && status == -5
|
|
||||||
&& "data.wait.confirm.newaccount".equals(uploadStatusDesc);
|
|
||||||
|
|
||||||
if (parseSuccess) {
|
|
||||||
// 解析成功
|
|
||||||
log.info("【文件上传】步骤6: 解析成功,保存主体信息");
|
|
||||||
|
|
||||||
// 提取主体名称和账号
|
|
||||||
List<String> enterpriseNames = logItem.getEnterpriseNameList();
|
|
||||||
List<String> accountNos = logItem.getAccountNoList();
|
|
||||||
|
|
||||||
String enterpriseNamesStr = enterpriseNames != null ? String.join(",", enterpriseNames) : "";
|
|
||||||
String accountNosStr = accountNos != null ? String.join(",", accountNos) : "";
|
|
||||||
|
|
||||||
|
|
||||||
log.info("【文件上传】主体信息已保存: enterpriseNames={}, accountNos={}",
|
|
||||||
enterpriseNamesStr, accountNosStr);
|
|
||||||
|
|
||||||
// 步骤7:获取流水数据并保存
|
|
||||||
log.info("【文件上传】步骤7: 获取流水数据");
|
|
||||||
FetchBankStatementResult fetchResult =
|
|
||||||
fetchAndSaveBankStatements(projectId, lsfxProjectId, logId);
|
|
||||||
if (!fetchResult.isSuccess()) {
|
|
||||||
updateFailedRecord(record, fetchResult.getErrorMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
record.setFileStatus("parsed_success");
|
|
||||||
record.setEnterpriseNames(enterpriseNamesStr);
|
|
||||||
record.setAccountNos(accountNosStr);
|
|
||||||
record.setErrorMessage(null);
|
|
||||||
recordMapper.updateById(record);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// 解析失败
|
|
||||||
log.warn("【文件上传】步骤6: 解析失败: status={}, desc={}", status, uploadStatusDesc);
|
|
||||||
updateFailedRecord(record, "解析失败: " + uploadStatusDesc);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("【文件上传】处理完成: fileName={}", record.getFileName());
|
log.info("【文件上传】处理完成: fileName={}", record.getFileName());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -496,6 +621,74 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processRecordAfterLogIdReady(Long projectId,
|
||||||
|
Integer lsfxProjectId,
|
||||||
|
CcdiFileUploadRecord record,
|
||||||
|
Integer logId) {
|
||||||
|
log.info("【文件上传】步骤3: 更新状态为解析中, logId={}", logId);
|
||||||
|
record.setLogId(logId);
|
||||||
|
record.setFileStatus("parsing");
|
||||||
|
recordMapper.updateById(record);
|
||||||
|
|
||||||
|
log.info("【文件上传】步骤4: 开始轮询解析状态");
|
||||||
|
boolean parsingComplete = waitForParsingComplete(lsfxProjectId, logId.toString());
|
||||||
|
if (!parsingComplete) {
|
||||||
|
throw new RuntimeException("解析超时(超过10分钟),请检查文件格式是否正确");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("【文件上传】步骤5: 获取文件上传状态: logId={}", logId);
|
||||||
|
GetFileUploadStatusRequest statusRequest = new GetFileUploadStatusRequest();
|
||||||
|
statusRequest.setGroupId(lsfxProjectId);
|
||||||
|
statusRequest.setLogId(logId);
|
||||||
|
|
||||||
|
GetFileUploadStatusResponse statusResponse = lsfxClient.getFileUploadStatus(statusRequest);
|
||||||
|
if (statusResponse == null || statusResponse.getData() == null
|
||||||
|
|| statusResponse.getData().getLogs() == null
|
||||||
|
|| statusResponse.getData().getLogs().isEmpty()) {
|
||||||
|
throw new RuntimeException("获取文件上传状态失败: 响应数据为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
GetFileUploadStatusResponse.LogItem logItem = statusResponse.getData().getLogs().get(0);
|
||||||
|
Integer status = logItem.getStatus();
|
||||||
|
String uploadStatusDesc = logItem.getUploadStatusDesc();
|
||||||
|
String fileName = StringUtils.hasText(logItem.getUploadFileName())
|
||||||
|
? logItem.getUploadFileName()
|
||||||
|
: logItem.getDownloadFileName();
|
||||||
|
if (StringUtils.hasText(fileName)) {
|
||||||
|
record.setFileName(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("【文件上传】文件状态: status={}, uploadStatusDesc={}", status, uploadStatusDesc);
|
||||||
|
boolean parseSuccess = status != null && status == -5
|
||||||
|
&& "data.wait.confirm.newaccount".equals(uploadStatusDesc);
|
||||||
|
if (!parseSuccess) {
|
||||||
|
log.warn("【文件上传】步骤6: 解析失败: status={}, desc={}", status, uploadStatusDesc);
|
||||||
|
updateFailedRecord(record, "解析失败: " + uploadStatusDesc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("【文件上传】步骤6: 解析成功,保存主体信息");
|
||||||
|
List<String> enterpriseNames = logItem.getEnterpriseNameList();
|
||||||
|
List<String> accountNos = logItem.getAccountNoList();
|
||||||
|
String enterpriseNamesStr = enterpriseNames != null ? String.join(",", enterpriseNames) : "";
|
||||||
|
String accountNosStr = accountNos != null ? String.join(",", accountNos) : "";
|
||||||
|
log.info("【文件上传】主体信息已保存: enterpriseNames={}, accountNos={}",
|
||||||
|
enterpriseNamesStr, accountNosStr);
|
||||||
|
|
||||||
|
log.info("【文件上传】步骤7: 获取流水数据");
|
||||||
|
FetchBankStatementResult fetchResult = fetchAndSaveBankStatements(projectId, lsfxProjectId, logId);
|
||||||
|
if (!fetchResult.isSuccess()) {
|
||||||
|
updateFailedRecord(record, fetchResult.getErrorMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
record.setFileStatus("parsed_success");
|
||||||
|
record.setEnterpriseNames(enterpriseNamesStr);
|
||||||
|
record.setAccountNos(accountNosStr);
|
||||||
|
record.setErrorMessage(null);
|
||||||
|
recordMapper.updateById(record);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 轮询解析状态(固定间隔2秒,最多300次)
|
* 轮询解析状态(固定间隔2秒,最多300次)
|
||||||
*
|
*
|
||||||
@@ -665,4 +858,11 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
|||||||
private void cleanupBankStatements(Long projectId, Integer logId) {
|
private void cleanupBankStatements(Long projectId, Integer logId) {
|
||||||
bankStatementMapper.deleteByProjectIdAndBatchId(projectId, logId);
|
bankStatementMapper.deleteByProjectIdAndBatchId(projectId, logId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Integer toUploadUserId(Long userId) {
|
||||||
|
if (userId == null) {
|
||||||
|
throw new IllegalArgumentException("当前登录用户ID不能为空");
|
||||||
|
}
|
||||||
|
return Math.toIntExact(userId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<insert id="insertBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insertBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="id">
|
||||||
insert into ccdi_file_upload_record (
|
insert into ccdi_file_upload_record (
|
||||||
project_id, lsfx_project_id, file_name, file_size, file_status,
|
project_id, lsfx_project_id, file_name, file_size, file_status,
|
||||||
upload_time, upload_user
|
enterprise_names, account_nos, upload_time, upload_user
|
||||||
) values
|
) values
|
||||||
<foreach collection="list" item="item" separator=",">
|
<foreach collection="list" item="item" separator=",">
|
||||||
(
|
(
|
||||||
#{item.projectId}, #{item.lsfxProjectId}, #{item.fileName},
|
#{item.projectId}, #{item.lsfxProjectId}, #{item.fileName},
|
||||||
#{item.fileSize}, #{item.fileStatus}, #{item.uploadTime},
|
#{item.fileSize}, #{item.fileStatus}, #{item.enterpriseNames},
|
||||||
#{item.uploadUser}
|
#{item.accountNos}, #{item.uploadTime}, #{item.uploadUser}
|
||||||
)
|
)
|
||||||
</foreach>
|
</foreach>
|
||||||
</insert>
|
</insert>
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.ruoyi.ccdi.project.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.ccdi.project.domain.dto.CcdiPullBankInfoSubmitDTO;
|
||||||
|
import com.ruoyi.ccdi.project.service.ICcdiFileUploadService;
|
||||||
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CcdiFileUploadControllerTest {
|
||||||
|
|
||||||
|
private static final Long PROJECT_ID = 100L;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private CcdiFileUploadController controller;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private ICcdiFileUploadService fileUploadService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseIdCardFile_shouldReturnAjaxResultSuccess() {
|
||||||
|
MockMultipartFile file = new MockMultipartFile(
|
||||||
|
"file",
|
||||||
|
"ids.xlsx",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"test".getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
when(fileUploadService.parseIdCardFile(file)).thenReturn(List.of("110101199001018888"));
|
||||||
|
|
||||||
|
AjaxResult result = controller.parseIdCardFile(file);
|
||||||
|
|
||||||
|
assertEquals(200, result.get("code"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void pullBankInfo_shouldUseCurrentLoginUserInfo() {
|
||||||
|
CcdiPullBankInfoSubmitDTO dto = new CcdiPullBankInfoSubmitDTO();
|
||||||
|
dto.setProjectId(PROJECT_ID);
|
||||||
|
dto.setIdCards(List.of("110101199001018888"));
|
||||||
|
dto.setStartDate("2026-03-01");
|
||||||
|
dto.setEndDate("2026-03-10");
|
||||||
|
|
||||||
|
try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
|
||||||
|
mocked.when(SecurityUtils::getUserId).thenReturn(9527L);
|
||||||
|
mocked.when(SecurityUtils::getUsername).thenReturn("admin");
|
||||||
|
when(fileUploadService.submitPullBankInfo(PROJECT_ID, dto.getIdCards(), "2026-03-01", "2026-03-10", 9527L, "admin"))
|
||||||
|
.thenReturn("batch-1");
|
||||||
|
|
||||||
|
AjaxResult result = controller.pullBankInfo(dto);
|
||||||
|
|
||||||
|
assertEquals(200, result.get("code"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package com.ruoyi.ccdi.project.service.impl;
|
|||||||
import ch.qos.logback.classic.Logger;
|
import ch.qos.logback.classic.Logger;
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
import ch.qos.logback.core.read.ListAppender;
|
import ch.qos.logback.core.read.ListAppender;
|
||||||
|
import com.alibaba.excel.EasyExcel;
|
||||||
|
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||||
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper;
|
||||||
@@ -10,6 +12,7 @@ import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
|||||||
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
||||||
import com.ruoyi.lsfx.domain.request.GetBankStatementRequest;
|
import com.ruoyi.lsfx.domain.request.GetBankStatementRequest;
|
||||||
import com.ruoyi.lsfx.domain.response.CheckParseStatusResponse;
|
import com.ruoyi.lsfx.domain.response.CheckParseStatusResponse;
|
||||||
|
import com.ruoyi.lsfx.domain.response.FetchInnerFlowResponse;
|
||||||
import com.ruoyi.lsfx.domain.response.GetBankStatementResponse;
|
import com.ruoyi.lsfx.domain.response.GetBankStatementResponse;
|
||||||
import com.ruoyi.lsfx.domain.response.GetFileUploadStatusResponse;
|
import com.ruoyi.lsfx.domain.response.GetFileUploadStatusResponse;
|
||||||
import com.ruoyi.lsfx.domain.response.UploadFileResponse;
|
import com.ruoyi.lsfx.domain.response.UploadFileResponse;
|
||||||
@@ -20,8 +23,12 @@ import org.mockito.InjectMocks;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
@@ -31,10 +38,13 @@ import java.nio.file.Path;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
@@ -72,6 +82,68 @@ class CcdiFileUploadServiceImplTest {
|
|||||||
@TempDir
|
@TempDir
|
||||||
Path tempDir;
|
Path tempDir;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseIdCardFile_shouldReadFirstSheetFirstColumnAndDeduplicate() throws Exception {
|
||||||
|
MultipartFile file = createIdCardExcel(
|
||||||
|
"身份证号",
|
||||||
|
"110101199001018888",
|
||||||
|
"",
|
||||||
|
"110101199001018888",
|
||||||
|
"110101199001019999"
|
||||||
|
);
|
||||||
|
|
||||||
|
List<String> result = service.parseIdCardFile(file);
|
||||||
|
|
||||||
|
assertEquals(List.of("110101199001018888", "110101199001019999"), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void parseIdCardFile_shouldRejectInvalidIdCard() throws Exception {
|
||||||
|
MultipartFile file = createIdCardExcel("身份证号", "123456");
|
||||||
|
|
||||||
|
RuntimeException exception = assertThrows(RuntimeException.class, () -> service.parseIdCardFile(file));
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("身份证"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void submitPullBankInfo_shouldInsertUploadingRecordsWithIdCardAsAccountNo() {
|
||||||
|
CcdiProject project = new CcdiProject();
|
||||||
|
project.setProjectId(PROJECT_ID);
|
||||||
|
project.setLsfxProjectId(LSFX_PROJECT_ID);
|
||||||
|
when(projectMapper.selectById(PROJECT_ID)).thenReturn(project);
|
||||||
|
|
||||||
|
AtomicReference<List<CcdiFileUploadRecord>> inserted = new AtomicReference<>();
|
||||||
|
doAnswer(invocation -> {
|
||||||
|
List<CcdiFileUploadRecord> records = invocation.getArgument(0);
|
||||||
|
for (int i = 0; i < records.size(); i++) {
|
||||||
|
records.get(i).setId((long) (i + 1));
|
||||||
|
}
|
||||||
|
inserted.set(new ArrayList<>(records));
|
||||||
|
return records.size();
|
||||||
|
}).when(recordMapper).insertBatch(any());
|
||||||
|
|
||||||
|
TransactionSynchronizationManager.initSynchronization();
|
||||||
|
try {
|
||||||
|
String batchId = service.submitPullBankInfo(
|
||||||
|
PROJECT_ID,
|
||||||
|
List.of("110101199001018888", "110101199001019999"),
|
||||||
|
"2026-03-01",
|
||||||
|
"2026-03-10",
|
||||||
|
9527L,
|
||||||
|
"admin"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertNotNull(batchId);
|
||||||
|
assertEquals("110101199001018888", inserted.get().get(0).getAccountNos());
|
||||||
|
assertEquals("admin", inserted.get().get(0).getUploadUser());
|
||||||
|
assertEquals("uploading", inserted.get().get(0).getFileStatus());
|
||||||
|
assertEquals(1, TransactionSynchronizationManager.getSynchronizations().size());
|
||||||
|
} finally {
|
||||||
|
TransactionSynchronizationManager.clearSynchronization();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void submitTasksAsync_shouldNotCreateLocalBatchLogFiles() throws Exception {
|
void submitTasksAsync_shouldNotCreateLocalBatchLogFiles() throws Exception {
|
||||||
setField("uploadPath", tempDir.toString());
|
setField("uploadPath", tempDir.toString());
|
||||||
@@ -151,6 +223,51 @@ class CcdiFileUploadServiceImplTest {
|
|||||||
verify(bankStatementMapper).deleteByProjectIdAndBatchId(PROJECT_ID, LOG_ID);
|
verify(bankStatementMapper).deleteByProjectIdAndBatchId(PROJECT_ID, LOG_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void processPullBankInfoAsync_shouldUpdateFileNameFromStatusResponse() {
|
||||||
|
when(lsfxClient.fetchInnerFlow(any())).thenReturn(buildFetchInnerFlowResponse(LOG_ID));
|
||||||
|
when(lsfxClient.checkParseStatus(LSFX_PROJECT_ID, String.valueOf(LOG_ID)))
|
||||||
|
.thenReturn(buildCheckParseStatusResponse(false));
|
||||||
|
when(lsfxClient.getFileUploadStatus(any())).thenReturn(buildParsedSuccessStatusResponse("XX身份证.xlsx"));
|
||||||
|
when(lsfxClient.getBankStatement(any(GetBankStatementRequest.class)))
|
||||||
|
.thenReturn(buildEmptyBankStatementResponse());
|
||||||
|
|
||||||
|
CcdiFileUploadRecord record = buildRecord();
|
||||||
|
service.processPullBankInfoAsync(
|
||||||
|
PROJECT_ID,
|
||||||
|
LSFX_PROJECT_ID,
|
||||||
|
record,
|
||||||
|
"110101199001018888",
|
||||||
|
"2026-03-01",
|
||||||
|
"2026-03-10",
|
||||||
|
9527L
|
||||||
|
);
|
||||||
|
|
||||||
|
verify(recordMapper, org.mockito.Mockito.atLeastOnce()).updateById(org.mockito.ArgumentMatchers.<CcdiFileUploadRecord>argThat(item ->
|
||||||
|
"XX身份证.xlsx".equals(item.getFileName())));
|
||||||
|
verify(recordMapper, org.mockito.Mockito.atLeastOnce()).updateById(org.mockito.ArgumentMatchers.<CcdiFileUploadRecord>argThat(item ->
|
||||||
|
"parsed_success".equals(item.getFileStatus())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void processPullBankInfoAsync_shouldMarkParsedFailedWhenFetchInnerFlowThrows() {
|
||||||
|
when(lsfxClient.fetchInnerFlow(any())).thenThrow(new RuntimeException("fetch inner flow failed"));
|
||||||
|
|
||||||
|
CcdiFileUploadRecord record = buildRecord();
|
||||||
|
service.processPullBankInfoAsync(
|
||||||
|
PROJECT_ID,
|
||||||
|
LSFX_PROJECT_ID,
|
||||||
|
record,
|
||||||
|
"110101199001018888",
|
||||||
|
"2026-03-01",
|
||||||
|
"2026-03-10",
|
||||||
|
9527L
|
||||||
|
);
|
||||||
|
|
||||||
|
verify(recordMapper, org.mockito.Mockito.atLeastOnce()).updateById(org.mockito.ArgumentMatchers.<CcdiFileUploadRecord>argThat(item ->
|
||||||
|
"parsed_failed".equals(item.getFileStatus())));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void processFileAsync_shouldFailWhenPagedFetchThrows() throws IOException {
|
void processFileAsync_shouldFailWhenPagedFetchThrows() throws IOException {
|
||||||
List<String> events = new ArrayList<>();
|
List<String> events = new ArrayList<>();
|
||||||
@@ -364,6 +481,18 @@ class CcdiFileUploadServiceImplTest {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private FetchInnerFlowResponse buildFetchInnerFlowResponse(Integer logId) {
|
||||||
|
FetchInnerFlowResponse response = new FetchInnerFlowResponse();
|
||||||
|
response.setData(List.of(logId));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GetFileUploadStatusResponse buildParsedSuccessStatusResponse(String uploadFileName) {
|
||||||
|
GetFileUploadStatusResponse response = buildParsedSuccessStatusResponse();
|
||||||
|
response.getData().getLogs().get(0).setUploadFileName(uploadFileName);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
private GetBankStatementResponse buildEmptyBankStatementResponse() {
|
private GetBankStatementResponse buildEmptyBankStatementResponse() {
|
||||||
GetBankStatementResponse.BankStatementData data = new GetBankStatementResponse.BankStatementData();
|
GetBankStatementResponse.BankStatementData data = new GetBankStatementResponse.BankStatementData();
|
||||||
data.setTotalCount(0);
|
data.setTotalCount(0);
|
||||||
@@ -420,6 +549,21 @@ class CcdiFileUploadServiceImplTest {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MultipartFile createIdCardExcel(String... values) throws IOException {
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
List<List<String>> rows = new ArrayList<>();
|
||||||
|
for (String value : values) {
|
||||||
|
rows.add(List.of(value));
|
||||||
|
}
|
||||||
|
EasyExcel.write(outputStream).sheet("Sheet1").doWrite(rows);
|
||||||
|
return new MockMultipartFile(
|
||||||
|
"file",
|
||||||
|
"id-cards.xlsx",
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
outputStream.toByteArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private int findEventIndex(List<String> events, String suffix) {
|
private int findEventIndex(List<String> events, String suffix) {
|
||||||
for (int i = 0; i < events.size(); i++) {
|
for (int i = 0; i < events.size(); i++) {
|
||||||
if (events.get(i).endsWith(suffix)) {
|
if (events.get(i).endsWith(suffix)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user