小功能:java导出excel,并与附件打包zip,excel中每条记录用超链接关联附件目录

代码仓库:ExcelAttach.java

配套视频:https://www.bilibili.com/video/BV1wT41197W8


需求

需求要做一个小小的员工信息收集系统,员工登录页面,填写一些信息,然后上传附件,比如身份证、证书照片、其他什么电子档之类的。
系统管理后台会提供列表和详情页来查看,同时,一般都会配备导出功能。
这篇博客就是实现导出这个小功能。
功能点:除了导出excel,还需要与附件一起打包zip,excel中每条记录要用超链接关联附件目录。

效果图

在这里插入图片描述

前置知识:excel 设置超链接到本地目录

方式1:单元格上 右键–>超链接 或者 CTRL+K 快捷键 调出超链接编辑窗口

image-20230205120035128

方式2:输入文件协议 file:// ,可以打开文件夹或者文件,但是只支持绝对路径

  • 相对路径会提示 无法打开指定的文件

    image-20230205120534517

方式3:超链接函数 HYPERLINK

java实现

1.主要代码

  • 使用poi生成excel
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.spire.ms.System.Collections.ArrayList;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import util.ZipUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 导出excel,并与附件打包zip,每条记录用超链接关联附件目录
 */
public class ExcelAttach {
    
    

    // zip文件生成根目录
    private static String rootDirLinux = "/opt/export_tmp"; // linux
    private static String rootDirWin = "D:\\export_tmp"; // windows
    
    public static void main(String[] args) throws Exception {
    
    
        exportZipByExcelAndAttach();
    }

