代码仓库:ExcelAttach.java
配套视频:/video/BV1wT41197W8
需求
需求要做一个小小的员工信息收集系统,员工登录页面,填写一些信息,然后上传附件,比如身份证、证书照片、其他什么电子档之类的。
系统管理后台会提供列表和详情页来查看,同时,一般都会配备导出功能。
这篇博客就是实现导出这个小功能。
功能点:除了导出excel,还需要与附件一起打包zip,excel中每条记录要用超链接关联附件目录。
效果图
前置知识:excel 设置超链接到本地目录
方式1:单元格上 右键–>超链接 或者 CTRL+K 快捷键 调出超链接编辑窗口
Excel小知识:超链接3种常用的使用方法选择左侧“原有文件或网页”图标,可以选择文件夹或者文件,然后点击确定按钮即可。打开效果:txt文件是直接记事本打开,图片会用浏览器打开。方式2:输入文件协议 file:// ,可以打开文件夹或者文件,但是只支持绝对路径
相对路径会提示 无法打开指定的文件
方式3:超链接函数 HYPERLINK
WPS学堂–表格系列课–查找和引用函数–HYPERLINK函数
HYPERLINK(链接位置, [显示文本]]) 第2个参数可省
可以文件夹或文件,可以相对路径或绝对路径
=HYPERLINK(“.”, “打开当前目录”)
java实现
1.主要代码
使用poi生成excelimport mon.collect.ImmutableMap;import mon.collect.Maps;import com.spire.ms.System.Collections.ArrayList;import mons.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 .HttpURLConnection;import .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"; // linuxprivate static String rootDirWin = "D:\\export_tmp"; // windowspublic 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("/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("/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h")));list.add(ImmutableMap.of("gh", "3", "name", "姓名-003", "deptName", "部门-003","attachUrls", Arrays.asList("/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h")));list.add(ImmutableMap.of("gh", "4", "name", "姓名-004", "deptName", "部门-004","attachUrls", Arrays.asList("/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h")));// 创建工作薄// xlsx格式 XSSFWorkbook 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);// 手动给超链接添加样式 /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);// 生成excelExcelUtil.saveExcelFile(workbook, excelFilePath);workbook.close();// 打成zipFile 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 fileType1-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><!-- /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* /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 + "/"));// 没有文件,不需要文件的copyzos.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();}}
如果觉得《小功能:java导出excel 并与附件打包zip excel中每条记录用超链接关联附件目录》对你有帮助,请点赞、收藏,并留下你的观点哦!