• Java实现Excel导入和导出,看这一篇就够了(珍藏版2.0)


    前言

    两年前,我发表了一篇自己基于POI实现的工具类 Java实现Excel导入和导出,看这一篇就够了(珍藏版),也就是1.0版本,受到了不少粉丝和朋友喜欢,谢谢大家认可。在这两年多的时间里,经过粉丝们的建议,工具类也在经历优化和升级,因此,今天发布2.0版本。
    在这里插入图片描述

    2.0版本 VS 1.0版本

    本次2.0版本是基于POI最新版5.2.3版本进行的编写的,相对于1.0版本,2.0版本的工具类主要进行了下面优化:

    (1)【类文件减少】
    在 1.0 版本中,大家会复制多个类文件,这个就可能会给到家带来一定的不便,在 2.0 版本中,整个工具类仅保留一个,即ExcelUtils,这样减少了复制粘贴的过程,使得工具类使用的过程中更加便捷。

    (2)【解析增强】
    1.0 版本在数据导入和导出的时候,日期和大数字等解析方式不够完善,2.0 版本则进行了补充,使得更加适应各种场景的解析和导入。

    (3)【功能增加】
    在2.0版本中,增加了水印、自适应列宽、批注等功能,使得功能更加的完善。

    (4)【其他优化】2.0 版本并不是在 1.0 版本基础上写的,而是对整个工具类的代码结构进行了重构,使得整体代码结构和数据解析层面进行了优化。

    功能说明

    导出功能

    按对象导出(基础)

    按对象进行导出时,需要在类的属性上,打上 @ExcelUtils.ExcelExport(name = “列名”) 即可。

    @Data
    public class ExportStandardVo {
    
        @ExcelUtils.ExcelExport(name = "姓名")
        private String name;
    
        @ExcelUtils.ExcelExport(name = "年龄")
        private Integer age;
    
        @ExcelUtils.ExcelExport(name = "存款(RMB)")
        private BigDecimal deposit;
    
        @ExcelUtils.ExcelExport(name = "备注")
        private String remark;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
        @GetMapping("/101")
        @ApiOperation(value = "按对象导出(基础)", produces = "application/octet-stream")
        public void forStandard(HttpServletResponse response) {
            ExportStandardVo o1 = new ExportStandardVo();
            o1.setName("诸葛亮");
            o1.setAge(28);
            o1.setDeposit(new BigDecimal("25000"));
            o1.setRemark("功盖三分国,名成八阵图");
    
            ExportStandardVo o2 = new ExportStandardVo();
            o2.setName("赵云");
            o2.setAge(26);
            o2.setDeposit(new BigDecimal("18000"));
            o2.setRemark("吾乃常山赵子龙");
    
            List<ExportStandardVo> list = Arrays.asList(o1, o2);
    
            ExcelUtils.export(response, "101", list, ExportStandardVo.class);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    按对象导出(数据映射)

    导出数据时,如果存在数据映射,只需将对应字段的 ExcelExport 注解上增加 kvs 属性,枚举出所有的映射关系即可,其中 k 为 java 数据值,k 为导出表格展示值,比如,在实体类中,我们用 0 和 1 分别表示男和女,那么可以如下设置。

    @Data
    public class ExportSelectVo {
    
        @ExcelUtils.ExcelExport(name = "姓名")
        private String name;
    
        @ExcelUtils.ExcelExport(name = "性别", kvs = {
                @ExcelUtils.KV(k = "0", v = "男"),
                @ExcelUtils.KV(k = "1", v = "女"),
        })
        private Integer sex;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
        @GetMapping("/102")
        @ApiOperation(value = "按对象导出(数据映射)", produces = "application/octet-stream")
        public void forSelect(HttpServletResponse response) {
            ExportSelectVo o1 = new ExportSelectVo();
            o1.setName("诸葛亮");
            o1.setSex(0);
    
            ExportSelectVo o2 = new ExportSelectVo();
            o2.setName("孙尚香");
            o2.setSex(1);
    
            List<ExportSelectVo> list = Arrays.asList(o1, o2);
    
            ExcelUtils.export(response, "102", list, ExportSelectVo.class);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述

    导入功能

    示例项目

    下载示例项目

    上述所有例子中,示例项目中(springboot 项目)均有展示。
    示例项目下载:https://zyq2024.oss-cn-chengdu.aliyuncs.com/ex.zip

    运行示例项目

    下载好示例项目后,解压后得到 ex 项目,将 ex 项目导入到开发工具中,待所有 maven 依赖下载完成后,即可执行项目中的 App类,启动项目。

    待项目启动成功后,控制台会打印相关示例的页面访问地址 http://localhost:8080/swagger-ui.html,将地址复制到浏览器打开,就能够看到文章中的相关示例:

    在这里插入图片描述

    执行示例

    比如,我们想要执行 102 示例,那么点开 102,然后点击 【Try it out】
    在这里插入图片描述

    待 102 执行完成后,就会返回对应的表格文件,我们点击【Download 102.xlsx】就可以得到示例 102 的导出文件。

    在这里插入图片描述
    下载后,我们即可打开执行的示例结果 102.xlsx 文件。
    在这里插入图片描述

    同理,我们可以在后端 102 示例中,查看对应的后端代码。

    从这里,我们可以看出,后端在导出设置的性别分别是 0 和 1,而导出的文件中,分别展示了男和女,那么它是怎么实现的呢?
    在这里插入图片描述
    我们继续打开对应的实体,即可看到,原来后端,只需要进行一个 kvs 的属性进行配置,即可实现。
    在这里插入图片描述
    因此,同理,大家可以根据示例中的写法,去实现自己的需要功能。

    Maven 依赖

            <dependency>
                <groupId>org.apache.poigroupId>
                <artifactId>poi-ooxmlartifactId>
                <version>5.2.3version>
            dependency>
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>fastjsonartifactId>
                <version>1.2.78version>
            dependency>
            <dependency>
                <groupId>org.apache.httpcomponentsgroupId>
                <artifactId>httpmimeartifactId>
                <version>4.5.13version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    注意

    使用 POI 5.2.3 版本,需要将 log4j2 的版本升级到 2.17.1 版本(如下图)
    在这里插入图片描述

        <properties>
            <log4j2.version>2.17.1log4j2.version>
        properties>
    
    • 1
    • 2
    • 3

    如果不升级,执行工具类时,可能会报如下异常:

    java.lang.NoSuchMethodError: org.apache.logging.log4j.Logger.atTrace()Lorg/apache/logging/log4j/LogBuilder;
    at org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl.(SchemaTypeSystemImpl.java:196) ~[xmlbeans-5.1.1.jar:na]
    at org.apache.poi.schemas.ooxml.system.ooxml.TypeSystemHolder.(TypeSystemHolder.java:9) ~[poi-ooxml-lite-5.2.3.jar:5.2.3]
    at org.apache.poi.schemas.ooxml.system.ooxml.TypeSystemHolder.(TypeSystemHolder.java:6) ~[poi-ooxml-lite-5.2.3.jar:5.2.3]
    at org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorkbook.(CTWorkbook.java:22) ~[poi-ooxml-lite-5.2.3.jar:5.2.3]
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.onWorkbookCreate(XSSFWorkbook.java:476) ~[poi-ooxml-5.2.3.jar:5.2.3]
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.(XSSFWorkbook.java:233) ~[poi-ooxml-5.2.3.jar:5.2.3]
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.(XSSFWorkbook.java:227) ~[poi-ooxml-5.2.3.jar:5.2.3]
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.(XSSFWorkbook.java:215) ~[poi-ooxml-5.2.3.jar:5.2.3]
    at com.zyq.utils.ExcelUtils.getExportXSSFWorkbook(ExcelUtils.java:223) ~[classes/:na]
    at com.zyq.utils.ExcelUtils.export(ExcelUtils.java:116) ~[classes/:na]

    ExcelUtils 工具类

    package com.zyq.utils;
    
    import com.alibaba.fastjson.JSONArray;
    import com.alibaba.fastjson.JSONObject;
    import lombok.Data;
    import org.apache.commons.io.FileUtils;
    import org.apache.poi.hssf.usermodel.HSSFWorkbook;
    import org.apache.poi.openxml4j.opc.PackagePartName;
    import org.apache.poi.openxml4j.opc.PackageRelationship;
    import org.apache.poi.openxml4j.opc.TargetMode;
    import org.apache.poi.ss.usermodel.*;
    import org.apache.poi.ss.util.CellRangeAddress;
    import org.apache.poi.xssf.usermodel.*;
    import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheetBackgroundPicture;
    import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet;
    import org.springframework.core.annotation.AliasFor;
    import org.springframework.web.multipart.MultipartFile;
    
    import javax.imageio.ImageIO;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.Color;
    import java.awt.Font;
    import java.awt.*;
    import java.awt.font.FontRenderContext;
    import java.awt.geom.Rectangle2D;
    import java.awt.image.BufferedImage;
    import java.io.*;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.lang.reflect.Field;
    import java.math.BigDecimal;
    import java.net.URL;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.Files;
    import java.text.DecimalFormat;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.List;
    import java.util.*;
    import java.util.regex.Pattern;
    
    /**
     * @author zyqok
     * @from https://blog.csdn.net/sunnyzyq/article/details/133912842
     * @since 2023/10/27
     */
    @SuppressWarnings("unused")
    public class ExcelUtils {
    
        private static <E> List<E> getList(List<E> list) {
            return list == null ? Collections.emptyList() : list;
        }
    
        /**
         * 判断集合是否为空
         *
         * @param c 集合对象
         * @return true-为空,false-不为空
         */
        private static <E> boolean isEmpty(Collection<E> c) {
            return c == null || c.isEmpty();
        }
    
        /**
         * 判断字符串是否为空
         *
         * @param c 集合对象
         * @return true-为空,false-不为空
         */
        private static boolean isEmpty(String c) {
            return c == null || c.trim().isEmpty();
        }
    
        private static <K, V> boolean isEmpty(Map<K, V> map) {
            return map == null || map.isEmpty();
        }
    
        private static <K, V> boolean notEmpty(Map<K, V> map) {
            return !isEmpty(map);
        }
    
    
        private static boolean notEmpty(String c) {
            return !isEmpty(c);
        }
    
        /**
         * 导出文件到本地
         *
         * @param file 本地excel文件,如(C:\excel\用户表.xlsx)
         * @param list 导出数据列表
         * @param c    泛型类
         * @param   泛型
         */
        public static <T> void export(File file, List<T> list, Class<T> c, List<ExportCellMerge> mergers) {
            checkExportFile(file);
            ExportWorkBook myWorkBook = getExportWorkBook(file.getName(), list, c, mergers);
            Workbook xssfWorkbook = getExportXSSFWorkbook(myWorkBook);
            export(xssfWorkbook, file);
        }
    
        /**
         * 导出文件到响应
         *
         * @param response 响应对象
         * @param fileName 导出文件名称(不带尾缀)
         * @param list     导出数据列表
         * @param c        泛型类
         * @param       泛型
         */
        public static <T> void export(HttpServletResponse response, String fileName, List<T> list, Class<T> c) {
            ExportWorkBook myWorkBook = getExportWorkBook(fileName, list, c, null);
            Workbook xssfWorkbook = getExportXSSFWorkbook(myWorkBook);
            export(response, xssfWorkbook, fileName);
        }
    
        /**
         * 导出文件到响应
         *
         * @param response 响应对象
         * @param fileName 导出文件名称(不带尾缀)
         * @param list     导出数据列表
         * @param c        泛型类
         * @param       泛型
         */
        public static <T> void export(HttpServletResponse response, String fileName, List<T> list, Class<T> c, List<ExportCellMerge> mergers) {
            ExportWorkBook myWorkBook = getExportWorkBook(fileName, list, c, mergers);
            myWorkBook.setName(fileName);
            export(response, fileName, myWorkBook);
        }
    
        public static <T> void export(HttpServletResponse response, String fileName,  List<ExportSheet> sheets) {
            ExportWorkBook myWorkBook = new ExportWorkBook();
            myWorkBook.setName(fileName);
            myWorkBook.setSheets(sheets);
            export(response, fileName, myWorkBook);
        }
    
        private static void export(HttpServletResponse response, String fileName, ExportWorkBook myWorkBook) {
            Workbook xssfWorkbook = getExportXSSFWorkbook(myWorkBook);
            export(response, xssfWorkbook, fileName);
        }
    
    
        /**
         * 将工作簿导出到本地文件
         *
         * @param workbook POI工作簿对象
         * @param file     本地文件对象
         */
        private static void export(Workbook workbook, File file) {
            FileOutputStream fos;
            try {
                fos = new FileOutputStream(file);
                ByteArrayOutputStream ops = new ByteArrayOutputStream();
                workbook.write(ops);
                fos.write(ops.toByteArray());
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 将工作簿导出到响应
         *
         * @param response 响应流对象
         * @param workbook POI工作簿对象
         * @param fileName 文件名称
         */
        private static void export(HttpServletResponse response, Workbook workbook, String fileName) {
            try {
                if (isEmpty(fileName)) {
                    fileName = System.currentTimeMillis() + "";
                }
                response.setContentType("application/vnd.ms-excel");
                response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
                String name = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1) + ExcelType.XLSX.val;
                response.addHeader("Content-Disposition", "attachment;filename=" + name);
                ServletOutputStream out = response.getOutputStream();
                workbook.write(out);
                out.flush();
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 获取导出表格对象
         *
         * @param fileName 文件全名
         * @param list     导出对象集合
         * @param c        泛型类
         * @param       泛型
         * @return ExportWorkBook
         */
        private static <T> ExportWorkBook getExportWorkBook(String fileName, List<T> list, Class<T> c, List<ExportCellMerge> merges) {
            // ExportSheet
            List<ExportSheet> mySheets = new ArrayList<>();
            ExportSheet mySheet = ExportSheetFactory.createExportSheet(list, c, merges);
            if (isEmpty(mySheet.getName())) {
                mySheet.setName(getFileName(fileName));
            }
            mySheets.add(mySheet);
            // ExportWorkBook
            ExportWorkBook myBook = new ExportWorkBook();
            myBook.setName(fileName);
            myBook.setSheets(mySheets);
            return myBook;
        }
    
        /**
         * 将工作簿导出到本地文件
         *
         * @param myWorkBook 导出工作簿对象
         * @return POI导出工作簿对象
         */
        private static Workbook getExportXSSFWorkbook(ExportWorkBook myWorkBook) {
            Workbook workbook = new XSSFWorkbook();
            List<ExportSheet> mySheets = myWorkBook.getSheets();
            for (ExportSheet mySheet : mySheets) {
                // 默认样式
                CellStyle headerStyle = getDefaultHeaderStyle(workbook);
                CellStyle dataStyle = getDefaultDataStyle(workbook);
                // 自定义样式
                setCellStyle(headerStyle, mySheet.getHeaderStyle());
                setCellStyle(dataStyle, mySheet.getDataStyle());
                // Sheet:数据
                Sheet sheet = workbook.createSheet(mySheet.getName());
                // Sheet:样式
                setSheetValue(sheet, mySheet, headerStyle, dataStyle);
                // Sheet:批注
                setSheetComment(sheet, mySheet);
                // Sheet:冻结表头
                setFreezeHeader(sheet, mySheet);
                // Sheet:水印
                setWatermark(workbook, sheet, mySheet);
                // Sheet:设置宽高
                setColumnWidth(sheet, mySheet);
                // Sheet:合并单元格
                mergeSheetCell(sheet, mySheet);
            }
            return workbook;
        }
    
        private static void mergeSheetCell(Sheet sheet, ExportSheet mySheet) {
            if (mySheet.getMerges() != null && !mySheet.getMerges().isEmpty()) {
                for (ExportCellMerge merge : mySheet.getMerges()) {
                    sheet.addMergedRegion(new CellRangeAddress(merge.getY1(), merge.getY2(), merge.getX1(), merge.getX2()));
                }
            }
        }
    
        private static void setColumnWidth(Sheet sheet, ExportSheet mySheet) {
            // 全局列宽(默认15个字符)
            int globalWidth = mySheet.getColumnWidth();
            if (globalWidth <= 0) {
                globalWidth = 15;
            }
            // 指定列宽
            Map<Integer, Integer> columnIndexWidthMap = mySheet.getColumnIndexWidthMap();
            if (notEmpty(columnIndexWidthMap)) {
                for (Map.Entry<Integer, Integer> entry : columnIndexWidthMap.entrySet()) {
                    int columnIndex = entry.getKey();
                    int columnWidth = entry.getValue();
                    if (columnWidth <= 0) {
                        columnWidth = globalWidth;
                    }
                    sheet.setColumnWidth(columnIndex, columnWidth * 256);
                }
            }
        }
    
        private static void setWatermark(Workbook workbook, Sheet sheet, ExportSheet mySheet) {
            ExportWatermark watermark = mySheet.getWatermark();
            if (watermark == null) {
                return;
            }
            int type = watermark.getType();
            String src = watermark.getSrc();
            if (!WatermarkType.containCode(type) || type == WatermarkType.NONE.code || ExcelUtils.isEmpty(src)) {
                return;
            }
            byte[] imageBytes = getWaterMarkImageBytes(type, src);
            if (imageBytes == null) {
                return;
            }
            XSSFSheet xssfSheet = (XSSFSheet) sheet;
            InputStream inputStream = new ByteArrayInputStream(imageBytes);
            int pictureIndex = workbook.addPicture(imageBytes, Workbook.PICTURE_TYPE_PNG);
            XSSFPictureData watermarkPictureData = (XSSFPictureData) workbook.getAllPictures().get(pictureIndex);
            PackagePartName ppn = watermarkPictureData.getPackagePart().getPartName();
            PackageRelationship packageRelationship = xssfSheet.getPackagePart().addRelationship(ppn, TargetMode.INTERNAL, XSSFRelation.IMAGES.getRelation(), null);
            CTWorksheet ctWorksheet = xssfSheet.getCTWorksheet();
            CTSheetBackgroundPicture backgroundPicture = ctWorksheet.addNewPicture();
            System.out.println(packageRelationship.getId());
            backgroundPicture.setId(packageRelationship.getId());
        }
    
        private static byte[] getWaterMarkImageBytes(int type, String src) {
            try {
                if (type == WatermarkType.TEXT.code) {
                    // 文本文字
                    return getImageBytesFromText(src);
                }
                if (type == WatermarkType.FILE.code) {
                    // 本地图片
                    return getImageBytesFromFile(src);
                }
                if (type == WatermarkType.URL.code) {
                    // 网络图片
                    return getImageBytesFromUrl(src);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private static byte[] getImageBytesFromFile(String imageFilePath) throws IOException {
            File file = new File(imageFilePath);
            if (!file.exists()) {
                throw new RuntimeException(String.format("水印图片%s不存在", imageFilePath));
            }
            // 文件限制为10MB下的图片
            int allowMaxMb = 10;
            int allowMaxByte = 10 * 1024 * 1024;
            if (file.length() > allowMaxByte) {
                throw new RuntimeException(String.format("水印图片%s不能超过%sMB", imageFilePath, allowMaxMb));
            }
            // 水印图片限制为 png,jpg,jpeg
            List<String> allowTypes = Arrays.asList(".png", ".jpg", ".jpeg");
            boolean isMatchType = false;
            String fileName = file.getName();
            for (String allowType : allowTypes) {
                if (fileName.endsWith(allowType)) {
                    isMatchType = true;
                    break;
                }
            }
            if (!isMatchType) {
                throw new RuntimeException(String.format("水印图片%s格式不正确,请填写%s格式的图片", imageFilePath, allowTypes));
            }
            // 获取图片文件二进制流
            return FileUtils.readFileToByteArray(file);
        }
    
        private static byte[] getImageBytesFromText(String text) throws IOException {
            int width = 400;
            int height = 300;
            // 创建画板
            BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            // 创建画笔
            Graphics2D g2d = bufferedImage.createGraphics();
            // 绘制透明背景
            bufferedImage = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
            g2d.dispose();
            g2d = bufferedImage.createGraphics();
            // 设置绘制颜色和旋转角度
            g2d.setColor(new Color(129, 12, 187, 80));
            g2d.setStroke(new BasicStroke(1));
            Font font = new Font(null, Font.BOLD, 40);
            g2d.setFont(font);
            g2d.rotate(-0.5, (double) bufferedImage.getWidth() / 2, (double) bufferedImage.getHeight() / 2);
            // 获取文本边界和绘制位置
            FontRenderContext context = g2d.getFontRenderContext();
            Rectangle2D bounds = font.getStringBounds(text, context);
            double x = (width - bounds.getWidth()) / 2;
            double y = (height - bounds.getHeight()) / 2;
            double ascent = -bounds.getY();
            double baseY = y + ascent;
            // 绘制文本
            g2d.drawString(text, (int) x, (int) baseY);
            // 保存绘制结果,并释放资源
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
            g2d.dispose();
            // 将 BufferedImage 转换为字节数组
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "png", os);
            return os.toByteArray();
        }
    
        private static byte[] getImageBytesFromUrl(String imageUrl) throws IOException {
            URL url = new URL(imageUrl);
            try (InputStream is = url.openStream();
                 ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = is.read(buffer)) != -1) {
                    bos.write(buffer, 0, bytesRead);
                }
                return bos.toByteArray();
            }
        }
    
        private static void setFreezeHeader(Sheet sheet, ExportSheet mySheet) {
            if (!mySheet.freezeHeader) {
                return;
            }
            int headerRows = 0;
            List<ExportRow> rows = mySheet.getRows();
            for (ExportRow row : rows) {
                if (!row.isHeader()) {
                    break;
                }
                headerRows++;
            }
            sheet.createFreezePane(0, headerRows);
        }
    
        private static CellStyle getDefaultHeaderStyle(Workbook workbook) {
            CellStyle headerStyle = workbook.createCellStyle();
            // 默认表头内容对齐为:居中显示
            headerStyle.setAlignment(HorizontalAlignment.CENTER);
            // 默认表头背景为:淡灰色(填充方式为全填充)
            headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.index);
            return headerStyle;
        }
    
        private static CellStyle getDefaultDataStyle(Workbook workbook) {
            CellStyle dataStyle = workbook.createCellStyle();
            // 默认数据内容对齐为:居左显示
            dataStyle.setAlignment(HorizontalAlignment.CENTER);
            return dataStyle;
        }
    
        /**
         * 自定义样式转化为表格样式
         *
         * @param style   POI样式
         * @param myStyle 自定义样式
         */
        private static void setCellStyle(CellStyle style, ExportCellStyle myStyle) {
            if (myStyle == null) {
                return;
            }
            if (myStyle.getBackgroundColor() > 0) {
                style.setFillBackgroundColor(myStyle.getBackgroundColor());
            }
        }
    
        /**
         * 设置批注
         *
         * @param poiSheet POI页Sheet对象
         * @param mySheet  导出Sheet对象
         */
        private static void setSheetComment(Sheet poiSheet, ExportSheet mySheet) {
            if (isEmpty(mySheet.getComments())) {
                return;
            }
            Drawing<?> drawing = poiSheet.createDrawingPatriarch();
            for (ExportComment myComment : mySheet.getComments()) {
                int x = myComment.getX();
                int y = myComment.getY();
                String commentStr = myComment.getComment();
                XSSFClientAnchor xssfClientAnchor = new XSSFClientAnchor(0, 0, 0, 0, x, y, x + 2, y + 6);
                xssfClientAnchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE);
                Comment comment = drawing.createCellComment(xssfClientAnchor);
                comment.setString(new XSSFRichTextString(commentStr));
                poiSheet.getRow(y).getCell(x).setCellComment(comment);
            }
        }
    
        /**
         * 设置Sheet数据
         *
         * @param sheet   POI页Sheet对象
         * @param mySheet 导出Sheet对象
         */
        private static void setSheetValue(Sheet sheet, ExportSheet mySheet, CellStyle headerStyle, CellStyle dataStyle) {
            List<ExportRow> myRows = getList(mySheet.getRows());
            for (int j = 0; j < myRows.size(); j++) {
                ExportRow myRow = myRows.get(j);
                List<ExportCell> myCells = getList(myRow.getCells());
                Row row = sheet.createRow(j);
                CellStyle tempStyle = myRow.isHeader() ? headerStyle : dataStyle;
                for (int k = 0; k < myCells.size(); k++) {
                    ExportCell myCell = myCells.get(k);
                    Cell cell = row.createCell(k);
                    setCellValue(cell, myCell);
                    cell.setCellStyle(tempStyle);
                }
            }
        }
    
        /**
         * 设置单元格数据
         *
         * @param cell   POI单元格对象
         * @param myCell 导出单元格对象
         */
        private static void setCellValue(Cell cell, ExportCell myCell) {
            // 无值情况,返回空字符串
            Object o = myCell.getValue();
            if (Objects.isNull(o) || isEmpty(o.toString())) {
                cell.setCellValue("");
                return;
            }
            // 字符串类型
            if (o instanceof String) {
                cell.setCellValue((String) o);
                return;
            }
            // Integer类型
            if (o instanceof Integer) {
                cell.setCellValue((Integer) o);
                return;
            }
            // Long类型
            if (o instanceof Long) {
                // 数字类型默认超过10位,excel默认显示科学计数法,因此长度超过10位,则显示为文本格式
                int length = o.toString().length();
                if (length > 10) {
                    cell.setCellValue(o.toString());
                } else {
                    cell.setCellValue((Long) o);
                }
                return;
            }
            // Double
            if (o instanceof Double || o instanceof Float || o instanceof BigDecimal) {
                // 去除科学计数法显示
                BigDecimal b = new BigDecimal(o.toString());
                String s = b.toPlainString();
                int length = s.length();
                if (length > 10) {
                    cell.setCellValue(s);
                } else {
                    cell.setCellValue(b.doubleValue());
                }
                return;
            }
            // 日期格式
            if (o instanceof Date) {
                Date date = (Date) o;
                String formatValue;
                String dateFormat = myCell.getDateFormat();
                if (isEmpty(dateFormat)) {
                    formatValue = new SimpleDateFormat(dateFormat).format("yyyy-MM-dd HH:mm:ss");
                } else {
                    if ("ts".equals(dateFormat)) {
                        formatValue = String.valueOf(date.getTime());
                    } else if ("tms".equals(dateFormat)) {
                        formatValue = String.valueOf(date.getTime() / 1000);
                    } else {
                        formatValue = new SimpleDateFormat(dateFormat).format(date);
                    }
                }
                cell.setCellValue(formatValue);
                return;
            }
            // 其他格式
            cell.setCellValue(o.toString());
        }
    
        /**
         * 获取文件名称(不带文件尾缀)
         *
         * @param fileFullName 文件名称(带尾缀)
         * @return 文件名称(不带尾缀)
         */
        private static String getFileName(String fileFullName) {
            int lastDotIndex = fileFullName.lastIndexOf(".");
            if (lastDotIndex != -1) {
                return fileFullName.substring(0, lastDotIndex);
            }
            return fileFullName;
        }
    
        /**
         * 检测导出文件是否OK
         *
         * @param file 到处在excel文件
         */
        public static void checkExportFile(File file) {
            if (Objects.isNull(file)) {
                throw new RuntimeException("文件不能为空");
            }
            if (file.exists()) {
                return;
            }
            String name = file.getName();
            if (!name.endsWith(ExcelType.XLSX.val)) {
                throw new RuntimeException("导出的excel文件请为xlsx类型的文件");
            }
            File parentFile = file.getParentFile();
            if (Objects.nonNull(parentFile) && !parentFile.exists()) {
                boolean mkdirs = parentFile.mkdirs();
                if (!mkdirs) {
                    throw new RuntimeException("父级文件夹创建失败");
                }
            }
            try {
                boolean createNewFile = file.createNewFile();
                if (!createNewFile) {
                    throw new RuntimeException("文件创建失败");
                }
            } catch (IOException e) {
                throw new RuntimeException("文件创建失败");
            }
        }
    
        /**
         * 读取excel文件,返回文件第一个sheet页
         *
         * @param file excel文件
         * @return 第一个sheet页
         */
        public static Sheet readSheet(File file) {
            return readManySheet(file).get(0);
        }
    
        /**
         * 读取excel文件,返回文件第一个sheet页
         *
         * @param file excel文件
         * @return 第一个sheet页
         */
        public static Sheet readSheet(MultipartFile file) {
            return readManySheet(file).get(0);
        }
    
        /**
         * 读取excel文件,返回所有sheet页
         *
         * @param file excel文件
         * @return 所有sheet页
         */
        public static List<Sheet> readManySheet(File file) {
            Workbook workbook = getWorkBook(file);
            return getSheets(workbook);
        }
    
    
        /**
         * 读取excel文件,返回所有sheet页
         *
         * @param file excel文件
         * @return 所有sheet页
         */
        public static List<Sheet> readManySheet(MultipartFile file) {
            Workbook workbook = getWorkBook(file);
            return getSheets(workbook);
        }
    
    
        /**
         * 读取exel文件,返回第一个 sheet 页数据,以JSONArray格式返回(JSONArray中每个元素为每行数据)
         *
         * @param file       excel文件
         * @param containKey 是否需要包含键
         *                   (1)当为true时,JSONArray中每个元素为JSONObject对象,其中键为表头(默认第一行为表头),值为单元格数据,
         *                   例如:[{"k1":"v1","k2":"v2"},{"k1":"v3","k2":"v4"}]
         *                   (2)当为false时,JSONArray中每个元素为每行单元格数据,位置和文件打开时的视图一一对应
         *                   例如:[["k1","k2"],["v1","v2"],["v3","v4"]]
         * @return JSONArray对象
         */
        public static JSONArray read(File file, boolean containKey) {
            Sheet sheet = readSheet(file);
            return pares(sheet, containKey);
        }
    
        /**
         * 读取exel文件,返回第一个 sheet 页数据,以JSONArray格式返回(JSONArray中每个元素为每行数据)
         *
         * @param file       excel文件
         * @param containKey 是否需要包含键
         *                   (1)当为true时,JSONArray中每个元素为JSONObject对象,其中键为表头(默认第一行为表头),值为单元格数据,
         *                   例如:[{"k1":"v1","k2":"v2"},{"k1":"v3","k2":"v4"}]
         *                   (2)当为false时,JSONArray中每个元素为每行单元格数据,位置和文件打开时的视图一一对应
         *                   例如:[["k1","k2"],["v1","v2"],["v3","v4"]]
         * @return JSONArray对象
         */
        public static JSONArray read(MultipartFile file, boolean containKey) {
            Sheet sheet = readSheet(file);
            return pares(sheet, containKey);
        }
    
        /**
         * 读取excel文件,返回第一个sheet页数据,以List格式返回(默认第一行为表头)
         *
         * @param file excel文件
         * @param   泛型对象
         * @return List
         */
        public static <T> List<T> read(File file, Class<T> c) {
            Sheet sheet = readSheet(file);
            return pares(sheet, c);
        }
    
        /**
         * 读取excel文件,返回第一个sheet页数据,以List格式返回(默认第一行为表头)
         *
         * @param file excel文件
         * @param   泛型对象
         * @return List
         */
        public static <T> List<T> read(MultipartFile file, Class<T> c) {
            Sheet sheet = readSheet(file);
            return pares(sheet, c);
        }
    
    
        /**
         * 解析sheet数据,并返回Java对象
         *
         * @param sheet sheet对象
         * @param    泛型对象
         * @return java对象
         */
        public static <T> List<T> pares(Sheet sheet, Class<T> c) {
            if (Objects.isNull(sheet) || Objects.isNull(c) || sheet.getLastRowNum() < 0) {
                return Collections.emptyList();
            }
            // 取得该类所有打了@ExcelImport注解的属性
            List<ImportClassField> importFields = getImportFields(c);
            if (isEmpty(importFields)) {
                return Collections.emptyList();
            }
            // 如果有columnIndex属性,则优先按columnIndex进行解析(这个解析不需要表头)
            if (hasColumnIndex(importFields)) {
                return pares(sheet, c, importFields, 0);
            }
            // 存在表头的话,先补充其他字段的表头下标,再进行解析(因为最终都是按列进行解析的)
            for (Cell cell : sheet.getRow(0)) {
                String value = getCellStringValue(cell);
                if (!isEmpty(value)) {
                    for (ImportClassField field : importFields) {
                        if (field.getColumnIndex() < 0 && value.equals(field.getColumnName())) {
                            field.setColumnIndex(cell.getColumnIndex());
                        }
                    }
                }
            }
            return pares(sheet, c, importFields, 1);
        }
    
        /**
         * 解析sheet数据,并返回Java对象
         *
         * @param sheet         sheet对象
         * @param importFields  本次需要解析的列
         * @param rowStartIndex 开始解析的行数
         * @param            泛型对象
         * @return Java对象
         */
        private static <T> List<T> pares(Sheet sheet, Class<T> c, List<ImportClassField> importFields, int rowStartIndex) {
            // 属性按表头索引进行区分
            Map<Integer, ImportClassField> indexFieldMap = new LinkedHashMap<>();
            for (ImportClassField field : importFields) {
                if (field.getColumnIndex() >= 0) {
                    indexFieldMap.put(field.getColumnIndex(), field);
                }
            }
            // 开始解析行下标不可大于sheet最大行下标
            int lastRowNum = sheet.getLastRowNum();
            if (rowStartIndex > lastRowNum) {
                return Collections.emptyList();
            }
            // 开始解析
            List<T> ts = new ArrayList<>();
            for (int i = rowStartIndex; i <= lastRowNum; i++) {
                Row row = sheet.getRow(i);
                // 判断是否为空行,非空行才进行数据解析
                if (!isBlankRow(row)) {
                    try {
                        T t = getBean(c, row, indexFieldMap);
                        ts.add(t);
                    } catch (Exception e) {
                        throw new RuntimeException(e.getMessage(), e);
                    }
                }
            }
            return ts;
        }
    
        /**
         * 行数据转化为java对象
         *
         * @param c             java对象类
         * @param row           行数据对象
         * @param indexFieldMap 关联的属性
         * @return java对象
         */
        private static <T> T getBean(Class<T> c, Row row, Map<Integer, ImportClassField> indexFieldMap) throws Exception {
            T t = c.newInstance();
            for (Map.Entry<Integer, ImportClassField> entry : indexFieldMap.entrySet()) {
                int columnIndex = entry.getKey();
                ImportClassField field = entry.getValue();
                Cell cell = row.getCell(columnIndex);
                setFieldValue(t, cell, field);
            }
            return t;
        }
    
        /**
         * 对象属性赋值
         *
         * @param t    java对象
         * @param cell 单元格对象
         */
        private static <T> void setFieldValue(T t, Cell cell, ImportClassField importClassField) {
            String value = getCellStringValue(cell);
            if (isEmpty(value)) {
                return;
            }
            // 映射取值
            LinkedHashMap<String, String> kvMap = importClassField.getKvMap();
            if (!kvMap.isEmpty()) {
                for (Map.Entry<String, String> entry : kvMap.entrySet()) {
                    if (entry.getValue().equals(value)) {
                        value = entry.getKey();
                        break;
                    }
                }
            }
            Field field = importClassField.getField();
            Class<?> type = field.getType();
            try {
                // 按照使用频率优先级赋值判断(包含: String + 8大基本数据类型 + Date + BigDecimal)
                if (type == String.class) {
                    field.set(t, value);
                } else if (type == int.class || type == Integer.class) {
                    field.set(t, Integer.parseInt(value));
                } else if (type == long.class || type == Long.class) {
                    field.set(t, Long.parseLong(value));
                } else if (type == Date.class) {
                    String dateFormat = importClassField.getDateFormat();
                    Date date = getDate(cell, dateFormat);
                    field.set(t, date);
                } else if (type == double.class || type == Double.class) {
                    field.set(t, Double.parseDouble(value));
                } else if (type == boolean.class || type == Boolean.class) {
                    field.set(t, Boolean.parseBoolean(value));
                } else if (type == BigDecimal.class) {
                    field.set(t, new BigDecimal(value));
                } else if (type == float.class || type == Float.class) {
                    field.set(t, Float.parseFloat(value));
                } else if (type == short.class || type == Short.class) {
                    field.set(t, Short.parseShort(value));
                } else if (type == char.class || type == Character.class) {
                    field.set(t, value.charAt(0));
                } else if (type == byte.class || type == Byte.class) {
                    field.set(t, Byte.parseByte(value));
                }
                // 其他数据类型根据需要自行添加处理
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 读取时间类型
         *
         * @return 时间
         */
        private static Date getDate(Cell cell, String dateFormat) {
            CellType cellType = cell.getCellType();
            // 如果单元格自己能够识别这是一个date类型数据,则忽略用户格式样式,直接返回
            if (DateUtil.isCellDateFormatted(cell)) {
                return cell.getDateCellValue();
            }
            // 获取表格数据
            String value = getCellStringValue(cell);
            // 如果是纯数字,则为时间戳
            if (Pattern.matches("\\d+", value)) {
                long timestamp = Long.parseLong(value);
                // 长度大于10的时候,则为毫秒级时间戳,毫秒级时间戳需要去掉最后三位
                if (value.length() > 10) {
                    timestamp = timestamp / 1000;
                }
                return new Date(timestamp);
            }
            // 下面按照时间格式进行格式化(如果用户填写了时间格式,则优先以用户格式进行校验)
            Date date = null;
            if (!isEmpty(dateFormat)) {
                date = parseDate(value, dateFormat);
            }
            // 尝试时间是否为yyyy-MM-dd HH:mm:ss格式
            if (Objects.isNull(date)) {
                date = parseDate(value, "yyyy-MM-dd HH:mm:ss");
            }
            // 尝试时间是否为yyyy-MM-dd格式
            if (Objects.isNull(date)) {
                date = parseDate(value, "yyyy-MM-dd");
            }
            // 如果还想进行其他时间解析尝试,可以在此自行添加
            return date;
        }
    
        /**
         * 将字符串格式日期转化为Date时间
         *
         * @param dateStr     字符串日期
         * @param formatStyle 格式化样式
         * @return Date时间
         */
        private static Date parseDate(String dateStr, String formatStyle) {
            try {
                return new SimpleDateFormat(formatStyle).parse(dateStr);
            } catch (ParseException e) {
                return null;
            }
        }
    
        /**
         * 判断是否为空行
         *
         * @param row 当前行数据
         * @return true-是空行,false-不是空行
         */
        private static boolean isBlankRow(Row row) {
            for (Cell cell : row) {
                if (!isEmpty(getCellStringValue(cell))) {
                    return false;
                }
            }
            return true;
        }
    
        /**
         * 判断本次导入的对象中是否存在按索性进行解析的
         *
         * @param importFields 本次导入对象的属性
         * @return true-存在;false-不存在
         */
        private static boolean hasColumnIndex(List<ImportClassField> importFields) {
            for (ImportClassField field : importFields) {
                if (field.getColumnIndex() >= 0) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 获取指定类中所有打了ExcelImport注解的属性(包括父类、祖类)
         *
         * @param c 指定类
         * @return 该类所有打了ExcelImport注解的属性(包括父类、祖类)
         */
        private static List<ImportClassField> getImportFields(Class<?> c) {
            List<ImportClassField> excelImportClassFields = new ArrayList<>();
            List<Field> allClassFields = getAllFields(c);
            for (Field field : allClassFields) {
                ExcelImport excelImport = field.getAnnotation(ExcelImport.class);
                if (Objects.nonNull(excelImport)) {
                    field.setAccessible(true);
                    ImportClassField importField = new ImportClassField();
                    importField.setClassFieldName(field.getName());
                    String name = excelImport.name();
                    if (ExcelUtils.isEmpty(name)) {
                        name = excelImport.value();
                    }
                    importField.setColumnName(name);
                    importField.setColumnIndex(excelImport.columnIndex());
                    importField.setDateFormat(excelImport.dateFormat());
                    LinkedHashMap<String, String> kvMap = new LinkedHashMap<>();
                    KV[] kvs = excelImport.kvs();
                    for (KV kv : kvs) {
                        kvMap.put(kv.k(), kv.v());
                    }
                    importField.setKvMap(kvMap);
                    importField.setField(field);
                    excelImportClassFields.add(importField);
                }
            }
            return excelImportClassFields;
        }
    
    
        /**
         * 获取一个类的所有属性(包括这个类的父类属性,祖类属性)
         *
         * @param c 所在类
         * @return 类的所有属性
         */
        private static List<Field> getAllFields(Class<?> c) {
            List<Field> allFields = new ArrayList<>();
            while (Objects.nonNull(c) && c != Object.class) {
                allFields.addAll(Arrays.asList(c.getDeclaredFields()));
                c = c.getSuperclass();
            }
            return allFields;
        }
    
        /**
         * 解析sheet数据,并返回JSONArray对象
         *
         * @param sheet      sheet对象
         * @param containKey 是否需要包含键
         *                   (1)当为true时,JSONArray中每个元素为JSONObject对象,其中键为表头(默认第一行为表头),值为单元格数据,
         *                   例如:[{"k1":"v1","k2":"v2"},{"k1":"v3","k2":"v4"}]
         *                   (2)当为false时,JSONArray中每个元素为每行单元格数据,位置和文件打开时的视图一一对应
         *                   例如:[["k1","k2"],["v1","v2"],["v3","v4"]]
         * @return JSONArray对象
         */
        public static JSONArray pares(Sheet sheet, boolean containKey) {
            if (Objects.isNull(sheet) || sheet.getLastRowNum() < 0) {
                return new JSONArray();
            }
            return containKey ? paresJsonArrayWithKey(sheet) : paresJsonArrayNoKey(sheet);
        }
    
        /**
         * 解析sheet数据,并返回JSONArray对象,其中键为表头(默认第一行为表头),值为单元格数据,
         * 例如:[{"k1":"v1","k2":"v2"},{"k1":"v3","k2":"v4"}]
         *
         * @param sheet sheet对象
         * @return 包含键的JSONArray对象
         */
        private static JSONArray paresJsonArrayWithKey(Sheet sheet) {
            // 第一行默认为表头,如果表头无有效数据,则不解析
            JSONArray array = new JSONArray();
            Row header = sheet.getRow(0);
            List<Integer> noEmptyHeaderIndexes = getNoEmptyHeaderIndexes(header);
            if (isEmpty(noEmptyHeaderIndexes)) {
                return array;
            }
            // 如果只有一行数据时,则默认该行为表头行,无数据行
            int lastRowIndex = sheet.getLastRowNum();
            if (lastRowIndex == 0) {
                JSONObject rowObj = getOrderlyJsonObject();
                for (Integer headerIndex : noEmptyHeaderIndexes) {
                    String k = getCellStringValue(header.getCell(headerIndex));
                    rowObj.put(k, "");
                }
                array.add(rowObj);
                return array;
            }
            // 存在多行数据时,则第一行为表头,其他行为数据
            for (int i = 1; i < lastRowIndex; i++) {
                Row row = sheet.getRow(i);
                JSONObject rowObj = getOrderlyJsonObject();
                for (Integer headerIndex : noEmptyHeaderIndexes) {
                    String k = getCellStringValue(header.getCell(headerIndex));
                    String v = getCellStringValue(row.getCell(headerIndex));
                    rowObj.put(k, v);
                }
                array.add(rowObj);
            }
            return array;
        }
    
        /**
         * 获取有序的JSONObject对象
         *
         * @return 有序的JSONObject对象
         */
        private static JSONObject getOrderlyJsonObject() {
            return new JSONObject(new LinkedHashMap<>());
        }
    
        /**
         * 解析sheet数据,并返回JSONArray对象
         *
         * @param sheet sheet对象
         * @return 包含键的JSONArray对象
         */
        private static JSONArray paresJsonArrayNoKey(Sheet sheet) {
            JSONArray array = new JSONArray();
            for (Row row : sheet) {
                JSONArray rowArray = new JSONArray();
                for (Cell cell : row) {
                    rowArray.add(getCellStringValue(cell));
                }
                array.add(rowArray);
            }
            return array;
        }
    
        /**
         * 获取表头不为空的下标集合
         *
         * @param header 表头行
         * @return 表头不为空的下标集合
         */
        private static List<Integer> getNoEmptyHeaderIndexes(Row header) {
            List<Integer> noEmptyHeaderIndexes = new ArrayList<>();
            for (int i = 0; i < header.getLastCellNum(); i++) {
                Cell cell = header.getCell(i);
                String cellValue = getCellStringValue(cell);
                if (!isEmpty(cellValue)) {
                    noEmptyHeaderIndexes.add(i);
                }
            }
            return noEmptyHeaderIndexes;
        }
    
        /**
         * 获取单元格数据
         *
         * @param cell 单元格对象
         * @return 单元格值(以字符串形式返回,如果是时间,则以时间戳格式返回)
         */
        private static String getCellStringValue(Cell cell) {
            if (Objects.isNull(cell) || Objects.isNull(cell.getCellType())) {
                return "";
            }
            CellType cellType = cell.getCellType();
            switch (cellType) {
                case NUMERIC:
                    if (DateUtil.isCellDateFormatted(cell)) {
                        return getCellDateString(cell);
                    } else {
                        return new DecimalFormat("0.####################").format(cell.getNumericCellValue());
                    }
                case STRING:
                    return cell.getRichStringCellValue().getString();
                case BOOLEAN:
                    return String.valueOf(cell.getBooleanCellValue());
                case BLANK:
                    return "";
                case ERROR:
                    return FormulaError.forInt(cell.getErrorCellValue()).getString();
                default:
                    return new DataFormatter().formatCellValue(cell);
            }
        }
    
        /**
         * 日期单元格解析(格式统一按照国人的习惯显示,即 yyyy-MM-dd HH:mm:ss)
         *
         * @param cell 单元格对象
         * @return 字符串时间
         */
        private static String getCellDateString(Cell cell) {
            String dataFormatString = cell.getCellStyle().getDataFormatString();
            Date date = cell.getDateCellValue();
            String formatStr;
            if (dataFormatString.contains("y") && dataFormatString.contains("d")) {
                if (dataFormatString.contains("h")) {
                    formatStr = "yyyy-MM-dd HH:mm:ss";
                } else {
                    formatStr = "yyyy-MM-dd";
                }
            } else {
                formatStr = dataFormatString;
            }
            return new SimpleDateFormat(formatStr).format(date);
        }
    
        private static Workbook getWorkBook(File file) {
            try {
                if (Objects.isNull(file) || !file.exists()) {
                    throw new RuntimeException("文件不能为空");
                }
                String fileName = file.getName();
                if (fileName.endsWith(ExcelType.XLSX.val)) {
                    return new XSSFWorkbook(file);
                } else if (fileName.endsWith(ExcelType.XLS.val)) {
                    return new HSSFWorkbook(Files.newInputStream(file.toPath()));
                } else {
                    throw new RuntimeException("文件格式错误");
                }
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    
        private static Workbook getWorkBook(MultipartFile file) {
            try {
                if (Objects.isNull(file) || file.isEmpty()) {
                    throw new RuntimeException("文件不能为空");
                }
                String fileName = file.getOriginalFilename();
                assert fileName != null;
                if (fileName.endsWith(ExcelType.XLSX.val)) {
                    return new XSSFWorkbook(file.getInputStream());
                } else if (fileName.endsWith(ExcelType.XLS.val)) {
                    return new HSSFWorkbook(file.getInputStream());
                } else {
                    throw new RuntimeException("文件格式错误");
                }
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    
        private static List<Sheet> getSheets(Workbook workbook) {
            int maxSheet = workbook.getNumberOfSheets();
            List<Sheet> sheets = new ArrayList<>();
            for (int i = 0; i < maxSheet; i++) {
                sheets.add(workbook.getSheetAt(i));
            }
            return sheets;
        }
    
        /**
         * excel文件类型
         */
        enum ExcelType {
            // excel 2007 +
            XLSX(".xlsx"),
            // excel 2007 -
            XLS(".xls");
    
            public final String val;
    
            ExcelType(String val) {
                this.val = val;
            }
        }
    
        @Target(ElementType.FIELD)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface ExcelImport {
    
            @AliasFor(value = "name")
            String value() default "";
    
            @AliasFor(value = "value")
            String name() default "";
    
            // 键值对集合
            KV[] kvs() default {};
    
            /**
             * 时间样式
             * 当该值无值时,则智能判断(智能判定可能会出错,建议指定格式)
             * 当该值为固定值 ts 时(TimestampSecond),则表示表格中数据为秒级时间戳
             * 当该值为固定值 tms 时(TimestampMillisecond),则表示表格中数据为毫秒级时间戳
             * 当该值为其他值时,则表示表格中数据格式
             */
            String dateFormat() default "yyyy-MM-dd HH:mm:ss";
    
            // 列索引(如果有该值大于0,则以该值解析为准,优先级高于value字段)
            int columnIndex() default -1;
        }
    
    
        @Target(value = {ElementType.FIELD})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface ExcelExport {
    
            @AliasFor(value = "name")
            String value() default "";
    
            @AliasFor(value = "value")
            String name() default "";
    
            // 键值对集合
            KV[] kvs() default {};
    
            /**
             * 时间样式
             * 当该值无值时,则智能判断(智能判定可能会出错,建议指定格式)
             * 当该值为固定值 ts 时(TimestampSecond),则表示表格中数据为秒级时间戳
             * 当该值为固定值 tms 时(TimestampMillisecond),则表示表格中数据为毫秒级时间戳
             * 当该值为其他值时,则表示表格中数据格式
             */
            String dateFormat() default "yyyy-MM-dd HH:mm:ss";
    
            // 列索引(列排序,默认 -1 不指定,默认按属性位置顺序排列,当设置为 0 时,则为第 1 列)
            int columnIndex() default -1;
    
            // 批注,如果要换行请用 \r\n 代替
            String comment() default "";
    
            // 行宽
            int columnWidth() default 0;
    
        }
    
        @Target(value = {ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface ExcelSheet {
            // sheet名称
            @AliasFor(value = "name")
            String value() default "";
    
            @AliasFor(value = "value")
            String name() default "";
    
            // 是否冻结表头所在行
            boolean freezeHeader() default false;
    
            // 水印
            Watermark watermark() default @Watermark();
    
            // 列宽
            int columnWidth() default 15;
    
            // 是否忽略表头
            boolean ignoreHeader() default false;
        }
    
        public @interface Watermark {
    
            /**
             * 1-文本文字水印;
             * 2-本地图片水印;
             * 3-网络图片水印;
             * 更多查看 {@link WatermarkType#code}
             */
            int type() default 0;
    
            /**
             * 当type=1时:src为文字内容,如:xxx科技有效公司
             * 当type=2时:src为本地文件路径,如:D:\img\icon.png
             * 当type=3时:src为网络图片水印,如:https://profile-avatar.csdnimg.cn/624a0ef43e224cb2bf5ffbcca1211e51_sunnyzyq.jpg
             */
            String src() default "";
    
        }
    
        public enum WatermarkType {
    
            NONE(0, "无水印"),
            TEXT(1, "文本文字水印"),
            FILE(2, "本地图片水印"),
            URL(3, "网络图片水印");
    
            public final int code;
            public final String name;
    
            WatermarkType(int code, String name) {
                this.code = code;
                this.name = name;
            }
    
            public int getCode() {
                return code;
            }
    
            public String getName() {
                return name;
            }
    
            public static boolean containCode(int code) {
                return NONE.code == code || TEXT.code == code || FILE.code == code || URL.code == code;
            }
    
        }
    
        @Retention(RetentionPolicy.RUNTIME)
        public @interface KV {
            // 枚举键
            String k();
    
            // 枚举值
            String v();
        }
    
        /**
         * 导入注解属性参数
         */
        @Data
        static class ImportClassField {
            // field 原属性
            private Field field;
            // 字段名称
            private String classFieldName;
            // 列名称
            private String columnName;
            // 列索引
            private int columnIndex = -1;
            // 时间格式
            private String dateFormat;
            // 键值对
            private LinkedHashMap<String, String> kvMap;
            // 列宽
            private int columnWidth;
        }
    
        /**
         * 导出注解属性参数
         */
        @Data
        static class ExportClassField {
            // 字段名称
            private String classFieldName;
            // excel表头名称
            private String columnName;
            // exel列索引
            private int columnIndex = -1;
            // 列宽
            private int columnWidth;
            // 时间格式
            private String dateFormat;
            // 批注
            private String comment;
            // 键值对
            private LinkedHashMap<String, String> kvMap;
            // field 原属性
            private Field field;
        }
    
        @Data
        public static class ExportWorkBook {
            // 文件名称(不用带文件尾缀)
            private String name;
            // 页数据
            private List<ExportSheet> sheets;
        }
    
        /**
         * 导出模板
         */
        @Data
       public static class ExportSheet {
            // 页名称
            private String name;
            // 表头样式
            private ExportCellStyle headerStyle;
            // 数据样式
            private ExportCellStyle dataStyle;
            // 数据
            private List<ExportRow> rows;
            // 批注
            private List<ExportComment> comments;
            // 水印
            private ExportWatermark watermark;
            // 是否冻结表头所在行
            private boolean freezeHeader;
            // 全局列宽
            private int columnWidth;
            // 是否忽略表头
            private boolean ignoreHeader;
            // 指定列宽
            private Map<Integer, Integer> columnIndexWidthMap;
            // 页其他设置
            private List<ExportCellMerge> merges;
        }
    
        public static class ExportSheetFactory {
            public static <T> ExportSheet createExportSheet(List<T> list, Class<T> c, List<ExportCellMerge> merges) {
                // 类名注解
                ExcelSheet excelSheet = c.getAnnotation(ExcelSheet.class);
                String name = "";
                boolean freezeHeader = false;
                ExportWatermark watermark = null;
                int columnWidth = 0;
                boolean ignoreHeader = false;
                if (excelSheet != null) {
                    name = excelSheet.name();
                    if (ExcelUtils.isEmpty(name)) {
                        name = excelSheet.value();
                    }
                    freezeHeader = excelSheet.freezeHeader();
                    ignoreHeader = excelSheet.ignoreHeader();
                    Watermark wmk = excelSheet.watermark();
                    watermark = new ExportWatermark();
                    watermark.setType(wmk.type());
                    watermark.setSrc(wmk.src());
                    columnWidth = excelSheet.columnWidth();
                }
                // 属性注解
                List<ExportClassField> exportFields = filterExportFields(c);
                Map<Integer, Integer> columnIndexWidthMap = getSheetColumnIndexWidthMap(exportFields);
                List<ExportRow> rows = getSheetRows(exportFields, list, ignoreHeader);
                List<ExportComment> comments = getSheetComments(exportFields);
                // 构建导出Sheet对象
                ExportSheet mySheet = new ExportSheet();
                mySheet.setIgnoreHeader(ignoreHeader);
                mySheet.setName(name);
                mySheet.setHeaderStyle(new ExportCellStyle());
                mySheet.setDataStyle(new ExportCellStyle());
                mySheet.setRows(rows);
                mySheet.setComments(comments);
                mySheet.setWatermark(watermark);
                mySheet.setFreezeHeader(freezeHeader);
                mySheet.setColumnWidth(columnWidth);
                mySheet.setColumnIndexWidthMap(columnIndexWidthMap);
                mySheet.setMerges(merges);
                return mySheet;
            }
    
            private static Map<Integer, Integer> getSheetColumnIndexWidthMap(List<ExportClassField> exportFields) {
                Map<Integer, Integer> columnIndexWidthMap = new HashMap<>();
                for (int i = 0; i < exportFields.size(); i++) {
                    columnIndexWidthMap.put(i, exportFields.get(i).getColumnWidth());
                }
                return columnIndexWidthMap;
            }
    
            private static List<ExportClassField> filterExportFields(Class<?> c) {
                List<ExportClassField> excelImportClassFields = new ArrayList<>();
                List<Field> allClassFields = ExcelUtils.getAllFields(c);
                for (Field field : allClassFields) {
                    ExcelExport ex = field.getAnnotation(ExcelExport.class);
                    if (Objects.nonNull(ex)) {
                        field.setAccessible(true);
                        ExportClassField exportField = new ExportClassField();
                        exportField.setClassFieldName(field.getName());
                        String name = ex.name();
                        if (ExcelUtils.isEmpty(name)) {
                            name = ex.value();
                        }
                        exportField.setColumnName(name);
                        exportField.setColumnIndex(ex.columnIndex());
                        exportField.setColumnWidth(ex.columnWidth());
                        exportField.setDateFormat(ex.dateFormat());
                        exportField.setComment(ex.comment());
                        LinkedHashMap<String, String> kvMap = new LinkedHashMap<>();
                        KV[] kvs = ex.kvs();
                        for (KV kv : kvs) {
                            kvMap.put(kv.k(), kv.v());
                        }
                        exportField.setKvMap(kvMap);
                        exportField.setField(field);
                        excelImportClassFields.add(exportField);
                    }
                }
                // 按 columnIndex 排序(小的在前,大的在后)
                excelImportClassFields.sort(Comparator.comparingInt(ExportClassField::getColumnIndex));
                return excelImportClassFields;
            }
    
            private static <T> List<ExportRow> getSheetRows(List<ExportClassField> exportFields, List<T> list, boolean ignoreHeader) {
                List<ExportRow> rows = new ArrayList<>();
                if (!ignoreHeader) {
                    rows.addAll(initHeaderRows(exportFields));
                }
                rows.addAll(initDataRows(exportFields, list));
                return rows;
            }
    
            private static List<ExportRow> initHeaderRows(List<ExportClassField> exportFields) {
                List<ExportCell> headerCells = new ArrayList<>();
                for (ExportClassField cf : exportFields) {
                    ExportCell cell = new ExportCell();
                    cell.setValue(cf.getColumnName());
                    headerCells.add(cell);
                }
                ExportRow headerRow = new ExportRow();
                headerRow.setHeader(true);
                headerRow.setCells(headerCells);
                List<ExportRow> headerRows = new ArrayList<>();
                headerRows.add(headerRow);
                return headerRows;
            }
    
            private static <T> List<ExportRow> initDataRows(List<ExportClassField> exportFields, List<T> list) {
                List<ExportRow> dataRows = new ArrayList<>();
                for (T t : list) {
                    List<ExportCell> dataCells = new ArrayList<>();
                    for (ExportClassField cf : exportFields) {
                        ExportCell cell = new ExportCell();
                        cell.setDateFormat(cf.getDateFormat());
                        try {
                            Object o = cf.getField().get(t);
                            // 判断该属性是否有映射,如果有映射,则转化为映射值
                            if (!cf.getKvMap().isEmpty() && o != null) {
                                System.out.println("cf.getKvMap() = " + cf.getKvMap());
                                System.out.println("o = " + o);
                                o = cf.getKvMap().get(o.toString());
                            }
                            cell.setValue(o);
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException("获取属性值失败: " + cf.getField().getName(), e);
                        }
                        dataCells.add(cell);
                    }
                    ExportRow dataRow = new ExportRow();
                    dataRow.setHeader(false);
                    dataRow.setCells(dataCells);
                    dataRows.add(dataRow);
                }
                return dataRows;
            }
    
            private static List<ExportComment> getSheetComments(List<ExportClassField> exportFields) {
                List<ExportComment> comments = new ArrayList<>();
                for (int i = 0; i < exportFields.size(); i++) {
                    ExportClassField cf = exportFields.get(i);
                    if (!isEmpty(cf.getComment())) {
                        ExportComment comment = new ExportComment();
                        comment.setX(i);
                        comment.setY(0);
                        comment.setComment(cf.getComment());
                        comments.add(comment);
                    }
                }
                return comments;
            }
        }
    
        @Data
        public static class ExportRow {
            private boolean header;
            private List<ExportCell> cells;
    
            public ExportRow appendCells(Object... values) {
                for (Object value : values) {
                    appendCell(value, null);
                }
                return this;
            }
    
            public ExportRow appendCell(Object value) {
                appendCell(value, null);
                return this;
            }
    
            public ExportRow appendCell(Object value, String dateFormat) {
                if (cells == null) {
                    cells = new ArrayList<>();
                }
                ExportCell cell = new ExportCell();
                cell.setValue(value);
                cell.setDateFormat(dateFormat);
                cells.add(cell);
                return this;
            }
        }
    
        @Data
        public static class ExportCellStyle {
            // 背景颜色
            private short backgroundColor;
    
        }
    
        @Data
        static
        public class ExportCell {
            private Object value;
            private String dateFormat;
        }
    
        @Data
        public static class ExportComment {
            // 横坐标
            private int x;
            // 纵坐标
            private int y;
            // 备注
            private String comment;
        }
    
        @Data
        public static class ExportWatermark {
    
            /**
             * 1-文本文字水印;
             * 2-本地图片水印;
             * 3-网络图片水印;
             * 更多查看 {@link WatermarkType#code}
             */
            private int type;
    
            /**
             * 当type=1时:src为文字内容,如:xxx科技有效公司
             * 当type=2时:src为本地文件路径,如:D:\img\icon.png
             * 当type=3时:src为网络图片水印,如:https://profile-avatar.csdnimg.cn/624a0ef43e224cb2bf5ffbcca1211e51_sunnyzyq.jpg
             */
            private String src;
    
        }
    
        @Data
        public static class ExportCellMerge {
    
            private int x1;
            private int y1;
    
            private int x2;
            private int y2;
    
            public ExportCellMerge(int x1, int y1, int x2, int y2) {
                this.x1 = x1;
                this.y1 = y1;
                this.x2 = x2;
                this.y2 = y2;
            }
    
        }
    
        public static String formatMergerString(int x1, int y1, int x2, int y2) {
            return String.format("%s,%s,%s,%s", x1, y1, x2, y2);
        }
    
    }
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486
    • 487
    • 488
    • 489
    • 490
    • 491
    • 492
    • 493
    • 494
    • 495
    • 496
    • 497
    • 498
    • 499
    • 500
    • 501
    • 502
    • 503
    • 504
    • 505
    • 506
    • 507
    • 508
    • 509
    • 510
    • 511
    • 512
    • 513
    • 514
    • 515
    • 516
    • 517
    • 518
    • 519
    • 520
    • 521
    • 522
    • 523
    • 524
    • 525
    • 526
    • 527
    • 528
    • 529
    • 530
    • 531
    • 532
    • 533
    • 534
    • 535
    • 536
    • 537
    • 538
    • 539
    • 540
    • 541
    • 542
    • 543
    • 544
    • 545
    • 546
    • 547
    • 548
    • 549
    • 550
    • 551
    • 552
    • 553
    • 554
    • 555
    • 556
    • 557
    • 558
    • 559
    • 560
    • 561
    • 562
    • 563
    • 564
    • 565
    • 566
    • 567
    • 568
    • 569
    • 570
    • 571
    • 572
    • 573
    • 574
    • 575
    • 576
    • 577
    • 578
    • 579
    • 580
    • 581
    • 582
    • 583
    • 584
    • 585
    • 586
    • 587
    • 588
    • 589
    • 590
    • 591
    • 592
    • 593
    • 594
    • 595
    • 596
    • 597
    • 598
    • 599
    • 600
    • 601
    • 602
    • 603
    • 604
    • 605
    • 606
    • 607
    • 608
    • 609
    • 610
    • 611
    • 612
    • 613
    • 614
    • 615
    • 616
    • 617
    • 618
    • 619
    • 620
    • 621
    • 622
    • 623
    • 624
    • 625
    • 626
    • 627
    • 628
    • 629
    • 630
    • 631
    • 632
    • 633
    • 634
    • 635
    • 636
    • 637
    • 638
    • 639
    • 640
    • 641
    • 642
    • 643
    • 644
    • 645
    • 646
    • 647
    • 648
    • 649
    • 650
    • 651
    • 652
    • 653
    • 654
    • 655
    • 656
    • 657
    • 658
    • 659
    • 660
    • 661
    • 662
    • 663
    • 664
    • 665
    • 666
    • 667
    • 668
    • 669
    • 670
    • 671
    • 672
    • 673
    • 674
    • 675
    • 676
    • 677
    • 678
    • 679
    • 680
    • 681
    • 682
    • 683
    • 684
    • 685
    • 686
    • 687
    • 688
    • 689
    • 690
    • 691
    • 692
    • 693
    • 694
    • 695
    • 696
    • 697
    • 698
    • 699
    • 700
    • 701
    • 702
    • 703
    • 704
    • 705
    • 706
    • 707
    • 708
    • 709
    • 710
    • 711
    • 712
    • 713
    • 714
    • 715
    • 716
    • 717
    • 718
    • 719
    • 720
    • 721
    • 722
    • 723
    • 724
    • 725
    • 726
    • 727
    • 728
    • 729
    • 730
    • 731
    • 732
    • 733
    • 734
    • 735
    • 736
    • 737
    • 738
    • 739
    • 740
    • 741
    • 742
    • 743
    • 744
    • 745
    • 746
    • 747
    • 748
    • 749
    • 750
    • 751
    • 752
    • 753
    • 754
    • 755
    • 756
    • 757
    • 758
    • 759
    • 760
    • 761
    • 762
    • 763
    • 764
    • 765
    • 766
    • 767
    • 768
    • 769
    • 770
    • 771
    • 772
    • 773
    • 774
    • 775
    • 776
    • 777
    • 778
    • 779
    • 780
    • 781
    • 782
    • 783
    • 784
    • 785
    • 786
    • 787
    • 788
    • 789
    • 790
    • 791
    • 792
    • 793
    • 794
    • 795
    • 796
    • 797
    • 798
    • 799
    • 800
    • 801
    • 802
    • 803
    • 804
    • 805
    • 806
    • 807
    • 808
    • 809
    • 810
    • 811
    • 812
    • 813
    • 814
    • 815
    • 816
    • 817
    • 818
    • 819
    • 820
    • 821
    • 822
    • 823
    • 824
    • 825
    • 826
    • 827
    • 828
    • 829
    • 830
    • 831
    • 832
    • 833
    • 834
    • 835
    • 836
    • 837
    • 838
    • 839
    • 840
    • 841
    • 842
    • 843
    • 844
    • 845
    • 846
    • 847
    • 848
    • 849
    • 850
    • 851
    • 852
    • 853
    • 854
    • 855
    • 856
    • 857
    • 858
    • 859
    • 860
    • 861
    • 862
    • 863
    • 864
    • 865
    • 866
    • 867
    • 868
    • 869
    • 870
    • 871
    • 872
    • 873
    • 874
    • 875
    • 876
    • 877
    • 878
    • 879
    • 880
    • 881
    • 882
    • 883
    • 884
    • 885
    • 886
    • 887
    • 888
    • 889
    • 890
    • 891
    • 892
    • 893
    • 894
    • 895
    • 896
    • 897
    • 898
    • 899
    • 900
    • 901
    • 902
    • 903
    • 904
    • 905
    • 906
    • 907
    • 908
    • 909
    • 910
    • 911
    • 912
    • 913
    • 914
    • 915
    • 916
    • 917
    • 918
    • 919
    • 920
    • 921
    • 922
    • 923
    • 924
    • 925
    • 926
    • 927
    • 928
    • 929
    • 930
    • 931
    • 932
    • 933
    • 934
    • 935
    • 936
    • 937
    • 938
    • 939
    • 940
    • 941
    • 942
    • 943
    • 944
    • 945
    • 946
    • 947
    • 948
    • 949
    • 950
    • 951
    • 952
    • 953
    • 954
    • 955
    • 956
    • 957
    • 958
    • 959
    • 960
    • 961
    • 962
    • 963
    • 964
    • 965
    • 966
    • 967
    • 968
    • 969
    • 970
    • 971
    • 972
    • 973
    • 974
    • 975
    • 976
    • 977
    • 978
    • 979
    • 980
    • 981
    • 982
    • 983
    • 984
    • 985
    • 986
    • 987
    • 988
    • 989
    • 990
    • 991
    • 992
    • 993
    • 994
    • 995
    • 996
    • 997
    • 998
    • 999
    • 1000
    • 1001
    • 1002
    • 1003
    • 1004
    • 1005
    • 1006
    • 1007
    • 1008
    • 1009
    • 1010
    • 1011
    • 1012
    • 1013
    • 1014
    • 1015
    • 1016
    • 1017
    • 1018
    • 1019
    • 1020
    • 1021
    • 1022
    • 1023
    • 1024
    • 1025
    • 1026
    • 1027
    • 1028
    • 1029
    • 1030
    • 1031
    • 1032
    • 1033
    • 1034
    • 1035
    • 1036
    • 1037
    • 1038
    • 1039
    • 1040
    • 1041
    • 1042
    • 1043
    • 1044
    • 1045
    • 1046
    • 1047
    • 1048
    • 1049
    • 1050
    • 1051
    • 1052
    • 1053
    • 1054
    • 1055
    • 1056
    • 1057
    • 1058
    • 1059
    • 1060
    • 1061
    • 1062
    • 1063
    • 1064
    • 1065
    • 1066
    • 1067
    • 1068
    • 1069
    • 1070
    • 1071
    • 1072
    • 1073
    • 1074
    • 1075
    • 1076
    • 1077
    • 1078
    • 1079
    • 1080
    • 1081
    • 1082
    • 1083
    • 1084
    • 1085
    • 1086
    • 1087
    • 1088
    • 1089
    • 1090
    • 1091
    • 1092
    • 1093
    • 1094
    • 1095
    • 1096
    • 1097
    • 1098
    • 1099
    • 1100
    • 1101
    • 1102
    • 1103
    • 1104
    • 1105
    • 1106
    • 1107
    • 1108
    • 1109
    • 1110
    • 1111
    • 1112
    • 1113
    • 1114
    • 1115
    • 1116
    • 1117
    • 1118
    • 1119
    • 1120
    • 1121
    • 1122
    • 1123
    • 1124
    • 1125
    • 1126
    • 1127
    • 1128
    • 1129
    • 1130
    • 1131
    • 1132
    • 1133
    • 1134
    • 1135
    • 1136
    • 1137
    • 1138
    • 1139
    • 1140
    • 1141
    • 1142
    • 1143
    • 1144
    • 1145
    • 1146
    • 1147
    • 1148
    • 1149
    • 1150
    • 1151
    • 1152
    • 1153
    • 1154
    • 1155
    • 1156
    • 1157
    • 1158
    • 1159
    • 1160
    • 1161
    • 1162
    • 1163
    • 1164
    • 1165
    • 1166
    • 1167
    • 1168
    • 1169
    • 1170
    • 1171
    • 1172
    • 1173
    • 1174
    • 1175
    • 1176
    • 1177
    • 1178
    • 1179
    • 1180
    • 1181
    • 1182
    • 1183
    • 1184
    • 1185
    • 1186
    • 1187
    • 1188
    • 1189
    • 1190
    • 1191
    • 1192
    • 1193
    • 1194
    • 1195
    • 1196
    • 1197
    • 1198
    • 1199
    • 1200
    • 1201
    • 1202
    • 1203
    • 1204
    • 1205
    • 1206
    • 1207
    • 1208
    • 1209
    • 1210
    • 1211
    • 1212
    • 1213
    • 1214
    • 1215
    • 1216
    • 1217
    • 1218
    • 1219
    • 1220
    • 1221
    • 1222
    • 1223
    • 1224
    • 1225
    • 1226
    • 1227
    • 1228
    • 1229
    • 1230
    • 1231
    • 1232
    • 1233
    • 1234
    • 1235
    • 1236
    • 1237
    • 1238
    • 1239
    • 1240
    • 1241
    • 1242
    • 1243
    • 1244
    • 1245
    • 1246
    • 1247
    • 1248
    • 1249
    • 1250
    • 1251
    • 1252
    • 1253
    • 1254
    • 1255
    • 1256
    • 1257
    • 1258
    • 1259
    • 1260
    • 1261
    • 1262
    • 1263
    • 1264
    • 1265
    • 1266
    • 1267
    • 1268
    • 1269
    • 1270
    • 1271
    • 1272
    • 1273
    • 1274
    • 1275
    • 1276
    • 1277
    • 1278
    • 1279
    • 1280
    • 1281
    • 1282
    • 1283
    • 1284
    • 1285
    • 1286
    • 1287
    • 1288
    • 1289
    • 1290
    • 1291
    • 1292
    • 1293
    • 1294
    • 1295
    • 1296
    • 1297
    • 1298
    • 1299
    • 1300
    • 1301
    • 1302
    • 1303
    • 1304
    • 1305
    • 1306
    • 1307
    • 1308
    • 1309
    • 1310
    • 1311
    • 1312
    • 1313
    • 1314
    • 1315
    • 1316
    • 1317
    • 1318
    • 1319
    • 1320
    • 1321
    • 1322
    • 1323
    • 1324
    • 1325
    • 1326
    • 1327
    • 1328
    • 1329
    • 1330
    • 1331
    • 1332
    • 1333
    • 1334
    • 1335
    • 1336
    • 1337
    • 1338
    • 1339
    • 1340
    • 1341
    • 1342
    • 1343
    • 1344
    • 1345
    • 1346
    • 1347
    • 1348
    • 1349
    • 1350
    • 1351
    • 1352
    • 1353
    • 1354
    • 1355
    • 1356
    • 1357
    • 1358
    • 1359
    • 1360
    • 1361
    • 1362
    • 1363
    • 1364
    • 1365
    • 1366
    • 1367
    • 1368
    • 1369
    • 1370
    • 1371
    • 1372
    • 1373
    • 1374
    • 1375
    • 1376
    • 1377
    • 1378
    • 1379
    • 1380
    • 1381
    • 1382
    • 1383
    • 1384
    • 1385
    • 1386
    • 1387
    • 1388
    • 1389
    • 1390
    • 1391
    • 1392
    • 1393
    • 1394
    • 1395
    • 1396
    • 1397
    • 1398
    • 1399
    • 1400
    • 1401
    • 1402
    • 1403
    • 1404
    • 1405
    • 1406
    • 1407
    • 1408
    • 1409
    • 1410
    • 1411
    • 1412
    • 1413
    • 1414
    • 1415
    • 1416
    • 1417
    • 1418
    • 1419
    • 1420
    • 1421
    • 1422
    • 1423
    • 1424
    • 1425
    • 1426
    • 1427
    • 1428
    • 1429
    • 1430
    • 1431
    • 1432
    • 1433
    • 1434
    • 1435
    • 1436
    • 1437
    • 1438
    • 1439
    • 1440
    • 1441
    • 1442
    • 1443
    • 1444
    • 1445
    • 1446
    • 1447
    • 1448
    • 1449
    • 1450
    • 1451
    • 1452
    • 1453
    • 1454
    • 1455
    • 1456
    • 1457
    • 1458
    • 1459
    • 1460
    • 1461
    • 1462
    • 1463
    • 1464
    • 1465
    • 1466
    • 1467
    • 1468
    • 1469
    • 1470
    • 1471
    • 1472
    • 1473
    • 1474
    • 1475
    • 1476
    • 1477
    • 1478
    • 1479
    • 1480
    • 1481
    • 1482
    • 1483
    • 1484
    • 1485
    • 1486
    • 1487
    • 1488
    • 1489
    • 1490
    • 1491
    • 1492
    • 1493
    • 1494
    • 1495
    • 1496
    • 1497
    • 1498
    • 1499
    • 1500
    • 1501
    • 1502
    • 1503
    • 1504
    • 1505
    • 1506
    • 1507
    • 1508
    • 1509
    • 1510
    • 1511
    • 1512
    • 1513
    • 1514
    • 1515
    • 1516
    • 1517
    • 1518
    • 1519
    • 1520
    • 1521
    • 1522
    • 1523
    • 1524
    • 1525
    • 1526
    • 1527
    • 1528
    • 1529
    • 1530
    • 1531
    • 1532
    • 1533
    • 1534
    • 1535
    • 1536
    • 1537
    • 1538
    • 1539
    • 1540
    • 1541
    • 1542
    • 1543
    • 1544
    • 1545
    • 1546
    • 1547
    • 1548
    • 1549
    • 1550
    • 1551
    • 1552
    • 1553
    • 1554
    • 1555
    • 1556
    • 1557
    • 1558
    • 1559
    • 1560
    • 1561
    • 1562
    • 1563
    • 1564
    • 1565
    • 1566
    • 1567
    • 1568
    • 1569
    • 1570
    • 1571
    • 1572
    • 1573
    • 1574
    • 1575
    • 1576
    • 1577
    • 1578
    • 1579
    • 1580
    • 1581
    • 1582
    • 1583
    • 1584
    • 1585
    • 1586
    • 1587
    • 1588
    • 1589
    • 1590
    • 1591
    • 1592
    • 1593
    • 1594
    • 1595
    • 1596
    • 1597
    • 1598
    • 1599
    • 1600
    • 1601
    • 1602
    • 1603
    • 1604
    • 1605
    • 1606
    • 1607
    • 1608
    • 1609
    • 1610
    • 1611
    • 1612
    • 1613
    • 1614
    • 1615
    • 1616
    • 1617
    • 1618
    • 1619
    • 1620
    • 1621
    • 1622
    • 1623
    • 1624
    • 1625
    • 1626
    • 1627
    • 1628
    • 1629
    • 1630
    • 1631
    • 1632
    • 1633
    • 1634
    • 1635
    • 1636
    • 1637
    • 1638
    • 1639
    • 1640
    • 1641
    • 1642
    • 1643
    • 1644
    • 1645
    • 1646
    • 1647
    • 1648
    • 1649
    • 1650
    • 1651
    • 1652
    • 1653
    • 1654
    • 1655
    • 1656
    • 1657
    • 1658
    • 1659
    • 1660
    • 1661
    • 1662
    • 1663
    • 1664
    • 1665
    • 1666
    • 1667
    • 1668
    • 1669
    • 1670
    • 1671
    • 1672
    • 1673
    • 1674
    • 1675
    • 1676
    • 1677
    • 1678
    • 1679
    • 1680
    • 1681
    • 1682
    • 1683
  • 相关阅读:
    SV基础知识---覆盖率 (概念理解)
    (C)一些题2
    JVM之运行时数据区、内存结构、内存模型
    一文看懂推荐系统:召回02:Swing 模型,和itemCF很相似,区别在于计算相似度的方法不一样
    jquery 获取具体时间(年月日)后3个月+1天的年月日
    数据结构-线性表
    Linux C 网络基础
    深入理解计算机网络-8网络层2
    Source Insight使用教程(3)——常用功能扩展篇
    一周侃 | 周末随笔
  • 原文地址:https://blog.csdn.net/sunnyzyq/article/details/133912842