(不需要copy代码,请专注于理解如何实现,我会详细解释代码!)
一、简要描述
One day,我参与的项目中,用户提了个新需求。要求:按照给定 word模板,填充数据并导出。
负责这个需求后,我查阅CSDN,虽然也找到了一些文章,依据文章中的顺序,写出了demo。但demo毕竟是demo,在项目实战中,还是让我遇到不少坑。
由于 easypoi
对Word的支持不太友好,不支持多图片导出
,因此研究之后,也找到一个办法,后续会展开叙述。
二、项目描述
主要涉及:SpringBoot + MySQL + Linux
实际项目会部署在Linux上,图片也是需要从服务器上解析到本地,转成Base64位字节才可以。不过我会提供全部的图片转Base64公共方法。
注意理解:Word支持显示的图片,是图片通过IO流转成的Base64字节
(正式开始 ===> follow me!)
第二步:导入Jar包
<!-- 集成 easypoi -->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>3.3.0</version>
</dependency>
第三步:Controller层 接口
@ApiOperation(value = "导出巡查整改通知单")
@PostMapping("/letter/notice/export")
@ApiImplicitParams({@ApiImplicitParam(name = "id", value = "巡查id")})
public void wordExport(@RequestParam Long id,HttpServletRequest request, HttpServletResponse response) throws IcmsException, IOException {
// 注入Service层
service.wordExport(id, request, response);
}
第四步:Service层 业务
@Override
public void wordExport(Long id, HttpServletRequest request, HttpServletResponse response) throws IcmsException, IOException {
// 1.查询数据
User user = userDao.selectList(new QueryWrapper<User>().eq("id",id));
// 2.创建 map
Map<String,Object> data = new HashMap<>();
data.put("number",new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())); // 通知单号
data.put("unitName", StringUtils.isEmpty(user.getUnitName()) ? " ":user.getUnitName()); // 单位
data.put("sendTime",null == user.getSendTime() ? " ":new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒").format(user.getSendTime()));
data.put("type",StringUtils.isEmpty(user.getType()) ? " ":user.getType()); // 问题类型
data.put("content",StringUtils.isEmpty(user.getContent()) ? " ":user.getContent()); // 问题描述
// 3.处理图片
String httpPath = request.getScheme() + "://" + request.getServerName() + user.getUrl(); // 图片路径
WordImageEntity image = new WordImageEntity(FileUtils.getImageBase64(httpPath),384,216);
image.setType(WordImageEntity.Data); // 设置成 Byte字节
data.put("image",image);
// 4.导出
FileUtils.easyPoiExport("/opt/baidu/web/file/word/template.docx","tempDir","test.docx",data,request,response);
}
描述:
1、进入Service层,第一步
是查询本次需要导出的数据。
2、创建map的作用
:easypoi会通过map的key值,映射到word模板上,替换value值。
重要:
真正在写业务的时候,请注意对替换的值,进行判空处理!如果值为空,请使用“ ”
,而不是“”
。中间要空格!
图片:
代码中的 String httpPath = “”
,由于我的项目部署在Linux服务器上,图片也在服务器中,需要获取全路径。Windows系统本地开发,可使用本地图片全路径。
String httpPath = request.getScheme() + "://" + request.getServerName() + file.getUrl();
输出结果:http://172.16.231.188/file/img/2020-03-29/161699762599480.jpg
WordImageEntity 详解
1、WordImageEntity类提供了2个构造方法,一个可直接传图片URl,一个传图片字节byte[]
。此处建议使用public WordImageEntity(byte[] data) --缩写
构造方法。
2、WordImageEntity类支持设置图片宽度、高度。
关于FileUtils.getImageBase64()
这个方法继续往下看会讲到。
第五步:FileUtils
通用工具类
public class FileUtils {
/**
* EasyPoi 替换数据 导出 word
* @param templatePath word模板地址
* @param tempDir 临时文件存放地址
* @param filename 文件名称
* @param data 替换参数
* @param request
* @param response
*/
public static void easyPoiExport(String templatePath, String tempDir, String filename, Map<String, Object> data, HttpServletRequest request, HttpServletResponse response) {
Assert.notNull(templatePath, "模板路径不能为空");
Assert.notNull(tempDir, "临时文件路径不能为空");
Assert.notNull(filename, "文件名称不能为空");
Assert.isTrue(filename.endsWith(".docx"), "文件名称仅支持docx格式");
if (!tempDir.endsWith("/")) {
tempDir = tempDir + File.separator;
}
File file = new File(tempDir);
if (!file.exists()) {
file.mkdirs();
}
try {
String userAgent = request.getHeader("user-agent").toLowerCase();
if (userAgent.contains("msie") || userAgent.contains("like gecko")) {
filename = URLEncoder.encode(filename, "UTF-8");
} else {
filename = new String(filename.getBytes("utf-8"), "ISO-8859-1");
}
XWPFDocument document = WordExportUtil.exportWord07(templatePath, data);
String tempPath = tempDir + filename;
FileOutputStream out = new FileOutputStream(tempPath);
document.write(out);
// 设置响应规则
response.setContentType("application/force-download");
response.addHeader("Content-Disposition", "attachment;filename=" + filename);
OutputStream stream = response.getOutputStream();
document.write(stream);
stream.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
deleteTempFile(tempDir, filename);
}
}
/**
* 删除临时生成的文件
*/
public static void deleteTempFile(String filePath, String fileName) {
File file = new File(filePath + fileName);
File f = new File(filePath);
file.delete();
f.delete();
}
}
至此,按照以上顺序,即可完成 easypoi 导出 word!
三、扩展
1、图片转字节数组(Linux生产环境、本地开发环境)
/**
* Linux生产环境 将图片转换为字节数组
* @param path 文件 http路径
* @return base64编码过的字节数组
* @throws IOException
*/
public static byte[] getImageBase64(String path) throws IOException {
byte[] data = null;
URL url = null;
InputStream input = null;
try{
url = new URL(path);
HttpURLConnection httpUrl = (HttpURLConnection) url.openConnection();
httpUrl.connect();
httpUrl.getInputStream();
input = httpUrl.getInputStream();
}catch (Exception e) {
e.printStackTrace();
return null;
}
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int numBytesRead = 0;
while ((numBytesRead = input.read(buf)) != -1) {
output.write(buf, 0, numBytesRead);
}
data = output.toByteArray();
output.close();
input.close();
return data;
}
/**
* 本地开发环境 将图片转换为字节数组存储
* @param path 文件 http路径
* @return base64编码过的字节数组
* @throws IOException
*/
public static byte[] getImageBase64(String path) throws IOException {
InputStream input = new FileInputStream(path);
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int numBytesRead = 0;
while ((numBytesRead = input.read(buf)) != -1) {
output.write(buf, 0, numBytesRead);
}
byte[] data = output.toByteArray();
output.close();
input.close();
return data;
}
2、多图片导出(easypoi 并不支持多图片导出,以下仅供参考!)
(1)修改模板
由于 easypoi并不支持多图片导出,因此该模板与原先模板不同之处就在于,在显示图片的地方,多定义标签,并设置下标。
(2)业务层代码
// 1.全部图片信息 files
List<FileInfoModel> files = detail.getFiles();
if(null != files && files.size() > 0){
List<WordImageEntity> images = new ArrayList<>();
for (FileInfoModel file : files) {
// 2.处理每一张图片,并将其添至 images集合中
String httpPath = request.getScheme() + "://" + request.getServerName() + file.getUrl(); // 图片路径
WordImageEntity image = new WordImageEntity(FileUtils.getImageBase64(httpPath),384,216);
image.setType(WordImageEntity.Data);
images.add(image);
}
// 3.图片赋值
for (int i = 0; i < images.size(); i++) {
data.put("image_"+i, images.get(i));
}
// 4.多余 {{image_x}} 标签要赋值为空
for (int i = images.size(); i < 5; i++) {
data.put("image_"+i, " ");
}
}else{
// 5.如果没有图片,所有图片标签置空
for (int i = 0; i < 5; i++) {
data.put("image_"+i, " ");
}
}
如果你对于 easypoi所实现的效果不满意,可以选择使用 freemarker技术来生成 word文档。
为此我也撰写了一份博客:Freemarker word 导出至此,完結~