    /**
     * 导出zip,将生成的excel和附件一起打包,每条记录的附件目录以超链接(相对路径)跳转
     */
    private static void exportZipByExcelAndAttach() throws Exception {
    
    
        // 准备数据
        List<Map<String, Object>> list = new ArrayList();

        Map<String, Object> map = ImmutableMap.of("gh", "1", "name", "姓名-001", "deptName", "部门-001",
                "attachUrls", Arrays.asList("https://i2.hdslb.com/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h"));
        // 不可变,一旦创建就不能再往里添加键值对了
        // 转为可变
        map = Maps.newHashMap(map);
        list.add(map);

        list.add(ImmutableMap.of("gh", "2", "name", "姓名-002", "deptName", "部门-002",
                "attachUrls", Arrays.asList("https://i2.hdslb.com/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h")));
        list.add(ImmutableMap.of("gh", "3", "name", "姓名-003", "deptName", "部门-003",
                "attachUrls", Arrays.asList("https://i2.hdslb.com/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h")));
        list.add(ImmutableMap.of("gh", "4", "name", "姓名-004", "deptName", "部门-004",
                "attachUrls", Arrays.asList("https://i2.hdslb.com/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h")));

        // 创建工作薄
        // xlsx格式  XSSF
        Workbook workbook = new XSSFWorkbook();

        // 创建工作表
        Sheet sheet = workbook.createSheet("用户数据");
        // 设置列宽 设置默认宽度
        sheet.setDefaultColumnWidth(25);

        // 设置表头 简单点,不设置样式了
        String[] headers = new String[]{
    
    "工号", "姓名", "部门", "附件"};
        // 创建第一行
        Row titleRow = sheet.createRow(0);
        Cell cell = null;
        for (int i = 0; i < headers.length; i++) {
    
    
            // 创建单元格
            cell = titleRow.createCell(i);
            cell.setCellValue(headers[i]);
        }

        long currentTimeMillis = System.currentTimeMillis();
        // 遍历集合数据,生成数据行
        if (CollectionUtils.isNotEmpty(list)) {
    
    
            // 从第2行开始
            int rowIndex = 1;
            Row row = null;
            for (Map<String, Object> item : list) {
    
    
                String gh = (String) item.get("gh");
                String name = (String) item.get("name");
                String deptName = (String) item.get("deptName");
                List<String> attachUrls = (List<String>) item.get("attachUrls");

                row = sheet.createRow(rowIndex);

                cell = row.createCell(0);
                cell.setCellValue(gh);

                cell = row.createCell(1);
                cell.setCellValue(name);

                cell = row.createCell(2);
                cell.setCellValue(deptName);

                cell = row.createCell(3);

                if (CollectionUtils.isNotEmpty(attachUrls)) {
    
    
                    for (int i = 0; i < attachUrls.size(); i++) {
    
    
                        String attachUrl = attachUrls.get(i);
                        // 附件目录 相对目录 和生成的excel同级
                        // 因为附件不止一个,所以直接打开附件目录即可
                        String codeLink = getFilePath(deptName, name, gh, null, currentTimeMillis, 3);
                        // 超链接描述
                        String code = "打开附件目录";
                        // 生成的超链接不带蓝色下划线样式
                        String formula = MessageFormat.format("HYPERLINK(\"{0}\",\"{1}\")", codeLink, code);
                        cell.setCellFormula(formula);

                        // 手动给超链接添加样式 https://blog.csdn.net/nhx900317/article/details/121489191
                        // 创建单元格样式
                        CellStyle cellStyle = workbook.createCellStyle();
                        // 不直接使用getCellStyle(),用cloneStyleFrom就能实现保持原有样式
                        cellStyle.cloneStyleFrom(cell.getCellStyle());
                        // 设置字体
                        Font font = workbook.createFont();
                        font.setColor(IndexedColors.BLUE.getIndex());
                        font.setUnderline((byte) 1);
                        cellStyle.setFont(font);
                        // 设置单元格样式
                        cell.setCellStyle(cellStyle);

                        String fileName = "附件_" + (i+1) + ".jpg";
                        // 获取附件保存地址
                        String filePath = getFilePath(deptName, name, gh, fileName, currentTimeMillis, 2);
                        // 要注意的点就是,如果附件太多,下载附件可能需要很久很久
                        // 不能实现前端立即下载,需要后台生成,然后消息通知之类的
                        // 如果把附件直接保存在本地服务器之上,速度会快一点
                        saveFile(attachUrl, filePath);
                    }
                }

                rowIndex++;
            }
        }

        // 获取excel保存地址
        String excelFilePath = getFilePath(null, null, null,
                "用户数据.xlsx", currentTimeMillis, 1);
        // 生成excel
        ExcelUtil.saveExcelFile(workbook, excelFilePath);
        workbook.close();

        // 打成zip
        File desc = new File(excelFilePath);
        // excel保存临时目录
        File dir = desc.getParentFile();

        SimpleDateFormat dateFormat = new SimpleDateFormat("用户数据(yyyy-MM-dd_HH-mm-ss)");
        String zipFilePath = dir.getParentFile().getParentFile().getAbsolutePath() +
                File.separator + dateFormat.format(new Date()) + ".zip";

        try (FileOutputStream out = new FileOutputStream(zipFilePath)) {
    
    
            ZipUtils.toZip(dir, out, true);
            // 删除临时文件夹
            ZipUtils.delFile(dir.getParentFile());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

    }

    /**
     * 获取文件路径
     *
     * @param deptName
     * @param name
     * @param gh
     * @param currentTimeMillis
     * @param fileName          文件名
     * @param fileType          1-excel,2-附件,3-附件保存目录(相对路径)
     */
    private static String getFilePath(String deptName, String name, String gh, String fileName, long currentTimeMillis, int fileType) {
    
    
        StringBuilder filePath = new StringBuilder();
        if (fileType != 3) {
    
    
            // 绝对路径
            String osName = System.getProperties().getProperty("os.name");
            if (osName.contains("Linux")) {
    
    
                filePath.append(rootDirLinux);
            } else {
    
    
                filePath.append(rootDirWin);
            }
            filePath.append(File.separator + "temp" + currentTimeMillis);
            filePath.append(File.separator + "用户数据");
            // 下方相对路径不需要 / 开头
            filePath.append(File.separator);
        }

        if (fileType == 1) {
    
    
            // 获取excel生成路径
            filePath.append(fileName);
            return filePath.toString();
        }
        // 获取当前用户附件保存路径
        filePath.append("附件");
        filePath.append(File.separator + deptName);
        filePath.append(File.separator + name + "(" + gh + ")");

        if (fileType == 3) {
    
    
            // 附件保存目录(相对路径)
            return filePath.toString();
        }

        filePath.append(File.separator + fileName);
        return filePath.toString();
    }

    /**
     * 下载网络附件,保存到临时目录
     *
     * @param urlString
     * @param filePath
     */
    private static String saveFile(String urlString, String filePath) {
    
    
        File desc = new File(filePath);
        File dir = desc.getParentFile();
        if (!dir.exists()) {
    
    
            dir.mkdirs();
        }

        boolean rs = false;
        if (!desc.exists()) {
    
    
            ReadableByteChannel rbc = null;
            FileOutputStream fos = null;
            try {
    
    
                URL website = new URL(urlString);
                HttpURLConnection urlCon = (HttpURLConnection) website.openConnection();
                // 指定超时时间,不指定可能会无限等待
                urlCon.setConnectTimeout(180000);
                urlCon.setReadTimeout(180000);

                rbc = Channels.newChannel(urlCon.getInputStream());
                fos = new FileOutputStream(desc);
                fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
                rs = true;
            } catch (Exception e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                if (fos != null) {
    
    
                    try {
    
    
                        fos.close();
                    } catch (Exception e) {
    
    
                        e.printStackTrace();
                    }
                }
                if (rbc != null) {
    
    
                    try {
    
    
                        rbc.close();
                    } catch (Exception e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        } else {
    
    
            rs = true;
        }

        // 输出绝对路径
        return rs ? desc.getAbsolutePath() : null;
    }

}

2.pom

<!--引入poi-ooxml,就会引入poi-->
<!--<dependency>-->
<!--	<groupId>org.apache.poi</groupId>-->
<!--	<artifactId>poi</artifactId>-->
<!--	<version>4.1.0</version>-->
<!--</dependency>-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1.1-jre</version>
</dependency>

3.zip工具类

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 压缩文件夹成zip
 * https://www.cnblogs.com/zeng1994/p/7862288.html
 */
public class ZipUtils {
    
    

    private static final int BUFFER_SIZE = 2 * 1024;

    /**
     * 压缩成ZIP 方法1
     *
     * @param sourceFile       压缩文件夹
     * @param out              压缩文件输出流
     * @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
     *                         false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
     * @throws RuntimeException 压缩失败会抛出运行时异常
     */
    public static void toZip(File sourceFile, OutputStream out, boolean KeepDirStructure)
            throws RuntimeException {
    
    
        try (ZipOutputStream zos = new ZipOutputStream(out)) {
    
    
            compress(sourceFile, zos, sourceFile.getName(), KeepDirStructure);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 递归压缩方法
     *
     * @param sourceFile       源文件
     * @param zos              zip输出流
     * @param name             压缩后的名称
     * @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
     *                         false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
     * @throws Exception
     */
    private static void compress(File sourceFile, ZipOutputStream zos, String name,
                                 boolean KeepDirStructure) throws Exception {
    
    
        byte[] buf = new byte[BUFFER_SIZE];
        if (sourceFile.isFile()) {
    
    
            // 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字
            zos.putNextEntry(new ZipEntry(name));
            // copy文件到zip输出流中
            int len;
            FileInputStream in = new FileInputStream(sourceFile);
            while ((len = in.read(buf)) != -1) {
    
    
                zos.write(buf, 0, len);
            }
            zos.closeEntry();
            in.close();
        } else {
    
    
            File[] listFiles = sourceFile.listFiles();
            if (listFiles == null || listFiles.length == 0) {
    
    
                // 需要保留原来的文件结构时,需要对空文件夹进行处理
                if (KeepDirStructure) {
    
    
                    // 空文件夹的处理
                    zos.putNextEntry(new ZipEntry(name + "/"));
                    // 没有文件,不需要文件的copy
                    zos.closeEntry();
                }
            } else {
    
    
                for (File file : listFiles) {
    
    
                    // 判断是否需要保留原来的文件结构
                    if (KeepDirStructure) {
    
    
                        // 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,
                        // 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
                        compress(file, zos, name + "/" + file.getName(), KeepDirStructure);
                    } else {
    
    
                        compress(file, zos, file.getName(), KeepDirStructure);
                    }
                }
            }
        }
    }

    /**
     * 删除本地文件及目录
     */
    public static boolean delFile(File file) {
    
    
        if (!file.exists()) {
    
    
            return false;
        }
        if (file.isDirectory()) {
    
    
            File[] files = file.listFiles();
            for (File f : files) {
    
    
                delFile(f);
            }
        }
        return file.delete();
    }

}

猜你喜欢

转载自blog.csdn.net/weixin_44174211/article/details/128890019