导入导出在一般系统中会用到多次。
表设计:
CREATE TABLE `data_process_task` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键(自增)',
`task_function_type` tinyint(4) NOT NULL COMMENT '任务类型(1:导入;2:导出)',
`task_function_code` varchar(50) NOT NULL COMMENT '任务功能编号',
`task_function_name` varchar(50) NOT NULL COMMENT '任务功能名称',
`biz_content` text COMMENT '业务数据',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态 0:待执行;1:执行中;2:完成;3:失败',
`fail_reason` varchar(150) NOT NULL DEFAULT '' COMMENT '失败原因',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '操作者ID',
`user_account` varchar(50) NOT NULL DEFAULT '' COMMENT '操作者账号',
`total_cnt` int(11) DEFAULT '0' COMMENT '总行数 ',
`success_cnt` int(11) DEFAULT '0' COMMENT '成功的行数',
`fail_cnt` int(11) DEFAULT '0' COMMENT '失败的行数',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`version_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '数据版本',
`orgi_file_url` text COMMENT '原始文件',
`out_file_url` text COMMENT '输出文件',
PRIMARY KEY (`id`),
KEY `idx_task_status` (`status`),
KEY `idx_gmt_create` (`gmt_create`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务记录表';
接口设计:
输入:
/**
* 任务类型(1:导入 2:导出)
*/
private Integer taskFunctionType;
/**
* 任务功能编号
*/
private String taskFunctionCode;
/**
* 业务数据, 比如导入的数据,属于哪个业务 或 哪个活动,把相关的数据,传过来,方便校验和处理
*/
private String bizContent;
/**
* 导入的文件,已经上传到服务端的地址
*/
private String orgiFileUrl;
核心处理:
public void submit(DPtaskSubmitDTO dataDto) {
DPTaskFunctionCodeEnum byCode = DPTaskFunctionCodeEnum.getByCode(dataDto.getTaskFunctionCode());
AssertUtil.notNull(byCode,"任务功能编号错误");
// 根据code,从工厂中获取处理类
DPtaskExecService service = dptaskFactory.getService(byCode);
DPtaskDTO newDPtaskDTO = service.checkParallel(dataDto,()->{
//校验是否存在在执行任务,默认校验,可以自行重写
LambdaQueryWrapper<DPTaskDO> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DPTaskDO::getTaskFunctionType,dataDto.getTaskFunctionType());
wrapper.eq(DPTaskDO::getTaskFunctionCode,dataDto.getTaskFunctionCode());
wrapper.in(DPTaskDO::getStatus, Arrays.asList(DPTaskStatusEnum.DO_ING.getCode(),DPTaskStatusEnum.INIT.getCode()));
List<DPTaskDO> list = dpTaskDAO.list(wrapper);
return list;
},() -> savedptask(dataDto));
AssertUtil.notNull(newDPtaskDTO,"任务未创建完成");
//
String traceId = MDC.get(Constant.TRACE_ID_NAME);
adTaskExecutor.execute(() -> {
MDC.put(Constant.TRACE_ID_NAME, traceId);
//
Long id = newDPtaskDTO.getId();
dpTaskDAO.changeTaskStatus(id, DPTaskStatusEnum.DO_ING,null,null);
//
try {
// 数据的处理
DPTaskExecDTO dpTaskExecDTO = service.executeDptask(newDPtaskDTO);
dpTaskDAO.changeTaskStatus(id, dpTaskExecDTO.getDpTaskStatusEnum(), dpTaskExecDTO.getFailReason(), dpTaskExecDTO.getOutFileUrl());
}catch (Exception e){
log.error("导入导出任务执行失败",e);
dpTaskDAO.changeTaskStatus(id, DPTaskStatusEnum.FAIL, "任务执行失败请联系管理员", "");
}
MDC.remove(Constant.TRACE_ID_NAME);
});
}
工厂模式:
@Component
public class DPtaskFactory {
@Autowired
private Map<String,DPtaskExecService> dpServiceMap = new HashMap<>();
public DPtaskExecService getService(DPTaskFunctionCodeEnum dpTaskFunctionCodeEnum){
String code = dpTaskFunctionCodeEnum.getCode();
DPtaskExecService dptaskExecService = dpServiceMap.get(code + DPtaskConstant.DP_EXEC_SERVICE_SUFFIX);
if (Objects.isNull(dptaskExecService)){
dptaskExecService=dpServiceMap.get(DPtaskConstant.OTHER_SERVER+DPtaskConstant.DP_EXEC_SERVICE_SUFFIX);
}
AssertUtil.notNull(dptaskExecService,"任务功能有误");
return dptaskExecService;
}
}
处理接口:
public interface DPtaskExecService {
/**
* 需要校验不可并行, ** 校验通过后,务必执行saveTask **
*/
default DPtaskDTO checkParallel(DPtaskSubmitDTO dataDto, Supplier<List<DPTaskDO>> supplier,Supplier<DPtaskDTO> saveTask){
return saveTask.get();
};
DPTaskExecDTO executeDptask(DPtaskDTO advDpTaskDTO);
}
数据处理:
public DPTaskExecDTO executeDptask(DPtaskDTO advDpTaskDTO) {
Long id = advDpTaskDTO.getId();
String orgiFileUrl = advDpTaskDTO.getOrgiFileUrl();
String userAccount = advDpTaskDTO.getUserAccount();
// 业务数据的转换,根据不同的业务,可以传多个字段,建议传JSON
xxcx xxxx = handleBizContent(advDpTaskDTO.getBizContent());
Long mediumSupplierId = dPtaskStoreDTO.getMediumSupplierId();
//
Map<Integer,String> errorRowMap = new HashMap<>();
//
InputStream inputStream = null;
try{
HttpResponse httpResponse = HttpUtil.createGet(orgiFileUrl).execute();
inputStream = httpResponse.bodyStream();
ExcelReader reader = ExcelUtil.getReader(inputStream);
//数据处理
importXXX(reader, errorRowMap, mediumSupplierId,userAccount);
//
String errFileUrl = hadleErrorRow(errorRowMap, reader, id);
return new DPTaskExecDTO(DPTaskStatusEnum.DONE,null,errFileUrl);
}catch (Exception e){
log.error("导入失败",e);
return new DPTaskExecDTO(DPTaskStatusEnum.FAIL,"导入失败请联系管理员",orgiFileUrl);
}finally {
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
log.error("====inputStream.close.error",e);
}
}
}
}
当然,也可以取所有的数据,直接转换:
ExcelReader reader = ExcelUtil.getReader(inputStream)) {
int rowCount = reader.getRowCount();
List<List<Object>> dataList = reader.read(1, rowCount);
// 通过反射,直接转换为DTO
List<updateDTO> updateDTOS = dpTaskCommonService.setExcelData(dataList, DPTaskStoreUpdateDTO.class);
setExcelData的实现:
public <T> List<T> setExcelData(List<List<Object>> dataList, Class<T> clazz) {
List<T> dtoList = new ArrayList<>();
for (List<Object> row : dataList) {
try {
T dto = clazz.getDeclaredConstructor().newInstance();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Column column = field.getAnnotation(Column.class);
if (column != null) {
int index = column.index().charAt(0) - 'A';
if (index < row.size()) {
field.setAccessible(true);
String value = row.get(index) == null ? null : String.valueOf(row.get(index));
field.set(dto, value);
}
}
}
dtoList.add(dto);
} catch (Exception e) {
throw new RuntimeException("导入格式转换失败:"+e.getMessage());
}
}
return dtoList;
}
失败的处理:
public String handleErrorRow(Map<Integer, String> errorRowMap, ExcelReader reader) {
if (CollectionUtils.isEmpty(errorRowMap)) {
return "";
}
File errorFile = new File(UUID.randomUUID() + ".xlsx");
try (ExcelWriter writer = ExcelUtil.getWriter(errorFile)) {
List<Object> titleList = reader.readRow(0);
titleList.add("失败原因");
writer.writeRow(titleList);
for (Map.Entry<Integer, String> entry : errorRowMap.entrySet()) {
List<Object> dataList = reader.readRow(entry.getKey());
dataList.add(entry.getValue());
writer.writeRow(dataList);
}
writer.flush();
return tencentCosAdapter.uploadFile("adv/xlsx", errorFile);
} finally {
if (errorFile.exists()) {
errorFile.delete();
}
}
}