All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gccloud.gcpaas.ooxml.excel.ExcelWriter Maven / Gradle / Ivy

The newest version!
package com.gccloud.gcpaas.ooxml.excel;

import com.gccloud.gcpaas.ooxml.excel.bean.ExcelExportParam;
import com.gccloud.gcpaas.ooxml.excel.bean.ExcelSheet;
import com.gccloud.gcpaas.ooxml.excel.bean.WaterMark;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.List;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * 基于Open XML 规范些Excel
 */
public class ExcelWriter {
    private static final Logger log = LoggerFactory.getLogger(ExcelWriter.class);
    /**
     * 定义列名,格式: A1,B1,C1.....AA1,AB1,AC1...BA1,BB1,BC1....
     */
    private static final String[] COL_NAMES = new String[702];
    /**
     * 字符串共享map,key:唯一字符串,val: 该值在 xl/sharedStrings.xml文件中的索引
     */
    private Map sharedStrMap = new HashMap<>();
    /**
     * 记录 sharedStrMap 中 val 的值索引
     */
    private Integer sharedStrIndex = 0;
    /**
     * 字符串共享有序集合,按照顺序写入 xl/sharedStrings.xml 文件中
     */
    private List sharedList = new ArrayList<>();
    /**
     * 待写入的sheet
     */
    private Map sheetMap = new HashMap<>();
    /**
     * 缓存工作目录
     */
    private String tmpDir;
    /**
     * 水印
     */
    private WaterMark waterMark;

    /**
     * 操作写入 sharedStrings.xml 文件
     */
    private FileWriter sharedStringsXmlWriter;

    /**
     * excel参数
     */
    private ExcelExportParam excelParam;

    static {
        // 初始化列
        char[] alpha = new char[26];
        char c = 'A';
        for (int i = 0; i < 26; i++) {
            alpha[i] = c;
            c++;
            COL_NAMES[i] = alpha[i] + "";
        }
        int index = 26;
        for (int i = 0; i < 26; i++) {
            for (int j = 0; j < 26; j++) {
                COL_NAMES[index] = alpha[i] + "" + alpha[j];
                index++;
            }
        }
    }

    public ExcelWriter(ExcelExportParam excelParam, WaterMark waterMark) {
        Assert.isTrue(excelParam != null, "excelParam不允许为空");
        this.excelParam = excelParam;
        this.waterMark = waterMark;
    }

