【Java实战】Excel文件导入方法详解


本文以一个实战案例,详解关于该案例中Excel导入方法。本篇会用更加通俗的讲法让读到这篇博客的伙伴能够更轻松的理解导入方法的整体流程,以及涉及到的一些类,例如consumer类的用法,更加偏向于理解而不是底层原理


I. 项目结构概述

本篇以导入历史问题为背景,进行项目结构的构造

  • module层:服务实现层,调用dao层中实现的各种数据库操作方法实现服务功能
    • controller层:定义控制器结构HistoricalIssuesController
    • service层:实现各种服务功能,定义HistoricalIssuesServiceHistoricalIssuesServiceImpl
  • dao层:数据库操作实现层,定义了TbIssuesDaoServiceReviewProcessDaoService来实现对应的数据库的CRUD
  • dto层:数据传输对象层,定义IssuesModel作为接收excel内容的对象
  • enums层:枚举类,定义IssueTypeEnum

II. Excel导入具体实现

1. controller层

HistoricalIssuesController中现将接口定义出来

@RestController
@RequestMapping("/file/operation")
@Api(tags = "问题导入")
public class HistoricalIssuesController extends BaseController {
	
	@Autowired
	private HistoricalIssuesService historicalIssuesService;

	@ApiOperation("导入历史问题清单")
	@PostMapping("/import-historicalIssues")
	public JsonResult<Void> importHistoricalIssues(@RequestParam("files")List<MultipartFile> files, @RequestParam("serialNo") String serialNo throws IOException {
		historicalIssuesService.importHistoricalIssues(files, serialNo);
		return okReturn(ReturnCodeEnum.S0002);
	}
}

其中,MultipartfFile类用来接收excel文件,文件可用postman在接口中上传。其中较为常用的方法是MultipartFile.getInputStream(),用来获取输入文件的流,流可以理解为文件进入程序之后的一种存在形式,可以调用InputStream中的read()方法读出流中的内容

2. dao层

dao层中实现数据库操作的逻辑,在本例中,TbIssuesDaoService用来操作存储问题的数据库表,Issues对象包含一个reviewProcessId字段,需要用ReviewProcessDaoService来获取。而每一个reviewProcessId对应一个serialNo,于是有getBySerialNo()方法

@Service
public class ReviewProcessDaoServiceImpl extends ServiceImpl<ReviewProcessMapper, ReviewProcessPo> implements ReviewProcessDaoService {
	
	@Override
	public ReviewProcessPo getBySerialNo(String serialNo) {
		if (StringUtils.isEmpty(serialNo)) {
			return null;
		}
		LambdQueryWrapper<ReviewProcessPo> wrapper = new LambdQueryWrapper<>();
		wrapper.eq(ReviewProcessPo::getSerialNo, serialNo);

		return this.getOne(wrapper) == null ? null : this.getOne(wrapper);

3. dto层

数据传输对象,用来存储excel导入的数据

@Data
public class IssuesModel {
	@ExcelProperty(index = 0)
	private String problemDescription;

	@ExcelProperty(index = 1)
	private String historicalIssueType;

	@ExcelProperty(index = 2)
	private String deadline;

	@ExcelProperty(index = 3)
	private String solution;

	@ExcelProperty(index = 4)
	private String principal;

	@ExcelProperty(index = 5)
	private StringprincipalName;

	@ExcelProperty(index = 6)
	private String serilaNo;
}

4. enum层以及converter方法

IssueTypeEnum定义了问题类型的枚举类,converter方法写在HistoricalIssuesServiceImpl

@Getter
public enum IssueTypeEnum {
	A(1, "A"),
	B(2, "B"),
	C(3, "C"),
	D(4, "D"),
	;

	@EnumValue
	private Integer value;
	private String message;

	IssueTypeEnum(Integer value, String message) {
		this.value = value;
		this.message = message;
	}
}

// converter写在HistoricalIssueaServiceImpl中
private IssueTypeEnum converter(String historicalIssuesType) {
	if (StringUtils.equals(historicalIssueType, "a类")) {
		return IssueTypeEnum.A;
	} else if (StringUtils.equals(historicalIssueType, "b类")) {
		return IssueTypeEnum.B;
	} else if (StringUtils.equals(historicalIssueType, "c类")) {
		return IssueTypeEnum.C;
	} else {
		return IssueTypeEnum.D;
	}
}

5. service层

service层实现HistoricalIssuesServiceImpl,首先是方法函数dataProcess

public Consumer<List<IssuesModel>> dataProcess(String serialNo)
	Consumer<List<IssuesModel>> consumer = models -> {
		// 根据业务逻辑进行代码书写
		if (CollectionUtils.isEmpty(models)) {
			return;
		}

		ReviewProcessPo reviewProcess = reviewProcessDaoService.getBySerialNo(serialNo);
		String reviewProcessId = reviewProcess.getId().toString();
		List<TbIssue> issues = models.stream().map(model -> {
			TbIssue issue = new TbIssue();
			......
		}).collect(Collectors.toList());
	};
	return consumer;
}

这部分说明一下Consumer类的用法。他可以理解为一个消费者,在本例中,Consumer<List<IssuesModel>>的消费对象即为List<IssuesModel>,意思就是需要投入一个List<IssuesModel>供这个consumer使用。从方法的第一行开始,models就代表了投入的List对象,接下来的代码就是对投入对象进行的处理,例如本例中,首先判断了数组是否为空,为空则直接返回。

接下来是代码的完整部分:

@Service
@Transactional(rollbackFor = Exception.class)
public HistoricalIssuesServiceImpl implements HistoricalIssuesService {
	@Autowired
	private TbIssueDaoService tbIssueDaoService;

	@Autowired
	private ReviewProcessDaoService reviewProcessDaoService;

	@Override
	public void importHistoricalIssues(List<MultipartFile> files, String serialNo) throws OPException {
		InputStream inputStream = files.get(0).getInputStream();
		Consumer<List<IssuesModel>> consumer = dataProcess(serialNo);
		MyAnaLysisEventListener<IssuesModel> readListener = ExcelReaderUtils.getReadListener(consumer);
		ExcelReader reader = EasyExcel.read(inputStream, IssueModel.class, readListener).build();
		ReadSheet sheet = EasyExcel.readSheet(0).build();
		reader.read(sheet);
		reader.finish();
	}

	public Consumer<List<IssuesModel>> dataProcess(String serialNo)
		Consumer<List<IssuesModel>> consumer = models -> {
			// 根据业务逻辑进行代码书写
			if (CollectionUtils.isEmpty(models)) {
				return;
			}
	
			ReviewProcessPo reviewProcess = reviewProcessDaoService.getBySerialNo(serialNo);
			String reviewProcessId = reviewProcess.getId().toString();
			List<TbIssue> issues = models.stream().map(model -> {
				TbIssue issue = new TbIssue();
				......
			}).collect(Collectors.toList());
		};
		return consumer;
	}

