【java超方便的导入导出工具类】SpringBoot操作Excel导入和导出

Excel导入和导出

一、前期准备

1、首先导入主要的依赖

<dependencies>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>

        <!-- 文件上传 -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.5.13</version>
        </dependency>

        <!-- Json -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.78</version>
        </dependency>

        <!-- POI-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.3</version>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
 </dependencies>

2、编写swagger配置类

这里需要注意SpringBoot的版本我的版本为2.1.3.RELEASE

@Configuration
@EnableSwagger2
public class SwaggerConfig {

        @Bean
        public Docket docket() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .groupName("默认模块")
                    .globalOperationParameters(Collections.emptyList()).apiInfo(getApiInfo())
                    .enable(true).select()
                    .apis(RequestHandlerSelectors.basePackage("com.excel.controller")).build();
        }

        private ApiInfo getApiInfo() {
            String title = "Excel导入导出示例(基于 ExcelUtils 2.0 版本)";
            String description = "Excel导入导出示例";
            String version = "v2.0";
            Contact contact = new Contact("", "", "");
            String license = "ExcelUtils 2.0 版本";
            String licenseUrl = "";
            ApiInfo apiInfo = new ApiInfo(title, description, version, "", contact, license, licenseUrl,
                    Collections.emptyList());
            return apiInfo;
        }

    }
}

3、导入ExcelUtils工具类

/**
 * @author ysl
 */
@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 <T>  泛型
     */
    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 <T>      泛型
     */
    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 <T>      泛型
     */
    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 <T>      泛型
     * @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<Java对象>格式返回(默认第一行为表头)
     *
     * @param file excel文件
     * @param <T>  泛型对象
     * @return List<Java对象>
     */
    public static <T> List<T> read(File file, Class<T> c) {
        Sheet sheet = readSheet(file);
        return pares(sheet, c);
    }

    /**
     * 读取excel文件,返回第一个sheet页数据,以List<Java对象>格式返回(默认第一行为表头)
     *
     * @param file excel文件
     * @param <T>  泛型对象
     * @return List<Java对象>
     */
    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 <T>   泛型对象
     * @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 <T>           泛型对象
     * @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);
    }

}

用熟这个工具类后异常简单,下面我用一个万能公式导出作为演示

二、导出编写

4、编写实体类entity

/**
 * @Author Ysl
 * @Date 2024/4/28
 * @name Myselflean
 **/
@Data
@ExcelUtils.ExcelSheet(name = "用户表", watermark = @ExcelUtils.Watermark(type = 3, src ="https://profileavatar.csdnimg.cn/37f95edca8614683923fa0b9455058f6_qq_53438100.jpg!1"))
public class User {
    @ExcelUtils.ExcelExport(name = "序号")
    private int id;
    @ExcelUtils.ExcelExport(name = "姓名")
    private String name;
    @ExcelUtils.ExcelExport(name = "性别")
    private int sex;
    @ExcelUtils.ExcelExport(name = "身份证号")
    private int id_card;
    @ExcelUtils.ExcelExport(name = "电话")
    private int phone;
    @ExcelUtils.ExcelExport(name = "会员类型")
    private String v_type;
    @ExcelUtils.ExcelExport(name = "开始时间")
    private Date start_time;
    @ExcelUtils.ExcelExport(name = "结束时间")
    private Date end_time;

}

5、编写Controller

/**
 * @Author Ysl
 * @Date 2024/4/29
 * @name Myselflean
 **/
@RestController
public class ExportController {

