背景

页面上有一份表单,包含基本信息和列表信息,用户填写完信息后,点击预览/导出,可以预览/导出word、pdf、excel文档。

因为代码重构过,所以看流程可能会有点绕,为方便能看懂,这里直接提供了项目的地址
文件生成项目

期望你在本文能收获什么
一套比较完整的文件生成的解决方案和思路
一些代码构建的思路和细节,文中代码的一些组成是我参考在用项目的结构,重构改过来的,用上了一些设计模式,扩展性、规范性应该也还是可以的。

效果示例

doc文件模板

doc生成效果

pdf效果一样

excel文件模板

excel生成效果

文件word、pdf、excel生成/预览的基本流程

  1. 前端页面,用户填写用户信息,可以包含基本信息和列表信息,用户提交数据
  2. 后端接收数据后依靠第三方的组件/工具,把文件生成到本地
  3. 把生成到本地的文件上传到文件服务器并返回文件id/文件访问地址
  4. 删除本地生成的文件
  5. 把文件id/文件访问地址返回给前端
  1. 有的项目组做项目喜欢上传/生成文件再上传后在数据库存文件信息,然后返回文件地址给前端,前端拿到文件地址就直接处理
  2. 有的项目组做项目喜欢上传/生成文件再上传后在数据库存文件信息,然后返回文件id给前端,前端默认是要知道文件服务器的基础访问地址,所以让前端拿到文件id后他们就会处理了(拼接字符串为文件访问地址(文件id为生成文件名)如 “http://文件地址/文件id”,或者再发个请求到数据库查文件具体信息(拿到地址)都可以)
  3. 本文使用的是第二种。单独有文件服务器做文件上传,然后把上传成功的文件的基本信息保存到数据库,然后返回文件id给前端。
  4. 本文主要是介绍如何生成各种类型的文档,所以不包含如何上传文件部分。当然市面上常用的文件上传服务实现这么多,相信你也是可以找到合适的解决方案的。
  5. 如果以后有空,再给补一个通用的文件上传服务好了,哈哈

主要用到的工具

  1. FreeMarker(生成word)、aspose(将word转换成pdf)、easyExcel(生成excel)

代码实现

基础工具类及依赖

  1. maven项目
  2. spring boot 项目
  3. 基础依赖

maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.wwt.file</groupId><artifactId>FileHandle</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.5.RELEASE</version></parent><properties><java.version>1.8</java.version></properties><dependencies><!--mysql运行时依赖--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--web--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--测试的起步依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--lombok用来简化实体类--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--用来生成word文档--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.5.2</version></dependency><!-- 转化pdf start --><dependency><groupId>aspose-words</groupId><artifactId>aspose-words</artifactId><version>15.8.0</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/aspose-words-15.8.0-jdk16.jar</systemPath></dependency><!--转excel--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.1</version></dependency><!--这个注意要加,我在看官网的时候没看到,只加了上面那个,结果后来实测就少了东西报错了--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel-core</artifactId><version>3.1.1</version></dependency></dependencies></project>

application.yml

#mysql数据库连接
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/xxx?serverTimezone=GMT%2B8username: rootpassword: 123456

这里看到我用了mysql,其实我这里数据是由前端传过来的,不是查出来的,所以没用到数据库。只是加个配置项目启动就不会找默认配置报错。当然你可以的通过加个注解的方式解决,这里不提了。

项目结构


4. 基本类

我把包名也复制进去,希望尽量能让你还原项目

实体类


User - 数据实体类

package com.wwt.file.entity;import lombok.Data;@Data
public class User {private Long id;private String name;private Integer age;private String email;
}

FileVO - 文件返回信息

package com.wwt.file.entity.vo;import lombok.Data;/*** 封装文件返回信息**/
@Data
public class FileVO {/*** 文件名*/private String fileName;/*** 文件id*/private Long fileId;public FileVO(String fileName, Long fileId) {this.fileName = fileName;this.fileId = fileId;}
}

UserExportDTO

package com.wwt.file.entity.dto;import cn.hutool.core.map.MapUtil;
import com.wwt.file.entity.User;
import lombok.Data;import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 封装前端传过来的用户请求信息*/
@Data
public class UserExportDTO {private User user1;private User user2;private List<User> users;/**** @return*/public Map<String, Object> structDataMap() {Map<String, Object> map = MapUtil.createMap(HashMap.class);map.put("user1",this.user1);map.put("user2",this.user2);map.put("users",this.users);return map;}
}

在controller中的使用示例

枚举类


FileTypeEnum - 文件类型的枚举类

package com.wwt.file.enums;/*** 文件类型*/
public enum  FileTypeEnum {WORD("word",".doc"),PDF("pdf",".pdf"),EXCEL("excel",".xlsx"),XML("xml",".xml");/*** 类型*/private String type;/*** 后缀*/private String suffix;FileTypeEnum(String type, String suffix) {this.type = type;this.suffix = suffix;}public String getType() {return type;}public String getSuffix() {return suffix;}
}

UserTemplate - 用户模板

package com.wwt.file.enums;/*** 用户模板*/
public enum UserTemplate {USER_TEMPLATE_XML("user_template","用户信息","这是一个用户模板",FileTypeEnum.XML),USER_TEMPLATE_EXCEL("user_template","用户信息","这是一个用户模板",FileTypeEnum.EXCEL);/*** 模板名称*/private String template;/*** 模板中文名*/private String chName;/*** 详情*/private String des;/*** 模板类型*/private String type;/*** 模板后缀*/private String suffix;UserTemplate(String template, String chName, String des, FileTypeEnum fileTypeEnum) {this.chName = chName;this.des = des;this.template = template + fileTypeEnum.getSuffix();this.type = fileTypeEnum.getType();this.suffix = fileTypeEnum.getSuffix();}public String getTemplate() {return template;}public String getChName() {return chName;}public String getDes() {return des;}public String getType() {return type;}public String getSuffix() {return suffix;}
}

这两个枚举类的关联关系

FileTypeEnum :该枚举是单一职责的,只表示文件的类型信息
UserTemplate :通过构造方法引入 FileTypeEnum ,在构造方法中接入 fileTypeEnum 实现
UserTemplate 基本信息的初始化,创建 UserTemplate 时更加清晰明了。

不太好的实现是这样的:

USER_TEMPLATE_XML("user_template","用户信息","这是一个用户模板",FileTypeEnum.XML.getSuffix(),FileTypeEnum.XML.getType()),
USER_TEMPLATE_EXCEL("user_template","用户信息","这是一个用户模板",FileTypeEnum.EXCEL.getSuffix(),FileTypeEnum.EXCEL.getType());UserTemplate(String template, String chName, String des, String suffix,String type) {this.chName = chName;this.des = des;this.template = template + suffix;this.type = type;this.suffix = suffix;}
//又或者不创建 FileTypeEnum ,直接在这些显示文件类型信息的地方都用字符串写死

统一响应结果


Result - 一个抽象的返回结果类

package com.wwt.file.result;import com.wwt.file.result.enums.ResultCodeEnum;
import lombok.Data;@Data
public abstract class Result<T> {public static Result success(){Result r = new Result(){};r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());r.setCode(ResultCodeEnum.SUCCESS.getCode());r.setMessage(ResultCodeEnum.SUCCESS.getMessage());return r;}public static Result error(){Result r = new Result(){};r.setSuccess(ResultCodeEnum.UNKNOWN_REASON.getSuccess());r.setCode(ResultCodeEnum.UNKNOWN_REASON.getCode());r.setMessage(ResultCodeEnum.UNKNOWN_REASON.getMessage());return r;}/*** 是否成功*/private Boolean success;/*** 响应码*/private Integer code;/*** 响应信息*/private String message;/*** 响应数据*/private T data;public Result data(T data){this.setData(data);return this;}public Result(){}
}

