第一次近距离接触freemarker,先让我们谈一谈这个技术:
1、什么是freemarker?
freemarker是一款模板搜索引擎,简单讲就是用于生成静态化页面的工具;
2、有什么作用?
可以将数据与模板进行结合,统一一次性批量生成静态化页面,也就是html页面,放到硬盘上,访问的时候,直接访问生成好的静态页面,这样可以不用访问数据库,给数据库降低并发访问压力;也不用访问应用服务器,给应用服务器降低并发压力;客户因为直接访问的是静态页面,所以不需要Tomcat解析,浏览器可以直接访问,速度快,客户体验好;
应用场景:
对于一些不经常变化的页面,数据也不经常变化,可以通过freemarker,统一生成静态化页面;
当然还有今天的主题,可以生成Word文档;
3、怎么用?
首先要导入freemarker的jar包:
还要准备好模板(这里要注意模板的格式是ftl或html)
准备好数据
然后使用freemarker中的Configuration类进行页面的生成;
还有就是freemarker可以与spring进行整合:
<bean id="StaticPageServiceImpl" class="cn.itcast.core.service.StaticPageServiceImpl">
<property name="freeMarkerConfigurer">
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/ftl/"/>
<property name="defaultEncoding" value="utf-8"/>
bean>
property>
bean>
beans>
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Map;
import javax.servlet.ServletContext;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import freemarker.template.Configuration;
import freemarker.template.Template;
/**
* 配置式静态化开发
* @author zj
*
*/
public class StaticPageServiceImpl implements StaticPageService, ServletContextAware{
//注入
private Configuration conf;
public void setFreeMarkerConfigurer(FreeMarkerConfigurer freeMarkerConfigurer) {
this.conf = freeMarkerConfigurer.getConfiguration();
}
//静态化程序
@Override
public void index(Map map, String id) throws Exception{
String path = "/html/product/" + id + ".html";
String url = getAllPath(path);
//判断没有父级文件夹
File f = new File(url);
File parentFile = f.getParentFile();
if(!parentFile.exists()){
parentFile.mkdirs();
}
//读
Template template = conf.getTemplate("product.html");
//输出到指定流文件
Writer out = new OutputStreamWriter(new FileOutputStream(f) , "utf-8");
//处理
template.process(map, out);
out.close();
}
//声明一个上下文
private ServletContext servletContext;
//获取全路径
public String getAllPath(String path){
return servletContext.getRealPath(path);
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}
下来就进入正题,完成Word的导出:
一、模板准备工作:
首先要知道如何制作模板:我是需要导出试卷,所以暂时没有涉及到表格的问题(可能是需求不太一样,本人只是做了简单的试卷导出);所以我在准备的时候只是准备了一个简单的模板:
先用Word文档写好,然后将其保存为xml的形式进行模板的编辑:看到上边的黑字就是咱们Word文档中的占位字,也是下边需要替换的字,说到替换就必须要了解其特有的模板语言:(本人只是了解了一些最基本的)
最后是一个map的遍历方式
<w:body>
<w:p w:rsidR="00A42CA5" w:rsidRDefault="00A42CA5" w:rsidP="003206CE">
<w:pPr>
<w:spacing w:line="360" w:lineRule="auto"/>
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
w:pPr>
<w:bookmarkStart w:id="0" w:name="_GoBack"/>
<w:bookmarkEnd w:id="0"/>
w:p>
<w:p w:rsidR="006B1E33" w:rsidRDefault="003064E2">
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:bCs/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>${paperName}w:t>
w:r>
w:p>
<#list titles as title>
<w:p w:rsidR="006B1E33" w:rsidRDefault="00E84B1E">
<w:pPr>
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:bCs/>
<w:sz w:val="24"/>
<w:szCs w:val="24"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>${title.title}w:t>
w:r>
w:p>
<#list (title.problemList) as subtitle>
<w:p w:rsidR="006B1E33" w:rsidRDefault="00E84B1E">
<w:pPr>
<w:spacing w:after="0"/>
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:color w:val="000000"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>${subtitle.problemContent}w:t>
w:r>
w:p>
<#if subtitle.option??>
<#list subtitle.option?keys as key>
<w:p w:rsidR="006B1E33" w:rsidRDefault="00E84B1E">
<w:pPr>
<w:spacing w:after="0"/>
<w:ind w:left="150"/>
w:pPr>
<w:r w:rsidRPr="00E84B1E">
<w:t>${key}:${subtitle.option["${key}"]}w:t>
w:r>
w:p>
#list>
#if>
#list>
#list>
<w:p w:rsidR="006B1E33" w:rsidRDefault="00BA3C07">
<w:pPr>
<w:spacing w:after="0"/>
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
w:pPr>
<w:r>
<w:rPr>
<w:color w:val="000000"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t xml:space="preserve"> w:t>
w:r>
w:p>
<w:p w:rsidR="006B1E33" w:rsidRDefault="00BA3C07">
<w:pPr>
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
w:pPr>
<w:r>
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:br w:type="page"/>
w:r>
w:p>
<w:p w:rsidR="006B1E33" w:rsidRDefault="00BA3C07">
<w:pPr>
<w:jc w:val="center"/>
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
w:pPr>
<w:r>
<w:rPr>
<w:b/>
<w:bCs/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>答案解析部分w:t>
w:r>
w:p>
<#list answerTitles as answerTitle>
<w:p w:rsidR="006B1E33" w:rsidRDefault="00A15A44">
<w:pPr>
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>${answerTitle.title}w:t>
w:r>
w:p>
<#list (answerTitle.answerList) as answers>
<w:p w:rsidR="006B1E33" w:rsidRPr="00A15A44" w:rsidRDefault="00A15A44">
<w:pPr>
<w:spacing w:after="0"/>
<w:rPr>
<w:color w:val="000000"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:color w:val="000000"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>${answers.answerSort}w:t>
w:r>
<w:r w:rsidR="00BA3C07">
<w:rPr>
<w:color w:val="0000FF"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>【答案】w:t>
w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:color w:val="000000"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>${answers.answer}w:t>
w:r>
<w:r w:rsidR="00BA3C07">
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:br/>
w:r>
<w:r w:rsidR="00BA3C07">
<w:rPr>
<w:color w:val="0000FF"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>【考点】w:t>
w:r>
<w:r w:rsidR="009D5F5A">
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:color w:val="000000"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>${answers.answerTestCenter}w:t>
w:r>
<w:r w:rsidR="00BA3C07">
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:br/>
w:r>
<w:r w:rsidR="00BA3C07">
<w:rPr>
<w:color w:val="0000FF"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>【解析】w:t>
w:r>
<w:r w:rsidR="00BA3C07">
<w:rPr>
<w:color w:val="000000"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>【分析】w:t>
w:r>
<w:r w:rsidR="000568F2">
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:color w:val="000000"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>${answers.answerAnalysis}w:t>
w:r>
<w:r w:rsidR="00BA3C07">
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:br/>
w:r>
<w:r w:rsidR="00BA3C07">
<w:rPr>
<w:color w:val="000000"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>【点评】w:t>
w:r>
<w:r w:rsidR="000568F2">
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:color w:val="000000"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t>${answers.answerComment}w:t>
w:r>
w:p>
#list>
#list>
<w:p w:rsidR="006B1E33" w:rsidRDefault="00BA3C07">
<w:pPr>
<w:spacing w:after="0"/>
<w:rPr>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
w:pPr>
<w:r>
<w:rPr>
<w:color w:val="000000"/>
<w:lang w:eastAsia="zh-CN"/>
w:rPr>
<w:t xml:space="preserve"> w:t>
w:r>
w:p>
这里是我替换的代码,里边使用了多个嵌套循环,暂时看不明白也不要紧,这要根据数据一起看;模板编辑好以后,将模板改为ftl格式放到项目中备用;
二、数据的准备工作,封装数据(其实都是从数据库中查询的,在这也将代码拿出来)
代码菜鸟,代码质量或逻辑上欠考虑的也请看到的大神指出:
public void createDoc(Map<String,Object> dataMap,String fileName) throws BusinessException {
//创建Configuration对象
Configuration configuration = new Configuration();
configuration.setDefaultEncoding("utf-8");
//dataMap 要填入模本的数据文件
//设置模本装置方法和路径,
Template t=null;
try {
configuration.setDirectoryForTemplateLoading(new File(exportPath));
//test.ftl为要装载的模板
t = configuration.getTemplate("paperModel.ftl");
//输出文档路径及名称
File outFile = new File(localPath.concat("/").concat(fileName).concat(".doc"));
Writer out = null;
FileOutputStream fos=null;
fos = new FileOutputStream(outFile);
OutputStreamWriter oWriter = new OutputStreamWriter(fos,"UTF-8");
//这个地方对流的编码不可或缺,使用main()单独调用时,应该可以,但是如果是web请求导出时导出后word文档就会打不开,并且包XML文件错误。主要是编码格式不正确,无法解析。
//out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile)));
out = new BufferedWriter(oWriter);
t.process(dataMap, out);
out.close();
fos.close();
} catch (Exception e) {
logger.error("导出出错", e);
e.printStackTrace();
throw new BusinessException(CommonResultEnum.COMMON_ERROR_637);
}
}
//下边是整个数据的封装过程
public Map<String, Object> getPaperByPaperId(Integer paperId) throws BusinessException{
//校验试卷id
if(paperId == null || paperId <= 0){
throw new BusinessException(CommonResultEnum.COMMON_ERROR_603, "试卷id");
}
//先通过paperId查询出试卷的名字
Paper paper = paperMapper.selectByPrimaryKey(paperId);
//校验paper对象
if(paper == null){
throw new BusinessException(CommonResultEnum.COMMON_ERROR_637);
}
//创建封装数据的dataMap
Map<String, Object> dataMap = new HashMap<String, Object>();
//试卷标题
dataMap.put("paperName", paper.getPaperName());
//获取试卷的信息:试卷的题型+题号+问题的主体
List
上边是整个数据的封装过程以及使用freemarker中的Configuration类进行封装:当然里边使用的一些方法(将阿拉伯数字转为大写,将JSon数据转为map等),看思路就好;最后完成的结果:
本功能的完成比较耗时间的点:
①模板的搭建,需要了解模板语言,并与数据进行逻辑结合;
②数据的封装,其实思路清晰这都不是事情
③一定要细心,尤其在准备模板的时候,一定要细心;