    @GetMapping("/export/ysl")
    @ApiOperation(value = "万能导入", produces = "application/octet-stream")
    public void export(HttpServletResponse response){

        ExcelUtils.ExportRow rows1 = new ExcelUtils.ExportRow();
        //首先判断是否为表头,然后将数据放入对应的行
        rows1.setHeader(true);
        rows1.appendCells("序号","姓名","性别","身份证号","电话","会员类型","开始时间","结束时间");
        ExcelUtils.ExportRow rows2 = new ExcelUtils.ExportRow();
        //首先判断是否为表头,然后将数据放入对应的行
        rows2.setHeader(false);
        rows2.appendCells(1,"王二","男","130432200109160256","15834569852","普通会员")
                .appendCell(new Date(),"yyyy-MM-dd")
                .appendCell(new Date(),"yyyy-MM-dd");
        //合并1,2列
        List<ExcelUtils.ExportRow> list = Arrays.asList(rows1,rows2);

        //设置批注
        ExcelUtils.ExportComment comment = new ExcelUtils.ExportComment();
        comment.setX(5);
        comment.setY(0);
        comment.setComment("普通会员; \n vip会员;\n 超级会员;" );
        //合并批注
        List<ExcelUtils.ExportComment> comments = Arrays.asList(comment);

        //设置水印
        ExcelUtils.ExportWatermark watermark = new ExcelUtils.ExportWatermark();
        watermark.setType(1);
        watermark.setSrc("ysl测试");

        //设置sheet页
        ExcelUtils.ExportSheet sheet = new ExcelUtils.ExportSheet();
        sheet.setWatermark(watermark);
        sheet.setRows(list);
        sheet.setComments(comments);
        sheet.setName("ysl测试");
        List<ExcelUtils.ExportSheet> sheets = Arrays.asList(sheet);
        ExcelUtils.export(response,"100",sheets);
    }
}

5.1、结果:

在这里插入图片描述
在这里插入图片描述

三、导入编写

和导出同样的实体类

1、前端代码

注意自己配置跨域和路由、根据自己电脑修改样式

<template>
  <div style="width: 1000px;margin: auto;margin-top: 200px;border: 1px solid #ebeef5">
    <div style="display: flex;justify-content: right">
    <el-upload
        class="upload-demo"
        action="http://localhost:8080/import"
        :on-preview="handlePreview"
        :on-remove="handleRemove"
        :before-remove="beforeRemove"
        multiple
        :limit="3"
        :on-exceed="handleExceed"
        :file-list="fileList">
      <el-button size="small" type="primary">点击上传</el-button>
      <div slot="tip" class="el-upload__tip">上传文件不超过500kb</div>
    </el-upload>
  </div>
  <el-table
      :data="tableData"
      border
      style="width: 100%">
    <el-table-column
        prop="id"
        label="序号"
        width="180">
    </el-table-column>
    <el-table-column
        prop="name"
        label="姓名"
        width="180">
    </el-table-column>
    <el-table-column
        prop="sex"
        label="性别">
    </el-table-column>
    <el-table-column
        prop="id_card"
        label="身份证号">
    </el-table-column>
    <el-table-column
        prop="phone"
        label="电话">
    </el-table-column>
    <el-table-column
        prop="v_type"
        label="会员类别">
    </el-table-column>
    <el-table-column
        prop="start_time"
        label="开始时间">
    </el-table-column>
    <el-table-column
        prop="end_time"
        label="结束时间">
    </el-table-column>
  </el-table>
  </div>
</template>

