1、创建工程
使用之前使用的Spring提供的向导,快速创建一个包含web模板的SpringBoot工程:springboot-web-restful;
1.1 pom.xml如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springboot.restful</groupId>
<artifactId>springboot-web-restful</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-web-restful</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入Thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--引入jquery-webjar-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
<!--引入bootstrap-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2 导入资源
导入我们需要用到的静态资源,以及dao接口和JaveBean,导入完成之后,目录结构如下:
2、首页设置
现在启动项目,访问:http://localhost:8080/ 展示的是resources静态目录的index.html首页,因为SpringBoot会静态资源目录中找index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>静态资源里面的首页</title>
</head>
<body>
index.html
</body>
</html>
但是这不是我们想要的结果,我们希望的首页是templates下面的index.html。那么该如何设置呢?
2.1 方式一:通过controller的返回值控制
编写一个IndexController,映射 / 请求,那么访问 就会访问到控制器方法,方法的返回值会被thymeleaf模板引擎解析,所以会到templates目录下去找index.html;
package com.springboot.restful.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
/**
* 返回值 index 会被模板引擎解析并渲染,
* 就会去templates下面找index.html
*/
@RequestMapping({"/","index.html"})
public String index(){
return "index";
}
}
启动项目,访问:http://localhost:8080/ 或者 http://localhost:8080/index.html 都能访问到我们期待的首页了。
2.2 通过WebMvcConfigurer配置视图映射关系
从之前分析SpringMVC的原理我们知道,所有的 WebMvcConfigurer 组件都会一起起作用,所以我们创建一个配置类,可以定义自己的 WebMvcConfigurer ,然后实现里面对应的方法,就能扩展SpringMVC了,再把它注册到IOC容器中,代码如下:
package com.springboot.restful.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration //表明这是一个配置类
public class RestfulMvcConfig implements WebMvcConfigurer {
//所有的WebMvcConfigurerAdapter组件都会一起起作用
@Bean
public WebMvcConfigurer webMvcConfigurer(){
WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//首页映射, / 和 /index.html 都会访问首页(templates/index.html)
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
};
return webMvcConfigurer;
}
}
注释掉上面IndexController中的方法,也可以访问首页成功。
3、国际化
从上面的首页可以看到,首页现在是英文的,现在我们要实现首页的国际化。语言要根据浏览器的语言设置进行切换,最后要实现我们鼠标点击下面的中文/English 实现语言的切换。
3.1 以前的SpringMVC的国际化实现步骤
-
1)、编写国际化配置文件;
-
2)、使用ResourceBundleMessageSource管理国际化资源文件;
-
3)、在页面使用fmt:message取出国际化内容;
具体实现可以参考:https://blog.csdn.net/zengdongwen/article/details/104555217
3.2 SpringBoot的国际化实现
3.2.1 编写国际化配置文件
编写国际化配置文件,抽取页面需要显示的国际化消息
在resources下面创建i18n目录,然后下面创建三个国际化文件:
login.properties:
login.btn=登录~
login.password=密码~
login.remember=记住我~
login.tip=请登录~
login.username=用户名~
login_zh_CN.properties:
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
login_en_US.properties:
login.btn=Sign In
login.password=Password
login.remember=Remember Me
login.tip=Please sign in
login.username=Username
3.2.2 SpringBoot自动配置好了管理国际化资源文件的组件
查看SpringBoot自动配置的国际化资源文件的组件的源码:
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnMissingBean(
name = {"messageSource"},
search = SearchStrategy.CURRENT
)
@AutoConfigureOrder(-2147483648)
@Conditional({MessageSourceAutoConfiguration.ResourceBundleCondition.class})
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = new Resource[0];
/**
* Comma-separated list of basenames (essentially a fully-qualified classpath
* location), each following the ResourceBundle convention with relaxed support for
* slash based locations. If it doesn't contain a package qualifier (such as
* "org.mypackage"), it will be resolved from the classpath root.
*/
public MessageSourceAutoConfiguration() {
}
@Bean
@ConfigurationProperties(
prefix = "spring.messages"
)
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
//设置国际化资源文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
protected static class ResourceBundleCondition extends SpringBootCondition {
private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap();
protected ResourceBundleCondition() {
}
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
//默认的国际化资源文件的基础名字是messages,意思就是SpringBoot默认是会去项目根目录下面找messages基础文件名称的国际化文件,而我们这里是配置了 i18n 下面的 login
String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
ConditionOutcome outcome = (ConditionOutcome)cache.get(basename);
if (outcome == null) {
outcome = this.getMatchOutcomeForBasename(context, basename);
cache.put(basename, outcome);
}
return outcome;
}
private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
Builder message = ConditionMessage.forCondition("ResourceBundle", new Object[0]);
String[] var4 = StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename));
int var5 = var4.length;
for(int var6 = 0; var6 < var5; ++var6) {
String name = var4[var6];
Resource[] var8 = this.getResources(context.getClassLoader(), name);
int var9 = var8.length;
for(int var10 = 0; var10 < var9; ++var10) {
Resource resource = var8[var10];
if (resource.exists()) {
return ConditionOutcome.match(message.found("bundle").items(new Object[]{resource}));
}
}
}
return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
}
private Resource[] getResources(ClassLoader classLoader, String name) {
String target = name.replace('.', '/');
try {
return (new PathMatchingResourcePatternResolver(classLoader)).getResources("classpath*:" + target + ".properties");
} catch (Exception var5) {
return MessageSourceAutoConfiguration.NO_RESOURCES;
}
}
}
}
3.2.3 配置国际化资源文件的basename
在application.properties中配置:
#配置国际化资源文件的基础名称
spring.messages.basename=i18n.login
3.2.4 修改首页,用#{}取国际化信息
1、首先需要引入thymeleaf的名称空间:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
2、用th:text取出国际化资源,用th:href修改css、js的路径
<!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 -->
<link href="asserts/css/bootstrap.min.css" th:href="@{/asserts/css/bootstrap.min.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
</head>
<body class="text-center">
<form class="form-signin" action="dashboard.html">
<img class="mb-4" src="asserts/img/bootstrap-solid.svg" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" placeholder="Username" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" placeholder="Password" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">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>
启动,访问浏览器,可以达到的效果就是:可以根据浏览器的语言设置,显示不同的语言信息了。
注意:如果中文出现乱码,那是因为IDEA需要如下设置:
3.2.5 国际化原理
查看SpringBoot的自动配置 WebMvcAutoConfiguration 源码,可以看到关于国际化 Locale的配置代码如下:
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc",name = {"locale"})
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
} else {
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
}
根据这句:AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); 查看 AcceptHeaderLocaleResolver :
public class AcceptHeaderLocaleResolver implements LocaleResolver {
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = this.getDefaultLocale();
//默认的就是根据请求头带来的区域信息获取Locale进行国际化
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
} else {
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = this.getSupportedLocales();
if (!supportedLocales.isEmpty() && !supportedLocales.contains(requestLocale)) {
Locale supportedLocale = this.findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
} else {
return defaultLocale != null ? defaultLocale : requestLocale;
}
} else {
return requestLocale;
}
}
}
}
默认的就是根据请求头带来的区域信息获取Locale进行国际化
这就是我们上面的操作可以实现根据浏览器的语言设置实现国际化的原因。
3.2.6 点击链接实现国际化
现在我们的效果是需要点击登录页面的 中文/English 来切换不同的语言信息。这样我们可以在点击链接的时候,加上对应的语言标识:
<a class="btn btn-sm" th:href="@{/index.html(locale='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(locale='en_US')}">English</a>
因为默认的区域解析器是根据浏览器的Accept-Language 请求头来进行判断的,所以我们如果要实现点击链接来获取区域信息,就需要自定义区域解析器:
package com.springboot.restful.resovler;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class MyLocaleResolver implements LocaleResolver {
/**
* 解析区域信息
*/
@Override
public Locale resolveLocale(HttpServletRequest request) {
String localeValue = request.getParameter("locale");
Locale locale = Locale.getDefault();
if(!StringUtils.isEmpty(localeValue)){//不为空说明链接带上了区域信息
String[] strings = localeValue.split("_");
//通过国家信息和语言信息得到区域对象
locale = new Locale(strings[0],strings[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
在配置类中,注册我们自定义的区域解析器:
//配置自定义的区域解析器
@Bean
public LocaleResolver localResolver(){
return new MyLocaleResolver();
}
当然,也可以在自定义的 MyLocaleResolver 加上 @Component(value = “localeResolver”) 注解,也是把该组件注册到容器中,但是一定要指定 value = “localeResolver” ,其他名称都不生效:
@Component(value = "localeResolver")
public class MyLocaleResolver implements LocaleResolver {
注意:
1、LocaleResolver 不要导错包,他是:org.springframework.web.servlet.LocaleResolver;
2、localResolver() 方法名是固定的,不能修改成其他的,否则不生效。
4、登录功能实现
此次案例都不链接数据库,数据都是代码写死的,等学了SpringBoot的数据访问之后,再连接数据库。
4.1 编写后台controller
package com.springboot.restful.controller;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Map;
@Controller
public class LoginController {
@PostMapping("user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Map<String,Object> map){
if(!StringUtils.isEmpty(username) && "123".equals(password)){
//登录成功,跳转到主页面
return "dashboard";
}
//登录失败,提示错误消息,跳转到登录页
map.put("msg","用户名或密码错误");
return "index";
}
}
4.2 禁用thymeleaf的缓存
禁用缓存只是为了在开发环境测试方便,比如我们在html中改了代码能及时生效。当然如果想及时生效的话,还需要 crtl + f9 来重新编译。
# 禁用缓存
spring.thymeleaf.cache=false
4.3 修改登录页
登录页面修改注意:
1、修改表单提交的action的路径;
2、添加错误提示的<p>标签;
3、要保证用户名和密码一定要存在name属性;
<form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
<img class="mb-4" src="asserts/img/bootstrap-solid.svg" th:src="@{/asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<!--登录错误的提示消息
th:text="${msg}" : 取出后台返回的错误提示
th:if="${not #strings.isEmpty(msg)}" : 判断错误消息msg有值才显示 p 标签消息,比较登录成功是没消息的
-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
<label class="sr-only" th:text="#{login.username}">Username</label>
<input type="text" name="username" class="form-control" th:placeholder="#{login.username}" placeholder="Username" required="" autofocus="">
<label class="sr-only" th:text="#{login.password}">Password</label>
<input type="password" name="password" class="form-control" th:placeholder="#{login.password}" placeholder="Password" required="">
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> [[#{login.remember}]]
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
<a class="btn btn-sm" th:href="@{/index.html(locale='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(locale='en_US')}">English</a>
</form>
启动,输入用户名和密码123是可以登录成功的,如果用户名错误,就会跳回到登录页面,并且提示错误消息。
4.4 存在的问题:重复提交表单
上面的操作我们是可以登录成功的,但是登录成功之后,如果点击浏览器的刷新按钮,就会提示如下消息:
这便是重复提交问题。
4.5 解决表单的重复提交问题
之前的LoginController中,我们登录成功,是通过转发跳转到 dashboard.html ,如果要避免重复提交表单,这里应该是重定向。
4.5.1 添加主页视图映射
在 RestfulMvcConfig 添加上主页的视图映射:registry.addViewController(“main.html”).setViewName(“dashboard”);
import com.springboot.restful.resovler.MyLocaleResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class RestfulMvcConfig implements WebMvcConfigurer {
//所有的WebMvcConfigurerAdapter组件都会一起起作用
@Bean
public WebMvcConfigurer webMvcConfigurer(){
WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//首页映射, / 和 /index.html 都会访问首页(templates/index.html)
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
//后台主页的视图映射
registry.addViewController("main.html").setViewName("dashboard");
}
};
return webMvcConfigurer;
}
//配置自定义的区域解析器
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
4.5.2 修改controller方法
package com.springboot.restful.controller;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Map;
@Controller
public class LoginController {
@PostMapping("user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Map<String,Object> map){
if(!StringUtils.isEmpty(username) && "123".equals(password)){
//登录成功,跳转到主页面
//return "dashboard";
return "redirect:/main.html";
}
//登录失败,提示错误消息,跳转到登录页
map.put("msg","用户名或密码错误");
return "index";
}
}
redirect:/main.html :redirect:/ 开头,SpringBoot就会以重定向的方式。
4.6 添加登录拦截器
上面的操作,没有拦截任何请求,我们可以直接访问:http://localhost:8080/main.html,就能绕开登录操作,直接访问到后台的首页。
于是我们需要添加登录的拦截器,如果没有登录就不能访问后台:
4.6.1 自定义拦截器
package com.springboot.restful.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义拦截器
*/
public class MyRestfulInterceptor implements HandlerInterceptor {
/**
* 目标方法执行之前:登录检查
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登录成功的时候,用户信息放到了session中,所以这时就从session中取
Object loginUser = request.getSession().getAttribute("loginUser");
if(loginUser==null){//没有登录
request.setAttribute("msg","您还没有登录,请先登录");
//转发到登陆页面
request.getRequestDispatcher("/index.html").forward(request,response);
return false;
}
//到了这里说明登录成功的,直接放行
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
关于静态资源会不会被自定义拦截器给拦截,可以参照:https://blog.csdn.net/ln1593570p/article/details/80607616
4.6.2 修改登录方法,把用户信息存入session中
@Controller
public class LoginController {
@PostMapping("user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Map<String,Object> map,
HttpSession httpSession){
if(!StringUtils.isEmpty(username) && "123".equals(password)){
//登录成功,跳转到主页面
httpSession.setAttribute("loginUser",username);//把用户信息放到session中
//return "dashboard";
return "redirect:/main.html";
}
//登录失败,提示错误消息,跳转到登录页
map.put("msg","用户名或密码错误");
return "index";
}
}
4.6.3 注册拦截器
在自定义配置类 RestfulMvcConfig 中注册拦截器:
//所有的WebMvcConfigurerAdapter组件都会一起起作用
@Bean
public WebMvcConfigurer webMvcConfigurer(){
WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//首页映射, / 和 /index.html 都会访问首页(templates/index.html)
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
//后台主页的视图映射
registry.addViewController("/main.html").setViewName("dashboard");
}
//注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyRestfulInterceptor())
.addPathPatterns("/**")// /** 拦截所有路径
//设置不拦截的路径,"/asserts/**","/webjars/**" 静态资源也不能拦截,
//SpringBoot1.x好像不会拦截静态资源,但是SpringBoot2.x会拦截静态资源,所以需要排除掉
.excludePathPatterns("/","/index.html","/user/login","/asserts/**","/webjars/**");
}
};
return webMvcConfigurer;
}
4.6.4 修改dashboard.html
修改dashboard.html中的获取静态资源的路径,用th:标签来修改,在页面取出用户信息:[[${session.loginUser}]]
5、CRUD员工信息
5.1 简介
RestfulCRUD:CRUD满足Rest风格;
URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
普通CRUD(uri来区分操作) | RestfulCRUD | |
---|---|---|
查询 | getEmp | emp—GET |
添加 | addEmp?xxx | emp—POST |
修改 | updateEmp?id=xxx&xxx=xx | emp/{id}—PUT |
删除 | deleteEmp?id=1 | emp/{id}—DELETE |
实验的请求架构:
实验功能 | 请求URI | 请求方式 |
---|---|---|
查询所有员工 | emps | GET |
查询某个员工(来到修改页面) | emp/1 | GET |
来到添加页面 | emp | GET |
添加员工 | emp | POST |
来到修改页面(查出员工进行信息回显) | emp/1 | GET |
修改员工 | emp | PUT |
删除员工 | emp/1 | DELETE |
5.1 员工列表
thymeleaf公共页面元素抽取:
1、抽取公共片段
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
2、引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模板名::选择器
~{templatename::fragmentname}:模板名::片段名
3、默认效果:
insert的公共片段在div标签中
如果使用th:insert等属性进行引入,可以不用写~{}:
行内写法可以加上:[[~{}]];[(~{})];
三种引入公共片段的th属性:
th:insert:将公共片段整个插入到声明引入的元素中
th:replace:将声明引入的元素替换为公共片段
th:include:将被引入的片段的内容包含进这个标签中
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
引入方式
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
效果
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
后台方法:
package com.springboot.restful.controller;
import com.springboot.restful.dao.EmployeeDao;
import com.springboot.restful.entities.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Collection;
import java.util.List;
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
//查询员工列表
@GetMapping("emps")
public String getEmps(Model model){
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("emps",employees);
return "emp/list";//跳转到templates/emp/list.html
}
}
5.2 员工添加
5.2.1 跳转到员工添加页面的controller方法
需要查询部门信息,然后传到添加页面:
@Autowired
DepartmentDao departmentDao;
//跳转到员工添加页面的方法,需要初始化一些字典数据,比如部门信息
@GetMapping("/emp")
public String toAddEmp(Map<String,Object> map){
Collection<Department> departments = departmentDao.getDepartments();
map.put("depts",departments);
return "emp/add";
}
5.2.2 员工添加页面add.html
<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<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>Dashboard Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="asserts/css/dashboard.css" th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
<style type="text/css">
/* Chart.js */
@-webkit-keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
@keyframes chartjs-render-animation {
from {
opacity: 0.99
}
to {
opacity: 1
}
}
.chartjs-render-monitor {
-webkit-animation: chartjs-render-animation 0.001s;
animation: chartjs-render-animation 0.001s;
}
</style>
</head>
<body>
<!--引入抽取的topbar-->
<!--模板名:会使用thymeleaf的前后缀配置规则进行解析-->
<div th:replace="commons/bar::topbar"></div>
<div class="container-fluid">
<div class="row">
<!--引入侧边栏-->
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<!--需要区分是员工修改还是添加;-->
<form th:action="@{/emp}" method="post">
<!--发送put请求修改员工数据-->
<!--
1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)
2、页面创建一个post表单
3、创建一个input项,name="_method";值就是我们指定的请求方式
-->
<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
<!--如果是修改员工信息的时候,需要传入员工id-->
<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
<div class="form-group">
<label>LastName</label>
<input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
</div>
<div class="form-group">
<label>Email</label>
<input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}">
</div>
<div class="form-group">
<label>Gender</label><br/>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}">
<label class="form-check-label">男</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<!--提交的是部门的id-->
<select class="form-control" name="department.id">
<option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">
</div>
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
</form>
</main>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js" th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
<script type="text/javascript" src="asserts/js/popper.min.js" th:src="@{/webjars/popper.js/1.11.1/dist/popper.js}"></script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/4.0.0/js/bootstrap.js}"></script>
<!-- Icons -->
<script type="text/javascript" src="asserts/js/feather.min.js" th:src="@{/asserts/js/feather.min.js}"></script>
<script>
feather.replace()
</script>
</body>
</html>
提交的数据格式不对:生日:日期;
2017-12-12;2017/12/12;2017.12.12;
日期的格式化;SpringMVC将页面提交的值需要转换为指定的类型;
2017-12-12—Date; 类型转换,格式化;
需要在application.properties中指定日期格式:
#设置日期格式,默认是yyyy/MM/dd
spring.mvc.date-format=yyyy-MM-dd
5.2.3 员工添加的方法
//新增员工方法
//SpringMVC自动将请求参数和入参对象的属性进行一一绑定;要求请求参数的名字和javaBean入参的对象里面的属性名是一样的
@PostMapping("/emp")
public String addEmp(Employee employee){
employeeDao.save(employee);
// redirect: 表示重定向到一个地址 /代表当前项目路径
// forward: 表示转发到一个地址
return "redirect:/emps";
}
5.3 员工修改
员工修改的页面和新增是同一个页面,可以参考上面的add.html
5.3.1 跳转到员工修改页面的方法
//修改员工的第一步,查询员工信息,跳转到员工修改页面
@GetMapping("/emp/{id}")
public String toEditEmp(@PathVariable("id") Integer id,
Model model){
//查询员工信息
Employee employee = employeeDao.get(id);
model.addAttribute("emp",employee);
//查询部门信息
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts",departments);
//修改和新增是同一个页面
return "emp/add";
}
5.3.2 修改员工的方法
//真正修改员工的方法
@PutMapping("/emp")
public String edit(Employee employee){
employeeDao.save(employee);
//重定向到列表页面的方法
return "redirect:/emps";
}
5.3.3 修改配置文件
# 启用hiddenMethod过滤器
spring.mvc.hiddenmethod.filter.enabled=true
Spring Boot 2.2.0 版本中使用 hiddenmethod 过滤器的问题,参考:https://blog.csdn.net/Arctan_/article/details/102957615
5.4 删除员工
<tr th:each="emp:${emps}">
<td th:text="${emp.id}"></td>
<td>[[${emp.lastName}]]</td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender}==0?'女':'男'"></td>
<td th:text="${emp.department.departmentName}"></td>
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"></td>
<td>
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
<button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button>
</td>
</tr>
<!--删除员工,构造一个表单,然后用js提交表单,请求方式是delete-->
<form id="deleteEmpForm" method="post">
<input type="hidden" name="_method" value="delete"/>
</form>
<script>
$(".deleteBtn").click(function(){
//删除当前员工的
$("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit();
return false;
});
</script>
后台方法:
//删除员工的方法
@DeleteMapping("/emp/{id}")
public String deleteEmp(@PathVariable("id") Integer id){
employeeDao.delete(id);
return "redirect:/emps";
}