技术背景
很多时候我们网站或者系统需要提供一些word文件,例如证明.docx或者订单.docx等文件供用户下载打印等。
用Java操作word文档,毫无疑问,当下最流行apache poi,对于poi如何操作word文档,这里不作过多介绍。
这里主要讲解如何通过一个制作好的word模板文件,通过数据填充,生成加工好的word文件。
官方网站
Maven
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.3.1</version>
</dependency>
实战内容
以下源码已经上传再我的开源项目里面的spring-cloud-study-poi
https://github.com/moshowgame/spring-cloud-study
说先是新建模板word文档(。docx格式,2007/2013/2016新格式的)
运行效果
核心实现代码
@RestController
public class PoiController {
@GetMapping("/generateWord")
public ApiReturnObject generateWord(String data){
//图片路径,请注意你是linux还是windows
String wordPath="C:\\Users\\Administrator\\Desktop\\";
String modelName="人物模板.docx";
String outputName="hanyunxi.docx";
Map<String, Object> datas = new HashMap<String, Object>() {
{
//本地图片
put("name","鞠婧祎");
put("team","前SNH48");
put("gender","女");
put("birthday","19940618");
put("remark","鞠婧祎,1994年6月18日出生于四川遂宁,毕业于四川音乐学院附属中学,中国女演员、歌手,原SNH48 TEAM NII成员[1]。2013年9月5日,升格为SNH48 TEAM NII二期生正式成员;11月2日,以《剧场女神》公演正式出道。2014年6月7日,拍摄个人首支MV《足球派对》;7月26日,参加SNH48“一心向前”演唱会暨SNH48第一届总选举,演唱《流着泪微笑》并获SNH48总选举第四名。2015年1月15日,发行出道两周年EP《青春的约定》。2016年12月10日,获“星光大赏”年度新锐电视剧女演员。2017年1月1日,参加安徽卫视《国剧盛典》;3月27日,获第24届东方风云榜音乐盛典“年度飞跃歌手”奖[2];5月4日,获团中央“五四优秀青年”称号;5月25日,发行第二张个人EP《等不到你》;[3]7月29日,参加“我心翱翔”第四届总选举发布演唱会,获得SNH48第四届总选举第1名;[4]12月15日,SNH48官方宣布,鞠婧祎正式从SNH48 Group单飞、成立个人工作室。2018年3月26日,获第25届《东方风云榜》音乐盛典年度跨界艺人奖。7月28日,参加湖南卫视综艺节目《快乐大本营》。");
put("active",new NumbericRenderData(new ArrayList<TextRenderData>(){{
add(new TextRenderData("FF00FF", "2013年 以《剧场女神》公演正式出道"));
add(new TextRenderData("FF00FF", "2014年 拍摄个人首支MV《足球派对》"));
add(new TextRenderData("FF00FF", "2015年 发行出道两周年EP《青春的约定》"));
add(new TextRenderData("FF00FF", "2016年 主演玄幻剧《九州天空城》"));
}}));
put("tables", new TableRenderData(new ArrayList<RenderData>(){{
add(new TextRenderData("d0d0d0", "节目"));
add(new TextRenderData("d0d0d0", "次数"));
}},new ArrayList<Object>(){{
add("《SNH星剧院公演》;999");
add("《敢ZUO敢为女声秀》;4");
add("《快乐大本营》;2");
}}, "no datas", 10600));
// //网路图片
put("picture", new PictureRenderData(200, 250, ".png", BytePictureUtils.getUrlByteArray("https://pic.baike.soso.com/ugc/baikepic2/18293/cut-20170602162513-2088410512.jpg/300")));
}
};
XWPFTemplate template = XWPFTemplate.compile(wordPath+modelName)
.render(datas);
FileOutputStream out;
try {
out = new FileOutputStream(wordPath+outputName);
template.write(out);
out.flush();
out.close();
template.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return ApiReturnUtil.success(data);
}
}
支持的模板标签
- 文本模板{{var}}
put("author", new TextRenderData("000000", "Sayi卅一"));
put("introduce", "http://www.deepoove.com");
- 图片模板{{@var}}
// 本地图片
put("localPicture", new PictureRenderData(120, 120, "./sayi.png"));
// 本地图片byte数据
byte[] localByteArray = BytePictureUtils.getLocalByteArray(new File("./logo.png"));
put("localBytePicture", new PictureRenderData(100, 120, ".png", localByteArray));
// 网络图片
put("urlPicture", new PictureRenderData(100, 100, ".png", BytePictureUtils.getUrlByteArray("https://avatars3.githubusercontent.com/u/1394854")));
// java 图片
put("bufferImagePicture", new PictureRenderData(100, 120, ".png", BytePictureUtils.getBufferByteArray(bufferImage)));
- 表格模板{{#var}}
put("changeLog", new TableRenderData(new ArrayList<RenderData>(){{
add(new TextRenderData("d0d0d0", ""));
add(new TextRenderData("d0d0d0", "introduce"));
}},new ArrayList<Object>(){{
add("1;add new # gramer");
add("2;support insert table");
add("3;support more style");
}}, "no datas", 10600));
- 列表模板{{*var}}
put("feature", new NumbericRenderData(new ArrayList<TextRenderData>() {
{
add(new TextRenderData("Plug-in grammar"));
add(new TextRenderData("Supports word text, header..."));
add(new TextRenderData("Not just templates, but also style templates"));
}
}));
- 文档模板{{+var}}
List<SegmentData> segments = new ArrayList<SegmentData>();
SegmentData s1 = new SegmentData();
s1.setTitle("经常抱怨的自己");
s1.setContent("每个人生活得都不容易,经常向别人抱怨的人,说白了就是把对方当做“垃圾场”,你一股脑地将自己的埋怨与不满倒给别人,自己倒是爽了,你有考虑过对方的感受吗?对方的脸上可能一笑了之,但是心里可能有一万只草泥马奔腾而过。");
segments.add(s1);
SegmentData s2 = new SegmentData();
s2.setTitle("拖拖拉拉的自己");
s2.setContent("能够今天做完的事情,不要拖到明天,你的事情没有任何人有义务去帮你做;不要做“宅男”、不要当“宅女”,放假的日子约上三五好友出去转转;经常动手做家务,既能分担伴侣的负担,又有一个干净舒适的环境何乐而不为呢?");
segments.add(s2);
put("docx_word", new DocxRenderData(new File("~/segment.docx"), segments));
可能的报错
如果出现这个错误,要判断文件后缀名是doc,还是docx。
对POI来说,如果是doc,使用HSSFWorkbook;如果是docx,使用XSSFWorkbook。
而POI-TL封装的是docx的版本,现在是主流格式,所以推荐用这个,当然,输出文件随意,你输出文件名弄成doc也ok(格式)
org.apache.poi.openxml4j.exceptions.OLE2NotOfficeXmlFileException: The supplied data appears to be in the OLE2 Format. You are calling the part of POI that deals with OOXML (Office Open XML) Documents. You need to call a different part of POI to process this data (eg HSSF instead of XSSF)
at org.apache.poi.openxml4j.opc.internal.ZipHelper.verifyZipHeader(ZipHelper.java:184)
at org.apache.poi.openxml4j.opc.internal.ZipHelper.openZipStream(ZipHelper.java:241)
at org.apache.poi.openxml4j.opc.ZipPackage.<init>(ZipPackage.java:98)
at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:324)
at org.apache.poi.util.PackageHelper.open(PackageHelper.java:37)
at org.apache.poi.xwpf.usermodel.XWPFDocument.<init>(XWPFDocument.java:116)
at com.deepoove.poi.NiceXWPFDocument.<init>(NiceXWPFDocument.java:85)
at com.deepoove.poi.XWPFTemplate.compile(XWPFTemplate.java:129)
at com.deepoove.poi.XWPFTemplate.compile(XWPFTemplate.java:110)
at com.deepoove.poi.XWPFTemplate.compile(XWPFTemplate.java:78)
at com.deepoove.poi.XWPFTemplate.compile(XWPFTemplate.java:74)
at com.softdev.system.demo.controller.PoiController.generateWord(PoiController.java:51)