Freemarker介绍
freemarker是一个模板引擎: 是基于模板和要改变的数据,合并生成输出文本(HTML网页,电子邮件,配置文件,源代码)。是一个Java类库
上边显示,在后端将数据查出来,会将数据封装起来,称之为数据模型model 将一个前端的模板使用freemarker进行结合,就会生成一个页面。
数据模型+模板=输出 例子
数据模型
模板
输出
freemarker使用步骤
1:添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
2 添加配置到配置文件
spring:
freemarker:
cache: false #关闭模板缓存,方便测试
settings:
template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
suffix: .ftl #指定Freemarker模板文件的后缀名
freemarker使用demo
1 创建模板
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello <br>
<hr>
<b>对象Student中的数据展示:</b><br/>
姓名:${stu.name}<br/>
年龄:${stu.age}
<hr>
</body>
</html>
2 创建数据模型
@Controller
public class Test01Basic {
@GetMapping("/test01-basic")
public String test01Basic(Model model) {
Student student = new Student();
student.setAge(18);
student.setMoney(20F);
student.setName("衡老师");
model.addAttribute("stu", student);
//这里返回的必须是模板的文件名称
return "01-basic";
}
}
合并的页面:
解释: 模型返回一个 01-basic 意思就是上边的模板名称。
在模型数据中将student对象添加到model中,key为stu 所以在上边的模板中就会stu.name 意思就是调用key为stu的值,将student对象调用出来然后再得到名称和年龄
注意:这里如果使用@RestController那么就会将返回的数据变为json,这不是想要的数据,所以要使用controller或者ViewAndModel
freemarker的基础使用
1 插值
格式: ${....}
模板
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello <br>
<hr>
<b>对象Student中的数据展示:</b><br/>
姓名:${stu.name}<br/>
年龄:${stu.age}
<hr>
</body>
</html>
数据模型
@Controller
public class Test01Basic {
@GetMapping("/test01-basic")
public String test01Basic(Model model) {
Student student = new Student();
student.setAge(18);
student.setMoney(20F);
student.setName("衡老师");
model.addAttribute("stu", student);
//这里返回的必须是模板的文件名称
return "01-basic";
}
}
2 集合 list map
模板
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<#-- list 数据的展示 -->
<b>展示list中的stu数据:</b>
<br>
<br>
<table>
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
<td>集合长度:${list?size}</td>
</tr>
<#--遍历集合list,将里面的值遍历取出来-->
<#list list as stu>
<#if stu.name=='刘墩墩'>
<tr style="color: aquamarine">
<td>${stu_index+1}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money!""}</td>
</tr>
<#else >
<tr>
<td>${stu_index+1}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money!""}</td>
</tr>
</#if>
</#list>
</table>
<hr>
<#-- Map 数据的展示 -->
<b>map数据的展示:</b>
<br/><br/>
<a href="###">方式一:通过map['keyname'].property</a><br/>
输出stu1的学生信息:<br/>
姓名:${map['tudent1'].name}<br/>
年龄:${map['tudent1'].age}
<br/>
<a href="###">方式二:通过map.keyname.property</a><br/>
输出stu2的学生信息:<br/>
姓名:${map.tudent2.name}<br/>
年龄:${map.tudent2.age}<br/>
<br/>
<a href="###">遍历map中两个学生信息:</a><br/>
<table>
<tr>
<td>序号</td>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
</tr>
<#list map?keys as key>
<tr>
<td>${key_index}</td>
<td>${map[key].name}</td>
<td>${map[key].age}</td>
<#--非空判断 如果money没有非空判断,那么Java中没有给money赋值,就会报错,这时候需要加入非空判断 非空有两种:1 ..money?? 2: ...money!"" -->
<td>${map[key].money!""}</td>
</tr>
</#list>
</table>
<#--就是比如有很长的数就会三位数一个逗号: 123,456,789 加了c:123456789-->
<b>c函数:</b>
不加c函数:${point}<br>
添加c函数:${point?c}
<hr>
</body>
</html>
模型
@Controller
public class Test03ListAndMap {
@GetMapping("/03-List-Map")
public String test01Basic(Model model) {
Student tudent1 = new Student();
tudent1.setAge(18);
// tudent1.setMoney(20F);
tudent1.setName("衡老师");
tudent1.setBirthday(new Date());
model.addAttribute("stu", tudent1);
Student tudent2 = new Student();
tudent2.setAge(18);
// tudent2.setMoney(20F);
tudent2.setName("刘老师");
tudent2.setBirthday(new Date());
model.addAttribute("stu2", tudent2);
Student tudent3 = new Student();
tudent3.setAge(18);
// tudent3.setMoney(20F);
tudent3.setName("刘小平");
tudent3.setBirthday(new Date());
model.addAttribute("stu3", tudent3);
Student tudent4 = new Student();
tudent4.setAge(18);
// tudent4.setMoney(20F);
tudent4.setName("刘墩墩");
tudent4.setBirthday(new Date());
model.addAttribute("stu4", tudent4);
//将上边的对象存到list中,再将list存到model
ArrayList<Student> list = new ArrayList<>();
Collections.addAll(list, tudent1, tudent2, tudent3, tudent4);
model.addAttribute("list", list);
//将上边的对象存到map中,再将map存到model
Map<String, Object> map = new HashMap<>();
map.put("tudent2", tudent2);
map.put("tudent3", tudent3);
map.put("tudent4", tudent4);
map.put("tudent1", tudent1);
model.addAttribute("map", map);
model.addAttribute("point", 123456789765L);
return "03-List-Map";
}
}
结果
解释: 这里的map可以通过遍历key来完成. 集合中如果想要1,2,3有序显示可以使用${传过来的key_index}
3 if指令
<table>
<tr>
<td>姓名</td>
<td>年龄</td>
<td>钱包</td>
</tr>
<#list stus as stu >
<#if stu.name='小红'>
<tr style="color: red">
<td>${stu_index}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
<#else >
<tr>
<td>${stu_index}</td>
<td>${stu.name}</td>
<td>${stu.age}</td>
<td>${stu.money}</td>
</tr>
</#if>
</#list>
</table>
3 运算符
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<b>比较运算符</b>
<br/>
<br/>
<dl>
<dt> =/== 和 != 比较:</dt>
<dd>
<#if "xiaoming" == "xiaoming">
字符串的比较 "xiaoming" == "xiaoming"
</#if>
</dd>
<dd>
<#if 10 != 100>
数值的比较 10 != 100
</#if>
</dd>
</dl>
<dl>
<dt>其他比较</dt>
<dd>
<#if 10 gt 5 >
形式一:使用特殊字符比较数值 10 gt 5
</#if>
</dd>
<dd>
<#-- 日期的比较需要通过?date将属性转为data类型才能进行比较 -->
<#if (date1?date >= date2?date)>
形式二:使用括号形式比较时间 date1?date >= date2?date
</#if>
</dd>
</dl>
<br/>
<hr>
</body>
</html>
4 空值判断
控制使用 ?? 代表是不是空
<#if stus??>
<#list stus as stu>
......
</#list>
</#if>
5 如期格式化
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello <br>
<hr>
<b>对象Student中的数据展示:</b><br/>
姓名:${stu.name}<br/>
年龄:${stu.age}<br/>
价格:${stu.money}<br/>
<#--使用时间类型的参数,就要在前端指定需要使用的是什么类型的时间格式,date:年月日 time:时分秒 datetime:年月日时分秒 自定义时间格式:string("yyyy-MM-dd HH-mm-ss")-->
生日date:${stu.birthday?date}<br/>
生日time:${stu.birthday?time}<br/>
生日datetime:${stu.birthday?datetime}<br/>
生日string自定义时间:${stu.birthday?string("yyyy-MM-dd HH-mm-ss")}<br/>
<hr>
</body>
</html>
freemarker生成HTML文件示例
@Test
public void testHtml01() throws Exception {
//1创建配置类信息
Configuration configuration = new Configuration(Configuration.getVersion());
//2对配置类进行配置
String path = this.getClass().getResource("/templates/").getPath();
configuration.setDirectoryForTemplateLoading(new File(path));
configuration.setDefaultEncoding("utf-8");
//获得模板文件对象
Template template = configuration.getTemplate("01-basic.ftl");
System.out.println("template = " + template); //结果为01-basic.ftl为赋值时的内容
//获得数据模型 就是将数据和模板进行结合
String content = FreeMarkerTemplateUtils.processTemplateIntoString(template, getTemplate());
System.out.println("content = " + content); //结果为template模板中的值和getTemplate()中的数据
//转换为字节流
InputStream inputStream = IOUtils.toInputStream(content);
//将模板和数据结好的数据模型转换为字节流再通过IOUtils.copy(inputStream, outputStream);将字节流写道指定的文件
FileOutputStream outputStream = new FileOutputStream(new File("G:\\Java就业班\\项目二\\每天资料\\day08资料\\index.html"));
IOUtils.copy(inputStream, outputStream);
}
将这个freemarker生成的HTML上传到七牛云中
//2 调用CoursePubService生成课程详情页并且上传到七牛云中
// 调用之前需要先确保消息的幂等性,防止重复数据 course_pub表中的is_pub
int count = this.count(
Wrappers.lambdaQuery(CoursePub.class).eq(CoursePub::getId, coursePubMsg.getPubId()).eq(CoursePub::getIsPub, CoursePub.IS_PUB)
);
//调用之前需要先确保消息的幂等性,防止重复数据
if (count > 0) {
// 说明存在这个pubid的数据 直接结束,不能抛出异常因为这是已经发布的数据所以不需要操作,如果抛出异常那么就会导致从新发送
log.info("课程已经发布,{}", coursePubMsg.getPubId());
return;
}
CoursePub coursePub = getById(coursePubMsg.getPubId());
Map<String, Object> modelMap = generateMap(coursePub);
//生成课程详情页并且上传到七牛云中
String HtmlString = null;
try {
Template template = configuration.getTemplate("learing_article.ftl");
HtmlString = FreeMarkerTemplateUtils.processTemplateIntoString(template, modelMap);
} catch (Exception e) {
// e.printStackTrace();
log.info("{},{}", CoursePublishErrorCode.E_120209.getDesc(), coursePubMsg.getPubId());
return;
}
//上传到七牛云中
//准备fileName
String fileName = coursePub.getId() + ".html";
//准备key 当kye中有例如 images/文件名 在七牛云中就是代表images文件夹中的文件
String path = position+fileName;
try {
QiniuUtils.upload2Qiniu(accessKey, secretKey, bucket, HtmlString, path);
} catch (Exception e) {
//e.printStackTrace();
log.info("上传HTML到七牛云失败,{}", coursePubMsg.getPubId());
ExceptionCast.cast(CoursePublishErrorCode.E_120210);
}
// 3 将课程发布状态设置未0已经发布状态is_pub 这样是为了保证幂等性
boolean updatePubStatus = update(
Wrappers.lambdaUpdate(CoursePub.class)
.eq(CoursePub::getId, coursePub.getId())
.set(CoursePub::getChangeDate, LocalDateTime.now())
.set(CoursePub::getIsPub, CoursePub.IS_PUB)
);
//如果修改状态没有成功就记录下来,不能抛出异常是因为走到一步说明页面也上传成功了,如果抛出异常就会导致从新发送,数据重复上传
if (!updatePubStatus) {
log.info(CoursePublishErrorCode.E_120208.getDesc(), coursePub.getId());
}
七牛云上传工具 Qiniu
@Transactional
public static boolean upload2Qiniu(String accessKey, String secretKey, String bucket, String contentText, String fileKey) {
//构造一个带指定 Region 对象的配置类
Configuration cfg = new Configuration(Zone.huadong());
//上传管理对象
UploadManager uploadManager = new UploadManager(cfg);
try {
byte[] uploadBytes = contentText.getBytes("utf-8");
Auth auth = Auth.create(accessKey, secretKey);
String upToken = auth.uploadToken(bucket);
try {
Response response = uploadManager.put(uploadBytes, fileKey, upToken);
//解析上传成功的结果
if (response != null && response.isOK()) {
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(),
DefaultPutRet.class);
log.info("qiNiu result, key:{}, hash:{}, response:{}",
putRet.key, putRet.hash, JSON.toJSONString(response));
return true;
} else {
return false;
}
} catch (QiniuException ex) {
Response r = ex.response;
log.error("qiNiu error, response: {}", JSON.toJSONString(r));
}
} catch (UnsupportedEncodingException ex) {
log.error("qiNiu error, response: {}", ex);
}
return false;
}