DefaultResult - Result的实现,默认的响应结果类,平时用这个类做响应结果就可以了

package com.wwt.file.result;import com.wwt.file.result.enums.ResultCodeEnum;/*** 默认响应结果* @param <T>*/
public class DefaultResult<T> extends Result<T> {public static DefaultResult setResult(ResultCodeEnum resultCodeEnum){DefaultResult r = new DefaultResult();r.setSuccess(resultCodeEnum.getSuccess());r.setCode(resultCodeEnum.getCode());r.setMessage(resultCodeEnum.getMessage());return r;}
}

ResultCodeEnum - 常用响应结果,有什么想用的响应结果,在这里增加枚举结果对象即可

package com.wwt.file.result.enums;import lombok.Getter;
import lombok.ToString;//这个类是在尚硅谷的相关资料里面抄过来的
@Getter
@ToString
public enum ResultCodeEnum {SUCCESS(true, 20000,"成功"),UNKNOWN_REASON(false, 20001, "未知错误"),BAD_SQL_GRAMMAR(false, 21001, "sql语法错误"),JSON_PARSE_ERROR(false, 21002, "json解析异常"),PARAM_ERROR(false, 21003, "参数不正确"),FILE_UPLOAD_ERROR(false, 21004, "文件上传错误"),FILE_DELETE_ERROR(false, 21005, "文件刪除错误"),EXCEL_DATA_IMPORT_ERROR(false, 21006, "Excel数据导入错误"),VIDEO_UPLOAD_ALIYUN_ERROR(false, 22001, "视频上传至阿里云失败"),VIDEO_UPLOAD_TOMCAT_ERROR(false, 22002, "视频上传至业务服务器失败"),VIDEO_DELETE_ALIYUN_ERROR(false, 22003, "阿里云视频文件删除失败"),FETCH_VIDEO_UPLOADAUTH_ERROR(false, 22004, "获取上传地址和凭证失败"),REFRESH_VIDEO_UPLOADAUTH_ERROR(false, 22005, "刷新上传地址和凭证失败"),FETCH_PLAYAUTH_ERROR(false, 22006, "获取播放凭证失败"),URL_ENCODE_ERROR(false, 23001, "URL编码失败"),ILLEGAL_CALLBACK_REQUEST_ERROR(false, 23002, "非法回调请求"),FETCH_ACCESSTOKEN_FAILD(false, 23003, "获取accessToken失败"),FETCH_USERINFO_ERROR(false, 23004, "获取用户信息失败"),LOGIN_ERROR(false, 23005, "登录失败"),COMMENT_EMPTY(false, 24006, "评论内容必须填写"),PAY_RUN(false, 25000, "支付中"),PAY_UNIFIEDORDER_ERROR(false, 25001, "统一下单错误"),PAY_ORDERQUERY_ERROR(false, 25002, "查询支付结果错误"),ORDER_EXIST_ERROR(false, 25003, "课程已购买"),GATEWAY_ERROR(false, 26000, "服务不能访问"),CODE_ERROR(false, 28000, "验证码错误"),LOGIN_PHONE_ERROR(false, 28009, "手机号码不正确"),LOGIN_MOBILE_ERROR(false, 28001, "账号不正确"),LOGIN_PASSWORD_ERROR(false, 28008, "密码不正确"),LOGIN_DISABLED_ERROR(false, 28002, "该用户已被禁用"),REGISTER_MOBLE_ERROR(false, 28003, "手机号已被注册"),LOGIN_AUTH(false, 28004, "需要登录"),LOGIN_ACL(false, 28005, "没有权限"),SMS_SEND_ERROR(false, 28006, "短信发送失败"),SMS_SEND_ERROR_BUSINESS_LIMIT_CONTROL(false, 28007, "短信发送过于频繁");private Boolean success;private Integer code;private String message;ResultCodeEnum(Boolean success, Integer code, String message) {this.success = success;this.code = code;this.message = message;}
}

正因为感觉把所有的响应结果都封装在一个类里面,后期类就太长了,所以我才把响应结果类 Result 做抽象化,让其实现类的 setResult() 方法来实现不同的响应结果类的入参。当然怕麻烦的话,就直接用 DefaultResult 和 ResultCodeEnum 基本就够用了。

我们这里用到的,扩展其他的响应结果枚举类,以及返回结果类的示例如下:
FileResult - 文件结果类

package com.wwt.file.result.file;import com.wwt.file.result.Result;
import com.wwt.file.result.enums.FileResultEnum;/*** 文件响应结果* @param <T>*/
public class FileResult<T> extends Result<T> {public static FileResult setResult(FileResultEnum FileResultEnum){FileResult r = new FileResult();r.setSuccess(FileResultEnum.getSuccess());r.setCode(FileResultEnum.getCode());r.setMessage(FileResultEnum.getMessage());return r;}}

FileResultEnum - 文件返回结果枚举对象 - 从 ResultCodeEnum 中分离出来了

package com.wwt.file.result.enums;import lombok.Getter;
import lombok.ToString;@Getter
@ToString
public enum FileResultEnum {//只要不是20000都是失败SUCCESS(true, 20000,"成功"),UNKNOWN_REASON(false, 20001, "未知错误"),UPLOAD_SUCCESS(true, 20000, "文件上传成功"),UPLOAD_FAIL(false, 20002, "文件上传失败");private Boolean success;private Integer code;private String message;FileResultEnum(Boolean success, Integer code, String message) {this.success = success;this.code = code;this.message = message;}
}

只要新的响应结果类和新的响应结果枚举类能用 setResult()方法使其一对一关联起来即可
新的响应结果类继承 Result 类即可

使用示例

工具类


FileUtil

