一、 前言
????????本篇是EasyExcel快速入門(mén)知識(shí),講解如何讀取Excel文件,對(duì)Excel中錯(cuò)誤信息如空字符、必填項(xiàng)為空、表格格式校驗(yàn)做到處理?,并給出了實(shí)際項(xiàng)目中示例代碼;為什么要使用easyexcel;原因是相比于poi,easyexcel更加輕量級(jí),讀取寫(xiě)入API方便,并且在工作中占用內(nèi)存較?。?/p>
????????官方描述如下:
????????Java解析、生成Excel比較有名的框架有Apache poi、jxl。但他們都存在一個(gè)嚴(yán)重的問(wèn)題就是非常的耗內(nèi)存,poi有一套SAX模式的API可以一定程度的解決一些內(nèi)存溢出的問(wèn)題,但POI還是有一些缺陷,比如07版Excel解壓縮以及解壓后存儲(chǔ)都是在內(nèi)存中完成的,內(nèi)存消耗依然很大。easyexcel重寫(xiě)了poi對(duì)07版Excel的解析,能夠原本一個(gè)3M的excel用POI sax依然需要100M左右內(nèi)存降低到幾M,并且再大的excel不會(huì)出現(xiàn)內(nèi)存溢出,03版依賴(lài)POI的sax模式。在上層做了模型轉(zhuǎn)換的封裝,讓使用者更加簡(jiǎn)單方便。
二、前端上傳Excel文件
????????關(guān)于前端上傳Excel文件分為兩種情況:
????????(1)前后端分離情況下(前端使用Vue)實(shí)現(xiàn)文件上傳。
????????????????該情況下直接借助elementUI提供的上傳組件可以很好的實(shí)現(xiàn)上傳文件,包括對(duì)上傳文件前后均有回調(diào)函數(shù),可以對(duì)各個(gè)時(shí)間節(jié)點(diǎn)進(jìn)行操作,對(duì)文件處理非常便捷,我這里就不多贅述。
? ? ? ? (2)前后端未分離情況下(springboot模板)實(shí)現(xiàn)文件上傳。
? ? ? ? ? ? ? ? 該情況下可以使用input標(biāo)簽的type=“file”來(lái)實(shí)現(xiàn)文件選取,但是使用它主要有兩點(diǎn)問(wèn)題,相信大家在做項(xiàng)目時(shí)也會(huì)遇到:1、默認(rèn)樣式很丑,修改后點(diǎn)擊無(wú)法觸發(fā)文件選擇的彈窗。2、如何取上傳的file?直接獲取input.value是fakePath路徑(瀏覽器處于安全考慮修改為虛擬路徑),因此不能通過(guò)此路徑訪問(wèn)到文件,解析路徑也比較麻煩。
????????(3)模板下載。
? ? ? ? ? ? ? ? 模板存在D盤(pán)img->download文件夾下,該路徑通過(guò)nginx配置(8091為我的nginx地址),訪問(wèn)該路徑實(shí)現(xiàn)文件下載,通過(guò)_blank實(shí)現(xiàn)不跳轉(zhuǎn)頁(yè)面。
????????????????下面代碼為解決上述(2)(3)兩個(gè)問(wèn)題:
<body>
<div>
<a class="mini-button" iconCls="icon-download" onclick="downloadFile()">導(dǎo)入模板下載</a>
<a class="mini-button" onclick="showInput()">學(xué)生數(shù)據(jù)導(dǎo)入</a>
<input id="articleImageFile" type="file" style="display: none;" accept=".xls,.xlsx" onchange="showFileName()"/>
<span id="showFileName" class="mini-button-info"></span>
</div>
</body>
<script>
<--導(dǎo)入模板-->
function downloadFile(){
const url = "http://192.168.3.96:8091/download/學(xué)生數(shù)據(jù)導(dǎo)入模板.xls";
// 使用 window.open() 方法發(fā)起 GET 請(qǐng)求,跳轉(zhuǎn)到下載頁(yè)面
window.open(url, '_blank')
}
<--點(diǎn)擊button后通過(guò)js實(shí)現(xiàn)點(diǎn)擊上傳文件input框-->
function showInput(){
deleteTemp();//刪除臨時(shí)表
$("#articleImageFile").click();
}
<--展示文件名稱(chēng)-->
function showFileName(){
var file = $("#articleImageFile")[0].files[0];
if(file == undefined || file == null){
$("#showFileName").text("")
return;
}
$("#showFileName").text(name.name)
showTips("文件上傳成功,請(qǐng)查看導(dǎo)入數(shù)據(jù)后確認(rèn)導(dǎo)入!");
}
<--提交之前可以借助以下代碼實(shí)現(xiàn)文件類(lèi)型判斷,具體提交代碼不再贅述,主要就是發(fā)送ajax請(qǐng)求,將file傳遞到后端-->
<--
if(file.name.substring(file.name.lastIndexOf(".")) !== '.xls' && file.name.substring(file.name.lastIndexOf(".")) !== '.xlsx'){
showTips("只能上傳.xls或.xlsx文件")
return;
}
-->
</script>
三、后端使用easyExcel解析文件
????????(1)準(zhǔn)備與Excel文件數(shù)據(jù)相同屬性的dto,需要注意的是每一列對(duì)應(yīng)的值。從0開(kāi)始,注意使用
注解。
public class ExcelWgsUserInfoFileDto extends BaseDto {
@ExcelProperty(index = 0)
private String index;
@ExcelProperty(index = 1)
private String id;
@ExcelProperty(index = 2)
private String name;
//get、set方法
}
????????(2)controller層接收文件(可以接收多個(gè))
/**
* 導(dǎo)入Excel信息
*/
@ApiOperation("導(dǎo)入Excel表")
@RequestMapping(value = "/toexcel",method = RequestMethod.POST)
public APIResponse toexcel(@RequestParam("file") MultipartFile file) throws Exception{
return wgsUserInfoService.loadScoreInfo(file);
}
????????(3)service層(最關(guān)鍵)
????????????????定義的變量解釋?zhuān)?/span>
????????????????????????BATCH_COUNT:集合最大緩存量,當(dāng)?shù)竭_(dá)最大緩存調(diào)用一次存方法。
? ? ? ? ? ? ? ? ? ? ? ? temp:對(duì)調(diào)用了幾次存函數(shù)進(jìn)行計(jì)數(shù),在判斷表頭、計(jì)算錯(cuò)誤行數(shù)時(shí)使用。
? ? ? ? ? ? ? ? ? ? ? ? isError:由于存方法中return只會(huì)跳出該方法,不會(huì)跳出整個(gè)read方法,使用它實(shí)現(xiàn)當(dāng)出現(xiàn)錯(cuò)誤后下一次1000行或者讀取完成不再調(diào)用存方法。
????????????????該代碼較長(zhǎng),我稍微做一下解釋?zhuān)浩鋵?shí)使用easyExcel解析文件,最重要的是要弄清楚整個(gè)讀取文件的流程是什么。在EasyExcel.read(參數(shù)1,參數(shù)2,參數(shù)3)中,最重要的是第三個(gè)參數(shù)讀的監(jiān)聽(tīng)器,該參數(shù)中重寫(xiě)的兩個(gè)方法:
????????1、invoke()每讀取一行調(diào)用一次,它的第一個(gè)參數(shù)ExcelWgsUserInfoDto就是我們最初定義的dto,是文件中一行的數(shù)據(jù),做的操作為將該行數(shù)據(jù)加入到集合中,當(dāng)?shù)竭_(dá)集合緩沖區(qū)最大值1000時(shí)調(diào)用存數(shù)據(jù)的方法。
????????2、doAfterAllAnalysed()該方法是當(dāng)Excel文件全部讀取完成后會(huì)調(diào)用一次存數(shù)據(jù)的方法。
? ? ? ? 以上兩個(gè)重寫(xiě)的方法主要作用是對(duì)Excel文件進(jìn)行操作,拿取數(shù)據(jù),這兩個(gè)方法照著寫(xiě)即可,但是對(duì)于我們來(lái)說(shuō)業(yè)務(wù)處理同樣重要,在saveData中主要做的是業(yè)務(wù)處理(判斷表格格式、存入數(shù)據(jù)庫(kù)、是否有必填項(xiàng)為空等),存入數(shù)據(jù)庫(kù)操作不多贅述,下面講一下代碼的大概思路:
? ? ? ? 進(jìn)入該方法會(huì)對(duì)存入集合的數(shù)據(jù)進(jìn)行遍歷,取數(shù)據(jù)存入數(shù)據(jù)庫(kù),在存數(shù)據(jù)之前便是判斷時(shí)間。首先進(jìn)行的是表頭格式的判斷,根據(jù)Excel模板文件第幾行為模板文字進(jìn)行判斷,由于我的是第二行,所以有if(temp<1&&i<2)的判斷,對(duì)Excel每行必填項(xiàng)進(jìn)行if判斷,若為null,返回提示,行數(shù)根據(jù)temp和i計(jì)算得來(lái)。
??????????Excel文件格式如下:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-754522.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-754522.html
@Transactional
@Override
public APIResponse<String> loadScoreInfo(MultipartFile file) throws IOException {
APIResponse<String> result = new APIResponse<>();
try {
/**
* 構(gòu)建一個(gè)讀的工作簿對(duì)象
* @param file
* 第一個(gè)參數(shù):文件輸入流
* @param head
* 第二個(gè)參數(shù):文件中每一行對(duì)應(yīng)的實(shí)體類(lèi)
* @param ReadListener
* 第三個(gè)參數(shù):讀的監(jiān)聽(tīng)器,每讀一行內(nèi)容,都會(huì)調(diào)用一次該對(duì)象的invoke方法,在invoke可以操作使用讀到的數(shù)據(jù)
*/
EasyExcel.read(file.getInputStream(), ExcelWgsUserInfoFileDto.class, new ReadListener<ExcelWgsUserInfoFileDto>() {
/**
* 單次緩存的數(shù)據(jù)量
*/
public static final int BATCH_COUNT = 1000;
//判斷執(zhí)行了幾次saveData
int temp = 0;
boolean isError = false;
/**
*臨時(shí)存儲(chǔ)
*/
private List<ExcelWgsUserInfoFileDto> cacheDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//導(dǎo)入數(shù)據(jù)庫(kù)的明細(xì)表list
private List<WgsUserInfoTempEntity> wgsUserInfoTempList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
/**
* 每第一行調(diào)一次的方法
* @param data
* @param analysisContext
*/
@Override
public void invoke(ExcelWgsUserInfoFileDto data, AnalysisContext analysisContext) {
cacheDataList.add(data);
if (cacheDataList.size() >= BATCH_COUNT && !isError) {
saveData();
temp++;
// 存儲(chǔ)完成清理 list
cacheDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 讀取完整個(gè)文檔后調(diào)用一次
* @param analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
if(!isError){
saveData();
}
temp = 0;
}
/**
* 存儲(chǔ)數(shù)據(jù)庫(kù)
*/
private void saveData() {
System.out.println("進(jìn)入存儲(chǔ)文件??!");
wgsUserInfoTempList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
for (int i = 0; i < cacheDataList.size(); i++) {
//取出每一行數(shù)據(jù)
ExcelWgsUserInfoFileDto data = cacheDataList.get(i);
//表頭判斷
if ( i == 1 && !"學(xué)號(hào)".equals(cacheDataList.get(1).getId()) && !"姓名".equals(cacheDataList.get(1).getName()) &&
!"性別".equals(cacheDataList.get(1).getGender()) && !"手機(jī)號(hào)碼".equals(cacheDataList.get(1).getMobile()) &&
!"身份證號(hào)".equals(cacheDataList.get(1).getIdCard()) && temp < 1 ) {
System.out.println("進(jìn)入格式檢查");
result.setMsg("請(qǐng)檢查表格格式是否符合規(guī)范!");
result.setMsgCode(201);
isError= true;
return;
}
//因?yàn)槲业谋眍^有兩行
if (temp < 1 && i < 2) {
continue;
}
if(isNull(data)){
result.setMsg("第"+(temp*BATCH_COUNT+i+1)+"行必填項(xiàng)為空!");
result.setMsgCode(201);
isError= true;
return;
}
WgsUserInfoTempEntity wgsUserInfoTempEntity = new WgsUserInfoTempEntity();
//數(shù)據(jù)準(zhǔn)備,每一行的數(shù)據(jù)存入對(duì)應(yīng)entity中
wgsUserInfoTempEntity.setId(data.getId());
wgsUserInfoTempEntity.setBh(data.getId());
wgsUserInfoTempList.add(wgsUserInfoTempEntity);
}
if (wgsUserInfoTempList.size() == 0) {
System.out.println("Excel表中未讀取到數(shù)據(jù)");
result.setMsg("文件數(shù)據(jù)異常!");
result.setMsgCode(201);
return;
}
List<WgsUserInfoTempEntity> wgsUserInfoTempEntities = wgsUserInfoTempRepository.saveAll(wgsUserInfoTempList);
if (wgsUserInfoTempEntities.size() > 0) {
logger.info("武工商UserInfo存入臨時(shí)表數(shù)據(jù)成功");
result.setMsg("文件導(dǎo)入成功");
result.setMsgCode(200);
} else {
result.setMsg("文件導(dǎo)入失敗");
result.setMsgCode(201);
}
}
public Boolean isNull(ExcelWgsUserInfoFileDto excelWgsUserInfoFileDto){
if(excelWgsUserInfoFileDto.getId() == null || excelWgsUserInfoFileDto.getId().isEmpty()){
return true;
}
return false;
}
}).sheet().headRowNumber(0).doRead();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
到了這里,關(guān)于后端:使用easyExcel實(shí)現(xiàn)解析Excel文件讀取數(shù)據(jù)。前端:Excel模板下載、前端上傳文件的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!