1 根據實體類生成對應的模板
/** * * excel 模板demo 還可以加一些屬性的格式化注解 比如時間 * @author: kuangql@fadada.com * @date: 2020/11/25 15:16 * @description: TODO */ @Data public class DemoExcelEntity { public static final String bigTitle= "填寫須知: \n" + "1.第1、2行為固定結構,不可更改;以下示例行,導入前請先刪除\n" + "2.請嚴格按照填寫規則輸入數據,不合規的數據無法成功導入"; @ExcelProperty(value = {bigTitle,"姓名(必填)"}, index = 0) private String userName; @ExcelProperty(value = {bigTitle,"性別(必填)"}, index = 1) private String userSexName; @ExcelProperty(value = {bigTitle,"手機號碼(必填)"}, index = 2) private String userMobile; @ExcelProperty(value = {bigTitle,"出生年月(必填)"}, index = 3) private String userBirthday; @ExcelProperty(value = {bigTitle,"工作單位(必填)"}, index = 4) private String deptName; @ExcelProperty(value = {bigTitle,"職務(必填)"}, index = 5) private String unitPosition; @ExcelProperty(value = {bigTitle,"干部類別(必填)"}, index = 6) private String leaderTypeName; @ExcelProperty(value = {bigTitle,"用戶狀態(必填)"}, index = 7) private String userStatusName; /** * 每個模板的首行高度, 換行數目+2 乘以15 */ public static int getHeadHeight(){ return (StringUtils.getCharCounts(bigTitle,"\n")+2)*15; } }
2 根據該實體類生成的excel模板樣式如下:

3 解釋下@ExcelProperty這個注解
3.1 在實體屬性上增加這個注解。表示這個屬性是會輸出到excel單元格里,怎么輸出呢,就是由 value 和index 來控制。
3.2 value 存放的值是一個數組。數組元素幾個,表示占用幾行作為表頭。index表示該實體類的屬性在單元格的那一列。
3.3 結合我的demo看,我的value 存放的元素大小為2,所以我的表頭為2行。如果需要出現占據多行的樣式,就在對應的元素位置放相同的內容,就會形成合並單元格。
4
excelUtil 工具類:
/**
* 生成excel模板
*
* @param response
* @param fileName 下載的文件名,
* @param sheetName sheet名
* @param data 導出的數據
* @param model 導出的頭
* @param heardHeight 頭行高
*/
public static void createTemplate(HttpServletResponse response, String fileName,
String sheetName, List<? extends Object> data,
Class<?> model, int heardHeight) {
HorizontalCellStyleStrategy horizontalCellStyleStrategy = setMyCellStyle();
EasyExcel.write(getOutputStream(fileName, response, ExcelTypeEnum.XLSX), model).
excelType(ExcelTypeEnum.XLSX).sheet(sheetName)
.registerWriteHandler(new TemplateCellWriteHandler(heardHeight))
.registerWriteHandler(horizontalCellStyleStrategy)
.doWrite(data);
}
/**
* 創建我的cell 策略
*
* @return
*/
public static HorizontalCellStyleStrategy setMyCellStyle() {
// 頭的策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 設置表頭居中對齊
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 顏色
headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short) 10);
// 字體
headWriteCellStyle.setWriteFont(headWriteFont);
headWriteCellStyle.setWrapped(true);
// 內容的策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 設置內容靠中對齊
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 這個策略是 頭是頭的樣式 內容是內容的樣式 其他的策略可以自己實現
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
// 這里 需要指定寫用哪個class去寫,然后寫到第一個sheet,名字為模板 然后文件流會自動關閉
return horizontalCellStyleStrategy;
}
4.1 controller相關代碼:
@ApiOperation(value = "下載模板文件測試-----該方法是測試使用")
@GetMapping("/template2/{type}")
public void template2(@PathVariable("type") String type, HttpServletResponse response) {
String fileName = "導入模板";
String sheetName = "模板";
try {
// List<DemoExcelEntity> sysUserImportExcelList = getSysUserImportExcel();
//輸出文件流
//后期根據傳參處理獲取對應的模板實體類 如果需要導出數據,將nul改為對應List<DemoExcelEntity> 傳進去
ExcelUtils.createTemplate(response, fileName, sheetName, null,
DemoExcelEntity.class, DemoExcelEntity.getHeadHeight());
} catch (Exception e) {
e.printStackTrace();
}
}
4.3 讀取文件數據:
1 我的文件數據截圖:

