wshanshi:总结记录…便于回顾…
一、什么是FreeMarker
FreeMarker 是一个用 Java 语言编写的模板引擎,基于模板来生成文本输出。
FreeMarker的原理:模板+数据模型=输出,模板只负责数据在页面中的表现,不涉及任何的逻辑代码,而所有的逻辑都是由数据模型来处理的。用户最终看到的输出是模板和数据模型合并后创建的。
二、FreeMarker优点
FreeMarker的诞生是为了取代JSP,降低了表现层和业务逻辑的耦合,提高了效率。
2.1、表现层、业务逻辑分离
FreeMarker职责明确,功能专注,仅仅负责页面的展示,从而去掉了繁琐的逻辑代码。
2.2、降低耦合度
JSP页面前后端的代码写到了一起,耦合度很高,前端开发需要熟悉后台环境,需要去调试,而后台开发人员需要去做不熟悉的前端界面设计。对两者而言,交替性的工作需要花费一定的学习成本,效率低下。而使用FreeMarker后,前后端完全分离,大家各干各的,互不影响。
2.3、简单易用,功能强大
FreeMarker支持JSP标签,同时内置了大量常用功能,比如html过滤,日期金额格式化等等。FreeMarker代码十分简洁,上手快,使用非常方便。
三、FreeMarker常用语法
在FreeMarker模板语言中,使用插值${…}将数据模型中的部分替代输出。
3.1、内置函数
内建函数:http://freemarker.foofun.cn/ref_builtins_string.html
<#–非空判断2种写法示例–>
<p>【姓名】:${name?if_exists?html}</p>
<p>【性别】:${sex!?html}</p>
3.2、数值类型
<p>【年龄(数值)】:${age?string.number}</p>
<p>【存款(¥)】:${age?string.currency}</p>
3.3、日期类型
FreeMarker中可以分别对date、time、datetime三种类型的日期时间设置格式。
config.setDateTimeFormat("yyyy-MM-dd HH:mm:ss");
config.setDateFormat("yyyy-MM-dd");
config.setTimeFormat("HH:mm:ss");
年月日:<td>${(f.birthday?string('yyyy-MM-dd'))!}</td>
时分秒:<td>${(f.birthday?string('HH:mm:ss'))!}</td>
年月日:<td>${(f.birthday?string('yyyy-MM-dd HH:mm:ss'))!}</td>
3.4、默认值设置
默认值设置的2种方法:
<p>【默认值】方法1:${default?default('这是一个默认值')}</p>
<p>【默认值】方法2:${default!('这是一个默认值')}</p>
3.5、拼接输出
拼接输出的2种方法:
<p>【变量拼接】方法1:${"拼接下地址:"+address!}</p>
<p>【变量拼接】方法2:${"拼接下地址:${address!}"}</p>
3.6、if判断
<#if f.sex =='1'>
${'男'}
<#elseif f.sex =='2'>
${'女'}
<#else>
${'未知'}
</#if>
3.7、list遍历
<#list friends as f>
<tr>
<td>${f.name?if_exists?html}</td>
<td>${f.age?if_exists?html}</td>
<td>${f.sex?if_exists?html}</td>
<#--设置日期-->
<td>${(f.birthday?string('yyyy-MM-dd'))!}</td>
</br>
</tr>
</#list>
3.8、list排序
<#--list升序:sort_by函数。sort对序列(sequence)进行排序,要求序列中的变量必须是:字符串(按首字母排序),数字,日期值-->
<#list friends?sort_by("age") as f>
<tr>
<td>${f.name?if_exists?html}</td>
<td>${f.age?if_exists?html}</td>
<td>${f.sex?if_exists?html}</td>
<#--设置日期-->
<td>${(f.birthday?string('HH:mm:ss'))!}</td>
</br>
</tr>
</#list>
<#--list降序:reverse降序排序-->
<#list friends?sort_by("age")?reverse as f>
<tr>
<td>${f.name?if_exists?html}</td>
<td>${f.age?if_exists?html}</td>
<td>${f.sex?if_exists?html}</td>
<#--设置日期-->
<td>${(f.birthday?string('yyyy-MM-dd HH:mm:ss'))!}</td>
</br>
</tr>
</#list>
四、FreeMarker使用
4.1、使用过程(核心简要)
说明:以下步骤,可以封装到一个工具类当中。主要操作:读取指定模板、数据填充、输出文件。
// 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是FreeMarker对应的版本号。
Configuration configuration = new Configuration(Configuration.getVersion());
// 第二步:设置模板文件所在的路径。
configuration.setDirectoryForTemplateLoading(new File("/WEB-INF/ftl"));
// 第三步:设置模板文件使用的字符集。一般就是utf-8.
configuration.setDefaultEncoding("utf-8");
// 第四步:加载一个模板,创建一个模板对象。
Template template = configuration.getTemplate("hello.ftl");
// 第五步:创建一个模板使用的数据集,可以是pojo也可以是map。一般是Map。
Map dataModel = new HashMap<>();
//向数据集中添加数据
dataModel.put("hello", "this is my first FreeMarker test.");
// 第六步:创建一个Writer对象,一般创建一FileWriter对象,指定生成的文件名。
Writer out = new FileWriter(new File("/hello.html"));
// 第七步:调用模板对象的process方法输出文件。
template.process(dataModel, out);
// 第八步:关闭流。
out.close();
4.2、html填充使用
<script src="https://cdn.staticfile.org/jquery/2.2.4/jquery.min.js" xmlns="http://www.w3.org/1999/html"
xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html"></script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试模板引擎</title>
</head>
<body>
<h1>测试模板引擎</h1>
<#--非空判断2种写法:变量存在,输出该变量,否则不输出-->
<p>【姓名】:${name?if_exists?html}</p>
<p>【性别】:
<#if sex =='1'>
${'男'}
<#elseif sex =='2'>
${'女'}
<#else>
${'未知'}
</#if>
</p>
<#--数字输出-->
<p>【年龄(数值)】:${age?string.number}</p>
<p>【存款(¥)】:${age?string.currency}</p>
<p>【生日(文本)】:${birthday?if_exists?html} </p>
<p>【地址】:${address?if_exists?html}</p>
<#--设置默认值2种写法-->
<p>【默认值】方法1:${default?default('这是一个默认值')}</p>
<p>【默认值】方法2:${default!('这是一个默认值')}</p>
<#--拼接输出2种写法-->
<p>【变量拼接】方法1:${"拼接下地址:"+address!}</p>
<p>【变量拼接】方法2:${"拼接下地址:${address!}"}</p>
<table border="0">
<tr>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>生日</th>
</tr>
<#--list遍历-->
<#list friends as f>
<tr>
<td>${f.name?if_exists?html}</td>
<td>${f.age?if_exists?html}</td>
<td>
<#if f.sex =='1'>
${'男'}
<#elseif f.sex =='2'>
${'女'}
<#else>
${'未知'}
</#if>
</td>
<#--设置日期-->
<td>${(f.birthday?string('yyyy-MM-dd'))!}</td>
</br>
</tr>
</#list>
<#--list升序:sort_by函数。sort对序列(sequence)进行排序,要求序列中的变量必须是:字符串(按首字母排序),数字,日期值-->
<#list friends?sort_by("age") as f>
<tr>
<td>${f.name?if_exists?html}</td>
<td>${f.age?if_exists?html}</td>
<td>
<#if f.sex =='1'>
${'男'}
<#elseif f.sex =='2'>
${'女'}
<#else>
${'未知'}
</#if>
</td>
<#--设置日期-->
<td>${(f.birthday?string('HH:mm:ss'))!}</td>
</br>
</tr>
</#list>
<#--list降序:reverse降序排序-->
<#list friends?sort_by("age")?reverse as f>
<tr>
<td>${f.name?if_exists?html}</td>
<td>${f.age?if_exists?html}</td>
<td>
<#if f.sex =='1'>
${'男'}
<#elseif f.sex =='2'>
${'女'}
<#else>
${'未知'}
</#if>
</td>
<#--设置日期-->
<td>${(f.birthday?string('yyyy-MM-dd HH:mm:ss'))!}</td>
</br>
</tr>
</#list>
</table>
</body>
</html>
4.3、word文件生成使用
对于项目当中一些复杂的指定模板word生成,可用模板引擎实现。
4.3.1、定义模板
说明:将需要生成的word定义好样式,排版等。如下图所示:
4.3.2、定义占位符
说明:将表格内容用占位符进行填充,如${xxxx}。xxxx一般为实体类的某个属性。
正常设置放实体属性就好了,如下图的“registerTime”,如果有特殊符号,直接在模板中添加上,如下图中的复选框。
4.3.3、改文件后缀
定义完占位符后,将该文件另存为xml后缀文件。改为xml文件后,可操作格式化一下,否则很乱。格式化之后,将该文件改为ftl文件。
注意:一定注意改后缀之后,占位符${}有没有被分开。否则后面获取模板进行填充时会有问题。
4.3.4、代码操作
4.3.4.1、maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
4.3.4.2、application配置
spring:
freemarker:
charset: utf-8
suffix: .ftl
template-loader-path: classpath:/templates/
4.3.4.3、操作类定义
上述步骤中已定义了占位符,一般会将这所有占位符定义为一个实体类。
4.3.4.4、接口定义
根据需求定义传参即可。比如传参编号进行查询之类的。
void download(HttpServletRequest request, HttpServletResponse response);
4.3.4.5、实现类定义
此处为模拟的数据,具体业务时,可从对应库表查出数据。
@Service
public class ExportDataServiceImpl implements ExportDataService {
@Override
public void download(HttpServletRequest request, HttpServletResponse response) {
ExportData exportData = ExportData.builder().company("某公司").project("关于疫情期间补贴的事项").registerTime(new Date()).registerAddress("上海市浦东新区")
.registerZB("222.22").beforeYearNS("2323.232").contant("wss").phone("13234567899").type("1").idCard("232323232323").total(999).jsPepole(34).scPeople(22).otherPeople(44).build();
Map<String, Object> map = setMap(exportData);
try {
WordUtils.exportMillCertificateWord(request, response, map, "apply1.ftl", this.getClass());
} catch (Exception e) {
System.out.println(e);
}
}
private Map<String, Object> setMap(ExportData exportData) {
Map<String, Object> map = new HashMap<>();
map.put("name", "申请表");
map.put("company", exportData.getCompany());
map.put("project", exportData.getProject());
map.put("registerTime", exportData.getRegisterTime());
map.put("registerAddress", exportData.getRegisterAddress());
map.put("registerZB", exportData.getRegisterZB());
map.put("beforeYearNS", exportData.getBeforeYearNS());
map.put("contant", exportData.getContant());
map.put("phone", exportData.getPhone());
map.put("type", exportData.getType());
map.put("idCard", exportData.getIdCard());
map.put("total", exportData.getTotal());
map.put("jsPepole", exportData.getJsPepole());
map.put("scPeople", exportData.getScPeople());
map.put("otherPeople", exportData.getOtherPeople());
List<Map<String, Object>> checkboxList = new ArrayList<>();
Map<String, Object> map1 = new HashMap<>();
map1.put("label", "新一代信息技术");
map1.put("value", 1);
checkboxList.add(map1);
Map<String, Object> map2 = new HashMap<>();
map2.put("label", "高端装备制造");
map2.put("value", 0);
checkboxList.add(map2);
Map<String, Object> map3 = new HashMap<>();
map3.put("label", "绿色低碳");
map3.put("value", 1);
checkboxList.add(map3);
Map<String, Object> map4 = new HashMap<>();
map4.put("label", "生物医药");
map4.put("value", 0);
checkboxList.add(map4);
Map<String, Object> map5 = new HashMap<>();
map5.put("label", "数字经济");
map5.put("value", 0);
checkboxList.add(map5);
Map<String, Object> map6 = new HashMap<>();
map6.put("label", "新材料");
map6.put("value", 1);
checkboxList.add(map6);
Map<String, Object> map7 = new HashMap<>();
map7.put("label", "海洋经济");
map7.put("value", 1);
checkboxList.add(map7);
Map<String, Object> map8 = new HashMap<>();
map8.put("label", "文化创意");
map8.put("value", 1);
checkboxList.add(map8);
map.put("labels", checkboxList);
return map;
}
}
4.3.4.6、控制层定义
@GetMapping("/download")
public void download(HttpServletRequest request, HttpServletResponse response) {
exportDataService.download(request, response);
}
4.3.4.7、工具类
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import cn.hutool.core.io.FileUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;
@Component
public class WordUtils {
private static final Configuration CONFIGURATION;
static {
Version incompatibleImprovements = new Version("2.3.29");
CONFIGURATION = new Configuration(incompatibleImprovements);
CONFIGURATION.setDefaultEncoding("utf-8");
}
public static void exportMillCertificateWord(HttpServletRequest request, HttpServletResponse response, Map<String, Object> map, String ftlName, Class<?> getClass) throws IOException {
CONFIGURATION.setClassForTemplateLoading(getClass, "/models/");
Template freemarkerTemplate = CONFIGURATION.getTemplate(ftlName, "utf-8");
File file = null;
ServletOutputStream out = null;
try {
// 调用工具类的createDoc方法生成Word文档
file = createDoc(map, freemarkerTemplate);
response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
// 设置浏览器以下载的方式处理该文件名
String fileName = map.get("name") + ".doc";
response.setHeader("Content-Disposition", "attachment;filename=".concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));
out = response.getOutputStream();
FileUtil.writeToStream(file, out);
} finally {
if (out != null) {
out.close();
}
if (file != null) {
file.delete();
}
}
}
private static File createDoc(Map<?, ?> dataMap, Template template) {
String name = "/test.doc";
File f = new File(name);
try {
// 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
Writer w = new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8);
template.process(dataMap, w);
w.close();
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
return f;
}
}
4.3.5、运行效果
数据会根据自定义占位符+相关语法定义,填充数据到对应的模板位置中。
五、注意事项
5.1、空值判断
报异常:freemarker.core.ParseException: Syntax error in template “test.ftl” in line 1695, column 53:Encountered “/”, but was expecting one of:
说明:word中定义完占位符后,改后缀为xml文件、ftl文件后,${}可能会被分隔开。需仔细检查。
异常信息如下:
freemarker.core.ParseException: Syntax error in template "test.ftl" in line 1695, column 53:
Encountered "/", but was expecting one of:
<STRING_LITERAL>
<RAW_STRING>
"false"
"true"
<INTEGER>
<DECIMAL>
"."
"+"
"-"
"!"
"["
"("
"{"
<ID>
如下图所示:
原因:FreeMarker对空值的处理非常严格,FreeMarker的变量必须有值,没有被赋值的变量就会抛出异常,因为FreeMarker未赋值的变量强制出错可以杜绝很多潜在的错误,如缺失潜在的变量命名,或者其他变量错误.这里所说的空值,实际上也包括那些并不存在的变量,对于一个Java的 null值而言,我们认为这个变量是存在的,只是它的值为null,但对于FreeMarker模板而言,它无法理解null值,null值和不存在的变量完全相同。
解决方法:把占位符补充完整,多的}删掉,否则会乱掉。
5.2、非必填
有些数据可能是非必填的,对于文本类型空值模板数据填充。有两种解决方法:
1、设置下占位符判断是否为空。<w:t>${CYR?if_exists?html}</w:t>
2、在数据填充时进行非空判断,为空赋值“”。
5.3、日期非空判断
日期非空判断(非必填),记得加判断。日期建议使用java.util.Date 类型。
${(f.birthday?string(‘yyyy-MM-dd’))!}
六、参考文档
相关文档:http://www.freemarker.net/
相关文档:http://freemarker.foofun.cn/ref_directive_if.html