文章目录
- 概要
- 整体架构流程
- 代码设计
- 配置类
- 通用API
- 分发器
- 处理器
- 业务逻辑处理service接口
- 策略模型
- 小结
概要
基于java原生 + easypoi结合适配器模式、策略模式、工厂模式设计一个通用的excel导入框架
整体架构流程
代码设计
由上到下,分别讲解代码
配置类
ExcelConfigEnum
该配置类是声明导入业务类型,导入参数与Handler之间的实例化关系。
@Getter
public enum ExcelConfigEnum {// 测试配置TEST("test", "测试", "com.xxx.TestExcelHandler"),;private String type;private String desc;private String importClazz;ExcelConfigEnum (String type, String desc, String importClazz) {// ... 全参构造函数}// 根据type获取enumpublic static ExcelConfigEnum getByType(String type) {// codes...}
}
ExcelPolicyConfiguration
该注解类用于配置handler对应的策略
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AsyncExcelPolicyConfiguration
/**
* 导入策略
*/
Class<?extends AsyncExcelPolicy>policy();
/**
* 分片大小
*/
int shardingNum()default 1000;
}
AsyncExcelTask
多线程调度任务类
@Sf4j
public class AsyncExcelTask implements Runnable{// 处理器private AsyncExcelHandler handler;AsyncExcelTask(AsyncExcelHandler handler){this.handler=handler;}@Overridepublic void run(){handler.importExcel();}
}
Factory
handler工厂,用于实例化handler
public class ExcelHandlerFactory {public static AsyncExcelHandler getInstance(String type,InputStream in,Object param,String importId,String userId)throws NoSuchMethodException,IllegalAccessException,InvocationTargetException,InstantiationException,ClassNotFoundException {AsyncExcelConfigEnum moduleEnum = AsyncExcelConfigEnum.getByType(type);Class clz = Class.forName(moduleEnum.getImportclazz());if (Objects.isNuLl(clz)){return null;}AsyncExcelPolicyConfiguration annotation = (AsyncExcelPolicyConfiguration)clz.getAnnotation(AsyncExcelPolicyConfiguration.class);if (Objects.isNuLL(annotation)){log.error("缺失导入策略注解");throw new ServiceException((500,"缺失导入策略注解");}Class<?extends AsyncExcelPolicy>policyClass = annotation.policy();int shardingNum = annotation.shardingNum();AsyncExcelPolicy policy = policyClass.newInstance();Constructor<?extends AbstractAsyncExcelHandler> constructor =clz.getDeclaredConstructor(InputStream.class,Object.class,String.class,String.class,AsyncExcelPolicy.class,int.class);return constructor.newInstance(in,param,importId,userId,policy,shardingNum);}
}
通用API
@Api(va1ue="异形exce1守人守出",tags={"异步exce1手人子田")
@RestController
@RequestMapping("/common/asyncExcel")
public class AsyncExcelController {@Autowiredprivate AuthUtils authUtils;private static final String MODULE_AME="异步exce1导入导出";@OperLogOption(module MODULE_NAME,oper OperLogOption.Oper.IMPORT,desc ="excel")@PostMapping("/import")public Boolean asyncImportExcel(ExcelImportParams excelImportParams){AsyncExcelDispatcher.asyncDispatch(excelImportParams.getMultipartFiles[0],excelImportParams.getType(),excelImportParams.getObject(),authUtils.currentUserId());return true;}
}
分发器
根据前段传递的type通过factory构建handler
@s1f4j
public class AsyncExcelDispatcher {public static void asyncDispatch(MultipartFile file,String type,Object param,String userId){//构造异步导入excel记录对象UserAsyncExcel userAsyncExcel buildAsyncExceLEntity(file,userId);try {AsyncExcelConfigEnum excelEnum AsyncExcelConfigEnum.getByType(type);// 检查导入配置是否存在if (Objects.isNuLL(excelEnum) || StringUtils.isBLank(excelEnum.getImportClazz())){throw new ServiceException(BizExceptionCommonEnum.INTERFACE_NOT_EXSIT_ERROR);}// 保存导入信息,状态执行中IUserFeignService userFeignService Springutils.getBean(IUserFeignService.class);userFeignService.save(userAsyncExcel);//获取handLer实例InputStream in = file.getInputStream();AsyncExcelHandler handler = ExcelHandlerFactory.getInstance(type,in,param,userAsyncExcel.getId(),userId);ExecutorService executor ThreadPoolFactory.ThreadPoolEnum.IMPORT_EXCEL.getThreadPool();//异步提交任务AsyncExcelTask asyncExcelTask = new AsyncExcelTask(handler);executor.submit(asyncExcelTask);catch (IOException | InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException | ClassNotFoundException ex){log.error("获取实例失败",ex);//写入状态IUserFeignService userFeignService SpringUtils.getBean(IUserFeignService.class);userAsyncExcel.setStatus(AsyncExcelStatus.EXCEPTION);userAsyncExcel.setMessage(ex.getMessage());userFeignService.update(userAsyncExcel);throw new ServiceException(BizExceptionCommonEnum.INTERFACE_NOT_EXSIT_ERROR);}private static UserAsyncExcel buildAsyncExcelEntity(MultipartFile file,String userId){UserAsyncExceluserAsyncExcel new UserAsyncExcel();String id UuidUtils.getId();userAsyncExcel.setCreateTime(Timestamp.valueof(LocalDateTime.now(Clock.systemDefaultZone())));userAsyncExcel.setCreateUserId(userId);userAsyncExcel.setstatus(AsyncExcelStatus.EXECUTING);userAsyncExcel.setTitle(file.getoriginalFilename());userAsyncExcel.setType("I");userAsyncExcel.setId(id)jreturn userAsyncExcel;}}
处理器
IAsyncExcelHandler
handler接口, 规范编码
public interface AsyncExcelHandler<T extends ExcelImportBaseEntity> {// 导入excelvoid importExcel();// 重置导入参数void resetImportParams();// 设置数据处理器void setDataHandler(IExcelDataHandler dataHandler);// 设置字典处理器void setDictHandler(IExcelDictHandler dictHandler);// 是否需要字段校验void needverify(boolean needverify);// 获取业务服务对象IAsyncExcelService getservice();
}
AbstractAsyncExcelHandler
处理器抽象实现类
public abstract class AbstractAsyncExcelHandler<T extends ExcelImportBaseEntity> implements AsyncExcelHandler{/**文件流*/protected InputStream in;/***导入参数*/protected Object object;/**导入id*/protected String importId;/**导入用户*/protected String userId;/**策略*/protected AsyncExcelPolicy policy;/** 分片大小*/protected int shardingNum;/** 导入设置参数*/protected ImportParams importParams new ImportParams();/**文件服务*/protected IFileFeignService fileFeignService=SpringUtils.getBean(IFileFeignService.class);/** user服务*/protected IUserFeignService userFeignService SpringUtils.getBean(IUserFeignService .class);/** 构造函数*/public AbstractAsyncExcelHandler(InputStream in,Object object,String importId,String userId,AsyncExcelPolicy policy,int shardingNum){this.in = in;this.object = object;this.importId = importId;this.userId = userId;this.policypolicy = policy;this.shardingNum = shardingNum;}@Overridepublic void importExcel(){//1导入之前设置参效resetImportParams();setDataHandler(new DefaultDataHandler());setDictHandler(new DefaultExcelDictHandler());needverify(true);ParameterizedType parameterizedType = (ParameterizedType) this.getclass().getGenericSuperclass();Class clazz = (Class) parameterizedType.getActualTypeArguments()[0];ExcelImportResult<T> result;ByteArrayOutputStream bos = new ByteArrayOutputStream();ByteArrayInputStream bis = null;try {//复制一份流用于输出校验结果IOUtils.copy(in,bos);bis = new ByteArrayInputStream(bos.toByteArray());result = ExcelImportUtil.importExceLMore(bis,clazz,importParams);}catch (Exception ex) {IOUtils.closeQuietly(this.in);IOUtils.cLoseQuietly(bos);IOUtils.closeQuietLy(bis);log,error("excel解析失败“,ex);// 更新异常状态updateMessage4Exception(ex.getMessage());throw new ServiceException(BizExceptionCommonEnum.POI_ERROR)}//导入后处理//注解校验结哭if (result.isverifyFail()) {// 校验结果处理postverifyFailed(result,bos,bis);return;}// 校验是否实现serviceIAsyncExcelService service = getService();if (Objects.isNuLL(service)){Log.error("业务服务未返回service对象");//更新导入异常状态updateMessage4Exception("业务服务未实现");throw new ServiceException(500,"业务服务未实现");}// 业务校验service.verify(result);if (result.isVerifyFail()){//校验结果处理postverifyFailed(result,bos,bis);return;}//业务逻辑runPolicy(result.getList());}// 策略运行protected void runPolicy(List<T> list){try {policy.runPolicy(list,shardingNum,getService(),importId);} catch (Exception ex){//标识异常Log.error("业务数据插入异常。",e×);UserAsyncExcel userAsyncExcel new UserAsyncExcel();userAsyncExcel.setId(importId);userAsyncExcel.setstatus(AsyncExcelStatus.EXCEPTION);userAsyncExcel.setMessage("分片号入发生异常");userFeignService.update(userAsvncExcel):}}// 校验失败后置处理private void postVerifyFailed(ExcelImportResult<T> result,ByteArrayOutputstream bos,ByteArrayInputstream bis){try {List<T> failList = result.getFailList();bis.reset();Workbook workbook = WorkbookFactory.create(bis);Sheet sheet = workbook.getsheetAt(index:0);int titleRowNum = importParams.getTitleRows(>+importParams.getHeadRows();short lastCellNum = sheet.getRow(titleRowNum).getLastCellNum();CellStyle errorCellStyle = createCellStyle(workbook);for (T failEntity failList){Integer rowNum failEntity.getRowNum();Rowrow sheet.getRow(rowNum)jCell cell row.createCell(lastCellNum);cell.setCellValue(failEntity.getErrorMsg());cell.setCellStyle(errorCellStyle);}workbook.getsheetAt(0).setColumnwidth(lastCellNum, 5000);UserAsyncExcel userAsyncExcel = userFeignService.getById(importId).getData();// 流清空,重用bos.flush();bos.reset();workbook.write(bos);Map<String,String> errFileMap = uploadVerifyFailedExcel(bos,userAsyncExcel.getTitle());// 更新导入结果为校验失败String fileId = String.join(StringPool.COMMA,errFileMap.keySet());userAsyncExcel.setstatus(AsyncExcelStatus.VERIFY_FATLED);userAsyncExcel.setResFileId(fileId);userFeignService.update(userAsyncExcel);}catch (IOException ex) {IOUtils.closeQuietly(bos);IOUtils.cLoseQuietly(bis);Log.error("设置校验结果异常",ex);/更新导入异常状态updateMessage4Exception(ex.getMessage());throw new ServiceException(5OB,"业务校验失败");} finally {Log.info("原始流复件关闭");IOUtils.cLoseQuietly(bos);IOUtils.cLoseQuietly(bis);}}private Map<String,String>uploadVerifyFailedExcel(ByteArrayOutputStream erroros, String fileName){long time System.currentTimeMiLLis();int idx fileName.lastIndexof(StringPool.DOT);String pre fileName.substring(0,idx);String suffer fileName.substring(idx);fileName pre time suffer;MultipartFile[]multipartFiles new MultipartFile[]{new MockMultipartFile(Constants.ExcelConstants.UPLOAD_FILE_FIELD,fileName,MediaType.MUL TIPART_FOR_DATA_VALUE,erroros.toByteArray())};return fileFeignService.uploadTemp(multipartFiles).getData();}protected Cellstyle createCellStyle(Workbook workbook){CellStyle errorCellStyle workbook.createCellstyle();Font font workbook.createFont();font.setColor(Font.COLOR_RED);errorCellStyle.setFont(font);// 背景色// 设置背景色填充方式为实线填充// errorCeLlStyle.setFiLLPattern(FillPatternType.SOLID_FOREGROUND);// errorceLlStyle.setFiLLForegroundcolor(IndexedColors.RED.getIndex());errorCellstyle.setWrapText(true);return errorCellStyle;}private void updateMessage4Exception(String message){UserAsyncExcel userAsyncExcel = new UserAsyncExcel();userAsyncExcel.setId(importId);userAsyncExcel.setStatus(AsyncExcelStatus.EXCEPTION);userAsyncExcel.setMessage(message);userFeignService.update(userAsyncExcel);}
@Override
public void resetImportParams(){//设置标题行数this.importParams.setTitleRows(1);//设置表头行数this.importParams.setHeadRows(2);
}@Overridepublic void setDataHandler(IExcelDataHandler dataHandler){this.importParams.setDataHandler(dataHandler)};@Overridepublic void setDictHandler(IExcelDictHandler dictHandler){this.importParams.setDictHandler(dictHandler)};@Overridepublic void needverify(boolean needVerify){this.importParams.setNeedVerify(needVerify)};}
业务逻辑处理service接口
IAsyncExcelService
public interface IAsyncExcelService<T extends ExcelImportBaseEntity>{/** 业务校验* @param result*/void verify(ExcelImportResult<T> result);/*** 处理导入数据**/void dealResultData(List<T> list);
}
策略模型
AsyncExcelPolicy 策略接口
规范策略实现
public interface IAsyncExcelService<T extends ExcelImportBaseEntity> {/*** 业务校验*/void verify(ExcelImportResult<T> result);/*** 处理导入数据*/void dealResultData(List<T> list);
}
ShardingPolicy分片策略
Public class phardangPolicy implements AsyncExcelPolicy{@Overridepublie void runPolicy(List list,int shardingNun,IAsyncExcelService service,String impertId){//数据分片NongoTerplate mongoTeplate = Springutils.getBean(NangoTerplate.class);List<List> splitList = Listutil.splitlist(shardinghun,list);List<AsyncExcelShardingEntity> nongoEntityList = splitList.streas().map(v->{AsyncExcelShardingEntity entity new AsyncExcelshardingEntity(entity.setImportId(impertId);entity.setshardingId(Uuidutils.getId());entity.setSucceeded(falae);entity.setData(v);return entity;}).collect(Collectors.tolist());// 分片数据存入mongomongoTerplate.insertAll(mongoEntityList);Log.info("分片数据:{}", ongoEntitylist);//分片进入业务导入方法for (AsyncExcelShardingEntity entity mangofntitylist){List subList = (List) entity.gotData();service.dealResultData(subList);// 执行成功成功删除数据mangoTenplate.remove(entity);}}
}
小结
业务服务使用只需要三步
- 配置对应的handler权限定名。
- 实现handler类,根据自己的excel样式设置参数,用于解析excel数据。
- 实现service服务接口,实现校验数据方法和导入方法