package com.wwt.file.util;import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.wwt.file.enums.FileTypeEnum;/*** 文件处理工具类*/
public class FileUtil {/*** 获取项目根路径* pathStr : D:\code\javaCode\FileHandle\target\classes* 如果是在ide中运行,则和target同级目录,如果是jar部署到服务器,则默认和jar包同级* pathStr : D:\code\javaCode\FileHandle\         FileHandle是我的项目名*/public static String getResourceBasePath() {ClassPathResource classPathResource = new ClassPathResource("");String pathStr = classPathResource.getFile().getAbsolutePath();pathStr = pathStr.replace("target\\classes", "");return pathStr;}/*** 如果父目录不存在,则创建* 保证父目录一定存在* 否则文件可能创建失败* 支持创建多级目录* @Param filePath 文件绝对路径*/public static void mkParentDirIfNotExist(String filePath) {cn.hutool.core.io.FileUtil.mkParentDirs(filePath);}/*** 删除文件或目录及其下文件* @param filePath 文件绝对路径*/public static void delDir(String filePath){cn.hutool.core.io.FileUtil.del(filePath);}/*** 生成随机文件名* @param fileTypeEnum 指定文件类型* @return*/public static String generateFileName(FileTypeEnum fileTypeEnum){if (ObjectUtil.isNull(fileTypeEnum)){return null;}//System.currentTimeMillis() 如果你不想用uuid,用时间也是可以的return StrUtil.concat(true, UUID.randomUUID().toString(true), fileTypeEnum.getSuffix());}}

生成word文档

原理

