1、Controller層
@PostMapping("upload") @ResponseBody public HashMap<String, Object> upload(@RequestParam(value = "file") MultipartFile file, @RequestParam(value = "ztId") int ztId, @RequestParam(value = "year") int year, @RequestParam(value = "month") int month) throws IOException { //初始化監聽器 ZwmxzListener zwmxzListener = new ZwmxzListener(zwmxzService, ztId, year, month); //解析數據 EasyExcel.read(file.getInputStream(), zwmxzListener).sheet(0).doReadSync(); HashMap<String, Object> hashMap = new HashMap<>(); //獲取校驗錯誤信息,並返回給前端 List<String> errMessage = zwmxzListener.getErrMessage(); if (errMessage.isEmpty()) { hashMap.put("success", true); } else { hashMap.put("success", false); hashMap.put("errMessage", errMessage); } return hashMap; }
2、Listener監聽器
package cn.xxxxxx.xxxxx.xxx.utils; import cn.xxxxxx.xxxxx.xxx.services.ZwmxzService; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; //繼承AnalysisEventListener<Map<Integer, String>> public class ZwmxzListener extends AnalysisEventListener<Map<Integer, String>> { //定義每多少條數據進行數據庫保存 private static final int BATCH_COUNT = 128; private int ztId; private int year; private int month; private ZwmxzService zwmxzService; //建一個errMessage集合保存校驗有問題的結果 private List<String> errMessage; //用list集合保存解析到的結果 private List<Map<Integer, Map<Integer, String>>> list; //重構,把傳來的值賦給對應的屬性 public ZwmxzListener(ZwmxzService zwmxzService, int ztId, int year, int month) { this.ztId = ztId; this.year = year; this.month = month; this.zwmxzService = zwmxzService; list = new ArrayList<>(); errMessage = new ArrayList<>(); } /** * 重寫invokeHeadMap方法,獲去表頭,如果有需要獲取第一行表頭就重寫這個方法,不需要則不需要重寫 * * @param headMap Excel每行解析的數據為Map<Integer, String>類型,Integer是Excel的列索引,String為Excel的單元格值 * @param context context能獲取一些東西,比如context.readRowHolder().getRowIndex()為Excel的行索引,表頭的行索引為0,0之后的都解析成數據 */ @Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { logger.info("解析到一條頭數據:{}, currentRowHolder: {}", headMap.toString(), context.readRowHolder().getRowIndex()); Map<Integer, Map<Integer, String>> map = new HashMap<>(); map.put(context.readRowHolder().getRowIndex(), headMap); list.add(map); } /** * 重寫invoke方法獲得除Excel第一行表頭之后的數據, * 如果Excel第二行也是表頭,那么也會解析到這里,如果不需要就通過判斷context.readRowHolder().getRowIndex()跳過 * * @param data 除了第一行表頭外,數據都會解析到這個方法 * @param context 和上面解釋一樣 */ @Override public void invoke(Map<Integer, String> data, AnalysisContext context) { logger.info("解析到一條數據:{}, currentRowIndex: {}----", data.toString(), context.readRowHolder().getRowIndex()); Map<Integer, Map<Integer, String>> map = new HashMap<>(); map.put(context.readRowHolder().getRowIndex(), data); list.add(map); // 達到BATCH_COUNT了,需要去存儲一次數據庫,防止數據幾萬條數據在內存,容易OOM if (list.size() >= BATCH_COUNT) { saveData(); // 存儲完成清理 list list.clear(); } } /** * 解析到最后會進入這個方法,需要重寫這個doAfterAllAnalysed方法,然后里面調用自己定義好保存方法 * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { // 這里也要保存數據,確保最后遺留的數據也存儲到數據庫 saveData(); logger.info("所有數據解析完成!"); } /** * 加上存儲數據庫 */ private void saveData() { logger.info("{}條數據,開始存儲數據庫!", list.size()); //用errMessage集合保存zwmxzService.save()返回校驗錯誤信息,根據自己需要來寫就行了 errMessage.addAll(zwmxzService.save(list, ztId, year, month, errMessage.size() > 0)); } public int getZtId() { return ztId; } public void setZtId(int ztId) { this.ztId = ztId; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public ZwmxzService getZwmxzService() { return zwmxzService; } public void setZwmxzService(ZwmxzService zwmxzService) { this.zwmxzService = zwmxzService; } public List<String> getErrMessage() { return errMessage; } public void setErrMessage(List<String> errMessage) { this.errMessage = errMessage; } public List<Map<Integer, Map<Integer, String>>> getList() { return list; } public void setList(List<Map<Integer, Map<Integer, String>>> list) { this.list = list; } private final static Logger logger = LoggerFactory.getLogger(ZwmxzListener.class); }
3、ServiceImpl實現類寫導入的數據校驗,根據自己的業務需求來寫校驗規則,當然也可以在監聽器里面就校驗,然后調用ServiceImpl實現類來保存數據
@Transactional(rollbackFor = Exception.class) @Override public List<String> save(List<Map<Integer, Map<Integer, String>>> list, int ztId, int year, int month, boolean hasErr) { logger.info("--start---導入判斷----"); List<String> error = new ArrayList<>(); List<Zwmxz> newZwmxz = new ArrayList<>(); String pattern = "^[-]?\\d+(\\.\\d+)?$"; //遍歷傳過來的數據集合 for (var item : list) { //每一行 for (var row : item.entrySet()) { //獲取行的索引 Integer rowIndex = row.getKey(); //索引為0表示為表頭 if (rowIndex == 0) { String leftYear = row.getValue().get(1); String rightYear = row.getValue().get(2); logger.info("---leftYear={}--------rightYear={}----------", leftYear, rightYear); String left = judgeYear(leftYear, year, "B"); if (left != null) { error.add(left); } String right = judgeYear(rightYear, year, "C"); if (right != null) { error.add(right); } } else { rowIndex++; logger.info("---rowIndex={}--------", rowIndex); Zwmxz it = new Zwmxz(); //遍歷每一行的每一列數據,並校驗 for (var col : row.getValue().entrySet()) { switch (col.getKey()) { case 1: if (col.getValue() == null) { error.add("序號為【" + rowIndex + "】的數據的B列【月份】不可為空!"); } else { if (Pattern.matches(pattern, col.getValue().trim())) { if (Integer.parseInt(col.getValue().trim()) == month) { it.setMonth(month); } else { error.add("序號為【" + rowIndex + "】的數據的B列【月份】與當前期間的月份不一致!"); } } else { error.add("序號為【" + rowIndex + "】的數據的B列【月份】需要是正常的阿拉伯數字月份!"); } } break ......................................................case 16: if (col.getValue() == null) { it.setYe(BigDecimal.ZERO); } else { if (Pattern.matches(pattern, col.getValue().trim())) { it.setYe(new BigDecimal(col.getValue())); } else { it.setYe(BigDecimal.ZERO); } } break; case 17: it.setSdocId(col.getValue()); break; default: break; } } it.setZtId(ztId); it.setYear(year); Date date = new Date(); it.setUpdatedAt(date); it.setCreatedAt(date); newZwmxz.add(it); } } } // 以上數據 若存在一條報錯的數據,則全部導入失敗! if (error.size() > 0 || hasErr) { zwmxzRepository.deleteAllByZtIdAndYearAndMonth(ztId, year, month); } else { for (var it : newZwmxz) { zwmxzRepository.save(it); } } return error; } private String judgeYear(String year1, int year2, String position) { String pattern = "^[0-9]+$"; if (year1 == null) { return "Excel的" + position + "列的表頭年份不可為空!"; } else { String trim = year1.trim(); if (trim.length() >= 4) { String substring = trim.substring(0, 4); logger.info("-----year1:{}", substring); if (Pattern.matches(pattern, substring)) { if (Integer.parseInt(substring) != year2) { return "Excel的" + position + "列的表頭年份與當前期間年份不一致!"; } } else { return "Excel的" + position + "列的表頭年份不規范!"; } } else { return "Excel的" + position + "列的表頭年份不規范!"; } } return null; }
導入的前端參考https://www.cnblogs.com/pzw23/p/12981617.html
注意:
(上面的代碼就不更改了,下面說一下特殊情況)
List<Map<Integer, Map<Integer, String>>> list里面,每次最好第一個Map<Integer, Map<Integer, String>>都是表頭,然后在獲取數據的時候,根據表頭的map來遍歷,根據表頭的key去拿每一行數據的value,判空!因為有些excel行的數據是不跟表頭一一對應的,比如說列數據少了或是多了,比如說表頭map={0='序號',1='編號',2='姓名',3='性別',4='年齡',5='收入'},而數據map可能是{0='1',1='xxx01',2='小武',3='男'},也可能是{0='1',1='xxx01',2='小武',3='男',4='',5=''},還可能是多出的數據{0='1',1='xxx01',2='小武',3='男',4='',5='',6='**asd'},主要看excel是怎么操作的,比如刪除列數據用delete鍵刪除的是第一種,注意這方面的特殊性。