添加依賴
<!-- easy poi --> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>4.1.0</version> </dependency> <!-- JSR 303 規范驗證包 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.2.4.Final</version> </dependency>
工具類

import org.apache.commons.lang3.StringUtils; import org.apache.poi.hssf.usermodel.HSSFClientAnchor; import org.apache.poi.hssf.usermodel.HSSFRichTextString; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFClientAnchor; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import java.lang.reflect.Field; import java.util.*; /** * Easy Poi Excel批注工具類 * * @Author:chenyanbin */ public class EasyPoiExcelCommentUtil { private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); /** * 添加批注 * * @param workbook 工作簿 * @param titleRowsIndex 標題的行索引,從0計數 * @param commentMap 批注map,key=列索引,從0計數;value=批注值 */ public static void buildComment(Workbook workbook, int titleRowsIndex, Map<Integer, String> commentMap) { Sheet sheet = workbook.getSheetAt(0); //創建一個圖畫工具 Drawing<?> drawing = sheet.createDrawingPatriarch(); Row row = sheet.getRow(titleRowsIndex); if (!commentMap.isEmpty()) { for (Map.Entry<Integer, String> entry : commentMap.entrySet()) { Cell cell = row.getCell(entry.getKey()); //創建批注 Comment comment = drawing.createCellComment(newClientAnchor(workbook)); //輸入批注信息 comment.setString(newRichTextString(workbook, entry.getValue())); //將批注添加到單元格對象中 cell.setCellComment(comment); // //設置單元格背景顏色 // CellStyle cellStyle = workbook.createCellStyle(); // //設置顏色 // cellStyle.setFillForegroundColor(IndexedColors.BLACK1.getIndex()); // //設置實心填充 // cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); // cell.setCellStyle(cellStyle); } } } /** * 添加批注 * * @param workbook 工作簿 * @param errorInfoList 批注錯誤集合 */ public static void buildComment(Workbook workbook, List<ExcelErrorInfoVo> errorInfoList) { Sheet sheet = workbook.getSheetAt(0); //創建一個圖畫工具 Drawing<?> drawing = sheet.createDrawingPatriarch(); for (ExcelErrorInfoVo vo : errorInfoList) { Row row = sheet.getRow(vo.getRowIndex()); if (StringUtils.isNotBlank(vo.getReasonText())) { Cell cell = row.getCell(vo.getCellIndex()); //創建批注 Comment comment = drawing.createCellComment(newClientAnchor(workbook)); //輸入批注信息 comment.setString(newRichTextString(workbook, vo.getReasonText())); //將批注添加到單元格對象中 cell.setCellComment(comment); //設置單元格背景顏色 CellStyle cellStyle = workbook.createCellStyle(); //設置顏色 cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); //設置實心填充 cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); cell.setCellStyle(cellStyle); } } } /** * 校驗Excel數據 * * @param obj Excel中當前行的數據對象 * @param clz Excel中當前行的數據對象的類 * @param rowIndex 該條記錄對應的行索引 * @return */ public static <T> List<ExcelErrorInfoVo> checkExcelData(T obj, Class<?> clz, int rowIndex) { List<ExcelErrorInfoVo> errorInfoList = new ArrayList<>(); Set<ConstraintViolation<T>> cvSet = validator.validate(obj); Field f = null; for (ConstraintViolation<T> cv : cvSet) { try { f = clz.getDeclaredField(cv.getPropertyPath().toString()); f.setAccessible(true); EasyPoiCellAnnotation annotation = f.getAnnotation(EasyPoiCellAnnotation.class); if (annotation == null) { continue; } int cellIndex = annotation.cellIndex(); ExcelErrorInfoVo vo = new ExcelErrorInfoVo(); vo.setRowIndex(rowIndex); vo.setCellIndex(cellIndex); vo.setReasonText(cv.getMessage()); errorInfoList.add(vo); } catch (NoSuchFieldException e) { } finally { if (f != null) { f.setAccessible(false); } } } return errorInfoList; } /** * 批注信息,默認解析:批注#列索引,比如用戶名不允許重復#0。可覆蓋此方法,解析自定義的批注格式 * * @param commentStr 當前行的所有批注信息 * @return key:列索引,value:對應列的所有批注信息 */ protected static Map<Integer, String> getCommentMap(String commentStr) { //每行的所有單元格的批注都在commentStr里,並用”__”分隔 String[] split = commentStr.split("__"); Map<Integer, String> commentMap = new HashMap<>(); for (String msg : split) { String[] cellMsg = msg.split("#"); //如果當前列沒有批注,會將該列的索引作為key存到map里;已有批注,以“,“分隔繼續拼接 int cellIndex = Integer.parseInt(cellMsg[0]); if (commentMap.get(cellIndex) == null) { commentMap.put(cellIndex, cellMsg[1]); } else { commentMap.replace(cellIndex, commentMap.get(cellIndex) + "," + cellMsg[1]); } } return commentMap; } private static ClientAnchor newClientAnchor(Workbook workbook) { //xls if (workbook instanceof HSSFWorkbook) { return new HSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6); } //xlsx else { return new XSSFClientAnchor(0, 0, 0, 0, (short) 3, 3, (short) 5, 6); } } private static RichTextString newRichTextString(Workbook workbook, String msg) { //xls if (workbook instanceof HSSFWorkbook) { return new HSSFRichTextString(msg); } //xlsx else { return new XSSFRichTextString(msg); } } }

import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.afterturn.easypoi.excel.entity.params.ExcelForEachParams; import cn.afterturn.easypoi.excel.export.styler.IExcelExportStyler; import org.apache.poi.ss.usermodel.*; /** * Easy Poi 導出樣式 * @Author:chenyanbin */ public class EasyPoiExcelExportStylerUitl implements IExcelExportStyler { private static final short STRING_FORMAT = (short) BuiltinFormats.getBuiltinFormat("TEXT"); private static final short FONT_SIZE_TEN = 10; private static final short FONT_SIZE_ELEVEN = 11; private static final short FONT_SIZE_TWELVE = 12; /** * 大標題樣式 */ private CellStyle headerStyle; /** * 每列標題樣式 */ private CellStyle titleStyle; /** * 數據行樣式 */ private CellStyle styles; public EasyPoiExcelExportStylerUitl(Workbook workbook) { this.init(workbook); } /** * 初始化樣式 * * @param workbook */ private void init(Workbook workbook) { this.headerStyle = initHeaderStyle(workbook); this.titleStyle = initTitleStyle(workbook); this.styles = initStyles(workbook); } /** * 大標題樣式 * * @param color * @return */ @Override public CellStyle getHeaderStyle(short color) { return headerStyle; } /** * 每列標題樣式 * * @param color * @return */ @Override public CellStyle getTitleStyle(short color) { return titleStyle; } /** * 數據行樣式 * * @param parity 可以用來表示奇偶行 * @param entity 數據內容 * @return 樣式 */ @Override public CellStyle getStyles(boolean parity, ExcelExportEntity entity) { return styles; } /** * 獲取樣式方法 * * @param dataRow 數據行 * @param obj 對象 * @param data 數據 */ @Override public CellStyle getStyles(Cell cell, int dataRow, ExcelExportEntity entity, Object obj, Object data) { return getStyles(true, entity); } /** * 模板使用的樣式設置 */ @Override public CellStyle getTemplateStyles(boolean isSingle, ExcelForEachParams excelForEachParams) { return null; } /** * 初始化--大標題樣式 * * @param workbook * @return */ private CellStyle initHeaderStyle(Workbook workbook) { CellStyle style = getBaseCellStyle(workbook); style.setFont(getFont(workbook, (short) 14, true)); return style; } /** * 初始化--每列標題樣式 * * @param workbook * @return */ private CellStyle initTitleStyle(Workbook workbook) { CellStyle style = getBaseCellStyle(workbook); style.setFont(getFont(workbook, FONT_SIZE_TWELVE, true)); //背景色 // style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); // style.setFillPattern(FillPatternType.SOLID_FOREGROUND); style.setDataFormat(STRING_FORMAT); return style; } /** * 初始化--數據行樣式 * * @param workbook * @return */ private CellStyle initStyles(Workbook workbook) { CellStyle style = getBaseCellStyle(workbook); style.setFont(getFont(workbook, FONT_SIZE_ELEVEN, false)); //背景色 // style.setFillForegroundColor(IndexedColors.RED.getIndex()); // style.setFillPattern(FillPatternType.SOLID_FOREGROUND); style.setDataFormat(STRING_FORMAT); return style; } /** * 基礎樣式 * * @return */ private CellStyle getBaseCellStyle(Workbook workbook) { CellStyle style = workbook.createCellStyle(); //下邊框 style.setBorderBottom(BorderStyle.THIN); //左邊框 style.setBorderLeft(BorderStyle.THIN); //上邊框 style.setBorderTop(BorderStyle.THIN); //右邊框 style.setBorderRight(BorderStyle.THIN); //水平居中 style.setAlignment(HorizontalAlignment.CENTER); //上下居中 style.setVerticalAlignment(VerticalAlignment.CENTER); //設置自動換行 style.setWrapText(true); return style; } /** * 字體樣式 * * @param size 字體大小 * @param isBold 是否加粗 * @return */ private Font getFont(Workbook workbook, short size, boolean isBold) { Font font = workbook.createFont(); //字體樣式 font.setFontName("宋體"); //是否加粗 font.setBold(isBold); //字體大小 font.setFontHeightInPoints(size); return font; } }

import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity; import cn.afterturn.easypoi.excel.entity.params.ExcelForEachParams; import cn.afterturn.easypoi.excel.export.styler.IExcelExportStyler; import org.apache.poi.ss.usermodel.*; /** * Easy Poi 導出樣式 * @Author:chenyanbin */ public class EasyPoiExcelExportStylerUitl implements IExcelExportStyler { private static final short STRING_FORMAT = (short) BuiltinFormats.getBuiltinFormat("TEXT"); private static final short FONT_SIZE_TEN = 10; private static final short FONT_SIZE_ELEVEN = 11; private static final short FONT_SIZE_TWELVE = 12; /** * 大標題樣式 */ private CellStyle headerStyle; /** * 每列標題樣式 */ private CellStyle titleStyle; /** * 數據行樣式 */ private CellStyle styles; public EasyPoiExcelExportStylerUitl(Workbook workbook) { this.init(workbook); } /** * 初始化樣式 * * @param workbook */ private void init(Workbook workbook) { this.headerStyle = initHeaderStyle(workbook); this.titleStyle = initTitleStyle(workbook); this.styles = initStyles(workbook); } /** * 大標題樣式 * * @param color * @return */ @Override public CellStyle getHeaderStyle(short color) { return headerStyle; } /** * 每列標題樣式 * * @param color * @return */ @Override public CellStyle getTitleStyle(short color) { return titleStyle; } /** * 數據行樣式 * * @param parity 可以用來表示奇偶行 * @param entity 數據內容 * @return 樣式 */ @Override public CellStyle getStyles(boolean parity, ExcelExportEntity entity) { return styles; } /** * 獲取樣式方法 * * @param dataRow 數據行 * @param obj 對象 * @param data 數據 */ @Override public CellStyle getStyles(Cell cell, int dataRow, ExcelExportEntity entity, Object obj, Object data) { return getStyles(true, entity); } /** * 模板使用的樣式設置 */ @Override public CellStyle getTemplateStyles(boolean isSingle, ExcelForEachParams excelForEachParams) { return null; } /** * 初始化--大標題樣式 * * @param workbook * @return */ private CellStyle initHeaderStyle(Workbook workbook) { CellStyle style = getBaseCellStyle(workbook); style.setFont(getFont(workbook, (short) 14, true)); return style; } /** * 初始化--每列標題樣式 * * @param workbook * @return */ private CellStyle initTitleStyle(Workbook workbook) { CellStyle style = getBaseCellStyle(workbook); style.setFont(getFont(workbook, FONT_SIZE_TWELVE, true)); //背景色 // style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); // style.setFillPattern(FillPatternType.SOLID_FOREGROUND); style.setDataFormat(STRING_FORMAT); return style; } /** * 初始化--數據行樣式 * * @param workbook * @return */ private CellStyle initStyles(Workbook workbook) { CellStyle style = getBaseCellStyle(workbook); style.setFont(getFont(workbook, FONT_SIZE_ELEVEN, false)); //背景色 // style.setFillForegroundColor(IndexedColors.RED.getIndex()); // style.setFillPattern(FillPatternType.SOLID_FOREGROUND); style.setDataFormat(STRING_FORMAT); return style; } /** * 基礎樣式 * * @return */ private CellStyle getBaseCellStyle(Workbook workbook) { CellStyle style = workbook.createCellStyle(); //下邊框 style.setBorderBottom(BorderStyle.THIN); //左邊框 style.setBorderLeft(BorderStyle.THIN); //上邊框 style.setBorderTop(BorderStyle.THIN); //右邊框 style.setBorderRight(BorderStyle.THIN); //水平居中 style.setAlignment(HorizontalAlignment.CENTER); //上下居中 style.setVerticalAlignment(VerticalAlignment.CENTER); //設置自動換行 style.setWrapText(true); return style; } /** * 字體樣式 * * @param size 字體大小 * @param isBold 是否加粗 * @return */ private Font getFont(Workbook workbook, short size, boolean isBold) { Font font = workbook.createFont(); //字體樣式 font.setFontName("宋體"); //是否加粗 font.setBold(isBold); //字體大小 font.setFontHeightInPoints(size); return font; } }

import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface EasyPoiCellAnnotation { /** * 字段索引位置,從0開始計數 * @return */ int cellIndex(); }

import lombok.Data; /** * Excel 錯誤批注信息vo * @Author:chenyanbin */ @Data public class ExcelErrorInfoVo { /** * 行索引,從0開始 */ private int rowIndex; /** * 列索引,從0開始 */ private int cellIndex; /** * 錯誤原因 */ private String reasonText; }

import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; /** * 阿里雲oss配置文件 * @Author:chenyanbin */ @Data @Configuration public class OssConfig { @Value("${aliyun.oss.endpoint}") private String endpoint; @Value("${aliyun.oss.accessKeyId}") private String accessKeyId; @Value("${aliyun.oss.accessSecret}") private String accessSecret; @Value("${aliyun.oss.bucketName}") private String bucketName; }

import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.ObjectMetadata; import com.aliyun.oss.model.PutObjectResult; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * 公共oss,本地文件上傳至阿里雲 * * @Author:chenyanbin */ @Service @Slf4j public class PubOssService { @Autowired OssConfig ossConfig; /** * 上傳客戶端本地文件 * * @param file * @return */ public String uploadClientFile(File file) { try { //獲取相關配置 String bucketName = ossConfig.getBucketName(); String endpoint = ossConfig.getEndpoint(); String accessKeyId = ossConfig.getAccessKeyId(); String accessSecret = ossConfig.getAccessSecret(); //創建OSS對象 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessSecret); //以輸入流的形式上傳文件 InputStream is = new FileInputStream(file); //文件名 String fileName = file.getName(); //文件大小 Long fileSize = file.length(); //創建上傳Object的Metadata ObjectMetadata metadata = new ObjectMetadata(); //上傳的文件的長度 metadata.setContentLength(is.available()); //指定該Object被下載時的網頁的緩存行為 metadata.setCacheControl("no-cache"); //指定該Object下設置Header metadata.setHeader("Pragma", "no-cache"); //指定該Object被下載時的內容編碼格式 metadata.setContentEncoding("utf-8"); //文件的MIME,定義文件的類型及網頁編碼,決定瀏覽器將以什么形式、什么編碼讀取文件。如果用戶沒有指定則根據Key或文件名的擴展名生成, //如果沒有擴展名則填默認值application/octet-stream metadata.setContentType(getContentType(fileName)); //指定該Object被下載時的名稱(指示MINME用戶代理如何顯示附加的文件,打開或下載,及文件名稱) metadata.setContentDisposition("filename/filesize=" + fileName + "/" + fileSize + "Byte."); //JDK8 日期格式化 LocalDateTime ldt = LocalDateTime.now(); DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd"); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); //文件路徑 String folder = dtf.format(ldt); //擴展名 String extension = fileName.substring(fileName.lastIndexOf(".")); String newFileName = "scm/excel/" + folder + "/" + dateTimeFormatter.format(ldt) + CommonUtil.generateUUID().substring(0, 8) + extension; //上傳文件 (上傳文件流的形式) PutObjectResult putResult = ossClient.putObject(bucketName, newFileName, is, metadata); String imgUrl = "https://" + bucketName + "." + endpoint + "/" + newFileName + extension; return imgUrl; } catch (Exception e) { } return null; } /** * 通過文件名判斷並獲取OSS服務文件上傳時文件的contentType * * @param fileName 文件名 * @return 文件的contentType */ private String getContentType(String fileName) { //文件的后綴名 String fileExtension = fileName.substring(fileName.lastIndexOf(".")); if (".bmp".equalsIgnoreCase(fileExtension)) { return "image/bmp"; } if (".gif".equalsIgnoreCase(fileExtension)) { return "image/gif"; } if (".jpeg".equalsIgnoreCase(fileExtension) || ".jpg".equalsIgnoreCase(fileExtension) || ".png".equalsIgnoreCase(fileExtension)) { return "image/jpeg"; } if (".html".equalsIgnoreCase(fileExtension)) { return "text/html"; } if (".txt".equalsIgnoreCase(fileExtension)) { return "text/plain"; } if (".vsd".equalsIgnoreCase(fileExtension)) { return "application/vnd.visio"; } if (".ppt".equalsIgnoreCase(fileExtension) || "pptx".equalsIgnoreCase(fileExtension)) { return "application/vnd.ms-powerpoint"; } if (".doc".equalsIgnoreCase(fileExtension) || "docx".equalsIgnoreCase(fileExtension)) { return "application/msword"; } if (".xml".equalsIgnoreCase(fileExtension)) { return "text/xml"; } //默認返回類型 return "image/jpeg"; } }

import cn.afterturn.easypoi.excel.ExcelExportUtil; import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import com.google.common.util.concurrent.ThreadFactoryBuilder; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** * EasyPoi 導出工具類 * * @Author:chenyanbin */ @Slf4j public class EasyPoiExportUtil { private static final Long KEEP_ALIVE_TIME = 60L; private static final int APS = Runtime.getRuntime().availableProcessors(); private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( APS * 2, APS * 4, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingDeque<>(512), new ThreadFactoryBuilder().setNameFormat("excel工具類-pool-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy() ); /** * 導出Excel 一對多關系 * * @param list Excel數據集合 * @param title Excel第一行標題,如果設置為null,默認不顯示 * @param sheetName sheet名稱 * @param pojoClass 泛型List的對象 * @param fileName 導出文件名 * @param type excel類型 HSSF || XSSF * @param isOneToMany 是否一對多 * @param response 響應體 */ public static void exportOneToManyExcel( List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, ExcelType type, boolean isOneToMany, HttpServletResponse response ) { ExportParams exportParams = new ExportParams(title, sheetName, type); exportParams.setStyle(EasyPoiExcelExportStylerUitl.class); exportParams.setType(ExcelType.XSSF); AtomicReference<Workbook> workbook = new AtomicReference<>(); workbookHandler(workbook, exportParams, pojoClass, list); if (workbook.get() == null) { return; } //判斷是否是一對多 if (isOneToMany) { setRowHeight(workbook.get()); } downLoadExcel(fileName, response, workbook.get()); } /** * 導出excel * * @param list Excel數據集合 * @param title Excel第一行標題,如果設置為null,默認不顯示 * @param sheetName sheet名稱 * @param pojoClass 泛型List的對象 * @param fileName 導出文件名 * @param setRowHeight 是否行高自適應 * @param response 響應體 */ public static void exportOneExcel( List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean setRowHeight, HttpServletResponse response ) { ExportParams exportParams = new ExportParams(title, sheetName); exportParams.setStyle(EasyPoiExcelExportStylerUitl.class); exportParams.setType(ExcelType.XSSF); AtomicReference<Workbook> workbook = new AtomicReference<>(); workbookHandler(workbook, exportParams, pojoClass, list); if (workbook.get() == null) { return; } //判斷是否根據內容自適應行高 if (setRowHeight) { Sheet sheet = workbook.get().getSheetAt(0); for (int i = 0; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); setRowHeight(row); } } downLoadExcel(fileName, response, workbook.get()); } /** * 下載excel導入模板 * * @param list Excel數據集合 * @param title Excel第一行標題,如果設置為null,默認不顯示 * @param sheetName sheet名稱 * @param pojoClass 泛型List的對象 * @param fileName 導出文件名 * @param setRowHeight 是否行高自適應 * @param response 響應體 * @param needComment 是否需要標題批注 * @param titleRowsIndex 標題的行索引,從0計數 * @param commentMap 批注map,key=列索引,從0計數;value=批注值 */ public static void downLoadExcelTemplate( List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean setRowHeight, HttpServletResponse response, boolean needComment, int titleRowsIndex, Map<Integer, String> commentMap ) { ExportParams exportParams = new ExportParams(title, sheetName); exportParams.setStyle(EasyPoiExcelExportStylerUitl.class); exportParams.setType(ExcelType.XSSF); AtomicReference<Workbook> workbook = new AtomicReference<>(); workbookHandler(workbook, exportParams, pojoClass, list); if (workbook.get() == null) { return; } //設置標題批注 if (needComment) { EasyPoiExcelCommentUtil.buildComment(workbook.get(), titleRowsIndex, commentMap); } //判斷是否根據內容自適應行高 if (setRowHeight) { Sheet sheet = workbook.get().getSheetAt(0); for (int i = 0; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); setRowHeight(row); } } downLoadExcel(fileName, response, workbook.get()); } /** * 下載excel校驗失敗導入模板 * * @param list Excel數據集合 * @param title Excel第一行標題,如果設置為null,默認不顯示 * @param sheetName sheet名稱 * @param pojoClass 泛型List的對象 * @param fileName 導出文件名 * @param setRowHeight 是否行高自適應 * @param response 響應體 * @param needComment 是否需要標題批注 * @param titleRowsIndex 標題的行索引,從0計數 * @param commentMap 批注map,key=列索引,從0計數;value=批注值 * @param errorInfoList 校驗模板參數錯誤集合 */ public static void downLoadExcelCheckFailseTemplate( List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean setRowHeight, HttpServletResponse response, boolean needComment, int titleRowsIndex, Map<Integer, String> commentMap, List<ExcelErrorInfoVo> errorInfoList ) { ExportParams exportParams = new ExportParams(title, sheetName); exportParams.setStyle(EasyPoiExcelExportStylerUitl.class); exportParams.setType(ExcelType.XSSF); AtomicReference<Workbook> workbook = new AtomicReference<>(); workbookHandler(workbook, exportParams, pojoClass, list); if (workbook.get() == null) { return; } //設置標題批注 if (needComment) { EasyPoiExcelCommentUtil.buildComment(workbook.get(), titleRowsIndex, commentMap); } //設置Excel參數校驗失敗批注 EasyPoiExcelCommentUtil.buildComment(workbook.get(), errorInfoList); //判斷是否根據內容自適應行高 if (setRowHeight) { Sheet sheet = workbook.get().getSheetAt(0); for (int i = 0; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); setRowHeight(row); } } downLoadExcel(fileName, response, workbook.get()); } /** * 下載Excel * * @param fileName * @param response * @param workbook */ private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) { ServletOutputStream outputStream = null; try { outputStream = response.getOutputStream(); response.setCharacterEncoding("UTF-8"); response.setHeader("content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Pragma", "No-cache"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8")); workbook.write(outputStream); outputStream.flush(); } catch (IOException e) { throw new RuntimeException(e.getMessage()); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { log.error("excel導出關閉輸出流異常:{}", e.getMessage()); } } } } /** * 一對多,設置行高 */ private static void setRowHeight(Workbook workbook) { Sheet sheet = workbook.getSheetAt(0); //設置第4列的列寬為60(下標從0開始),TestExportSub2Vo 不知道為什么設置了列寬但是不起作用,只能在這里單獨設置 sheet.setColumnWidth(3, 60 * 256); for (int i = 0; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); if (i == 0) { //設置第一行的行高(表格標題) row.setHeightInPoints(35); } else if (i == 1) { //設置第二行的行高(表格表頭) row.setHeightInPoints(25); } else { //設置其他行的行高根據內容自適應 setRowHeight(row); } } } private static void setRowHeight(Row row) { //根據內容長度設置行高 int enterCnt = 0; for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) { int rwsTemp = row.getCell(j).toString().length(); //這里取每一行中的每一列字符長度最大的那一列的字符 if (rwsTemp > enterCnt) { enterCnt = rwsTemp; } } //設置默認行高為35 row.setHeightInPoints(35); //如果字符長度大於35,判斷大了多少倍,根據倍數來設置相應的行高 if (enterCnt > 35) { float d = enterCnt / 35; float f = 35 * d; /*if (d>2 && d<4){ f = 35*2; }else if(d>=4 && d<6){ f = 35*3; }else if (d>=6 && d<8){ f = 35*4; }*/ row.setHeightInPoints(f); } } private static void workbookHandler(AtomicReference<Workbook> workbook, ExportParams exportParams, Class<?> pojoClass, List<?> list) { CountDownLatch latch = new CountDownLatch(1); THREAD_POOL_EXECUTOR.execute(() -> { try { workbook.set(ExcelExportUtil.exportExcel(exportParams, pojoClass, list)); } finally { latch.countDown(); } }); try { latch.await(); } catch (InterruptedException e) { log.error("多線程導出等待異常:{}", e.getMessage()); } } }
導入導出實體類

import cn.afterturn.easypoi.excel.annotation.Excel; import io.swagger.annotations.ApiModel; import lombok.Data; import org.hibernate.validator.constraints.NotBlank; import javax.validation.constraints.Size; import java.io.Serializable; /** * @Author:chenyanbin */ @Data @ApiModel(value = "ProductFilingImportExcelVo對象", description = "商品備案明細導入信息") public class ProductFilingImportExcelVo implements Serializable { @Excel(name = "HS編碼", width = 25) @NotBlank(message = "必輸項,長度不超過20") @Size(min = 1, max = 50, message = "必輸項,長度不超過20") @EasyPoiCellAnnotation(cellIndex = 0) private String hsCode; @Excel(name = "商品名稱(不要有空格和特殊符號)", width = 40) @NotBlank(message = "必輸項,長度不超過100") @Size(min = 1, max = 100, message = "必輸項,長度不超過100") @EasyPoiCellAnnotation(cellIndex = 1) private String productName; /** * 品牌id */ private Integer brandId; @Excel(name = "品牌") @NotBlank(message = "必輸項,長度不超過100") @Size(min = 1, max = 100, message = "必輸項,長度不超過100") @EasyPoiCellAnnotation(cellIndex = 2) private String brandName; @Excel(name = "規格型號", width = 20) @NotBlank(message = "必輸項,長度不超過100") @Size(min = 1, max = 100, message = "必輸項,長度不超過100") @EasyPoiCellAnnotation(cellIndex = 3) private String specificationModel; @Excel(name = "申報要素", width = 40) @NotBlank(message = "必輸項,長度不超過100") @Size(min = 1, max = 100, message = "必輸項,長度不超過100") @EasyPoiCellAnnotation(cellIndex = 4) private String reportElements; @Excel(name = "產品成分(必須和產品包裝背面成分翻譯一模一樣,成分100%)", width = 80) @NotBlank(message = "必輸項,長度不超過500") @Size(min = 1, max = 500, message = "必輸項,長度不超過500") @EasyPoiCellAnnotation(cellIndex = 5) private String productComposition; @Excel(name = "生產企業", width = 25) @NotBlank(message = "必輸項,長度不超過100") @Size(min = 1, max = 100, message = "必輸項,長度不超過100") @EasyPoiCellAnnotation(cellIndex = 6) private String enterprise; @Excel(name = "生產國/地區", width = 20) @NotBlank(message = "必輸項,參照國別代碼表,長度不超過20") @Size(min = 1, max = 20, message = "必輸項,參照國別代碼表,長度不超過20") @EasyPoiCellAnnotation(cellIndex = 7) private String country; @Excel(name = "適用標准", width = 20) @NotBlank(message = "必輸項,長度不超過100") @Size(min = 1, max = 100, message = "必輸項,長度不超過100") @EasyPoiCellAnnotation(cellIndex = 8) private String standards; @Excel(name = "商品條碼", width = 30) @NotBlank(message = "必輸項,長度不超過100") @Size(min = 1, max = 100, message = "必輸項,長度不超過100") @EasyPoiCellAnnotation(cellIndex = 9) private String productBarCode; @Excel(name = "功能(保健品需寫,不是保健品不用寫)", width = 40) @EasyPoiCellAnnotation(cellIndex = 10) private String function; @Excel(name = "其他說明", width = 40) @EasyPoiCellAnnotation(cellIndex = 11) private String remark; //mac上存在bug,必須指定savePath!!!Windows和Linux不需要指定保存路徑 @Excel(name = "商品正面", type = 2, savePath = "/Users/chenyanbin/upload") @NotBlank(message = "您還有未填寫的內容") @EasyPoiCellAnnotation(cellIndex = 12) private String productFrontPic; /** * 臨時商品正面url */ private String productFrontUrl; //mac上存在bug,必須指定savePath!!!Windows和Linux不需要指定保存路徑 @Excel(name = "商品背面", type = 2, savePath = "/Users/chenyanbin/upload") @EasyPoiCellAnnotation(cellIndex = 14) private String productBackPic; /** * 臨時商品背面url */ private String productBackUrl; //mac上存在bug,必須指定savePath!!!Windows和Linux不需要指定保存路徑 @Excel(name = "商品其他面", width = 15, type = 2, savePath = "/Users/chenyanbin/upload") @EasyPoiCellAnnotation(cellIndex = 15) private String productOtherPic; /** * 臨時商品其他面url */ private String productOtherUrl; }

import cn.afterturn.easypoi.excel.annotation.Excel; import io.swagger.annotations.ApiModel; import lombok.Data; import java.io.Serializable; /** * @Author:chenyanbin */ @Data @ApiModel(value = "ProductFilingExportExcelVo對象", description = "商品備案明細導出信息") public class ProductFilingExportExcelVo implements Serializable { /** * 主鍵 */ private Long id; @Excel(name = "商品編碼", width = 20) private String productCode; @Excel(name = "HS編碼", width = 25) private String hsCode; @Excel(name = "商品名稱", width = 40) private String productName; @Excel(name = "品牌") private String brandName; @Excel(name = "規格型號", width = 20) private String specificationModel; @Excel(name = "申報要素", width = 40) private String reportElements; @Excel(name = "產品成分", width = 50) private String productComposition; @Excel(name = "生產企業", width = 25) private String enterprise; @Excel(name = "生產國/地區", width = 20) private String country; @Excel(name = "適用標准", width = 20) private String standards; @Excel(name = "商品條碼", width = 30) private String productBarCode; @Excel(name = "功能", width = 40) private String function; @Excel(name = "其他說明", width = 40) private String remark; @Excel(name = "商品正面", type = 2, imageType = 2) private byte[] productFrontPic; /** * 臨時商品正面url */ private String tempProductFrontUrl; @Excel(name = "商品背面", type = 2, imageType = 2) private byte[] productBackPic; /** * 臨時商品背面url */ private String tempProductBackUrl; @Excel(name = "商品其他面", width = 15, type = 2, imageType = 2) private byte[] productOtherPic; /** * 臨時商品其他url */ private String tempProductOtherUrl; }
下載Excel導入模板,下載和導出代碼雷同,dto需要獲取數據庫中真實數據
@ApiOperation("下載商品備案明細導入Excel模板") @GetMapping("down_load_excel_template") public void downloadExcelTemplate( HttpServletResponse response ) { EasyPoiExportExcelDto<ProductFilingImportExcelVo> dto = new EasyPoiExportExcelDto<>(); dto.setSheetName("商品備案導入"); dto.setFileName("備案商品導入.xlsx"); dto.setTitle("進口商品備案"); dto.setPojoClass(new ProductFilingImportExcelVo()); dto.setExcelDataList(new ArrayList<>()); Map<Integer, String> commentMap = new LinkedHashMap<>(); commentMap.put(0, "必填項,長度不超過20"); commentMap.put(1, "必填項,長度不超過100"); commentMap.put(2, "必填項,長度不超過100"); commentMap.put(3, "必填項,長度不超過100"); commentMap.put(4, "必填項,長度不超過100"); commentMap.put(5, "必填項,長度不超過500"); commentMap.put(6, "必填項,長度不超過100"); commentMap.put(7, "必填項,參照國別代碼表"); commentMap.put(8, "必填項,長度不超過100"); commentMap.put(9, "必填項,長度不超過30"); commentMap.put(12, "商品正面必填"); EasyPoiExportUtil.downLoadExcelTemplate( dto.getExcelDataList(), dto.getTitle(), dto.getSheetName(), dto.getPojoClass().getClass(), dto.getFileName(), false, response, true, 1, commentMap ); }
導入Excel
@ApiOperation("商品備案明細導入Excel模塊") @PostMapping("import_excel_template") public void importExcelTemplate( @ApiParam(value = "文件上傳", required = true) @RequestPart("file") MultipartFile file, HttpServletResponse response, @ApiParam(value = "緩存編碼", required = true) @RequestParam(value = "cache_code") String cacheCode ) throws Exception { //1、導入參數配置 ImportParams params = new ImportParams(); params.setNeedSave(true); params.setTitleRows(1); //mac上必須指定導入Excel保存路徑,Windows和Linux不需要指定!!! params.setSaveUrl("/Users/chenyanbin/upload"); //2、拿到excel數據 List<ProductFilingImportExcelVo> excelList = ExcelImportUtil.importExcel( file.getInputStream(), ProductFilingImportExcelVo.class, params ); //3、處理上傳圖片問題,轉字節流 List<ExcelErrorInfoVo> errorList = new ArrayList<>(); CountDownLatch latch = new CountDownLatch(1); THREAD_POOL_EXECUTOR.execute(() -> { for (int i = 0; i < excelList.size(); i++) { //正面 if (StringUtils.isNotBlank(excelList.get(i).getProductFrontPic())) { excelList.get(i).setProductFrontUrl( pubOssService.uploadClientFile( new File( excelList.get(i).getProductFrontPic() ) ) ); } //背面 if (StringUtils.isNotBlank(excelList.get(i).getProductBackPic())) { excelList.get(i).setProductBackUrl( pubOssService.uploadClientFile( new File( excelList.get(i).getProductBackPic() ) ) ); } //其他面 if (StringUtils.isNotBlank(excelList.get(i).getProductOtherPic())) { excelList.get(i).setProductOtherUrl( pubOssService.uploadClientFile( new File( excelList.get(i).getProductOtherPic() ) ) ); } //校驗數據 errorList.addAll( EasyPoiExcelCommentUtil .checkExcelData( excelList.get(i), ProductFilingImportExcelVo.class, i + 2 ) ); } latch.countDown(); }); latch.await(); //4、校驗數據通過 if (errorList.size() == 0) { //保存到數據庫 System.err.println(excelList); } else { //5、將錯誤excel導出 EasyPoiExportExcelDto<ProductFilingImportExcelVo> dto = new EasyPoiExportExcelDto<>(); dto.setSheetName("商品備案導入"); dto.setFileName("備案商品導入-錯誤.xlsx"); dto.setTitle("進口商品備案"); dto.setPojoClass(new ProductFilingImportExcelVo()); dto.setExcelDataList(new ArrayList<>()); Map<Integer, String> commentMap = new LinkedHashMap<>(); commentMap.put(0, "必填項,長度不超過20"); commentMap.put(1, "必填項,長度不超過100"); commentMap.put(2, "必填項,長度不超過100"); commentMap.put(3, "必填項,長度不超過100"); commentMap.put(4, "必填項,長度不超過100"); commentMap.put(5, "必填項,長度不超過500"); commentMap.put(6, "必填項,長度不超過100"); commentMap.put(7, "必填項,參照國別代碼表"); commentMap.put(8, "必填項,長度不超過100"); commentMap.put(9, "必填項,長度不超過30"); commentMap.put(12, "商品正面必填"); EasyPoiExportUtil.downLoadExcelCheckFailseTemplate( excelList, dto.getTitle(), dto.getSheetName(), dto.getPojoClass().getClass(), dto.getFileName(), false, response, true, 1, commentMap, errorList ); }