  1. word文档生成主要用到的工具是freemarker
  2. 提供一份word文档模板,在需要插入数据的地方填写占位符,代码中将数据和文档绑定时,会用实际数据替换掉占位符

步骤

依赖

<!--用来生成word文档-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

新建项目模板目录

新建项目模板


另存为xml文件

把文件模板放进template目录

用notepad++之类的工具类打开并修改模板文件
实体类数据

小提示:如果文件内容不是格式化的,你复制内容到idea里的随便一个xml文档,ctrl+alt+L代码格式化之后再复制回来也是可以的。
为什么先预写数据,然后再查找替换。而不是在word文档里面直接写${xxx.xxx},然后生成xml文件呢?因为实测过可能$符号会因为一些格式问题,预写替换符再成xml的话会出奇怪的问题,比如说$符号和属性分开了,导致数据填不进去,或者报错。

list数据


全部替换后,转为word文档打开应该是如下面这个样子的。但是因为加了list标签的原因,可能替换完就打不开了。不过还是再看看替换完应该是长什么样的吧。

前端数据

{"user1": {"id": 1, "name": "张三", "age": 77, "email": "22667344.@qq.com"}, "user2": {"id": 19, "name": "李四", "age": 68, "email": "33447899@qq.com"}, "users": [{"id": 33, "name": "帅哥1", "age": 18, "email": "23217744@qq.com"}, {"id": 34, "name": "帅哥2", "age": 18, "email": "23217744@qq.com"}, {"id": 35, "name": "帅哥3", "age": 18, "email": "23217744@qq.com"}, {"id": 36, "name": "帅哥4", "age": 18, "email": "23217744@qq.com"}, {"id": 37, "name": "帅哥5", "age": 18, "email": "23217744@qq.com"}, {"id": 38, "name": "帅哥6", "age": 18, "email": "23217744@qq.com"}, {"id": 39, "name": "帅哥7", "age": 18, "email": "23217744@qq.com"}, {"id": 40, "name": "帅哥8", "age": 18, "email": "23217744@qq.com"}]
}

后端接收数据

controller写请求接口

package com.wwt.file.controller;import com.wwt.file.entity.User;
import com.wwt.file.entity.dto.UserExportDTO;
import com.wwt.file.entity.vo.FileVO;
import com.wwt.file.result.DefaultResult;
import com.wwt.file.result.Result;
import com.wwt.file.result.enums.FileResultEnum;
import com.wwt.file.result.file.FileResult;
import com.wwt.file.service.IUserService;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;
import java.io.IOException;@RestController
@RequestMapping("/user")
public class UserController {@Resourceprivate IUserService userService;@PostMapping("/export")public Result<FileVO> export(@RequestBody UserExportDTO upd, String type){return FileResult.setResult(FileResultEnum.UPLOAD_SUCCESS).data(userService.export(upd,type));}}

service

IUserService接口
package com.wwt.file.service;import com.wwt.file.entity.dto.UserExportDTO;
import com.wwt.file.entity.vo.FileVO;public interface IUserService {public FileVO export(UserExportDTO upd, String type);}
UserService实现类
package com.wwt.file.service.impl;import cn.hutool.core.util.StrUtil;
import com.wwt.file.entity.dto.UserExportDTO;
import com.wwt.file.entity.vo.FileVO;
import com.wwt.file.enums.FileTypeEnum;
import com.wwt.file.enums.UserTemplate;
import com.wwt.file.service.ExportService;
import com.wwt.file.service.IUserService;
import com.wwt.file.util.excel.UserExcelDataWriter;
import org.springframework.stereotype.Service;import javax.annotation.Resource;@Service
public class UserService implements IUserService {@Resourcepublic ExportService exportService;@Overridepublic FileVO export(UserExportDTO upd, String type){//用工具类生成word文件if (StrUtil.equals(FileTypeEnum.WORD.getType(),type) || StrUtil.equals(FileTypeEnum.PDF.getType(),type)){return exportService.export(upd.structDataMap(), type, UserTemplate.USER_TEMPLATE_XML.getTemplate(),null);}//生成excel的话要提供excelwriterreturn exportService.export(upd.structDataMap(), type, UserTemplate.USER_TEMPLATE_EXCEL.getTemplate(),new UserExcelDataWriter());}
}

解析

ExportService



package com.wwt.file.service;import cn.hutool.core.util.StrUtil;
import com.wwt.file.entity.vo.FileVO;
import com.wwt.file.enums.FileTypeEnum;
import com.wwt.file.enums.UserTemplate;
import com.wwt.file.util.DocUtil;
import com.wwt.file.util.excel.ExcelDataWriter;
import com.wwt.file.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;import java.io.File;
import java.util.Map;@Slf4j
@Service
public class ExportService {/*** 临时生成文件的路径*/private static final String TEMP_FILE_DIR = "tmp\\";public FileVO export(Map<String, Object> dataMap, String type ,String tempFileName,ExcelDataWriter writer){if (StrUtil.equals(FileTypeEnum.WORD.getType(),type)){return new FileVO(UserTemplate.USER_TEMPLATE_XML.getChName(),exportWord(dataMap,tempFileName));}if (StrUtil.equals(FileTypeEnum.PDF.getType(),type)){return new FileVO(UserTemplate.USER_TEMPLATE_XML.getChName(),exportPdf(dataMap,tempFileName));}if (StrUtil.equals(FileTypeEnum.EXCEL.getType(),type)){return new FileVO(UserTemplate.USER_TEMPLATE_EXCEL.getChName(),exportExcel(dataMap,writer));}return new FileVO(null,null);}/*** @param dataMap        填充的数据,每个值都不能为null,可以为empty* @param tempFileName   模板文件名,放置在template目录下* @return 文件服务器返回的fileId* @description 模板导出word**/public Long exportWord(Map<String, Object> dataMap, String tempFileName) {//生成文件名 - 随机文件名,防止重名String fileName = FileUtil.generateFileName(FileTypeEnum.WORD);DocUtil.saveWord(tempFileName, fileName, dataMap);//获取文件绝对路径String filePath = StrUtil.concat(true,FileUtil.getResourceBasePath(),TEMP_FILE_DIR,fileName);//上传文件到文件服务器//Long fileId = fileManagerService.uploadFile(filePath, fileName, "temp", ttl);//删除生成的文件//FileUtil.delDir(filePath);//模拟上传成功返回的id return 1234L; //fileId}/*** @param dataMap        填充的数据,每个值都不能为null,可以为empty* @param tempFileName   模板文件名,放置在template目录下* @return 文件服务器返回的fileId* @description 模板导出pdf**/public Long exportPdf(Map<String, Object> dataMap, String tempFileName) {//生成文件名String fileName = FileUtil.generateFileName(FileTypeEnum.PDF);DocUtil.savePdf(tempFileName, fileName, dataMap);String filePath = new File(FileUtil.getResourceBasePath(), TEMP_FILE_DIR + fileName).getAbsolutePath();//上传文件到文件服务器//Long fileId = fileManagerService.uploadFile(filePath, fileName, "temp", ttl);//FileUtil.delDir(filePath);return 1234L; //fileId}/*** 导出excel* 包括生成文档和上传文件* @param dataMap 数据* @param writer 不同的writer实现类处理不同的模板写入* @return*/public Long exportExcel(Map<String, Object> dataMap,ExcelDataWriter writer){writer.export(dataMap);return 1234L;}}

DocUtil 为实现具体文件生成到本地的操作。因为 ExportService 中还涉及到文件上传的操作,所以又把这个生成操作单独提取了出来,封装到DocUtil 中,实现上传和生成的逻辑解耦。

DocUtil

主要看这个 saveWord 方法

package com.wwt.file.util;import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.OsInfo;
import cn.hutool.system.SystemUtil;
import com.aspose.words.Document;
import com.aspose.words.FontSettings;
import com.aspose.words.License;
import com.aspose.words.SaveFormat;
import com.wwt.file.enums.FileTypeEnum;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Map;@Slf4j
public class DocUtil {/*** 临时生成文件的路径*/private static final String TEMP_FILE_DIR = "tmp\\";private static final String LICENSE_FILENAME = "aspose_license.xml";/*** @param templateFileName 模板文件名称* @param fileName      生成的文件名.doc结尾* @param dataMap          数据,* @description 用模板生成word, 模板存放路径在resources/template下,生成的文件路径在jar包的同级目录/tmp路径下* @attention dataMap中的数据不能为null, 可用空值代替* @other 导出的doc文件其实是文本文件, 而docx文件是二进制文件(其实是一个压缩包).关于如何导出docx可参考 https://blog.csdn.net/wantLight/article/details/106105416**/public static void saveWord(String templateFileName, String fileName, Map<String, Object> dataMap) {Writer out = null;try {//根据配置获取FreeMarker模板对象Template template = FreeMarkerTemplateFactory.getTemplate(templateFileName);//拼接出即将生成文件的绝对路径 /xxx/xxx/xxx.docString filePath = StrUtil.concat(true,FileUtil.getResourceBasePath(),TEMP_FILE_DIR,fileName);//如果文件父目录不存在,则创建父目录FileUtil.mkParentDirIfNotExist(filePath);// 创建一个Word文档的输出流out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(new File(filePath).toPath()), StandardCharsets.UTF_8));template.process(dataMap, out);out.flush();} catch (Exception e) {log.error("saveWord error:{}", e.getMessage(), e);} finally {IoUtil.close(out);}}/*** @param templateFileName 模板文件名称* @param fileName         生成的文件名.pdf结尾* @param dataMap          数据,* @description 用模板先生成生成word, 再转成pdf,模板存放路径在resources/template下,生成的文件路径在jar包的同级目录/tmp路径下* @attention dataMap中的数据不能为null, 可用空值代替**/public static void savePdf(String templateFileName, String fileName, Map<String, Object> dataMap) {InputStream docInputStream = null;OutputStream outputStream = null;try {String docFileName = fileName.replace(FileTypeEnum.PDF.getSuffix(), FileTypeEnum.WORD.getSuffix());//先生成wordsaveWord(templateFileName, docFileName, dataMap);String docFilePath = StrUtil.concat(true,FileUtil.getResourceBasePath(),TEMP_FILE_DIR,docFileName);//aspose将word转成pdfif (!getLicense()) {// 验证License 若不验证则转化出的pdf文档会有水印产生return;}OsInfo osInfo = SystemUtil.getOsInfo();//linux环境的字体,需要将c:\windows\fonts下的字体文件放到指定目录下,否则会乱码if (osInfo.isLinux()) {FontSettings.setFontsFolder("/usr/share/fonts/chinese", true);}docInputStream = Files.newInputStream(new File(docFilePath).toPath());File outputFile = new File(FileUtil.getResourceBasePath(), "tmp/" + fileName);outputStream = Files.newOutputStream(outputFile.toPath());Document doc = new Document(docInputStream);                    //sourcerFile是将要被转化的word文档doc.save(outputStream, SaveFormat.PDF);//全面支持DOC, DOCX, OOXML, RTF HTML, OpenDocument, PDF, EPUB, XPS, SWF 相互转换//删除生成的临时word文件FileUtil.delDir(docFilePath);} catch (Exception e) {log.error("savePdf error:{}", e.getMessage(), e);} finally {IoUtil.close(docInputStream);IoUtil.close(outputStream);}}/*** 判断是否有授权文件 如果没有则会认为是试用版,转换的文件会有水印** @return*/public static boolean getLicense() {boolean result = false;try {InputStream is = DocUtil.class.getClassLoader().getResourceAsStream(LICENSE_FILENAME);License asposeLic = new License();asposeLic.setLicense(is);result = true;} catch (Exception e) {log.error("getLicense error:{}", e.getMessage());}return result;}}

如果在测试word,还没用到pdf的功能,代码报错,先把报错的注释掉等会再测试就好了

FreeMarkerTemplateFactory - 用来创建 template对象

package com.wwt.file.util;import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;import java.io.IOException;/*** FreeMarker 创建模板对象的工厂*/
public class FreeMarkerTemplateFactory {/*** FreeMarker的版本*/private static final String FREEMARKER_VERSION = "2.3.28";/*** 模板存放目录*/private static final String TEMPLATE_DIR = "/template";/*** 编码格式*/private static final String CHARSET = "utf-8";/*** 获取模板对象* @param templateFileName* @return* @throws IOException*/public static Template getTemplate(String templateFileName) throws IOException {Configuration configuration = new Configuration(new Version(FREEMARKER_VERSION));configuration.setDefaultEncoding("utf-8");//设置模板位置,默认是classpath下的指定目录下,idea中运行时为resources下的指定目录下configuration.setClassForTemplateLoading(DocUtil.class, TEMPLATE_DIR);Template template = configuration.getTemplate(templateFileName, CHARSET);return template;}}

测试

请求及数据


请求地址:http://localhost:8080/user/export?type=word
请求数据:

{"user1": {"id": 1, "name": "张三", "age": 77, "email": "22667344.@qq.com"}, "user2": {"id": 19, "name": "李四", "age": 68, "email": "33447899@qq.com"}, "users": [{"id": 33, "name": "帅哥1", "age": 18, "email": "23217744@qq.com"}, {"id": 34, "name": "帅哥2", "age": 18, "email": "23217744@qq.com"}, {"id": 35, "name": "帅哥3", "age": 18, "email": "23217744@qq.com"}, {"id": 36, "name": "帅哥4", "age": 18, "email": "23217744@qq.com"}, {"id": 37, "name": "帅哥5", "age": 18, "email": "23217744@qq.com"}, {"id": 38, "name": "帅哥6", "age": 18, "email": "23217744@qq.com"}, {"id": 39, "name": "帅哥7", "age": 18, "email": "23217744@qq.com"}, {"id": 40, "name": "帅哥8", "age": 18, "email": "23217744@qq.com"}]
}


可能会遇到的问题

