? 深夜,在東莞,7天酒店,打開(kāi)電腦,訪問(wèn)國(guó)內(nèi)最大的同性交友網(wǎng)站。
?? ? ? ? 日常開(kāi)發(fā)中,導(dǎo)出導(dǎo)入場(chǎng)景非常多,尤其是對(duì)于后臺(tái)管理更是一個(gè)列表一個(gè)導(dǎo)出,如果從導(dǎo)出的業(yè)務(wù)中抽離出復(fù)用代碼,專注于邏輯開(kāi)發(fā),對(duì)于開(kāi)發(fā)者而言非常重要。前有使用POI,但作者還是更喜EasyExcel的簡(jiǎn)潔高效不拖沓,所以特意寫(xiě)篇文章記錄下。
# 準(zhǔn)備工作
?? ? ? ? 準(zhǔn)備工作是看文檔了解EasyExcel嗎?不,我們直接上手吧!我發(fā)現(xiàn)最近的業(yè)務(wù)里面,最簡(jiǎn)單的例子已經(jīng)應(yīng)付下來(lái)了!所以準(zhǔn)備工作自然只需導(dǎo)入EasyExcel的jar包,這里我們由于是springboot項(xiàng)目,所以直接使用maven。直接上最新的版本了!pom.xml給它加上:
```java
<dependency>
? ? ? ? ? ? <groupId>com.alibaba</groupId>
? ? ? ? ? ? <artifactId>easyexcel</artifactId>
? ? ? ? ? ? <version>2.2.5</version>
</dependency>
```
# 導(dǎo)出
?? ? ? ? 準(zhǔn)備工作已經(jīng)完成,導(dǎo)出開(kāi)始,首先需要一個(gè)Bean類,導(dǎo)出的字段和Excel文件的字段一樣即可。@Data是用了lombok,@ExcelProperty則包含了Excel首行的名稱和字段所在位置,從0開(kāi)始,不能重復(fù)。
```java
@Data
public class ExportVo {
? @ExcelProperty(value = "名稱", index = 0)
? private String name;
? @ExcelProperty(value = "時(shí)間", index = 1)
? private Date time;
? @NumberFormat("#.##%")
? @ExcelProperty(value = "完成率", index = 2)
? private Float rate;
}
```
?? ? ? ? 接下來(lái)是邏輯實(shí)現(xiàn):
```java
? @PostMapping("/export")
? public void export(@RequestBody ExportDto dto, HttpServletResponse response)
? ? ? ? ? throws IOException {
? ? String fileName = "統(tǒng)計(jì)表";
? ? ExcelUtil.download(response, fileName, ExportVo.class,
? ? ? ? ? ? getExportVoList());
? }
? private List getExportVoList() {
? ? //? 這里主要是獲取List<ExportVo>內(nèi)容,不提供實(shí)現(xiàn)了,需要說(shuō)下注意點(diǎn)。
? ? ? 1、導(dǎo)出列表應(yīng)該有時(shí)間之類的限制(例如最近一個(gè)月),避免導(dǎo)出Excel過(guò)大,過(guò)大Excel一般采用分sheet導(dǎo)出(尤其xls文件,有65535條數(shù)限制)、分文件打包導(dǎo)出,上傳文件服務(wù)器異步導(dǎo)出,不可突破的最大導(dǎo)出(限死5w條)。
? ? ? 2、一來(lái)考慮數(shù)據(jù)庫(kù)查詢和內(nèi)存壓力,二來(lái)分頁(yè)插件可能存在最大頁(yè)數(shù)限制,所以較多條數(shù)時(shí)必須做分頁(yè)查詢,再組合List對(duì)象。
? ? ? 3、一般從數(shù)據(jù)庫(kù)直接查出數(shù)據(jù)不滿足導(dǎo)出字段需要,有必要時(shí)需要做字段轉(zhuǎn)換。
? }
```
?? ? ? 導(dǎo)出的處理類:
```java
public class ExcelUtil {
? public static void download(HttpServletResponse response, String fileName,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Class cls, List dataList)
? ? ? ? ? throws IOException {
? ? response.setContentType("application/vnd.ms-excel");
? ? response.setCharacterEncoding("utf-8");
? ? String fname = URLEncoder.encode(fileName, "utf-8");
? ? response.setHeader("Content-disposition",
? ? ? ? ? ? "attachment;filename=" + fname + ExcelTypeEnum.XLSX.getValue());
? ? LongestMatchColumnWidthStyleStrategy longestMatchColumnWidthStyleStrategy =
? ? ? ? ? ? new LongestMatchColumnWidthStyleStrategy();
? ? EasyExcel.write(response.getOutputStream(), cls)
? ? ? ? ? ? .sheet("sheet1")
? ? ? ? ? ? .registerWriteHandler(longestMatchColumnWidthStyleStrategy)
? ? ? ? ? ? .doWrite(dataList);
? ? response.flushBuffer();
? }
```
?? ? ? ? 啟動(dòng)程序,使用postman試下,直接點(diǎn)Send的話會(huì)返回一堆亂碼,選擇Send and Download則可導(dǎo)出Excel,不過(guò)文件名是URL編碼過(guò)的,這個(gè)文件名編碼問(wèn)題在瀏覽器則不會(huì)存在。

