一、Thymeleaf
我们以前开发,前端需要动态展示数据,用的的最多的就是jsp,jsp好处就是当我们查出一些数据转发到JSP页面以后,我们可以用jsp轻松实现数据的显示,及交互等。
jsp支持非常强大的功能,包括能写Java代码,但是呢,我们现在的这种情况,SpringBoot这个项目首先是以jar的方式,不是war,像第二,我们用的还是嵌入式的Tomcat,所以呢,他现在默认是不支持jsp的。
那不支持jsp,如果我们直接用纯静态页面的方式,那给我们开发会带来非常大的麻烦,那怎么办呢?
SpringBoot推荐你可以来使用模板引擎:
模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想都是一样的,什么样一个思想呢我们来看一下这张图:
模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的,我们写一些表达式。而这些值,从哪来呢,就是我们在后台封装一些数据。
然后把这个模板和这个数据交给我们模板引擎,模板引擎按照我们这个数据帮你把这表达式解析、填充到我们指定的位置,然后把这个数据最终生成一个我们想要的内容给我们写出去,这就是我们这个模板引擎,不管是jsp还是其他模板引擎,都是这个思想。
只不过呢,就是说不同模板引擎之间,他们可能这个语法有点不一样。SpringBoot给我们推荐的Thymeleaf模板引擎,这模板引擎呢,是一个高级语言的模板引擎,他的这个语法更简单。而且呢,功能更强大。
首先,我们来看在SpringBoot里边怎么用。
可以参考一下:
Thymeleaf 官网:https://www.thymeleaf.org/
Thymeleaf 在Github 的主页:https://github.com/thymeleaf/thymeleaf
Spring官方文档:找到我们对应的版本
https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter
找到对应的pom依赖:可以适当点进源码看下本来的包!
我们首先得按照SpringBoot的自动配置原理看一下我们这个Thymeleaf的自动配置规则,在按照那个规则,我们进行使用。
我们去找一下Thymeleaf的自动配置类:ThymeleafProperties
我们可以在其中看到默认的前缀和后缀!
我们只需要把我们的html页面放在类路径下的templates下,thymeleaf就可以帮我们自动渲染了。
使用thymeleaf什么都不需要配置,只需要将他放在指定的文件夹下即可!
二、环境准备
- 首先创建一个springboot项目,并引入thymeleaf依赖。贴一下pom.xml
<!--引入thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 准备一个静态资源页面,可以参考一下我的素材springboot-thymeleaf素材.rar
静态资源存放目录如上图。
三、使用Thymeleaf模板更改页面
1.在html标签添加命名空间:
2.修改css目录访问路径
springboot项目默认classpath目录路径由四个:分别是:
1.“classpath:/META-INF/resources/”,
2.“classpath:/resources/”,
3.“classpath:/static/”,
4. "classpath:/public/"
可以参考一下上一篇文章:springboot静态资源加载规则
所以我们这里引入CSS文件可以省略static目录直接从css目录开始引入。
这里要注意:如果css前面不写 /
的话,使用localhost:8080是可以访问到默认index.html的并且也是有样式的,但是经过servlet后台重新转发后将无法使用css样式,引入路径会被修改。
现在我们启动项目看一下:
是可以访问的,因为springboot项目启动后会默认找classpath目录下面的index.html
页面,所以我们一般吧index.html
作为首页。
可以看到,使用localhost:8080
和 localhost:8080/index.html
的效果是一样的。
四、修改mvc视图配置
我们一般在访问网站的时候,如果访问了错误的路径,是不是,一般都直接跳转到首页了。这里需要自己去定义一个视图转发器,就可以实现这种效果。
我们新建一个config目录,并创建一个MyWebConfig
类,实现WebMvcConfigurer
接口。
package com.example.demo.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 自定义配置中心
* create by c-pown on 2020-07-16
*/
@Configuration
@Slf4j
public class MyWebConfig implements WebMvcConfigurer {
/**
* 自定义视图转发器
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
log.info("进入视图转发");
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/login").setViewName("index");
}
}
- 这里自定义的配置文件,一定要加上
@Configuration
注解,这样springboot在启动的时候就会加载我们的配置项,装配到系统里。 - 重写
addViewControllers
方法,并自定义转发路径。就可以在项目访问其他路径的时候转发到index首页。
启动一下:
发现在项目启动时已经加载到我们的配置类。
这时候发现访问http://localhost:8080/login
路径也是可以跳转到受首页的。
四、用户登录校验
index.html代码:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Signin Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<!--此处路径必须带有 / 否则仓后台跳转过来会默认增加一级目录导致 css访问失效-->
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link th:href="@{/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" th:action="@{/user/login}">
<img class="mb-4" th:src="@{/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<!--@{ not strings.notEmpty(msg)} 可以校验msg是否为空 不为空才展示-->
<p style="color: red" th:text="${msg}" th:if="@{ not strings.notEmpty(msg)}"></p>
<label class="sr-only">Username</label>
<input type="text" class="form-control" placeholder="Username" name="userName" required="" autofocus="">
<label class="sr-only">Password</label>
<input type="password" class="form-control" placeholder="Password" name="password" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm">中文</a>
<a class="btn btn-sm">English</a>
</form>
</body>
</html>
th:action="@{user/login}
为我们请求的接口地址,thymeleaf默认为我们添加了 localhost:8080项目访问地址。所以可以直接写接口地址即可。
<form class="form-signin" th:action="@{/user/login}">
- 此处可以展示登录报错信息,如果校验失败返回错误信息以展示。
<p style="color: red" th:text="${msg}" th:if="@{ not strings.notEmpty(msg)}"></p>
后端代码:
package com.example.demo.controller;
import com.alibaba.druid.util.StringUtils;
import com.example.demo.model.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.HashMap;
import java.util.Map;
/**
* 用户登录controller
* create by c-pown on 2020-07-16
*/
@Controller
@Slf4j
@RequestMapping("user")
public class LoginUserController {
private static Map<String,Object> userDb = new HashMap<>();
/**
* 模拟数据库 有一个用户张三 密码是:123456
*/
static {
userDb.put("张三",new SysUser("张三","123456"));
}
/**
* /登录接口
*/
@RequestMapping("/login")
public String LoginUser(@RequestParam("userName")String userName,@RequestParam("password") String password, Model model){
log.info("user login ...");
//如果张三不存在 提醒用户去注册
if(userDb.get(userName) == null){
log.info("user login error :用户不存在,请先注册用户");
model.addAttribute("msg","用户不存在,请先注册用户");
return "index";
}else{
SysUser user = (SysUser)userDb.get(userName);
if(!StringUtils.equals(password,user.getPassword())){
log.info("user login error :密码错误");
model.addAttribute("msg","密码错误");
return "index";
}else{
log.info("user login success");
return "dashboard";
}
}
}
}
- 这里使用Map模拟一下数据库 (在后面我会陆续集成数据库等其他资源)
private static Map<String,Object> userDb = new HashMap<>();
/**
* 模拟数据库 有一个用户张三 密码是:123456
*/
static {
userDb.put("张三",new SysUser("张三","123456"));
}
- 我们接受前端请求,如果用户存在,就跳转到列表dashboard页面,如果不存在就返回首页,并提醒用户注册,
如果密码错误,则返回首页提醒密码错误。
使用{张三,123456}登录成功。
**注意:**我们做登录页面以及一些安全性较高的页面,可以修改请求方式为post;
<form class="form-signin" th:action="@{/user/login}" method="post">
五、访问权限校验
上面我们写了,用户登录校验,但是还有个缺陷,那就是我们在后台转发dashboard页面,是无论如何都能转发成功的,一般我们在实际项目中都是登录过后,处于登录状态才能够跳转。
这里我们可以使用session + Interceptor 拦截器,来对用户是否登录做校验,同时判断是否放行该请求。
<1> 我们先修改controller代码,如果用户登录成功,则往session里面添加用户session:
package com.example.demo.controller;
import com.alibaba.druid.util.StringUtils;
import com.example.demo.model.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
/**
* 用户登录controller
* create by c-pown on 2020-07-16
*/
@Controller
@Slf4j
@RequestMapping("user")
public class LoginUserController {
private static Map<String,Object> userDb = new HashMap<>();
/**
* 模拟数据库 有一个用户张三 密码是:123456
*/
static {
userDb.put("张三",new SysUser("张三","123456"));
}
/**
* /登录接口
*/
@RequestMapping(value = "/login")
public String LoginUser(@RequestParam("userName")String userName, @RequestParam("password") String password, Model model, HttpSession session){
log.info("user login ...");
//如果张三不存在 提醒用户去注册
if(userDb.get(userName) == null){
log.info("user login error :用户不存在,请先注册用户");
model.addAttribute("msg","用户不存在,请先注册用户");
return "index";
}else{
SysUser user = (SysUser)userDb.get(userName);
if(!StringUtils.equals(password,user.getPassword())){
log.info("user login error :密码错误");
model.addAttribute("msg","密码错误");
return "index";
}else{
//登录成功 添加session
session.setAttribute("loginUser",user);
log.info("user login success 添加用户到 session");
return "dashboard";
}
}
}
}
- 登陆成功将SysUser对象放入session。
<2> 再定义一个拦截器 MyInterceptor:
package com.example.demo.config;
import com.example.demo.model.SysUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义请求拦截器
* create by c-pown on 2020-07-17
*/
@Slf4j
public class MyInterceptor implements HandlerInterceptor {
/**
* 这里可以对请求之前进行拦截
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("拦截到请求");
//判断session里面有没有用户
SysUser loginUser = (SysUser)request.getSession().getAttribute("loginUser");
//如果没有 转发到首页登录
if(loginUser == null ){
log.info("没有登录,请到首页登录");
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}
log.info("session User 登录用户:"+loginUser);
//如果用户不等于 空 放行
return true;
}
}
- 需要实现HandlerInterceptor 接口,并重写preHandle方法。
- 如果有请求被拦截到,就判断session里面是否有SysUser对象,如果没有就跳转到首页index.
<3> 将拦截器加载到配置MyConfig
package com.example.demo.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 自定义配置中心
* create by c-pown on 2020-07-16
*/
@Configuration
@Slf4j
public class MyWebConfig implements WebMvcConfigurer {
/**
* 自定义视图转发器
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
log.info("进入视图转发");
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
registry.addViewController("/login").setViewName("index");
}
/**
* 加载自定义拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截所有请求 除了转发到首页的请求
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns("/index","/index.html","/user/login","/login","/css/*","/img/*","/js/*");
}
}
registry.addInterceptor(new MyInterceptor())
将自定义拦截器放入。addPathPatterns("/**")
默认拦截所有请求。excludePathPatterns("/index","/index.html","/user/login","/login","/css/*","/img/*","/js/*")
排除首页登录页,以及静态资源。
重启项目,试一下:
- 现在访问任何路径,只要没有登录都可以跳转到首页,因为我们拦截了所有请求。
- 使用{张三,123456}登录是可以的。