目录
Spring-boot 结合Thymeleaf
官方文档
基本介绍
● Thymeleaf 是什么
● Thymeleaf 的优点
● Thymeleaf 的缺点
● Thymeleaf 机制说明
Thymeleaf 语法
表达式
1. 表达式一览
2.字面量
3. 文本操作
4.运算符
3. 比较运算
4. 条件运算
th 属性
迭代
条件运算
使用Thymeleaf -th 属性需要注意点
Thymeleaf 综合案例
需求说明
需求说明
代码实现
配置pom.xml
引入starter-Thymeleaf , 项目会自动完成配置, 程序员按照规则开发即可
创建index.html 和manage.html 和静态图片到指定目录,
创建Admin.java
创建User.java
创建IndexController.java 默认进入登录页面
创建AdminController.java 处理登录请求完成测试
Spring-boot-拦截器-HandlerInterceptor
基本介绍
拦截器应用实例
创建LoginInterceptor.java √ 编写一个拦截器实现HandlerInterceptor 接口
√拦截器注册到配置类中 创建WebConfig.java
修改AdminController.java
注意事项和细节
1、URI 和URL 的区别
Identifier:标识符
Spring-boot--文件上传
应用实例
需求:
1. 创建/upload.html , 要求头像只能选择一个, 而宠物可以上传多个图片
创建UploadController.java
创建WebUtils
创建resources\application.yml 修改文件上传配置参数,
完成注册用户/上传图片-测试
Spring-boot 结合Thymeleaf
官方文档
1.在线文档: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
2 离线文档: usingthymeleaf.pdf
基本介绍
● Thymeleaf 是什么
1. Thymeleaf 是一个跟Velocity、FreeMarker 类似的模板引擎,可完全替代JSP
2. Thymeleaf 是一个java 类库,他是一个xml/xhtml/html5 的模板引擎,可以作为mvc 的web 应用的view 层
● Thymeleaf 的优点
1. 实现JSTL、OGNL 表达式效果, 语法相似, java 程序员上手快
2. Thymeleaf 模版页面无需服务器渲染,也可以被浏览器运行,页面简洁。
3. SpringBoot 支持FreeMarker、Thymeleaf、veocity 。
● Thymeleaf 的缺点
1. Thymeleaf: Thymeleaf is a modern server-side Java template engine for both web and standalone environments
2. 缺点: 并不是一个高性能的引擎,适用于单体应用
3. 说明:如果要做一个高并发的应用, 选择前后端分离更好,但是作为SpringBoot 推
荐的模板引擎 Thymeleaf 使用还是要了解的, 这样小伙伴在工作中使用到, 也能搞定
● Thymeleaf 机制说明
1. Thymeleaf 是服务器渲染技术, 页面数据是在服务端进行渲染的
2. 比如: manage.html 中一段thymeleaf 代码, 是在用户请求该页面时,有thymeleaf 模板引擎完成处理的(在服务端完成), 并将结果页面返回.
3. 因此使用了Thymeleaf , 并不是前后端分离.
Thymeleaf 语法
表达式
1. 表达式一览
表达式名字 | 语法 | 用途 |
---|---|---|
变量取值 | ${...} | 获取请求域、session 域、对象等值 |
选择变量 | *{...} | 获取上下文对象值 |
消息 | #{...} | 获取国际化等值 |
链接 | @{...} | 生成链接 |
片段表达式 | ~{...} | jsp:include 作用,引入公共页面片段 |
2.字面量
文本值: 'dajiahao' , 'hello' ,…...
数字: 10 , 7 , 36.8 , ….....
布尔值: true , false
空值: null
变量: name,age,...... 变量不能有空格
3. 文本操作
字符串拼接: +
变量替换: |age= ${age}|
4.运算符
1. 数学运算
运算符: + , - , * , / , %
2. 布尔运算
运算符: and , or
一元运算: ! , not
3. 比较运算
比较: > , < , >= , <= ( gt , lt , ge , le )等式: == , != ( eq , ne )
4. 条件运算
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
th 属性
html 有的属性,Thymeleaf 基本都有,而常用的属性大概有七八个。其中th 属性执行的优
先级从1~8,数字越低优先级越高
● th:text :设置当前元素的文本内容,相同功能的还有th:utext,两者的区别在于前者不会转义html 标签,后者会。优先级不高:order=7
.
● th:value:设置当前元素的value 值,类似修改指定属性的还有th:src,th:href。优先级不高:order=6
.
● th:each:遍历循环元素,和th:text 或th:value 一起使用。注意该属性修饰的标签位置,详细往后看。优先级很高:order=2
.
● th:if:条件判断,类似的还有th:unless,th:switch,th:case。优先级较高:order=3
.
● th:insert:代码块引入,类似的还有th:replace,th:include,三者的区别较大,若使用不恰当会破坏html 结构,常用于公共代码块提取的场景。优先级最高:order=1
.
● th:fragment:定义代码块,方便被th:insert 引用。优先级最低:order=8
.
● th:object:声明变量,一般和*{}一起配合使用,达到偷懒的效果。优先级一般:order=4
.
● th:attr:修改任意属性,实际开发中用的较少,因为有丰富的其他th 属性帮忙,类似的还有th:attrappend,th:attrprepend。优先级一般:order=5
迭代
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>//如果没有渲染就显示Onions
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
<td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>
条件运算
<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view</a>
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
<p th:case="*">User is some other thing</p>
</div>
使用Thymeleaf -th 属性需要注意点
1、若要使用Thymeleaf 语法,首先要声明名称空间: xmlns:th="http://www.thymeleaf.org"
2、设置文本内容th:text,设置input 的值th:value,循环输出th:each,条件判断th:if,插入代码块th:insert,定义代码块th:fragment,声明变量th:object
3、th:each 的用法需要格外注意,打个比方:如果你要循环一个div 中的p 标签,则th:each属性必须放在p 标签上。若你将th:each 属性放在div 上,则循环的是将整个div。
4、变量表达式中提供了很多的内置方法,该内置方法是用#开头,请不要与#{}消息表达式弄混。
Thymeleaf 综合案例
需求说明
说明: 使用SpringBoot + Thymeleaf 完成简单的用户登录-列表功能
需求说明
说明: 使用SpringBoot + Thymeleaf 完成简单的用户登录-思路分析/图解
代码实现
配置pom.xml
<!--导入springboot父工程-规定写法-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.5.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!--引入thymeleaf-start: 会进行默认配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
引入starter-Thymeleaf , 项目会自动完成配置, 程序员按照规则开发即可
创建index.html 和manage.html 和静态图片到指定目录,
从准备好的拷贝即可, 注意我将html 文件放到templates/ 目录下, 该目录, 不能直接访问
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
<h1>用户登陆</h1>
<form action="#" th:action="@{/login}" method="post">
<label style="color: red" th:text="${msg}"></label><br/>
用户名:<input type="text" style="width:150px" name="name"/><br/><br/>
密 码:<input type="password" style="width:150px" name="password"/><br/><br/>
<input type="submit" value="登录"/>
<input type="reset" value="重新填写"/>
</form>
</div>
<hr/>
</body>
</html>
manage.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>管理后台</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<a href='#'>返回管理界面</a> <a href='#' th:href="@{/}">安全退出</a> 欢迎您:[[${session.loginAdmin.name}]]
<hr/>
<div style="text-align: center">
<h1>管理雇员~</h1>
<table border="1px" cellspacing="0" bordercolor="green" style="width:800px;margin: auto">
<tr bgcolor="pink">
<td>id</td>
<td>name</td>
<td>pwd</td>
<td>email</td>
<td>age</td>
</tr>
<tr bgcolor="#ffc0cb" th:each="user:${users}">
<td th:text="${user.id}">a</td>
<td th:text="${user.name}">b</td>
<td th:text="${user.password}">c</td>
<td th:text="${user.email}">d</td>
<td th:text="${user.age}">e</td>
</tr>
</table>
<br/>
</div>
<hr/>
</body>
</html>
创建Admin.java
@Data
public class Admin {
private String name;
private String password;
}
创建User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private String password;
private Integer age;
private String email;
}
创建IndexController.java 默认进入登录页面
注意这里解释一下 就是说 直接http://localhost:8080/可以访问 和http://localhost:8080/login可以访问
@Controller
public class IndexController {
//编写方法,转发到登录页面
@GetMapping(value = {"/", "/login"})
public String login() {
/**
* 解读
* 1. 因为我们引入了starter-thymeleaf
* 2. 这里就会直接使用视图解析到 thymeleaf下的模板文件adminLogin.html
*/
return "adminLogin";
}
}
创建AdminController.java 处理登录请求完成测试
@Controller
@Slf4j
public class AdminController {
//响应用户的登录请求
@PostMapping("/login")
public String login(Admin admin, HttpSession session, Model model) {
//验证用户是否合法
if (StringUtils.hasText(admin.getName()) && "666".equals(admin.getPassword())) {
//将登录用户保存到session
session.setAttribute("loginAdmin", admin);
//合法, 重定向到manage.html
//请回忆,java web, 不使用请求转发是防止刷新页面会重复提交
//这里为什么是写的 manage.html, 因为这样可以更加明确的表示到哪个页面
//manage.html表示要去找 方法的映射路径为 manage.html
return "redirect:/manage.html";
} else {
//不合法,就重新登录, 请求转发
model.addAttribute("msg", "账号/用户错误");
return "adminLogin";
}
}
//处理用户的请求到 manage.html
@GetMapping("/manage.html")
public String mainPage(Model model, HttpSession session) {
//这里暂时使用在方法验证,后面我们统一使用拦截器来验证
Object loginAdmin = session.getAttribute("loginAdmin");
if(null != loginAdmin) {//说明成功登录过
//可以这里集合-模拟用户数据, 放入到request域中,并显示
ArrayList<User> users = new ArrayList<>();
users.add(new User(1, "关羽~", "666666", 20, "gy@sohu.com"));
users.add(new User(2, "张飞", "666666", 30, "zf@sohu.com"));
users.add(new User(3, "赵云", "666666", 22, "zy@sohu.com"));
users.add(new User(4, "马超", "666666", 28, "mc@sohu.com"));
users.add(new User(5, "黄忠", "666666", 50, "hz@sohu.com"));
//将数据放入到request域
model.addAttribute("users", users);
return "manage"; //这里才是我们的视图解析到 /templates/manage.html
} else {
//这里就返回登录页,并给出提示
model.addAttribute("msg","你没有登录/请登录");
return "adminLogin";//请求转发到adminLogin.html
}
}
}
Spring-boot-拦截器-HandlerInterceptor
基本介绍
1. 在Spring Boot 项目中, 拦截器是开发中常用手段,要来做登陆验证、性能检查、日志
记录等。
2. 基本步骤:
√ 编写一个拦截器实现HandlerInterceptor 接口
√ 拦截器注册到配置类中(实现WebMvcConfigurer 的addInterceptors)
√ 指定拦截规则
√ 回顾SpringMVC 中讲解的Interceptor 链接
拦截器应用实例
需求: 使用拦截器防止用户非法登录, 如图- 使用拦截器就不需要在每个方法验证了
● 浏览器输入: http://localhost:8080/manage.html , 如果用户没有登录,则返回登录界面.
创建LoginInterceptor.java √ 编写一个拦截器实现HandlerInterceptor 接口
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行前被调用.
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//为了让小伙伴看到访问的URI
String requestURI = request.getRequestURI();
String requestURL = request.getRequestURL().toString();
log.info("preHandle拦截到的请求的URI={}", requestURI);
log.info("preHandle拦截到的请求的URL={}", requestURL);
//进行登录的校验
HttpSession session = request.getSession();
Object loginAdmin = session.getAttribute("loginAdmin");
if (null != loginAdmin) {//说明该用户已经成功登录
//放行
return true;
}
//拦截, 重新返回到登录页面
request.setAttribute("msg", "你没有登录/请登录~~");
request.getRequestDispatcher("/").forward(request, response);
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle执行了...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行了...");
}
}
√拦截器注册到配置类中 创建WebConfig.java
注意这里有二种方式来实现 一种是实现接口 一种是@Bean
@Configuration
public class WebConfig /*implements WebMvcConfigurer*/ {
//@Override
//public void addInterceptors(InterceptorRegistry registry) {
//
// //注册自定义拦截器LoginInterceptor
// registry.addInterceptor(new LoginInterceptor())
// .addPathPatterns("/**") //拦截所有的请求
// .excludePathPatterns("/","/login","/images/**");//指定要放行的,后面可以根据业务需求,来添加放行的请求路径
//}
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
System.out.println("addInterceptors~~~");
//注册拦截器
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/","/login","/images/**","/upload.html","/upload","/sql");
}
};
}
}
修改AdminController.java
去掉session 验证用户是否登录代码, 并完成测试, 注意看页面和后台日志.
@GetMapping("/manage.html")
public String mainPage(Model model, HttpSession session) {
//有了拦截器, 不需再使用sessoin 验证
log.info("进入mainPage()");
//可以这里集合-模拟用户数据, 放入到request域中,并显示
ArrayList<User> users = new ArrayList<>();
users.add(new User(1, "关羽~", "666666", 20, "gy@sohu.com"));
users.add(new User(2, "张飞", "666666", 30, "zf@sohu.com"));
users.add(new User(3, "赵云", "666666", 22, "zy@sohu.com"));
users.add(new User(4, "马超", "666666", 28, "mc@sohu.com"));
users.add(new User(5, "黄忠", "666666", 50, "hz@sohu.com"));
//将数据放入到request域
model.addAttribute("users", users);
return "manage"; //这里才是我们的视图解析到 /templates/manage.html
}
}
注意事项和细节
1、URI 和URL 的区别
URI = Universal Resource Identifier
URL = Universal Resource Locator
Identifier:标识符
Locator:定位器从字面上来看,
URI 可以唯一标识一个资源, URL 可以提供找到该资源的路径
String requestURI = request.getRequestURI();
String requestURL = request.getRequestURL().toString();
Spring-boot--文件上传
应用实例
需求:
演示Spring-Boot 通过表单注册用户,并支持上传图片
我们可以先看看Spring MVC 的文件上传 (5条消息) 处理 json 和 HttpMessageConverter--文件下载-ResponseEntity --SpringMVC 文件上传_尘觉的博客-CSDN博客
1. 创建/upload.html , 要求头像只能选择一个, 而宠物可以上传多个图片
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>upload</title>
</head>
<body bgcolor="#CED3FE">
<img src="images/1.GIF"/>
<hr/>
<div style="text-align: center">
<h1>注册用户~</h1>
<form action="#" th:action="@{/upload}" method="post" enctype="multipart/form-data">
用户名:<input type="text" style="width:150px" name="name"/><br/><br/>
电 邮:<input type="text" style="width:150px" name="email"/><br/><br/>
年 龄:<input type="text" style="width:150px" name="age"/><br/><br/>
职 位:<input type="text" style="width:150px" name="job"/><br/><br/>
头 像:<input type="file" style="width:150px" name="header"><br/><br/>
<!--注意 multiple 这个意思是可以选择多个-->
宠 物:<input type="file" style="width:150px" name="photos" multiple><br/><br/>
<input type="submit" value="注册"/>
<input type="reset" value="重新填写"/>
</form>
</div>
<hr/>
</body>
</html>
创建UploadController.java
这里也解决了
如果文件名相同, 会出现覆盖问题, 如何解决
解决文件分目录存放问题, 如果将文件都上传到一个目录下,当上传文件很多时,会造成访问文件速度变慢,因此可以将文件上传到不同目录比如一天上传的文件,统一放到一个文件夹年/月/日, 比如2022/11/11 目录
@Controller
@Slf4j
public class UploadController {
//处理转发到用户注册-可以完成文件上传页面
@GetMapping("/upload.html")
public String uploadPage() {
return "upload";// 视图解析,转发到templates/upload.html
}
//处理用户的注册请求-包括处理文件上传
@PostMapping("/upload")
@ResponseBody
public String upload(@RequestParam("name") String name,
@RequestParam("email") String email,
@RequestParam("age") Integer age,
@RequestParam("job") String job,
@RequestParam("header") MultipartFile header,
@RequestParam("photos") MultipartFile[] photos) throws IOException {
//输出获取到的信息
log.info("上传的信息 name={} email={} age={} job={} header={} photos={} ",
name, email, age, job, header.getSize(), photos.length);
//如果信息都成功得到,我们就将文件保存到指定的目录,比如d:\\temp_upload
//1. 我们先将文件保存到指定的目录 比如d:\\temp_upload
//2. 后面我们在演示把文件保存到动态创建的目录.
// 比如:
//得到类路径(运行的时候)
String path = ResourceUtils.getURL("classpath:").getPath();
//log.info("path={}", path);
//动态创建指定目录
File file = new File(path + WebUtils.getUploadFileDirectory());
if (!file.exists()) {//如果目录不存在,我们就创建, 在java io
file.mkdirs();
}
if (!header.isEmpty()) {//处理头像
//获取上传文件的名字
String originalFilename = header.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + "_" + System.currentTimeMillis() + "_" + originalFilename;
//这里我们需要指定保存文件的绝对路径 ,
//即 D:\excel_springboot\springboot-usersys\target\classes\static\images\
//header.transferTo(new File("d:\\temp_upload\\" + originalFilename));
//log.info("保存文件的绝对路径={}" + file.getAbsolutePath());
//保存到动态创建的目录
header.transferTo(new File(file.getAbsolutePath() + "/" + fileName));
}
//处理宠物的图片
if (photos.length > 0) {
for (MultipartFile photo : photos) {//遍历
if (!photo.isEmpty()) {
String originalFilename = photo.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + "_" + System.currentTimeMillis() + "_" + originalFilename;
//photo.transferTo(new File("d:\\temp_upload\\" + originalFilename));
//保存到动态创建的目录
photo.transferTo(new File(file.getAbsolutePath() + "/" + fileName));
}
}
}
return "注册用户成功/文件上传成功";
}
}
创建WebUtils
//定义一个文件上传的路径这样直接调用就好了
public class WebUtils {
//定义一个文件上传的路径
public static String UPLOAD_FILE_DIRECTORY = "static/images/upload/";
//编写方法,生成一个目录-根据当前日期 年/月/日
public static String getUploadFileDirectory() {
return UPLOAD_FILE_DIRECTORY + new SimpleDateFormat("yyyy/MM/dd").format(new Date());
}
}
根据项目需求修改文件上传的参数, 否则文件上传会抛出异常, [可以演示错误]
创建resources\application.yml 修改文件上传配置参数,
max-file-size 单个文件大小,
max-request-size 一次请求最大上传大小(多个文件.)
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 50MB
完成注册用户/上传图片-测试