	private IssueTypeEnum converter(String historicalIssuesType) {
		if (StringUtils.equals(historicalIssueType, "a类")) {
			return IssueTypeEnum.A;
		} else if (StringUtils.equals(historicalIssueType, "b类")) {
			return IssueTypeEnum.B;
		} else if (StringUtils.equals(historicalIssueType, "c类")) {
			return IssueTypeEnum.C;
		} else {
			return IssueTypeEnum.D;
		}
	}
}

其中MyAnalysisEventListener较为复杂,后面细说。较为容易理解的是EasyExcel中的方法,EasyExcelExcel处理接口类,EasyExcel.read()方法的三个参数分别为输入数据的流、承接流的类型类和回调监听器。其中回调监听器MyAnaLysisEventListener继承了AnaLysisEventListener对输入数据进行了处理,实现如下:

public calss ExcellReaderUtils {
	public static<T> MyAnalysisEventListener<T> getReadListener(Consumer<List<T>> consumer, int threshold) {
		return new MyAnalysisEventListener<T>(consumer, threshold):
	}

	public static<T> MyAnalysisEventListener<T> getReadListener(Consumer<List<T>> consumer) {
		return getReadListener(consumer, 2000);

	@Getter
	public static class MyAnalysisEventListener<T> extends AnalysisEventListener<T> {
	
		private final List<T> dataList = new LinkedList<>();
	
		private AtomicInteger count = new AtomicInteger(0);
	
		private Consumer<List<T>> consumer;
	
		private int threshole;
	
		public MyAnalysisEventListener(Consumer<List<T>> consumer, int shreshold) {
			this.consumer = consumer;
			this.threshold = threshole;
		}
	
		@Override
		public void invoke(T data, AnalysisContext analysisContext) {
			dataList.add(data);
			// 到达阈值就处理一次存储的数据
			if (dataList.size() >= threshold) {
				consumer.accept(dataList);
				dataList.clear();
			}
			count.incrementAndGet();
		}
		
		@Override
		public void doAfterAllAnalysed(AnalysisContext analysisContext) {
			if (dataList.size() > 0) {
				consumer.accept(dataList);
			}
		}
	}
}

其中重写了两个方法,invoke()方法定义了数据处理的方式,consumeraccept()方法表示向其中投入一个数据;doAfterAllAnalysed()方法表示在处理完所有数据之后再进行的操作。至此,这就是本篇所有代码的讲解。


以上就是本文的全部内容,如果有疑问或者纰漏欢迎大家评论以及交流!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值