  1. 文件生成到本地不成功,没权限(window下)
    Could not open/create prefs root node Software\JavaSoft\Prefs at root
    参考:https://blog.csdn.net/yxlzfx/article/details/117767879
  2. 数据填不进去word文档,但是代码执行没报错。这个一方面你要看看你模板中替换符的名字是否和你构建的map的key对应得上。另一方面,你是不是用过idea在启动项目的情况下修改了xml的模板文件,如果是,你看看target/class/template是不是没有替换符。我改用nodpad来修改xml就是这个意思,可能会修改不成功。打包打不进去修改了的内容。所以还是停止项目,用nodpad等工具来改比较好。

生成PDF文档

原理

  1. 生成pdf主要用的是 aspose
  2. 流程就是先生成word文档到临时文件目录,然后用 aspose-word 将word文档转换成pdf,再上传到文件服务器返回文件id,再删除pdf文件,返回文件id给前端。
  3. 生成word的工作调用上面已实现接口,即可。

步骤

下载jar包

地址: https://repository.aspose.com/repo/com/aspose/

进去后选一个版本下即可
把jar包添加到项目中

添加依赖

<!-- 转化pdf start --><dependency><groupId>aspose-words</groupId><artifactId>aspose-words</artifactId><version>15.8.0</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/lib/aspose-words-15.8.0-jdk16.jar</systemPath></dependency>

注意版本和jar包名字要对应的上

添加认证文件

<License><Data><Products><Product>Aspose.Total for Java</Product><Product>Aspose.Words for Java</Product></Products><EditionType>Enterprise</EditionType><SubscriptionExpiry>20991231</SubscriptionExpiry><LicenseExpiry>20991231</LicenseExpiry><SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber></Data><Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature>
</License>

Service

UserService

没有改变,因为都是通过 ExportService 的export 接口根据类型判断统一实现的。

package com.wwt.file.service.impl;import cn.hutool.core.util.StrUtil;
import com.wwt.file.entity.dto.UserExportDTO;
import com.wwt.file.entity.vo.FileVO;
import com.wwt.file.enums.FileTypeEnum;
import com.wwt.file.enums.UserTemplate;
import com.wwt.file.service.ExportService;
import com.wwt.file.service.IUserService;
import com.wwt.file.util.excel.UserExcelDataWriter;
import org.springframework.stereotype.Service;import javax.annotation.Resource;@Servicepublic class UserService implements IUserService {@Resourcepublic ExportService exportService;@Overridepublic FileVO export(UserExportDTO upd, String type){//用工具类生成word文件if (StrUtil.equals(FileTypeEnum.WORD.getType(),type) || StrUtil.equals(FileTypeEnum.PDF.getType(),type)){return exportService.export(upd.structDataMap(), type, UserTemplate.USER_TEMPLATE_XML.getTemplate(),null);}return exportService.export(upd.structDataMap(), type, UserTemplate.USER_TEMPLATE_EXCEL.getTemplate(),new UserExcelDataWriter());}}
ExportService

package com.wwt.file.service;import cn.hutool.core.util.StrUtil;
import com.wwt.file.entity.vo.FileVO;
import com.wwt.file.enums.FileTypeEnum;
import com.wwt.file.enums.UserTemplate;
import com.wwt.file.util.DocUtil;
import com.wwt.file.util.excel.ExcelDataWriter;
import com.wwt.file.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;import java.io.File;
import java.util.Map;@Slf4j
@Service
public class ExportService {/*** 临时生成文件的路径*/private static final String TEMP_FILE_DIR = "tmp\\";public FileVO export(Map<String, Object> dataMap, String type ,String tempFileName,ExcelDataWriter writer){if (StrUtil.equals(FileTypeEnum.WORD.getType(),type)){return new FileVO(UserTemplate.USER_TEMPLATE_XML.getChName(),exportWord(dataMap,tempFileName));}if (StrUtil.equals(FileTypeEnum.PDF.getType(),type)){return new FileVO(UserTemplate.USER_TEMPLATE_XML.getChName(),exportPdf(dataMap,tempFileName));}if (StrUtil.equals(FileTypeEnum.EXCEL.getType(),type)){return new FileVO(UserTemplate.USER_TEMPLATE_EXCEL.getChName(),exportExcel(dataMap,writer));}return new FileVO(null,null);}/*** @param dataMap        填充的数据,每个值都不能为null,可以为empty* @param tempFileName   模板文件名,放置在template目录下* @return 文件服务器返回的fileId* @description 模板导出word**/public Long exportWord(Map<String, Object> dataMap, String tempFileName) {//生成文件名String fileName = FileUtil.generateFileName(FileTypeEnum.WORD);DocUtil.saveWord(tempFileName, fileName, dataMap);//获取文件绝对路径String filePath = StrUtil.concat(true,FileUtil.getResourceBasePath(),TEMP_FILE_DIR,fileName);//上传文件到文件服务器//Long fileId = fileManagerService.uploadFile(filePath, fileName, "temp", ttl);//删除生成的文件//FileUtil.delDir(filePath);//模拟上传成功返回的idreturn 1234L; //fileId}/*** @param dataMap        填充的数据,每个值都不能为null,可以为empty* @param tempFileName   模板文件名,放置在template目录下* @return 文件服务器返回的fileId* @description 模板导出pdf**/public Long exportPdf(Map<String, Object> dataMap, String tempFileName) {//生成文件名String fileName = FileUtil.generateFileName(FileTypeEnum.PDF);DocUtil.savePdf(tempFileName, fileName, dataMap);String filePath = new File(FileUtil.getResourceBasePath(), TEMP_FILE_DIR + fileName).getAbsolutePath();//上传文件到文件服务器//Long fileId = fileManagerService.uploadFile(filePath, fileName, "temp", ttl);//FileUtil.delDir(filePath);return 1234L; //fileId}/*** 导出excel* 包括生成文档和上传文件* @param dataMap 数据* @param writer 不同的writer实现类处理不同的模板写入* @return*/public Long exportExcel(Map<String, Object> dataMap,ExcelDataWriter writer){writer.export(dataMap);return 1234L;}}
DocUtil


package com.wwt.file.util;import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.OsInfo;
import cn.hutool.system.SystemUtil;
import com.aspose.words.Document;
import com.aspose.words.FontSettings;
import com.aspose.words.License;
import com.aspose.words.SaveFormat;
import com.wwt.file.enums.FileTypeEnum;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Map;@Slf4j
public class DocUtil {/*** 临时生成文件的路径*/private static final String TEMP_FILE_DIR = "tmp\\";private static final String LICENSE_FILENAME = "aspose_license.xml";/*** @param templateFileName 模板文件名称* @param fileName      生成的文件名.doc结尾* @param dataMap          数据,* @description 用模板生成word, 模板存放路径在resources/template下,生成的文件路径在jar包的同级目录/tmp路径下* @attention dataMap中的数据不能为null, 可用空值代替* @other 导出的doc文件其实是文本文件, 而docx文件是二进制文件(其实是一个压缩包).关于如何导出docx可参考 https://blog.csdn.net/wantLight/article/details/106105416**/public static void saveWord(String templateFileName, String fileName, Map<String, Object> dataMap) {Writer out = null;try {//根据配置获取FreeMarker模板对象Template template = FreeMarkerTemplateFactory.getTemplate(templateFileName);String filePath = StrUtil.concat(true,FileUtil.getResourceBasePath(),TEMP_FILE_DIR,fileName);//如果文件父目录不存在,则创建父目录FileUtil.mkParentDirIfNotExist(filePath);// 创建一个Word文档的输出流out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(new File(filePath).toPath()), StandardCharsets.UTF_8));template.process(dataMap, out);out.flush();} catch (Exception e) {log.error("saveWord error:{}", e.getMessage(), e);} finally {IoUtil.close(out);}}/*** @param templateFileName 模板文件名称* @param fileName         生成的文件名.pdf结尾* @param dataMap          数据,* @description 用模板先生成生成word, 再转成pdf,模板存放路径在resources/template下,生成的文件路径在jar包的同级目录/tmp路径下* @attention dataMap中的数据不能为null, 可用空值代替**/public static void savePdf(String templateFileName, String fileName, Map<String, Object> dataMap) {InputStream docInputStream = null;OutputStream outputStream = null;try {String docFileName = fileName.replace(FileTypeEnum.PDF.getSuffix(), FileTypeEnum.WORD.getSuffix());//先生成wordsaveWord(templateFileName, docFileName, dataMap);String docFilePath = StrUtil.concat(true,FileUtil.getResourceBasePath(),TEMP_FILE_DIR,docFileName);//aspose将word转成pdfif (!getLicense()) {// 验证License 若不验证则转化出的pdf文档会有水印产生return;}OsInfo osInfo = SystemUtil.getOsInfo();//linux环境的字体,需要将c:\windows\fonts下的字体文件放到指定目录下,否则会乱码if (osInfo.isLinux()) {FontSettings.setFontsFolder("/usr/share/fonts/chinese", true);}docInputStream = Files.newInputStream(new File(docFilePath).toPath());File outputFile = new File(FileUtil.getResourceBasePath(), "tmp/" + fileName);outputStream = Files.newOutputStream(outputFile.toPath());Document doc = new Document(docInputStream);                    //sourcerFile是将要被转化的word文档doc.save(outputStream, SaveFormat.PDF);//全面支持DOC, DOCX, OOXML, RTF HTML, OpenDocument, PDF, EPUB, XPS, SWF 相互转换//删除生成的临时word文件FileUtil.delDir(docFilePath);} catch (Exception e) {log.error("savePdf error:{}", e.getMessage(), e);} finally {IoUtil.close(docInputStream);IoUtil.close(outputStream);}}/*** 判断是否有授权文件 如果没有则会认为是试用版,转换的文件会有水印** @return*/public static boolean getLicense() {boolean result = false;try {InputStream is = DocUtil.class.getClassLoader().getResourceAsStream(LICENSE_FILENAME);License asposeLic = new License();asposeLic.setLicense(is);result = true;} catch (Exception e) {log.error("getLicense error:{}", e.getMessage());}return result;}}

测试

请求地址: http://localhost:8080/user/export?type=pdf
数据同word

生成excel

原理

  1. 使用的是easyExcel
  2. 原理也是生成生成Excel文件,然后上传文件服务器,然后返回文件id,再删除临时文件
  3. 因为excel的处理不再是模板不同,构建不同的dataMap即可生成。而是对于每种模板,都要相应地设计不同的写入方式,所以这里我引入了自定义的数据处理抽象类 ExcelDataWriter 。我们处理不同的模板,就构建其子类,实现抽象方法即可。
  4. 写数据的时候,基本对象信息,列表信息是要分次写入的,列表数据如果过大,还能分批写入,easyExcel会提供文件缓存。

步骤

依赖

<!--转excel--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.1</version></dependency><!--这个注意要加,我在看官网的时候没看到,只加了上面那个,结果后来实测就少了东西报错了--><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel-core</artifactId><version>3.1.1</version></dependency>

添加excel模板


Service

UserService

@Servicepublic class UserService implements IUserService {@Resourcepublic ExportService exportService;@Overridepublic FileVO export(UserExportDTO upd, String type){//用工具类生成word文件if (StrUtil.equals(FileTypeEnum.WORD.getType(),type) || StrUtil.equals(FileTypeEnum.PDF.getType(),type)){return exportService.export(upd.structDataMap(), type, UserTemplate.USER_TEMPLATE_XML.getTemplate(),null);}return exportService.export(upd.structDataMap(), type, UserTemplate.USER_TEMPLATE_EXCEL.getTemplate(),new UserExcelDataWriter());}}
ExportService

package com.wwt.file.service;import cn.hutool.core.util.StrUtil;
import com.wwt.file.entity.vo.FileVO;
import com.wwt.file.enums.FileTypeEnum;
import com.wwt.file.enums.UserTemplate;
import com.wwt.file.util.DocUtil;
import com.wwt.file.util.excel.ExcelDataWriter;
import com.wwt.file.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;import java.io.File;
import java.util.Map;@Slf4j
@Service
public class ExportService {/*** 临时生成文件的路径*/private static final String TEMP_FILE_DIR = "tmp\\";public FileVO export(Map<String, Object> dataMap, String type ,String tempFileName,ExcelDataWriter writer){if (StrUtil.equals(FileTypeEnum.WORD.getType(),type)){return new FileVO(UserTemplate.USER_TEMPLATE_XML.getChName(),exportWord(dataMap,tempFileName));}if (StrUtil.equals(FileTypeEnum.PDF.getType(),type)){return new FileVO(UserTemplate.USER_TEMPLATE_XML.getChName(),exportPdf(dataMap,tempFileName));}if (StrUtil.equals(FileTypeEnum.EXCEL.getType(),type)){return new FileVO(UserTemplate.USER_TEMPLATE_EXCEL.getChName(),exportExcel(dataMap,writer));}return new FileVO(null,null);}/*** @param dataMap        填充的数据,每个值都不能为null,可以为empty* @param tempFileName   模板文件名,放置在template目录下* @return 文件服务器返回的fileId* @description 模板导出word**/public Long exportWord(Map<String, Object> dataMap, String tempFileName) {//生成文件名String fileName = FileUtil.generateFileName(FileTypeEnum.WORD);DocUtil.saveWord(tempFileName, fileName, dataMap);//获取文件绝对路径String filePath = StrUtil.concat(true,FileUtil.getResourceBasePath(),TEMP_FILE_DIR,fileName);//上传文件到文件服务器//Long fileId = fileManagerService.uploadFile(filePath, fileName, "temp", ttl);//删除生成的文件//FileUtil.delDir(filePath);//模拟上传成功返回的idreturn 1234L; //fileId}/*** @param dataMap        填充的数据,每个值都不能为null,可以为empty* @param tempFileName   模板文件名,放置在template目录下* @return 文件服务器返回的fileId* @description 模板导出pdf**/public Long exportPdf(Map<String, Object> dataMap, String tempFileName) {//生成文件名String fileName = FileUtil.generateFileName(FileTypeEnum.PDF);DocUtil.savePdf(tempFileName, fileName, dataMap);String filePath = new File(FileUtil.getResourceBasePath(), TEMP_FILE_DIR + fileName).getAbsolutePath();//上传文件到文件服务器//Long fileId = fileManagerService.uploadFile(filePath, fileName, "temp", ttl);//FileUtil.delDir(filePath);return 1234L; //fileId}/*** 导出excel* 包括生成文档和上传文件* @param dataMap 数据* @param writer 不同的writer实现类处理不同的模板写入* @return*/public Long exportExcel(Map<String, Object> dataMap,ExcelDataWriter writer){String fileName = writer.export(dataMap);String filePath = StrUtil.concat(true,FileUtil.getResourceBasePath(),TEMP_FILE_DIR,fileName);//文件上传逻辑自己实现//Long fileId = fileManagerService.uploadFile(filePath, fileName, "temp", ttl);//FileUtil.delDir(filePath);return 1234L;}}
excel的处理逻辑类

ExcelDataWriter
package com.wwt.file.util.excel;import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.StrUtil;
import com.wwt.file.enums.FileTypeEnum;
import com.wwt.file.util.FileUtil;
import lombok.Data;import java.util.Map;@Data
public abstract class ExcelDataWriter {/*** 模板名* template.xlsx* 子类构造方法去定义*/private String template;/*** 模板所在目录路径* /xxx/xxx/xxx/target/classes/template/*/private String tempBasePath;/*** 模板绝对路径* /xxx/xxx/xxx/target/classes/template/template.xlsx*/private String templatePath;/*** 生成的文件名* aaa.xlsx*/private String fileName;/*** 生成的文件的绝对路径* /xxx/xxx/xxx/aaa.xlsx*/private String filePath;//抽象方法,让子类实现怎么根据dataMap去写数据生成文件,返回文件名字public abstract String export(Map<String, Object> dataMap);/*** 初始化参数*/public ExcelDataWriter(String template) {//D:\code\javaCode\FileHandle\target\classesClassPathResource classPathResource = new ClassPathResource("");String classPath = classPathResource.getFile().getAbsolutePath();this.template = template;//D:\code\javaCode\FileHandle\target\classes\template\this.tempBasePath =  StrUtil.concat(true,classPath,"\\template\\");//D:\code\javaCode\FileHandle\target\classes\template\xxx.xlsxthis.templatePath = StrUtil.concat(true,tempBasePath,template);this.fileName = FileUtil.generateFileName(FileTypeEnum.EXCEL);//D:\code\javaCode\FileHandle\tmp\123456.xlsxthis.filePath = StrUtil.concat(true,FileUtil.getResourceBasePath(),"tmp\\",this.fileName);//写数据前要保证父目录一定要存在FileUtil.mkParentDirIfNotExist(this.filePath);}
}
UserExcelDataWriter
package com.wwt.file.util.excel;import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.enums.WriteDirectionEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.excel.write.metadata.fill.FillWrapper;
import com.wwt.file.entity.User;
import com.wwt.file.enums.UserTemplate;
import lombok.extern.slf4j.Slf4j;import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 用户excel模板生成处理类*/
@Slf4j
public class UserExcelDataWriter extends ExcelDataWriter{//指定模板名private final String template = UserTemplate.USER_TEMPLATE_EXCEL.getTemplate();/*** 初始化参数**/public UserExcelDataWriter() {super(UserTemplate.USER_TEMPLATE_EXCEL.getTemplate());}/*** 导出文件到本地* 返回文件的绝对路径* @param dataMap* @return*/@Overridepublic String export(Map<String, Object> dataMap) {User user1 = MapUtil.get(dataMap, "user1", User.class);User user2 = MapUtil.get(dataMap, "user2", User.class);List<User> user3 = MapUtil.get(dataMap, "users", List.class);List<User> user4 = MapUtil.get(dataMap, "users", List.class);List<User> user5 = MapUtil.get(dataMap, "users", List.class);String templateFileName = super.getTemplatePath();String filePath = super.getFilePath();ExcelWriter excelWriter = null;String fileName = null;try{//技巧,数据分段写入excelWriter = EasyExcel.write(filePath).withTemplate(templateFileName).build();WriteSheet writeSheet = EasyExcel.writerSheet().build();HashMap<String, Object> baseMessMap = MapUtil.newHashMap();HashMap<String, Object> user3ListMap = MapUtil.newHashMap();HashMap<String, Object> user4ListMap = MapUtil.newHashMap();//简单模板数据写入baseMessMap.put("name1",user1.getName());baseMessMap.put("age1",user1.getAge());baseMessMap.put("email1",user1.getEmail());baseMessMap.put("id1",user1.getId());baseMessMap.put("name2",user2.getName());baseMessMap.put("age2",user2.getAge());baseMessMap.put("email2",user2.getEmail());baseMessMap.put("id2",user2.getId());excelWriter.fill(baseMessMap, writeSheet);//横向列表数据写入FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();// 如果有多个list 模板上必须有{前缀.} 这里的前缀就是 data5,然后多个list必须用 FillWrapper包裹excelWriter.fill(new FillWrapper("user5", user5), fillConfig, writeSheet);//纵向列表数据写入 如果数据量很大,可以看看官网,有让你分段查询插入数据的办法,会用到缓存,效率还是很高的excelWriter.fill(new FillWrapper("user3", user3), writeSheet);excelWriter.fill(new FillWrapper("user4", user4), writeSheet);fileName = super.getFileName();}catch (Exception e){log.error("excel create error : ",e.getMessage(), e);}finally {if (ObjectUtil.isNotNull(excelWriter)){excelWriter.finish();}}return fileName;}
}

测试

请求地址:http://localhost:8080/user/export?type=excel
数据同word,只是构建方法不一样,看上面的 UserExcelDataWriter 的 export

根据文件模板实现预览、生成word、pdf、excel(后端-项目)相关推荐

  1. vue实战--vue+elementUI实现多文件上传+预览(word/PDF/图片/docx/doc/xlxs/txt)

    需求 最近在做vue2.0+element UI的项目中遇到了一个需求:需求是多个文件上传的同时实现文件的在线预览功能.需求图如下: 看到这个需求的时候,小栗脑袋一炸.并不知道该如何下手,之前的实践项 ...

  2. 前端技术实现文件预览(word、excel、pdf、ppt、mp4、图片、文本)

    前言 因为业务需要,很多文件需要在前端实现预览,今天就来了解一下吧. Demo地址[1]:https://zhuye1993.github.io/file-view/dist/index.html 实 ...

  3. fastdfs+nginx+keepalived+openoffice+lua 实现文件上传、下载、水印、预览(word、excel、ppt、txt),feign文件上传

    前言 最近刚刚实现的文件服务中心,记录一下,为没做过的人提供一下思路,由于本人技术有限,不足之处欢迎批评指正,共同学习,共同进步. 目录 Fastdfs集群搭建 搭建keepalived 实现ngxi ...

  4. Java实现office办公文档在线预览(word、excel、ppt、txt等)

    文章目录 一.官网下载openOffice 安装包,运行安装(不同系统的安装请自行百度,这里不做过多描述) 二.pom中引入依赖 三.office文件转为pdf流的工具类 四.service层代码 五 ...

  5. android 生成 资源文件,SVG-Android开源库——SVG生成Vector资源文件的编辑预览工具...

    Vector矢量图在Android项目中的利用愈来愈广泛,但是如果你想用Android Studio自带的工具将SVG图片转化成Vector资源文件却是相当麻烦,首先能支持的SVG规范较少,其次操作流 ...

  6. Web方式预览Office/Word/Excel/pdf文件解决方案

    Web方式预览Office/Word/Excel/pdf文件解决方案 参考文章: (1)Web方式预览Office/Word/Excel/pdf文件解决方案 (2)https://www.cnblog ...

  7. 前端ppt\word等等文件实现在线预览功能

    前端ppt\word等等文件实现在线预览功能 方法1:https://view.xdocin.com/view?src=你的文档地址 10天免费 方法2:https://view.officeapps ...

  8. SAP SmartForms PDF Preview Test 打印预览生成PDF文件

    SAP SmartForms PDF Preview Test 打印预览生成PDF文件 *&-------------------------------------------------- ...

  9. 【vue2】纯前端实现本地的pdf/word/epub文件预览(包括pdf选中文字,epub高亮等)

    前言 需求是预览本地的pdf/word/epub格式的文件,但是搜索后发现没有可以直接使用的,格式不同,显示的方式和效果也都略有不同. 最后还是分别实现预览的功能. 补充功能:pdf选中文字,epub ...

最新文章

  1. R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、自定义overall的标签名称
  2. 机器学习——线性回归的原理,推导过程,源码,评价
  3. 【FluidSynth】SoundFont 音源文件资料收集 ( SoundFont 规范 | SoundFont 音源下载 | SoundFont 编辑器 | 博客资源 )
  4. Java黑皮书课后题第2章:2.11(人口统计)重写编程练习题1.11,提示用户输入年数,然后显示这个年数之后的人口值,将1.11中的提示用于这个程序
  5. photoshop最全快捷键列表
  6. c2665 “initgraph”: 2 个重载中没有一个可以转换所有参数类型_一个使用延迟候选项生成的用于自动化程序修复方法的工具...
  7. 面向对象2:类和对象
  8. 利用BS爬取单词音标
  9. java.io.FileNotFoundException: /opt/shan/es/logs/elasticsearch_index_indexing_slowlog.json (权限不够)
  10. 数据分析SQL环比增长率、同比增长率
  11. 武汉新时标文化传媒有限公司短视频创作者实现突围?
  12. js判断是否为电话号码
  13. 配置webpack不打包第三方包
  14. 创建Direct3D9设备-------VB6编程学习DX9游戏编程DirectX9编程2D小游戏源码冷风引擎CoolWind2D游戏引擎(5)
  15. Java 的反射机制
  16. Cadence Allegro16.6完整安装包+和谐文件 下载
  17. canvas 花蕊绽放
  18. 天融信EDR 彻底卸载方法
  19. java currenthread_currentThread
  20. 《晚风》 带来阵阵清凉

热门文章

  1. python在屏幕上画画_想学画画?python满足你!
  2. 100以内的三连加减法JAVA_100以内加减法三连算练习题.doc
  3. 895计算机专业基础,北京工业大学2020年考研895计算机学科专业基础考试大纲
  4. 入住阿里云MQTT物联网平台
  5. 实现简单的直播互动功能,直播软件源码是如何做的
  6. django连接数据库获取数据
  7. 【数据库MySQL】2021最新官网下载及查看MySQL版本步骤教程
  8. “鸡”不可失—— 咕咕机3代VS2代对比测评
  9. web前端《叮叮书店》编写
  10. c语言程序立体几何计算机,立体几何教学中巧妙利用信息技术 -----培养高中学生数学学习的兴趣   张芬     2014年11月10日...