# 導(dǎo)入
?? ? ? ? 導(dǎo)入是為了減少人工錄入大量數(shù)據(jù)的煩惱,挺好的。
```java
? @Autowired
? private ImportService importService;
? @PostMapping("/import")
? public void import(
? ? ? ? ? @RequestParam(value = "file") MultipartFile file,
? ? ? ? ? @Min(1) @RequestParam("type") int type
? ) throws IOException {
? ? ImportQueryDto dto = new ImportQueryDto();
? ? dto.setType(type);
? ? // ImportDto是導(dǎo)入對(duì)應(yīng)類,UploadDataListener是處理類,邏輯處理服務(wù)需要以參數(shù)形式傳入
? ? EasyExcel.read(file.getInputStream(), ImportDto.class,
? ? ? ? ? ? new UploadDataListener(importService)).sheet().doRead();
? }
```
?? ? 導(dǎo)入Excel對(duì)應(yīng)類,ExcelProperty對(duì)應(yīng)導(dǎo)入字段的首部。
```java
@Data
public class ImportDto {
? @ExcelProperty("名稱")
? private String name;
? @ExcelProperty("數(shù)量")
? private Integer num;
}
```
?? ? ? 導(dǎo)入處理:
```java
public class UploadDataListener
? ? ? ? extends AnalysisEventListener<ImportDto> {
? //? 一次導(dǎo)入多少便入庫(kù),避免大量入庫(kù)
? private static final int BATCH_COUNT = 50;
? //? 存放導(dǎo)入列表
? List<ImportDto> list = new ArrayList<>();
? private ImportService importService;
? public UploadDataListener(ImportService importService) {
? ? this.importService = importService;
? }
? @Override
? public void invoke(ImportDto importDto, AnalysisContext analysisContext) {
? ? list.add(importDto);
? ? if (list.size() >= BATCH_COUNT) {
? ? ? saveData();
? ? ? list.clear();
? ? }
? }
? @Override
? public void doAfterAllAnalysed(AnalysisContext analysisContext) {
? ? saveData();
? }
? private void saveData() {
? ? List<ImportModel> importModelList = new ArrayList<>();
? ? for (ImportDto dto : list) {
? ? ? ImportModel model = new ImportModel();
? ? ? model.setName(dto.getName());
? ? ? model.setNum(dto.getNum())
? ? ? importModelList.add(model);
? ? }
? ? importService.saveBatch(importModelList);
? }
}
```
?? ? ? ? 實(shí)際上,導(dǎo)入文件的內(nèi)容是需要校驗(yàn)的,數(shù)據(jù)格式校驗(yàn)等,需要詢問(wèn)產(chǎn)品是否忽略錯(cuò)誤的數(shù)據(jù),只導(dǎo)入正常的數(shù)據(jù),然后怎么友好提示錯(cuò)誤的內(nèi)容,通過(guò)返回一個(gè)包含錯(cuò)誤信息Excel或提示框,更進(jìn)一步的提示,則是提示到哪一個(gè)格子有問(wèn)題。
?? ? ? 睡覺(jué)。