<script>
export default {
  name: "Login",
  data() {
    return {
      tableData: [],
      fileList: [],
    };
  },
  methods: {
    handleRemove(file, fileList) {
      console.log(file, fileList);
    },
    handlePreview(file) {
      console.log(file)
      this.tableData = file.response;
      console.log(this.tableData)
    },
    handleExceed(files, fileList) {
      this.$message.warning(`当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
    },
    beforeRemove(file, fileList) {
      return this.$confirm(`确定移除 ${file.name}`);
    }
  }
}
</script>

<style scoped>

</style>

2、后端代码Controller

/**
 * @Author Ysl
 * @Date 2024/4/28
 * @name Myselflean
 **/

@RestController
@Api(tags = {"Excel导入示例"})
public class ExcelImportController {

    @ApiOperation(value = "按对象导入(基础)", produces = "application/octet-stream")
    @PostMapping("/import")
    public JSONArray importExcel(@RequestPart("file") MultipartFile file){
        JSONArray users = ExcelUtils.read(file, true);
        for (Object user : users) {
            String userString = JSON.toJSONString(user);
            try {
                User userObject = JSON.parseObject(userString, User.class);
                System.out.println(userObject);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
        return users;
    }
}

3、导入的excel为

在这里插入图片描述

4、导入成功后

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/584119.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Stable Diffusion 模型分享:Counterfeit-V3.0(动漫)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八 下载地址 模型介绍 高质量动漫风格模型。 条目内容类型大模型基础模…

关于使用SpringSecurity框架发起JSON请求,但因登陆失效导致响应403的问题。

这里记录一个生产中遇到的一个问题。 现有环境是基于SpringBoot 2.6.8&#xff0c;然后是前后台一体化的项目。 安全框架使用的是内置版本的SpringSecurity。 在实际使用过程中遇到一个问题。 就是当用户登陆失效后&#xff0c;前端操作JSON请求获取列表数据&#xff0c;但…

【知识学习/复习】损失函数篇,包含理解应用与分类:回归、分类、排序、生成等任务

损失函数总结 一、损失函数理解二、不同任务的损失函数的应用1.图像分类2.目标检测3.语义分割4.自然语言处理&#xff08;NLP&#xff09;5.图神经网络&#xff08;GNN&#xff09;6.生成式网络 三、损失函数1. 回归任务损失函数常见损失函数IoU系列损失函数1. IoU损失函数&…

微信开发api、微信视频号开发

接口地址&#xff1a; http://api.videostui.com/finder/v2/api/login/checkLogin 接口说明 获取到登录二维码后需每间隔5s调用本接口来判断是否登录成功新设备登录平台&#xff0c;次日凌晨会掉线一次&#xff0c;重新登录时需调用获取二维码且传appId取码&#xff0c;登录成…

Outlook邮箱如何撤回一封已发送邮件?~网页版上

点【已发送邮件】 双击要撤回的已发送的那个邮件 点【…】 点击【撤回消息】 点【确定】 结束&#xff01;

使用LocalGPT+cpolar打造可远程访问的本地私有类chatgpt服务

文章目录 前言环境准备1. localGPT部署2. 启动和使用3. 安装cpolar 内网穿透4. 创建公网地址5. 公网地址访问6. 固定公网地址 前言 本文主要介绍如何本地部署LocalGPT并实现远程访问&#xff0c;由于localGPT只能通过本地局域网IP地址端口号的形式访问&#xff0c;实现远程访问…

【源代码】使用Vision Pro远程操控机器人

1、OpenTeleVision 是一个开源项目&#xff0c;提供远程操作功能 2、可以从 VisionPro 或 Meta Quest 3 中流式传输头部、手部和腕部数据 3、可以将实时立体视频从摄像头流式传输到 VR 设备 4、需要在 Ubuntu 机器上安装 Zed SDK 和 mkcert&#xff0c;以便在本地进行测试 …

全新突破:「Paraverse平行云」实现3D/XR企业级应用全面接入Apple Vision Pro

在前不久举行的GTC开发者大会上&#xff0c;英伟达引领行业风向&#xff0c;宣布其Omniverse平台能够助力企业将3D/XR应用流畅传输至Apple Vision Pro混合现实头显。在英伟达与苹果这两大科技巨头的合作下,此举标志着3D/XR技术迈向新纪元的关键一步。「Paraverse平行云」实时云…

基于STM32单片机的汽车胎压、速度及状态监测系统设计与实现

基于STM32单片机的汽车胎压、速度及状态监测系统设计与实现 摘要&#xff1a; 随着汽车电子技术的快速发展&#xff0c;车辆状态实时监控系统的需求日益增长。本文设计并实现了一种基于STM32单片机的汽车胎压、速度及状态监测系统。该系统能够实时监测汽车的胎压、速度以及其他…

算法设计与分析4.1 迷宫问题 栈与队列解法、打印矩阵、三壶问题、蛮力匹配

1.ROSE矩阵 实现&#xff1a; 使用算法2 分析&#xff1a; 每半圈元素值的增长规律变换一次 设增量为t&#xff0c;每半圈变换一次t <— -t . 设矩阵边长为i&#xff0c;每半圈的元素个数是2*(i-1)个&#xff0c;hc为记数变量&#xff0c;则1≤hc<2i-1&#xff0c;前1/…

ChatGLM2-6B的部署步骤_A3

ChatGLM2-6B 下载地址 一、VisualGLM-6B环境安装 1、硬件配置 操作系统&#xff1a;Ubuntu_64&#xff08;ubuntu22.04.3&#xff09; GPU&#xff1a;4050 显存&#xff1a;16G 2、配置环境 建议最好自己新建一个conda环境 conda create -n chatglm2 python3.8pip …

【go项目01_学习记录day01】

博客系统 1 vscode开发go项目插件推荐1.1 CtrlShiftP&#xff08;俗称万能键&#xff09; &#xff1a;打开命令面板。在打开的输入框内&#xff0c;可以输入任何命令。1.2 开发时&#xff0c;我们需要经常查阅 Go 语言官方文档&#xff0c;可惜因国内访问外网不稳定&#xff0…

STM32开启停止模式,用外部中断唤醒程序运行

今天学习了一下STM32的停止模式&#xff0c;停止模式下&#xff0c;所有外设的时钟和CPU的电源都会被关闭&#xff0c;所以会很省电&#xff0c;打破这种停止模式的方式就是外部中断可以唤醒停止模式。要想实现这个功能&#xff0c;其实设置很简单的&#xff0c;总共就需要两步…

《C语言深度解剖》(10):数组指针、指针数组和数组指针数组

&#x1f921;博客主页&#xff1a;醉竺 &#x1f970;本文专栏&#xff1a;《C语言深度解剖》《精通C指针》 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多C语言深度解剖点击专栏…

情感类ppt素材

小清新手绘插画风毕业季毕业相册同学录画册纪念册PPT下载 - 觅知网这是一张关于清新毕业相册的PPT模板&#xff0c;清新风格设计&#xff0c;加上风为装饰元素&#xff0c;包含毕业相册、毕业季、毕业、同学、纪念等主题内容&#xff0c;也可用作毕业相册PPT、毕业季PPT、毕业P…

陪诊小程序:温情陪伴,就医无忧

在快节奏的现代生活中&#xff0c;健康问题时常困扰着我们。每当身体出现不适&#xff0c;前往医院就诊便成为了一项必要的任务。然而&#xff0c;面对陌生的医院环境、繁琐的就诊流程&#xff0c;许多人往往会感到无助和困惑。此时&#xff0c;一款贴心的陪诊小程序便显得尤为…

国内首个图计算平台团体标准发布,创邻科技参与编撰

2024年&#xff0c;由中国通信标准协会批准的团体标准《大数据 图计算平台技术要求与测试方法》&#xff08;编号&#xff1a;T/CCSA 470—2023&#xff09;&#xff08;下称&#xff1a;标准&#xff09;正式实施。该标准于1月4日在全国团体标准信息平台&#xff08;https://w…

AI系列:大语言模型的RAG(检索增强生成)技术(上)

前言 大型语言模型&#xff08;LLM&#xff09;虽然在生成文本方面表现出色&#xff0c;但仍然存在一些局限性&#xff1a;数据是静态的&#xff0c;而且缺乏垂直细分领域的知识。为了克服这些限制&#xff0c;有时候会进行进一步的模型训练和微调。在实际应用中&#xff0c;我…

在Android中,如何通过Kotlin协程处理多个API调用

在Android中&#xff0c;如何通过Kotlin协程处理多个API调用 在Android开发中&#xff0c;如何使用Kotlin协程处理多个API调用的示例呢&#xff1f;假设我们已经对Kotlin协程有了一定的了解&#xff0c;包括定义、简单用例和示例等。现在&#xff0c;让我们来看一些真实的Andr…

如何下载钉钉群直播回放:完整步骤解析

在当今快节奏的商业和教育环境中&#xff0c;钉钉群直播已经成为了沟通和学习的重要工具。直播结束后&#xff0c;很多观众都希望回顾内容&#xff0c;但却不知如何开始。如果你错过了实时直播&#xff0c;或者只是想再次观看精彩的演讲和讨论&#xff0c;那么下载钉钉群直播回…