2 表頭占據了2行,所以讀取數據的時候需要跳過2行表頭。 2行表頭等於@ExcelProperty里的value 元素個數
代碼如下:
/** * 讀取 Excel(第一個 sheet) 指定行開始讀取 * @param excel 文件 * @param rowType 模板實體類 * @param header 指定不讀取的表頭行數, * @param <T> * @return 集合數據 * @throws ExcelException */ public static <T> List<T> readFirstSheetExcel(MultipartFile excel, Class<T> rowType,int header) throws ExcelException { ExcelReader reader = getReader(excel, header); if (reader == null) { return new ArrayList<>(); } return readExcel(reader, rowType, 0); }
3 如果在導入數據的時候,需要將導入的數據的錯誤信息追加到后一列,可以新建一個實體類
/** * 用於輸出 導入的時候,錯誤信息輸出 * @author: kuangql@fadada.com * @date: 2020/11/27 13:37 * @description: TODO */ @Data public class DemoExcelEntityError extends DemoExcelEntity { /** * 說明: 該對象只針對導入的時候,在用戶上傳的文件后面追加一列錯誤信息描述 */ @ExcelProperty(value = {bigTitle,"錯誤描述"}, index = 8) private String errorMessage; }
// 該方法是測試使用 @ApiOperation(value = "上傳文件數據------測試使用 返回錯誤信息 ") @PostMapping("/upload") public void template2(MultipartFile multipartFile, HttpServletResponse response) { List<DemoExcelEntity> demoExcelEntities = ExcelUtils.readFirstSheetExcel(multipartFile, DemoExcelEntity.class, 2); List<DemoExcelEntityError> demoExcelEntityErrorList = new ArrayList<>(); demoExcelEntities.forEach( demoExcelEntity -> { System.out.println(demoExcelEntity.toString()); DemoExcelEntityError demoExcelEntityError = new DemoExcelEntityError(); BeanUtils.copyProperties(demoExcelEntity, demoExcelEntityError); demoExcelEntityError.setErrorMessage("我的錯誤信息描述"); demoExcelEntityErrorList.add(demoExcelEntityError); } ); ExcelUtils.writeExcel(response, demoExcelEntityErrorList, "demo", "1", ExcelTypeEnum.XLSX); }
效果如下:

好了,基本描述完畢。如下是所有相關的代碼:
/**
* excel導入導出工具類
* BeanCopy ExcelException 屬於自定義數據,屬於可自定義依賴
*
* @version 2.0.0
* @author:
* @date: 2020/10/27
*/
@NoArgsConstructor
public class ExcelUtils {
/**
* 讀取 Excel(多個 sheet)
* @param reader 讀取的excel
* @param rowModel excel模板實體類
* @param sheetCount sheet
* @param <T>
* @return
*/
public static <T> List<T> readExcel(ExcelReader reader, Class<T> rowModel, int sheetCount) {
if (reader == null) {
return new ArrayList<>();
}
List<ReadSheet> readSheetList = new ArrayList<>();
ExcelListener<T> excelListener = new ExcelListener<>();
ReadSheet readSheet = EasyExcel.readSheet(sheetCount)
.head(rowModel)
.registerReadListener(excelListener)
.build();
readSheetList.add(readSheet);
reader.read(readSheetList);
return getExtendsBeanList(excelListener.getDataList(), rowModel);
}
/**
* 讀取 Excel(多個 sheet)
* 將多sheet合並成一個list數據集,通過自定義ExcelReader繼承AnalysisEventListener
* 重寫invoke doAfterAllAnalysed方法
* getExtendsBeanList 主要是做Bean的屬性拷貝 ,可以通過ExcelReader中添加的數據集直接獲取
*
* @param excel 文件
* @param header 不讀取表頭數目(表頭2行,讀取數據時候從第三行開始,讀取幾行看對應的模板實體類)
* @param sheetNo
* @param rowModel excel模板實體類
* @return
* @throws ExcelException
*/
private static List[] readExcel(MultipartFile excel,int header, Integer sheetNo, Class<?>[] rowModel) throws ExcelException {
ExcelReader reader = getReader(excel,header);
if (reader == null) {
return new ArrayList[rowModel.length];
}
List[] result = new ArrayList[rowModel.length];
for (int sheetCount = 0; sheetCount < rowModel.length; sheetCount++) {
if (sheetNo != null && sheetNo != sheetCount) {
continue;
}
result[sheetCount].addAll(readExcel(reader, rowModel[sheetCount], sheetCount));
}
return result;
}
/**
* 讀取 Excel(多個 sheet)
* 將多sheet合並成一個list數據集,通過自定義ExcelReader繼承AnalysisEventListener
* 重寫invoke doAfterAllAnalysed方法
* getExtendsBeanList 主要是做Bean的屬性拷貝 ,可以通過ExcelReader中添加的數據集直接獲取
* @param excel
* @param header 不讀取表頭數目(表頭2行,讀取數據時候從第三行開始,讀取幾行看對應的模板實體類)
* @param rowModel
* @return
* @throws ExcelException
*/
public static List[] readExcel(MultipartFile excel, int header ,Class<?>... rowModel) throws ExcelException {
ExcelReader reader = getReader(excel,header);
if (reader == null) {
return new ArrayList[rowModel.length];
}
List[] result = new ArrayList[rowModel.length];
for (int sheetCount = 0; sheetCount < rowModel.length; sheetCount++) {
result[sheetCount] = new ArrayList<>(readExcel(reader, rowModel[sheetCount], sheetCount));
}
return result;
}
/**
* 讀取 Excel(單個 sheet)
* 將多sheet合並成一個list數據集,通過自定義ExcelReader繼承AnalysisEventListener
* 重寫invoke doAfterAllAnalysed方法
* getExtendsBeanList 主要是做Bean的屬性拷貝 ,可以通過ExcelReader中添加的數據集直接獲取
*/
/*
public static <T> List<T> readFirstSheetExcel(MultipartFile excel, Class<T> rowType) throws ExcelException {
ExcelReader reader = getReader(excel);
if (reader == null) {
return new ArrayList<>();
}
return readExcel(reader, rowType, 0);
}
*/
/**
* 讀取某個 sheet 的 Excel
*
* @param excel 文件
* @param rowModel 實體類映射
* @param sheetNo sheet 的序號 從1開始
* @return Excel 數據 list
*/
public static <T> List readExcel(MultipartFile excel, Class<T> rowModel, int sheetNo) throws ExcelException {
Class[] classes = {rowModel};
return ExcelUtils.readExcel(excel, sheetNo, classes)[0];
}
/**
* 導出 Excel :一個 sheet,帶表頭
* 自定義WriterHandler 可以定制行列數據進行靈活化操作
*
* @param response HttpServletResponse
* @param list 數據 list
* @param fileName 導出的文件名
* @param sheetName 導入文件的 sheet 名
*/
public static <T> void writeExcel(HttpServletResponse response, List<T> list,
String fileName, String sheetName, ExcelTypeEnum excelTypeEnum) throws ExcelException {
if (sheetName == null || "".equals(sheetName)) {
sheetName = "sheet1";
}
if (CollectionUtils.isEmpty(list)) {
return;
}
EasyExcel.write(getOutputStream(fileName, response, excelTypeEnum), list.get(0).getClass()).sheet(sheetName).doWrite(list);
}
/**
* 導出 Excel :一個 sheet,帶表頭
* 自定義WriterHandler 可以定制行列數據進行靈活化操作
*
* @param response HttpServletResponse
* @param list 數據 list
* @param fileName 導出的文件名
*/
public static <T> void writeExcel(HttpServletResponse response, List<T> list,
String fileName, ExcelTypeEnum excelTypeEnum) throws ExcelException {
if (CollectionUtils.isEmpty(list)) {
return;
}
String sheetName = list.get(0).getClass().getAnnotation(SheetName.class).value();
sheetName = StrUtil.isNotBlank(sheetName) ? sheetName : "sheet1";
EasyExcel.write(getOutputStream(fileName, response, excelTypeEnum), list.get(0).getClass()).sheet(sheetName).doWrite(list);
}
/**
* 導出 Excel :一個 sheet,帶表頭
* 自定義WriterHandler 可以定制行列數據進行靈活化操作
*
* @param response HttpServletResponse
* @param fileName 導出的文件名
*/
public static void writeExcel(HttpServletResponse response, String fileName,
ExcelTypeEnum excelTypeEnum, List... lists) throws ExcelException {
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(getOutputStream(fileName, response, excelTypeEnum)).build();
for (int count = 0; count < lists.length; count++) {
if (CollectionUtils.isEmpty(lists[count])) {
continue;
}
String sheetName = lists[count].get(0).getClass().getAnnotation(SheetName.class).value();
sheetName = StrUtil.isNotBlank(sheetName) ? sheetName : "sheet" + (count + 1);
WriteSheet writeSheet = EasyExcel.writerSheet(count, sheetName)
.head(lists[count].get(0).getClass())
.build();
excelWriter.write(lists[count], writeSheet);
}
} finally {
if (excelWriter != null) {
excelWriter.finish();
}
}
}
/**
* 導出文件時為Writer生成OutputStream
*/
private static OutputStream getOutputStream(String fileName, HttpServletResponse response, ExcelTypeEnum excelTypeEnum) throws ExcelException {
//創建本地文件
String filePath = fileName + excelTypeEnum.getValue();
try {
fileName = new String(filePath.getBytes(), StandardCharsets.ISO_8859_1);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType("application/vnd.ms-excel");
response.addHeader("Content-Disposition", "filename=" + fileName);
return response.getOutputStream();
} catch (IOException e) {
throw new ExcelException(ResultCode.File.CREATE_FILE_FAILED);
}
}
/**
* 返回 ExcelReader
*
* @param excel 需要解析的 Excel 文件
*/
/*
public static ExcelReader getReader(MultipartFile excel) throws ExcelException {
String fileName = excel.getOriginalFilename();
if (fileName == null) {
throw new ExcelException(ResultCode.File.FILE_FORMAT_ERROR);
}
if (!fileName.toLowerCase().endsWith(ExcelTypeEnum.XLS.getValue()) && !fileName.toLowerCase().endsWith(ExcelTypeEnum.XLSX.getValue())) {
throw new ExcelException(ResultCode.File.FILE_FORMAT_ERROR);
}
InputStream inputStream;
try {
inputStream = excel.getInputStream();
return EasyExcel.read(inputStream).build();
} catch (IOException e) {
//do something
}
return null;
}
*/
/**
* 利用BeanCopy轉換list
*/
public static <T> List<T> getExtendsBeanList(List<?> list, Class<T> typeClazz) {
return BeanConvertUtils.copyList(list, typeClazz);
}
/**
* 生成excel模板
*
* @param response
* @param fileName 下載的文件名,
* @param sheetName sheet名
* @param data 導出的數據
* @param model 導出的頭
* @param heardHeight 頭行高
*/
public static void createTemplate(HttpServletResponse response, String fileName,
String sheetName, List<? extends Object> data,
Class<?> model, int heardHeight) {
HorizontalCellStyleStrategy horizontalCellStyleStrategy = setMyCellStyle();
EasyExcel.write(getOutputStream(fileName, response, ExcelTypeEnum.XLSX), model).
excelType(ExcelTypeEnum.XLSX).sheet(sheetName)
.registerWriteHandler(new TemplateCellWriteHandler(heardHeight))
.registerWriteHandler(horizontalCellStyleStrategy)
.doWrite(data);
}
/**
* 創建我的cell 策略
*
* @return
*/
public static HorizontalCellStyleStrategy setMyCellStyle() {
// 頭的策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 設置表頭居中對齊
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 顏色
headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short) 10);
// 字體
headWriteCellStyle.setWriteFont(headWriteFont);
headWriteCellStyle.setWrapped(true);
// 內容的策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 設置內容靠中對齊
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
// 這個策略是 頭是頭的樣式 內容是內容的樣式 其他的策略可以自己實現
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
// 這里 需要指定寫用哪個class去寫,然后寫到第一個sheet,名字為模板 然后文件流會自動關閉
return horizontalCellStyleStrategy;
}
/**
* 讀取 Excel(第一個 sheet) 指定行開始讀取
* @param excel 文件
* @param rowType 模板實體類
* @param header 指定不讀取的表頭行數,
* @param <T>
* @return 集合數據
* @throws ExcelException
*/
public static <T> List<T> readFirstSheetExcel(MultipartFile excel, Class<T> rowType,int header) throws ExcelException {
ExcelReader reader = getReader(excel, header);
if (reader == null) {
return new ArrayList<>();
}
return readExcel(reader, rowType, 0);
}
/**
*
* @param excel 需要解析的 Excel 文件
* @param header 指定不讀取表頭行數,
* @return
* @throws ExcelException
*/
public static ExcelReader getReader(MultipartFile excel,int header) throws ExcelException {
String fileName = excel.getOriginalFilename();
if (fileName == null) {
throw new ExcelException(ResultCode.File.FILE_FORMAT_ERROR);
}
if (!fileName.toLowerCase().endsWith(ExcelTypeEnum.XLS.getValue()) && !fileName.toLowerCase().endsWith(ExcelTypeEnum.XLSX.getValue())) {
throw new ExcelException(ResultCode.File.FILE_FORMAT_ERROR);
}
InputStream inputStream;
try {
inputStream = excel.getInputStream();
return EasyExcel.read(inputStream).
headRowNumber(header).
build();
} catch (IOException e) {
//do something
}
return null;
}
}
2 實體類:
/**
*
* excel 模板demo 還可以加一些屬性的格式化注解 比如時間
* @author: kuangql@fadada.com
* @date: 2020/11/25 15:16
* @description: TODO
*/
@Data
public class DemoExcelEntity {
public static final String bigTitle= "填寫須知: \n" +
"1.第1、2行為固定結構,不可更改;以下示例行,導入前請先刪除\n" +
"2.請嚴格按照填寫規則輸入數據,不合規的數據無法成功導入";
@ExcelProperty(value = {bigTitle,"姓名(必填)"}, index = 0)
private String userName;
@ExcelProperty(value = {bigTitle,"性別(必填)"}, index = 1)
private String userSexName;
@ExcelProperty(value = {bigTitle,"手機號碼(必填)"}, index = 2)
private String userMobile;
@ExcelProperty(value = {bigTitle,"出生年月(必填)"}, index = 3)
private String userBirthday;
@ExcelProperty(value = {bigTitle,"工作單位(必填)"}, index = 4)
private String deptName;
@ExcelProperty(value = {bigTitle,"職務(必填)"}, index = 5)
private String unitPosition;
@ExcelProperty(value = {bigTitle,"干部類別(必填)"}, index = 6)
private String leaderTypeName;
@ExcelProperty(value = {bigTitle,"用戶狀態(必填)"}, index = 7)
private String userStatusName;
/**
* 每個模板的首行高度, 換行數目+2 乘以15
*/
public static int getHeadHeight(){
return (StringUtils.getCharCounts(bigTitle,"\n")+2)*15;
}
}
/** * 用於輸出 導入的時候,錯誤信息輸出 * @author: kuangql@fadada.com * @date: 2020/11/27 13:37 * @description: TODO */ @Data public class DemoExcelEntityError extends DemoExcelEntity { /** * 說明: 該對象只針對導入的時候,在用戶上傳的文件后面追加一列錯誤信息描述 */ @ExcelProperty(value = {bigTitle,"錯誤描述"}, index = 8) private String errorMessage; }
/**
* excel通用單元格格式類
* @description:
* @author:
* @time: 2020/9/20 9:54
*/
public class TemplateCellWriteHandler implements CellWriteHandler {
/**
* 模板的首行行高 ,通過構造器注入
*/
private int height;
public TemplateCellWriteHandler(int height) {
this.height = height;
}
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
CellStyle cellStyle = workbook.createCellStyle();
Font font = workbook.createFont();
if (cell.getRowIndex() == 0) {
font.setFontHeightInPoints((short) 10);
font.setFontName("宋體");
//加粗
font.setBold(true);
cellStyle.setFont(font);
cellStyle.setAlignment(HorizontalAlignment.CENTER);
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//設置 自動換行
cellStyle.setWrapText(true);
Row row = cell.getRow();
row.setHeightInPoints(height);
} if (cell.getRowIndex() == 1) {
font.setFontHeightInPoints((short) 11);
font.setFontName("宋體");
font.setBold(true);
cellStyle.setFont(font);
cellStyle.setAlignment(HorizontalAlignment.LEFT);
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
//設置 自動換行
cellStyle.setWrapText(true);
Row row = cell.getRow();
row.setHeightInPoints(26);
}
cell.setCellStyle(cellStyle);
}
}