    /**
     * 写入一行数据
     *
     * @param sheetIndex sheet索引,从1开始
     * @param singleRow  按照列的顺序存放
     * @throws IOException
     */
    public void write(int sheetIndex, String[] singleRow) throws IOException {
        if (tmpDir == null) {
            // 复制模板
            tmpDir = excelParam.getTemplateExcelDir() + File.separator + UUID.randomUUID();
            log.info("导出的缓存目录为【{}】", tmpDir);
            File templateExcelFile = new File(excelParam.getTemplateExcelDir() + File.separator + "template.xlsx");
            if (!templateExcelFile.exists()) {
                // 从resource目录下复制
                try (InputStream is = ExcelWriter.class.getClassLoader().getResourceAsStream("template.xlsx")) {
                    // 修改复制的路径
                    FileOutputStream fos = new FileOutputStream(templateExcelFile);
                    // 直接复制
                    IOUtils.copy(is, fos);
                } catch (Exception e) {
                    log.error(ExceptionUtils.getStackTrace(e));
                }
            }
            FileUtils.copyFile(templateExcelFile, new File(tmpDir + File.separator + "template.xlsx"));
        }
        ExcelSheet currentSheet = sheetMap.get(sheetIndex);
        if (currentSheet == null) {
            // 新建一个sheet
            currentSheet = new ExcelSheet();
            currentSheet.setIndex(sheetIndex);
            currentSheet.setName("sheet" + sheetIndex);
            sheetMap.put(sheetIndex, currentSheet);
            String sheetFileName = tmpDir + File.separator + "sheet" + sheetIndex + ".xml";
            File sheetFile = new File(sheetFileName);
            if (!sheetFile.exists()) {
                sheetFile.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(sheetFileName);
            currentSheet.setSheetXmlWriter(fileWriter);
            fileWriter.write("\n" +
                    "#DIMENSION#");
        }
        StringBuilder rowBuilder = new StringBuilder();
        rowBuilder.append(" ");
        for (int col = 0; col < singleRow.length; col++) {
            String cellVal = singleRow[col];
            if (currentSheet.getCurrentRowIndex() == 1) {
                // 设置表头
                rowBuilder.append(" ");
            } else {
                rowBuilder.append(" ");
            }
            Integer index = sharedStrMap.computeIfAbsent(cellVal, (k) -> {
                sharedList.add(cellVal);
                return sharedStrIndex++;
            });
            rowBuilder.append("" + index + "");
        }
        currentSheet.resetMaxCol(singleRow.length);
        currentSheet.incrementCurrentRowIndex();
        rowBuilder.append(" ");
        currentSheet.getSheetXmlWriter().write(rowBuilder.toString());
    }

    /**
     * 清空对象
     */
    public void clear() {
        sharedStrMap.clear();
        sharedList.clear();
        sheetMap.clear();
    }

    /**
     * 结束,最后必须调用,将数据写入Excel
     *
     * @return 导出的excel文件
     * @throws IOException
     */
    public File finish() throws IOException {
        if (waterMark != null && StringUtils.isNotBlank(waterMark.getText())) {
            // 添加水印
            generateWaterMarkImg();
            // 遍历所有的sheet、写入水印
            for (ExcelSheet sheet : sheetMap.values()) {
                sheet.getSheetXmlWriter().write("");
                sheet.getSheetXmlWriter().close();
                log.info("生成【{}】文件", tmpDir + File.separator + "sheet" + sheet.getIndex() + ".xml");
            }
        } else {
            for (ExcelSheet sheet : sheetMap.values()) {
                sheet.getSheetXmlWriter().write("");
                sheet.getSheetXmlWriter().close();
                log.info("生成【{}】文件", tmpDir + File.separator + "sheet" + sheet.getIndex() + ".xml");
            }
        }
        String sharedStringsFileName = tmpDir + File.separator + "sharedStrings.xml";
        File sharedStringsFile = new File(sharedStringsFileName);
        if (!sharedStringsFile.exists()) {
            sharedStringsFile.createNewFile();
        }
        long time1 = System.currentTimeMillis();
        sharedStringsXmlWriter = new FileWriter(sharedStringsFileName);
        // 计算多个sheet总单元格个数
        Integer totalCellCount = 0;
        for (ExcelSheet sheet : sheetMap.values()) {
            int count = (sheet.getTotalRow() - 1) * sheet.getMaxCol();
            totalCellCount += count;
        }
        sharedStringsXmlWriter.write("\n");
        for (String val : sharedList) {
            sharedStringsXmlWriter.write("" + val + "");
        }
        sharedStringsXmlWriter.write("");
        sharedStringsXmlWriter.close();
        log.info("生成【sharedStrings.xml】文件耗时: {} ms", System.currentTimeMillis() - time1);
        // 写入压缩包
        archive();
        // 删除生成的缓存文件
        new File(tmpDir + File.separator + "template.xlsx").deleteOnExit();
        new File(tmpDir + File.separator + "sharedStrings.xml").deleteOnExit();
        // 删除缓存的sheet.xml
        for (Integer sheetIndex : sheetMap.keySet()) {
            new File(tmpDir + File.separator + "sheet" + sheetIndex + ".xml").deleteOnExit();
        }
        new File(tmpDir + File.separator + "image1.png").deleteOnExit();
        // 删除缓存数据
        sharedList.clear();
        sharedStrMap.clear();
        return new File(tmpDir + File.separator + excelParam.getOutFileName() + ".xlsx");
    }

    private void archive() {
        long time1 = System.currentTimeMillis();
        try {
            // 读取Excel模板文件
            ZipInputStream templateExcelZipIs = new ZipInputStream(new FileInputStream(tmpDir + File.separator + "template.xlsx"));
            // 待写入的Excel文件
            ZipOutputStream outputExcelOs = new ZipOutputStream(new FileOutputStream(new File(tmpDir + File.separator + excelParam.getOutFileName() + ".xlsx")));

            // 写入文件的缓存大小
            byte[] buffer = new byte[1024 * 1024 * 10];
            ZipEntry entry;
            while ((entry = templateExcelZipIs.getNextEntry()) != null) {
                long compressStart = System.currentTimeMillis();
                if (entry.getName().equals("xl/media/image1.png")) {
                    if (waterMark != null && StringUtils.isNotBlank(waterMark.getText())) {
                        // 写入水印
                        ZipEntry imageEntry = new ZipEntry("xl/media/image1.png");
                        outputExcelOs.putNextEntry(imageEntry);
                        FileInputStream imageFileInputStream = new FileInputStream(tmpDir + File.separator + "image1.png");
                        int len;
                        while ((len = imageFileInputStream.read(buffer)) > 0) {
                            outputExcelOs.write(buffer, 0, len);
                        }
                        imageFileInputStream.close();
                    }
                } else if (entry.getName().startsWith("xl/worksheets/sheet")) {
                    // 循环写入sheet
                    for (ExcelSheet sheet : sheetMap.values()) {
                        ZipEntry sheet1Entry = new ZipEntry("xl/worksheets/sheet" + sheet.getIndex() + ".xml");
                        outputExcelOs.putNextEntry(sheet1Entry);
                        int len;
                        FileInputStream sheetFis = new FileInputStream(tmpDir + File.separator + "sheet" + sheet.getIndex() + ".xml");
                        boolean head = true;
                        while ((len = sheetFis.read(buffer)) > 0) {
                            if (head) {
                                String line = new String(buffer, 0, len, "utf-8");
                                // 解决预先不知道数据有多少行和列问题
                                String newLine = line.replace("#DIMENSION#", "");
                                outputExcelOs.write(newLine.getBytes("utf-8"));
                                head = false;
                                continue;
                            }
                            outputExcelOs.write(buffer, 0, len);
                        }
                        sheetFis.close();
                    }
                } else if (entry.getName().equals("xl/sharedStrings.xml")) {
                    ZipEntry zipEntry = new ZipEntry("xl/sharedStrings.xml");
                    outputExcelOs.putNextEntry(zipEntry);
                    int len;
                    FileInputStream sharedStringsFis = new FileInputStream(tmpDir + File.separator + "sharedStrings.xml");
                    while ((len = sharedStringsFis.read(buffer)) > 0) {
                        outputExcelOs.write(buffer, 0, len);
                    }
                    sharedStringsFis.close();
                } else if (entry.getName().equals("xl/workbook.xml")) {
                    ZipEntry zipEntry = new ZipEntry(entry.getName());
                    outputExcelOs.putNextEntry(zipEntry);
                    String content = readCurrentFileFromZip(templateExcelZipIs);
                    int start = content.indexOf("");
                    int end = content.indexOf("");
                    String oldSheet = content.substring(start, end);
                    String newSheet = "";
                    for (ExcelSheet sheet : sheetMap.values()) {
                        newSheet += "";
                    }
                    String newContent = content.replaceAll(oldSheet, newSheet);
                    outputExcelOs.write(newContent.getBytes("utf-8"));
                } else if (entry.getName().equals("xl/_rels/workbook.xml.rels")) {
                    ZipEntry zipEntry = new ZipEntry(entry.getName());
                    outputExcelOs.putNextEntry(zipEntry);
                    String content = readCurrentFileFromZip(templateExcelZipIs);
                    int start = 0;
                    while ((start = content.indexOf("", start) + "/>".length();
                        String oldStr = content.substring(start, end);
                        if (oldStr.contains("worksheets/sheet")) {
                            // 添加sheet关联
                            String newStr = "";
                            for (ExcelSheet sheet : sheetMap.values()) {
                                newStr += "";
                            }
                            String newContent = content.replaceAll(oldStr, newStr);
                            outputExcelOs.write(newContent.getBytes("utf-8"));
                            break;
                        }
                        start = end;
                    }
                } else if (entry.getName().startsWith("xl/worksheets/_rels/sheet")) {
                    // 循环写入sheet
                    for (ExcelSheet sheet : sheetMap.values()) {
                        ZipEntry sheet1Entry = new ZipEntry("xl/worksheets/_rels/sheet" + sheet.getIndex() + ".xml.rels");
                        outputExcelOs.putNextEntry(sheet1Entry);
                        String content = "";
                        content += "";
                        content += "";
                        outputExcelOs.write(content.getBytes("utf-8"));
                    }
                } else {
                    copyCurrentFileToZip(entry.getName(), templateExcelZipIs, outputExcelOs);
                }
                log.info("文件【{}】读取及插入压缩包耗时 {} ms", entry.getName(), System.currentTimeMillis() - compressStart);
                outputExcelOs.closeEntry();
            }
            buffer = null;
            outputExcelOs.close();
        } catch (IOException e) {
            log.error("写入压缩包失败", e);
        }
        log.info("生成压缩包 【{}.xlsx】 文件耗时 {} ms", excelParam.getOutFileName(), System.currentTimeMillis() - time1);
    }

    /**
     * 从一个压缩包中复制文件到另一个压缩包中
     *
     * @param fileName
     * @param zis
     * @param zos
     * @throws IOException
     */
    private void copyCurrentFileToZip(String fileName, ZipInputStream zis, ZipOutputStream zos) throws IOException {
        ZipEntry zipEntry = new ZipEntry(fileName);
        zos.putNextEntry(zipEntry);
        int len;
        byte[] buffer = new byte[1024 * 1024 * 10];
        while ((len = zis.read(buffer)) > 0) {
            zos.write(buffer, 0, len);
        }
        buffer = null;
    }

    /**
     * 读取压缩包中当前选中的文件内容
     *
     * @param zis
     * @return
     * @throws IOException
     */
    private String readCurrentFileFromZip(ZipInputStream zis) throws IOException {
        byte[] buffer = new byte[1024 * 1024 * 10];
        int len;
        String str = "";
        while ((len = zis.read(buffer)) > 0) {
            str = str + new String(buffer, 0, len, "utf-8");
        }
        return str;
    }

    /**
     * 生成水印图
     *
     * @throws IOException
     */
    private void generateWaterMarkImg() throws IOException {
        long start = System.currentTimeMillis();
        int width = waterMark.getWidth();
        int height = waterMark.getHeight();
        // 获取bufferedImage对象
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // 获取Graphics2d对象
        Graphics2D g2d = image.createGraphics();
        image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
        g2d.dispose();
        g2d = image.createGraphics();
        // 设置字体颜色和透明度,最后一个参数为透明度
        g2d.setColor(waterMark.getFontColor());
        // 设置字体类型  加粗 大小
        g2d.setStroke(new BasicStroke(1));
        // 设置字体
        Font font = new Font("微软雅黑", Font.BOLD, waterMark.getFontSize());
        g2d.setFont(font);
        // 设置倾斜度
        g2d.rotate(-0.3, (double) image.getWidth() / 2, (double) image.getHeight() / 2);
        FontRenderContext context = g2d.getFontRenderContext();
        Rectangle2D bounds = font.getStringBounds(waterMark.getText(), context);
        double x = (width - bounds.getWidth()) / 2;
        double y = (height - bounds.getHeight()) / 2;
        double ascent = -bounds.getY();
        double baseY = y + ascent;
        // 写入水印文字原定高度过小,所以累计写水印,增加高度
        g2d.drawString(waterMark.getText(), (int) x, (int) baseY);
        // 设置透明度
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
        // 释放对象
        g2d.dispose();
        ImageIO.write(image, "png", new File(tmpDir + File.separator + "image1.png"));
        log.info("文本【{}】生成水印图耗时 {} ms", waterMark.getText(), System.currentTimeMillis() - start);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy