java使用freemarker完成试卷的Word导出
来源:互联网 发布:如何评价网络架构 编辑:程序博客网 时间:2024/05/29 10:05
第一次近距离接触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<String, Object> 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<Map<String,Object>> problemTypeList = problemMapper.selectProblemNameByPaperId(paperId); //校验查询出的信息是否为null if(problemTypeList.isEmpty() || null == problemTypeList){ throw new BusinessException(CommonResultEnum.COMMON_ERROR_637); } //创建一个list集合来封装problemMap数据 List<Object> titles = new ArrayList<Object>(); //创建一个list集合来封装 List<Object> answerTitles = new ArrayList<Object>(); int index = 1; for (Map<String, Object> map : problemTypeList) { //获取问题的id和试卷的id查询出本试卷中本类问题信息 Integer typeId = StringUtils.objToInt(map.get("id")); if(typeId == 0){ throw new BusinessException(CommonResultEnum.COMMON_ERROR_637); } List<Map<String,Object>> problemList = problemMapper.selectProblemByTypeIdAndPaperId(paperId,typeId); //校验problemList if(problemList.isEmpty() || null == problemList){ throw new BusinessException(CommonResultEnum.COMMON_ERROR_637); } //获取试卷类型,并封装数据 Map<String, Object> problemMap = new HashMap<String, Object>(); problemMap.put("title",StringUtils.toChinese(index+"").concat("、").concat(StringUtils.objToStr(map.get("typeName")))); //获取试卷答案 Map<String, Object> answerMap = new HashMap<>(); answerMap.put("title",StringUtils.toChinese(index+"").concat("、").concat(StringUtils.objToStr(map.get("typeName")))); index++; //创建一个List<Map<String,Object>> 来封装问题数据 List<Map<String,Object>> problemList2 = new ArrayList<>(); //创建一个List<Map<String,Object>> 来封装问题数据 List<Map<String,Object>> answerList = new ArrayList<>(); for (Map<String, Object> map2 : problemList) { //获取map2中的部分数据将其封装 Map<String,Object> problemMap2 = new HashMap<>(); //问题主体 problemMap2.put("problemContent",StringUtils.toReplace(StringEscapeUtils.unescapeHtml4(StringUtils.objToStr(map2.get("problemContent"))))); //问题选项 String options = StringUtils.toReplace(StringEscapeUtils.unescapeHtml4(StringUtils.objToStr(map2.get("problemAnswer")))); TreeMap<String, Object> itemMap = null; if(options != null && !"\"\"".equals(options) && !"".equals(options)){ itemMap = JSON.parseObject(options, TreeMap.class); } //问题的选项,将问题的选项转为map problemMap2.put("option",itemMap); problemList2.add(problemMap2); //封装答案的数据 Map<String,Object> answerMap2 = new HashMap<>(); //题号 answerMap2.put("answerSort",StringUtils.objToStr(map2.get("sort"))); //答案 answerMap2.put("answer",StringUtils.objToStr(map2.get("answer"))); //考点 answerMap2.put("answerTestCenter",""); //解析 answerMap2.put("answerAnalysis",StringUtils.objToStr(map2.get("analyses"))); answerMap2.put("answerComment",""); //将答案数据封装到list集合中 answerList.add(answerMap2); } problemMap.put("problemList",problemList2); titles.add(problemMap); //创建封装答案的map answerMap.put("answerList",answerList); //创建一个list封装answerMap answerTitles.add(answerMap); } //将问题的信息封装到大的数据Map中集合中 dataMap.put("titles", titles); dataMap.put("answerTitles", answerTitles); return dataMap; }
上边是整个数据的封装过程以及使用freemarker中的Configuration类进行封装:当然里边使用的一些方法(将阿拉伯数字转为大写,将JSon数据转为map等),看思路就好;最后完成的结果:
本功能的完成比较耗时间的点:
①模板的搭建,需要了解模板语言,并与数据进行逻辑结合;
②数据的封装,其实思路清晰这都不是事情
③一定要细心,尤其在准备模板的时候,一定要细心;
- java使用freemarker完成试卷的Word导出
- java使用freemarker导出word
- java使用Freemarker+xml导出word
- Java使用freemarker导出word模板
- java使用freemarker模版导出分页word
- Java使用freemarker导出word文档
- java 使用freemarker 导出word 和图片
- java中使用freemarker导出word文档
- 使用freemarker 导出word
- 使用FreeMarker导出Word
- 使用freemarker导出Word
- 使用freemarker导出Word
- 使用 freemarker 导出 word
- 使用freemarker导出word
- JAVA FreeMarker导出word
- java导出word(Freemarker)
- java用freemarker导出word的问题
- 【freemarker】使用模板导出word
- 学习资源汇总!
- 学习C语言的必备书籍-从入门到精通
- 看程序员如何用纯css代码写出小球弹跳上楼梯
- 【linux 学习】linux上安装Tim(linux mint)
- 欢迎使用CSDN-markdown编辑器
- java使用freemarker完成试卷的Word导出
- 三星S8面部识别存隐患:一张照片就能解锁手机
- 特写 | Launchpad加速器的故事:谷歌要在新兴市场“创造下一个硅谷”
- 自勉
- 重磅:Elon Musk 新创公司曝光——让人脑连接电脑
- inet addr、bcast、mask
- 微软 Azure Service Fabric 朝开源迈出了第一步
- IO流(二)字节流
- String类的写时拷贝