SpringBoot整合EasyPoi導出(帶批注)、導入(參數校驗導出、帶圖片)


添加依賴

            <!-- 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);
        }
    }
}
EasyPoiExcelCommentUtil.java
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;
    }
}
EasyPoiExcelExportStylerUitl.java
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;
    }
}
EasyPoiExcelExportStylerUitl.java
import java.lang.annotation.*;

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

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

    /**
     * 錯誤原因
     */
    private String reasonText;
}
ExcelErrorInfoVo.java
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;
}
OssConfig.java阿里雲Oss配置類
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";
    }
}
PubOssService.java本地文件上傳至阿里雲Oss
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());
        }
    }
}
EasyPoiExportUtil.java

 

導入導出實體類

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;
}
ProductFilingImportExcelVo.java
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;
}
ProductFilingExportExcelVo.java

下載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
            );
        }

演示


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM