有一个word文档,内容如下图所示,现在需要定义一个模板,通过后台查询获取我们需要的数据,然后自动填充到我们的word之中,生成需要的文档供我们下载。
<!--word模板工具包-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<!--打包压缩工具包-->
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.10.5</version>
</dependency>
打开word文档–>另存为—>‘将word文档保存类型选择xml类型’–>保存就得到一个名为***.xml的模板文件
将生成的**.xml文件重命名,将后缀的“xml”改为“ftl”的格式:
然后打开这个后缀为ftl的word模板文件,将我们需要通过后台生成的内容替换成${***}的形式
解释一下:${***}是ftl模板的占位符,我们可以通过这个占位符来进行对模板内容进行赋值,生成我们想要的word文档。
这里我打开ftl模板用的是工具是Edit with Notepad++
(可以用其他的,文件编辑器也可以)。
搜索我们需要替换的内容信息:这里我将“我的姓名”改为占位符的形式
下面按照这种方式挨个将我们需要修改的内容全部替换掉。
注意: 占位符中${studentname}的studentname是自己命名的名字,自己按需要起名。
将修改完成的模板保存起来备用。
Map<String, Object> dataMap = new HashMap<String, Object>();
String studentName = "张三";
dataMap.put("studentname", studentName); // 姓名
dataMap.put("gender", "男"); // 性别
dataMap.put("hometown", "江苏省-苏州市"); // 籍贯: **省-**市
dataMap.put("birthday", "1996-04-05"); // 出生年月
dataMap.put("classname", "三年级一班"); // 班级
dataMap.put("subjectname", "英语"); // 学科
dataMap.put("fraction", "98"); // 分数
dataMap.put("rank", "3"); // 排名
String phoneImage = downPicture("C:/Users/wangxk/Pictures/question.jpg");
dataMap.put("imagephone", phoneImage); // 照片
这里我们用到了一个方法downPicture("")
,该方法时将指令路径下的图片处理成base64的格式,然后返回处理后的字符串:
/**
* 图片转码处理
*/
public String downPicture(String picture) {
InputStream in = null;
byte[] data = null;
//读取图片字节数组
try {
String fileName = picture; // 图片的存储路径
in = new FileInputStream(fileName);
data = new byte[in.available()];
in.read(data);
in.close();
} catch (Exception e) {
e.printStackTrace();
}
BASE64Encoder encoder = new BASE64Encoder();
// String address = "data:image/jpeg;base64," + encoder.encode(data); // 返回Base64编码过的字节数组字符串
String address = encoder.encode(data); // 返回Base64编码过的字节数组字符串
return address;
}
注意:一般图片转换成base64格式时,有一个特定的开头:
data:image/jpeg;base64,
,如果我们需要用base64还原图片时,需要将格式头加上
package com.blog.web.controller.tool;
import com.blog.framework.config.properties.BlogConfig;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.*;
import java.util.Map;
/**
* word文档模板支持工具
* @author Aaron Wang
* @data 2020/7/14
*/
public class FreeMarkerFileUtils {
// 文档数据填充
public static String documentDataFilling(Map<String,Object> dataMap, String fileNmae){
try{
// 模板
//Configuration 用于读取ftl文件
Configuration configuration = new Configuration();
//设置编码
configuration.setDefaultEncoding("UTF-8");
/**
* 以下是两种指定ftl文件所在目录路径的方式,注意这两种方式都是
* 指定ftl文件所在目录的路径,而不是ftl文件的路径
*/
//指定路径的第一种方式(根据某个类的相对路径指定)
// configuration.setClassForTemplateLoading(PrjWorkerController.class,"/");
//指定路径的第二种方式,我的路径是C:/a.ftl
// 设置模板文件的文件夹路径(注意:这里是模板文件所在文件夹的路径,不是模板文件的路径)
File templeteFile = new File(BlogConfig.getProfile() + "laborTemplete/");
if (!templeteFile.exists()) {
// 创建模板文件目录,如果目录不存在则创建
templeteFile.mkdirs();
}
configuration.setDirectoryForTemplateLoading(templeteFile);
//获取inu模板文件
Resource resource = new ClassPathResource("wordTemplete/word模板.ftl");
File inuModel = new File(BlogConfig.getProfile() + "/laborTemplete/laborFile.ftl");
FileUtils.copyToFile(resource.getInputStream(), inuModel);
//输出文档路径及名称
File fileLeave = new File(BlogConfig.getProfile() + "laborFIle/");
if (!fileLeave.exists()) {
// 创建档案存放目录,如果目录不存在则创建
fileLeave.mkdirs();
}
String wordName = fileNmae + ".doc";
File outFile = new File(BlogConfig.getProfile() + "/laborFIle/" + new String( wordName.getBytes("utf-8") , "utf-8"));
//以utf-8的编码读取ftl文件
Template template = configuration.getTemplate("laborFile.ftl");
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8"), 10240);
template.process(dataMap, out);
// 关闭文件流
out.close();
// 文档存放的目录
return BlogConfig.getProfile() + "/laborFIle/"; // 这里的getProfile()是我自己定义的一个方法:可以直接指定路径,如:D:/profile
}catch (Exception e){
e.printStackTrace();
}
return BlogConfig.getProfile() + "/laborFIle/";
}
}
注意:代码中引用了一个
BlogConfig.getProfile()
的方法,这是我自己定义的一个方法,用于获取生成的word文档的存储目录,可以直接指定我们自己电脑下的目录,如:D:/profile
package com.blog.web.controller.tool;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 文件压缩工具
* @author Aaron Wang
* @data 2020/7/25
*/
public class CompressUtil {
/**
* @param sourcePath 要压缩的文件路径
* @param suffix 生成的格式后最(zip、rar)
*/
public static void generateFile(String sourcePath, String suffix) throws Exception {
File file = new File(sourcePath);
// 压缩文件的路径不存在
if (!file.exists()) {
throw new Exception("路径 " + sourcePath + " 不存在文件,无法进行压缩...");
}
// 用于存放压缩文件的文件夹
String generateFile = file.getParent() + File.separator +"CompressFile";
File compress = new File(generateFile);
// 如果文件夹不存在,进行创建
if( !compress.exists() ){
compress.mkdirs();
}
// 目的压缩文件
String generateFileName = compress.getAbsolutePath() + File.separator + "AAA" + file.getName() + "." + suffix;
// 输入流 表示从一个源读取数据
// 输出流 表示向一个目标写入数据
// 输出流
FileOutputStream outputStream = new FileOutputStream(generateFileName);
// 压缩输出流
ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(outputStream));
generateFile(zipOutputStream,file,"");
System.out.println("源文件位置:" + file.getAbsolutePath() + ",目的压缩文件生成位置:" + generateFileName);
// 关闭 输出流
zipOutputStream.close();
}
/**
* @param out 输出流
* @param file 目标文件
* @param dir 文件夹
* @throws Exception
*/
private static void generateFile(ZipOutputStream out, File file, String dir) throws Exception {
// 当前的是文件夹,则进行一步处理
if (file.isDirectory()) {
//得到文件列表信息
File[] files = file.listFiles();
//将文件夹添加到下一级打包目录
out.putNextEntry(new ZipEntry(dir + "/"));
dir = dir.length() == 0 ? "" : dir + "/";
//循环将文件夹中的文件打包
for (int i = 0; i < files.length; i++) {
generateFile(out, files[i], dir + files[i].getName());
}
} else {
// 当前是文件
// 输入流
FileInputStream inputStream = new FileInputStream(file);
// 标记要打包的条目
out.putNextEntry(new ZipEntry(dir));
// 进行写操作
int len = 0;
byte[] bytes = new byte[1024];
while ((len = inputStream.read(bytes)) > 0) {
out.write(bytes, 0, len);
}
// 关闭输入流
inputStream.close();
}
}
/**
* 递归压缩文件
* @param output ZipOutputStream 对象流
* @param file 压缩的目标文件流
* @param childPath 条目目录
*/
private static void zip(ZipOutputStream output,File file,String childPath){
FileInputStream input = null;
try {
// 文件为目录
if (file.isDirectory()) {
// 得到当前目录里面的文件列表
File list[] = file.listFiles();
childPath = childPath + (childPath.length() == 0 ? "" : "/")
+ file.getName();
// 循环递归压缩每个文件
for (File f : list) {
zip(output, f, childPath);
}
} else {
// 压缩文件
childPath = (childPath.length() == 0 ? "" : childPath + "/")
+ file.getName();
output.putNextEntry(new ZipEntry(childPath));
input = new FileInputStream(file);
int readLen = 0;
byte[] buffer = new byte[1024 * 8];
while ((readLen = input.read(buffer, 0, 1024 * 8)) != -1) {
output.write(buffer, 0, readLen);
}
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
// 关闭流
if (input != null) {
try {
input.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
/**
* 压缩文件(文件夹)
* @param path 目标文件流
* @param format zip 格式 | rar 格式
* @throws Exception
*/
public static String zipFile(File path,String format) throws Exception {
String generatePath = "";
if( path.isDirectory() ){
generatePath = path.getParent().endsWith("/") == false ? path.getParent() + File.separator + path.getName() + "." + format: path.getParent() + path.getName() + "." + format;
}else {
generatePath = path.getParent().endsWith("/") == false ? path.getParent() + File.separator : path.getParent();
generatePath += path.getName().substring(0,path.getName().lastIndexOf(".")) + "." + format;
}
// 输出流
FileOutputStream outputStream = new FileOutputStream( generatePath );
// 压缩输出流
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(outputStream));
zip(out, path,"");
out.flush();
out.close();
return generatePath;
}
/**
* 递归删除目录下的所有文件及子目录下所有文件
* @param dir 将要删除的文件目录
*/
public static boolean deleteDir(File dir) {
if (dir.isDirectory()) {
String[] children = dir.list();
//递归删除目录中的子目录下
for (int i=0; i<children.length; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) {
return false;
}
}
}
// 目录此时为空,可以删除
return dir.delete();
}
//测试
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String sd = sdf.format(new Date()); // 时间戳转换成时间
System.out.println(sd);
String path = "D:\\profile\\laborFIle";
// String path = "D:\\profile\\laborFIle";
// String format = "zip";
try {
System.out.println(deleteDir(new File(path)));
} catch (Exception e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
/**
* 下载生成的word文档
* @param zjhms 勾选的民工证件号码集合
* @param prjnumber 项目合同备案号
* @return
* @throws IOException
*/
@GetMapping("/downnloadFiles")
@ResponseBody
public AjaxResult downnloadOtherFiles(String zjhms, String prjnumber) {
try {
return downFile(zjhms, prjnumber);
}catch (Exception e){
e.printStackTrace();
return AjaxResult.error("下载失败,请联系管理员!");
}
}
/**
* 下载档案文件
* @param zjhms 员工证件号码集合
* @return
*/
public AjaxResult downFile(String zjhms, String prjnumber){
Map<String, Object> dataMap = new HashMap<String, Object>();
String studentName = "张三";
dataMap.put("studentname", studentName); // 姓名
dataMap.put("gender", "男"); // 性别
dataMap.put("hometown", "江苏省-苏州市"); // 籍贯: **省-**市
dataMap.put("birthday", "1996-04-05"); // 出生年月
dataMap.put("classname", "三年级一班"); // 班级
dataMap.put("subjectname", "英语"); // 学科
dataMap.put("fraction", "98"); // 分数
dataMap.put("rank", "3"); // 排名
String phoneImage = downPicture("C:/Users/wangxk/Pictures/question.jpg");
dataMap.put("imagephone", phoneImage); // 照片
// 根据模板生成档案文档
String wordName = studentName + "_" + "成绩单"; // 文档名称
String wordsPath = FreeMarkerFileUtils.documentDataFilling(dataMap, wordName); // 接受文档存放目录
// 将存放文件的文件夹打包压缩
try {
// 压缩文件
// zipFile()参数说明:被压缩的文件所在目录路径,压缩格式
String path = CompressUtil.zipFile(new File(wordsPath), "zip");
// System.out.println("压缩后存放目录:" + path);
// 压缩完成后,删除目录wordsPath中生成的word文件
boolean delete = CompressUtil.deleteDir(new File(wordsPath));
// System.out.println("word目录删除状态:" + delete);
return AjaxResult.success("laborFIle.zip");
} catch (Exception e) {
e.printStackTrace();
}
return AjaxResult.error("下载失败。。。");
}
我们指定了前端访问的路径为:
***/downnloadFiles
的形式,当我们通过网页请求到该路径时,我们就可以下载生成的word文档文件压缩包
<a class="btn btn-outline btn-info btn-rounded" onclick="downnloadOtherFiles()">
<i class="fa fa-download">i> word文档下载
a>
<script th:inline="javascript">
var prefix = ctx + "blogback";
// 下载选中的民工档案
function downnloadOtherFiles() {
$.modal.loading("正在下载,请稍后。。。"); // 打开遮罩层
var url = prefix + "/downnloadFiles";
var param = "?zjhms=" + "411521185477412587" + "&prjnumber=" + "3256987422114";
console.log("建筑民工登记表的url = " + url + param);
$.get(url + param, function (result) {
if (result.code == web_status.SUCCESS) {
window.location.href = ctx + "laborerFileMess/downloadZip?fileName=" + encodeURI(result.msg) + "&delete=" + true;
// window.location.href = ctx + "laborerFileMess/download?fileName=" + encodeURI("laborFIle.zip") + "&delete=" + true;
$.modal.closeLoading(); // 关闭遮罩层
} else if (result.code == web_status.WARNING) {
$.modal.closeLoading(); // 关闭遮罩层
$.modal.alertWarning(result.msg)
} else {
$.modal.alertError(result.msg);
}
});
}
script>
点击按钮直接请求到后台我们指定的方法进行word文档的下载。
laborerFileMess/downloadZip
这是一个下载的方法,比较简单,下面试方法的代码。
/**
* 下载压缩包
* @param fileName 文件名称
* @param delete 是否删除
*/
@GetMapping("laborerFileMess/downloadZip")
public void downloadZip(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) {
try {
String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
String filePath = BlogConfig.getProfile() + fileName;
response.setCharacterEncoding("utf-8");
response.setContentType("multipart/form-data");
response.setHeader("Content-Disposition",
"attachment;fileName=" + setFileDownloadHeader(request, realFileName));
FileUtils.writeBytes(filePath, response.getOutputStream());
if (delete) {
FileUtils.deleteFile(filePath);
}
} catch (Exception e) {
System.out.println("压缩包下载失败。。");
e.printStackTrace();
}
}
public String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException {
final String agent = request.getHeader("USER-AGENT");
String filename = fileName;
if (agent.contains("MSIE")) {
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
} else if (agent.contains("Firefox")) {
// 火狐浏览器
filename = new String(fileName.getBytes(), "ISO8859-1");
} else if (agent.contains("Chrome")) {
// google浏览器
filename = URLEncoder.encode(filename, "utf-8");
} else {
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
这里还用到了一个文件处理类:FileUtils
package com.blog.common.utils.file;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import javax.servlet.http.HttpServletRequest;
/**
* 文件处理工具类
*/
public class FileUtils
{
public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
/**
* 输出指定文件的byte数组
* @param filePath 文件路径
* @param os 输出流
* @return
*/
public static void writeBytes(String filePath, OutputStream os) throws IOException
{
FileInputStream fis = null;
try
{
File file = new File(filePath);
if (!file.exists())
{
throw new FileNotFoundException(filePath);
}
fis = new FileInputStream(file);
byte[] b = new byte[1024];
int length;
while ((length = fis.read(b)) > 0)
{
os.write(b, 0, length);
}
}
catch (IOException e)
{
throw e;
}
finally
{
if (os != null)
{
try
{
os.close();
}
catch (IOException e1)
{
e1.printStackTrace();
}
}
if (fis != null)
{
try
{
fis.close();
}
catch (IOException e1)
{
e1.printStackTrace();
}
}
}
}
/**
* 删除文件
*
* @param filePath 文件
* @return
*/
public static boolean deleteFile(String filePath)
{
boolean flag = false;
File file = new File(filePath);
// 路径为文件且不为空则进行删除
if (file.isFile() && file.exists())
{
file.delete();
flag = true;
}
return flag;
}
/**
* 文件名称验证
*
* @param filename 文件名称
* @return true 正常 false 非法
*/
public static boolean isValidFilename(String filename)
{
return filename.matches(FILENAME_PATTERN);
}
/**
* 下载文件名重新编码
*
* @param request 请求对象
* @param fileName 文件名
* @return 编码后的文件名
*/
public static String setFileDownloadHeader(HttpServletRequest request, String fileName)
throws UnsupportedEncodingException
{
final String agent = request.getHeader("USER-AGENT");
String filename = fileName;
if (agent.contains("MSIE"))
{
// IE浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+", " ");
}
else if (agent.contains("Firefox"))
{
// 火狐浏览器
filename = new String(fileName.getBytes(), "ISO8859-1");
}
else if (agent.contains("Chrome"))
{
// google浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
else
{
// 其它浏览器
filename = URLEncoder.encode(filename, "utf-8");
}
return filename;
}
}
将下载的压缩包解压后就得到我们想生成的成绩单的word文档文件。
其实这个想实现很简单,在文档的第五步,就是根据信息生成一个word文档,然后将该文档所在的目录打包成压缩文件供我们下载,想要实现生成多个word文档然后在同一打包压缩,我们只需要先循环定义我们生成word文档的Map集合信息,生成多个文档存放在我们指定的目录下,最后再打包压缩该目录即可。
文章到这里就告一段落了,如果有什么错误的地方欢迎大家指正。我们一起学习。
欢迎大家评论留言。我都会一一回复。
项目下载源码