只是练习thymeleaf的用法,所以没有使用数据库;如果觉得非要加数据库的同学,可以自行修改。
文章目录
项目已上传到github:https://github.com/angenin/SpringBootDemo(在原基础上加上mybatis)
环境与技术
电脑环境:macOS
电脑软件:IDEA
基础框架:SpringBoot2.3.0
前端框架:bootstrap
模板引擎:thymeleaf
服务器:Tomcat
依赖管理:Maven3.5.4
资源下载
静态资源、页面和dao代码下载:https://pan.baidu.com/s/1IZ5UvyYm1iCN1V276qMqQw 密码:tgnt
新建项目
下一步,创建项目
引入依赖
pom文件还需要引入jquery、bootstrap的依赖:
<!-- 引入jquery-webjar -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.4.1</version>
</dependency>
<!-- 引入bootstrap -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.3.1</version>
</dependency>
导入资源
把整个asserts目录放到项目的resources/static目录下,把dao和entities两个目录放到main/java/com/angenin/springboot目录下,剩下4个页面放到resources/templates目录下。
RestfulCRUD
1. 默认访问首页
在java/com/angenin/springboot新建config/MyMvcConfig配置类,专门用于mvc相关的配置。
MyMvcConfig:
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override //添加视图映射
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
}
把resources/templates目录下的index.html改名为login.html。
启动项目,在浏览器输入http://localhost:8080
,成功访问到登录页面。
-
往每个页面的< html >标签中加入
xmlns:th="http://www.thymeleaf.org"
-
修改login.html里的< link >标签
<link th:href="@{/webjars/bootstrap/4.3.1/css/bootstrap.css}" rel="stylesheet"> <link th:href="@{/asserts/css/signin.css}" rel="stylesheet">
-
其他页面的link标签为:
<link th:href="@{/webjars/bootstrap/4.3.1/css/bootstrap.min.css}" rel="stylesheet"> <link th:href="@{/asserts/css/dashboard.css}" rel="stylesheet">
-
往login.html里的< img >标签加入
th:src="@{/asserts/img/bootstrap-solid.svg}"
这样修改以后,然后以后项目名变了,thymeleaf会自动帮我们修改资源的访问路径。
例如这样,我们来修改项目的访问名
在application.properties配置文件中加入
server.servlet.context-path=/crud
现在在需要浏览器输入http://localhost:8080/crud
才能访问到首页,并且页面代码也自动加了/crud。
2. 国际化
-
编写国际化配置文件,抽取页面需要显示的国际化消息
在resources下新建一个i18n目录,在目录里新建login.properties和login_zh_CN.properties文件。当IDEA识别到我们要做国际化文件时,会自动切换到国际化视图。
再新建一个文件
添加属性
抽取页面需要显示的国际化消息
性 login.properties(默认) login_en_US.properties(英文) login_zh_CN.properties(中文) login.tip 请登录~ Please sign in 请登录 login.username 用户名~ username 用户名 login.password 密码~ password 密码 login.remember 记住我~ Remember Me 记住我 login.btn 登录~ Sign in 登录 -
在国际化的自动配置类中,使用了ResourceBundleMessageSource管理国际化资源文件。
MessageSourceAutoConfiguration.class@Bean @ConfigurationProperties( prefix = "spring.messages" ) public MessageSourceProperties messageSourceProperties() { return new MessageSourceProperties(); } @Bean //MessageSource(ResourceBundleMessageSource)是管理国际化的组件 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; }
在application.properties中加入
spring.messages.basename=i18n.login
-
修改页面取出国际化内容
修改login.html<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" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus=""> <label class="sr-only" th:text="#{login.password}">Password</label> <input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.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>
我的google浏览器是选的是中文,所有会使用中文的国际化内容
启动项目,刷新页面
如果出现乱码:
但是如果在项目里这个修改,这是在这个项目里解决了乱码问题,下次打开别的项目还是一样会乱码(相当于局部配置)。
而如果想修改后,每个项目都不会乱码的话,需要进行全局修改。
然后按照上一张图修改即可,这样就解决了乱码问题。
修改浏览器语言
刷新页面就切换成英语的国际化内容
-
实现点击登录页面的中文或English切换语言
原理:
国际化Locale:区域信息对象
LocaleResolver:获取区域信息对象
在WebMvcAutoConfiguration.class自动配置类源码中注册了LocaleResolver组件@Bean @ConditionalOnMissingBean //当容器中没有用户注册LocaleResolver才自动注册默认的LocaleResolver @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; } }
默认的就是根据请求头带来的的区域信息获取Locale来进行国际化。
浏览器语言切换成中文
修改login.html的两个语言的a标签<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a> <a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
在main/java/com/angenin/springboot下新建component/MyLocaleResolver
import org.springframework.util.StringUtils; import org.springframework.web.servlet.LocaleResolver; public class MyLocaleResolver implements LocaleResolver { @Override //解析区域信息 public Locale resolveLocale(HttpServletRequest httpServletRequest) { //获取请求的l参数 String l = httpServletRequest.getParameter("l"); //默认的信息 Locale locale = Locale.getDefault(); //检测l参数是否为空 if(!StringUtils.isEmpty(l)){ //不为空 String[] s = l.split("_"); //第一个参数为语言代码,第二个参数为国家代码 locale = new Locale(s[0], s[1]); } return locale; } @Override public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { } }
然后在MyMvcConfig配置类中注册我们的LocaleResolver
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
重新启动,刷新页面即可点击实现语言的切换。
3. 登录
登录只是在后台简单的进行判断,不使用数据库,提交也不使用ajax。
-
在login页面的form标签里加上
th:action="@{/user/login}" method="post"
设置提交的地址和请求类型,并且在username和password的input标签加上name=username
和name=password
。 -
在application.properties里添加
spring.thymeleaf.cache=false
禁用模板引擎使用缓存 -
在main/java/com/angenin/springboot下新建controller/LoginController
@Controller public class LoginController { // @DeleteMapping // @PutMapping // @GetMapping // @RequestMapping(value = "/user/login", method = RequestMethod.POST) @PostMapping("/user/login") //直接使用PostMapping表示要映射一个post的请求 public String login(@RequestParam("username") String username, @RequestParam("password") String password, Map<String, Object> map){ if(!StringUtils.isEmpty(username) && "123456".equals(password)){ //登录成功跳转到dashboard.html页面 return "dashboard"; }else { map.put("msg", "用户名或密码错误"); return "login"; } } }
-
在login页面Please sign in下添加
<!--th:if进行判断,smg不为空显示th:text="${msg}"(#strings是thymeleaf的内置工具对象)--> <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
运行项目,随便输入一个用户名和密码,显示错误信息
密码输入123456进入到首页,因为路径的改变,所以所有的页面的需要修改引用的路径。
此时,如果刷新页面会出现表单重复提交现象(问题一)。
解决办法是登录成功后使用重定向到首页。
- 往config/MyMvcConfig的视图映射addViewControllers方法中添加
registry.addViewController("/main.html").setViewName("dashboard");
- controller/LoginController的login方法里,把
return "dashboard";
改为
//登录成功跳转到dashboard.html页面,防止表单重复提交,重定向到主页
//加上redirect:/即为重定向
return "redirect:/main.html";
重新启动项目,登录后进入主页,而且由于视图映射,重定向过来后,样式也生效了。
但是复制此时的网站地址,用其他浏览器打开,发现可以直接到后台主页,只要就失去了登录的意义了(问题二)。(这里使用了火狐浏览器)
解决办法是使用拦截器进行登录检查。
- 在LoginController中的login添加一个传参HttpSession,并把用户名保存到里面。
...
public String login(... , HttpSession httpSession){
if(!StringUtils.isEmpty(username) && "123456".equals(password)){
//把用户名保存到session域中
httpSession.setAttribute("loginUser", username);
...
- 在main/java/com/angenin/springboot/component下新建LoginHandlerInterceptor.java拦截器。
/**
* 登录检查
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
@Override //目标方法执行之前
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");
if(loginUser == null){
//未登录,返回登录页面
//保存错误信息
request.setAttribute("msg", "没有权限,请先登录");
//转发到登录页面
request.getRequestDispatcher("/index.html").forward(request, response);
return false;
}else{
//已登录,放行请求
return true;
}
}
}
- 然后在MyMvcConfig配置类重写addInterceptors方法,在方法里注册拦截器。
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册拦截器,addPathPatterns添加拦截路径(/**表示拦截所有请求),excludePathPatterns排除拦截路径
registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")
//排除登录页面的请求、提交表单的路径、静态资源路径
.excludePathPatterns("/index.html", "/", "/user/login", "/webjars/**", "/asserts/**");
}
清理缓存,直接访问http://localhost:8080/crud/main.html
在dashboard.html中把Company name
换成[[${session.loginUser}]]
并把剩下的页面静态资源引入的路径改为使用thymeleaf方式引入。
实现在html标签里加入xmlns:th="http://www.thymeleaf.org"
。
4. CRUD-员工列表
RestfulCRUD:CRUD满足Rest风格
URI:/资源名称/资源标识 HTTP请求方式区分对资源CRUD的操作。
普通CRUD(uri来区分操作) | RestfulCRUD | |
---|---|---|
查询 | getEmp | emp(get请求) |
添加 | addEmp?xxx | emp(post请求) |
修改 | updateEmp?id=xx&xxx=xx | emp/{id}(put请求) |
删除 | deleteEmp?id=xx | emp/{id}(delete请求) |
Rest的请求架构
请求URI | 请求方式 | |
---|---|---|
查询所有员工 | emps | get |
查询单个员工 | emp/{id} | get |
来到添加页面 | emp | get |
添加员工 | emp | post |
来到修改页面(查出员工信息回显) | emp | get |
修改员工 | emp | put |
删除员工 | emp/{id} | delete |
1. CRUD-员工列表
把dashboard.html页面的Customers改为员工管理,并修改a标签的请求路径。
<li class="nav-item">
<a class="nav-link" th:href="@{/emps}">
...
</svg>
员工管理
</a>
</li>
在resources/templates里新建一个emp目录,用于存放emp相关的页面,把list.html放入。
在controller里添加一个EmployeeController。
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
//查询所有员工返回列表页面
@GetMapping("/emps")
public String list(Model model){
//获取所有员工数据
Collection<Employee> all = employeeDao.getAll();
//把获取的数据放到请求域中
model.addAttribute("emps", all);
//thymeleaf默认会进行拼串
//classpath:/templates/xxx 加上后缀 .html
return "emp/list";
}
}
运行项目,登录进入主页后,点击员工管理,跳转到list页面。
页面中的导航条都是相同的,所以我们需要使用thymeleaf对其进行抽取。
使用thymeleaf的抽取公共片段
-
抽取公共头部元素
在dashboard.html页面中的body下nav标签里的内容就是头部元素,我们在nav标签内加入th:fragment="topbar"
元素。
删除list页面中body下nav的内容(即头部元素),然后引入抽取的topbar头部。<!-- 引入抽取的topbar(模板名::片段名) --> <!-- 模板名:会使用thymeleaf的前后缀配置规则进行解析 --> <div th:insert="~{dashboard::topbar}"></div>
启动项目,然后登录跳转到list页面
但是在浏览器查看页面代码发现,dashboard页面nav是在body下,而list页面nav是在body的div下,list页面多了个div,为了防止以后出现问题,可以用thymeleaf的其他引入方式。
dashboard页面:
list页面:
thymeleaf的三种引入功能片段属性:- th:insert :将公共片段整个插入到声明引入的元素中
- th:replace :将声明引入的元素替换为公共片段
- th:include :将被引入的片段的内容包含进这个标签中
三种引入属性的区别
<!--抽取片段--> <footer th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </footer> <!--三种引入方式--> <!--1--> <div th:insert="footer :: copy"></div> <!--2--> <div th:replace="footer :: copy"></div> <!--3--> <div th:include="footer :: copy"></div> <!--效果--> <!--1--> <div> <footer> © 2011 The Good Thymes Virtual Grocery </footer> </div> <!--2--> <footer> © 2011 The Good Thymes Virtual Grocery </footer> <!--3--> <div> © 2011 The Good Thymes Virtual Grocery </div>
所以我们应该使用th:replace标签来引入,把list页面的th:inset改成th:replace。
而且使用上面三种方式,可以省略~ {},直接写成th:replace="dashboard::topbar"
。(如果是行内写法[[]],[()]就一定要加上~{}) -
抽取测边导航条
侧边导航条在头部导航条nav下的div里的div下的nav。
这里我们用另一种方法抽取,给侧边导航条nav加上一个id="sidebar"
属性,这里使用第二种方式,用id来引用。
然后把list页面的侧边栏的代码替换成:<!--引入侧边栏(模板名::选择器) --> <div th:replace="dashboard::#sidebar"></div>
-
动态显示高亮
但是,在list页面时,侧边栏的员工管理没高亮,我们需要让其动态的显示。
查看代码发现负责显示高亮的是a标签class里的active。
所以我们要在引入片段的同时引入参数,让其动态的添加和删除active。
为了层次清晰,我们要在templates目录下再新建一个commons目录,在里面新建一个bar.html页面,然后把顶部栏,侧边栏写在页面里。
把dashboard.html页面的顶部栏和侧边栏的代码放到bar.html页面里,dashboard.html使用引入标签引入顶部栏和侧边栏,list页面的引入也需要改。<!--dashboard、list都用这两个--> <div th:replace="commons/bar::topbar"></div> <div th:replace="commons/bar::#sidebar"></div>
修改bar.html页面的侧边栏Dashboard的a标签,让其能正常跳转,并且加上th:class进行三元判断,如果是首页,a标签的class属性就加上active。
<a class="nav-link active" th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}" th:href="@{/main.html}">
同样,员工管理的a标签也加上th:class。
<a class="nav-link active" th:class="${activeUri=='emps'?'nav-link active':'nav-link'}" th:href="@{/emps}">
然后在侧边栏引入的标签里加入传参activeUri
<!--dashboard页面--> <div th:replace="commons/bar::#sidebar(activeUri='main.html')"></div> <!--list页面--> <div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
重新运行,实现随着页面的切换动态高亮。
显示员工数据
修改list页面的表格
<h2>
<button class="btn btn-sm btn-success">添加员工</button>
</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>#</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.id}"></td>
<!--行内写法-->
<td>[[${emp.lastName}]]</td>
<td th:text="${emp.email}"></td>
<!--0为女,1为男-->
<td th:text="${emp.gender}==0?'女':'男'"></td>
<!--取出department对象的departmentName属性-->
<td th:text="${emp.department.departmentName}"></td>
<!--使用dates工具对象格式化日期格式-->
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd')}"></td>
<td>
<button class="btn btn-sm btn-primary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</td>
</tr>
</tbody>
</table>
运行结果
5. CRUD-添加员工
把添加按钮button改为a标签
里并添加th:href="@{/emp}"
属性。
在resources/templates/emp目录里,复制list.html,来新建add.html添加页面。并删除add页面main标签里所有的代码。
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<!--添加使用post请求-->
<form th:action="@{/emp}" method="post">
<div class="form-group">
<label>LastName</label>
<input name="lastName" type="text" class="form-control" placeholder="angenin">
</div>
<div class="form-group">
<label>Email</label>
<input name="email" type="email" class="form-control" placeholder="angenin@qq.com">
</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">
<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">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select class="form-control" name="department.id">
<!--提交的是部门的id-->
<option th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}"></option>
</select>
</div>
<div class="form-group">
<label>Birth</label>
<input name="birth" type="text" class="form-control" placeholder="2020-5-20">
</div>
<button type="submit" class="btn btn-primary">添加</button>
</form>
</main>
在EmployeeController添加
@Autowired
DepartmentDao departmentDao;
//来到员工添加页面
@GetMapping("/emp")
public String toAddPage(Model model){
//查出所有的部门并添加到model
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts", departments);
//来到添加页面
return "emp/add";
}
//添加员工
//SpringMVC自动将请求参数和入参对象的属性进行一一绑定(请求参数名必须和javaBean入参的对象里面的属性名相同)
@PostMapping("/emp")
public String addEmp(Employee employee){
//打印出保存的员工数据(
System.out.println("保存的员工信息:" + employee);
//模拟保存员工数据
employeeDao.save(employee);
//redirect:表示重定向到一个地址
//forward:表示转发到一个地址
// /:代表当前项目路径
//转发到员工列表页面
return "redirect:/emps";
}
运行项目,点击添加按钮来到添加页面。
点击添加后重定向到list页面。
这里有个问题就是,如果提交的生日数据格式不是用/分隔的,而是用-或.来分隔,那么会出现400错误。
解决办法:日期格式化,让SpringMVC将页面提交的值转换为指定的类型。
在application.properties配置文件中用spring.mvc.format.date=yyyy-MM-dd
修改日期格式(只这样做只能有一种格式,可以使用第三方插件)。
6. CRUD-修改员工
修改list页面的编辑按钮。
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
在EmployeeController添加方法,获取被修改员工的信息。
//来到修改页面,查出当前员工信息,然后在页面回显数据
@GetMapping("/emp/{id}") //@PathVariable:路径变量
public String toEditPage(@PathVariable("id") Integer id, Model model){
Employee employee = employeeDao.get(id);
model.addAttribute("emp", employee);
//查出所有的部门并添加到model
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts", departments);
//跳转到修改页面(add是一个修改添加二合一的页面)
return "emp/add";
}
修改add.html页面,修改员工页面时,复用add页面,使其当点击员工的修改按钮时回显被点击员工的数据,修改每一个input标签,使其能回显数据。
<!--回显用户名-->
<input name="lastName" type="text" class="form-control" placeholder="angenin" th:value="${emp.lastName}">
<!--回显邮箱-->
<input name="email" type="email" class="form-control" placeholder="angenin@qq.com" th:value="${emp.email}">
<!--回显性别-->
<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp.gender==1}">
<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp.gender==0}">
<!--回显部门-->
<option th:selected="${dept.id==emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}"></option>
<!--回显生日-->
<input name="birth" type="text" class="form-control" placeholder="2020-5-20" th:value="${#dates.format(emp.birth, 'yyyy-MM-dd')}">
成功回显数据:
此时,点击添加员工按钮会出现Property or field 'lastName' cannot be found on null
错误,这是因为二合一后,跳转到add页面后,add页面会去使用th:value去取值,而点击添加按钮,Controller没往model中放员工信息,所以报错。
解决办法是add页面在回显用户名时,th:value取值进行判断来区分是用于添加员工还是修改员工。
<!--修改用户名的input标签,th:value进行判断,如果emp不等于空才赋值,修改之前取值的那部分,都要加上${emp!=null}?-->
th:value="${emp!=null}?${emp.lastName}"
<!--button也需要进行判断-->
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
修改表单提交的方式,修改发送put请求,添加发送post请求。
这里因为表单只支持get和post请求,不然用th:method更简单。
在add页面表单里添加一个隐藏域:
<!--需要区分是修改还是添加-->
<form th:action="@{/emp}" method="post">
<!--发送put请求修改员工数据-->
<!--
1. SpringMVC中配置hiddenHttpMethodFilter(SpringBoot自动配置好了)
2. 在页面创建一个post表单
3. 创建一个input项hidden,name="_method",value="put"只要在emp存在才显示
-->
<!--SpringBoot中有过滤器针对name为_method的请求,如果有,过滤器就会把post请求修改成value值的请求-->
<input type="hidden" name="_method" value="put" th:i="${emp!=null}">
<!--提交时加上员工id-->
<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
在EmployeeController添加:
//员工修改,需要提交员工id
@PutMapping("/emp")
public String updateEmployee(Employee employee){
System.out.println("修改的员工数据:" + employee);
//保存修改的数据
employeeDao.save(employee);
return "redirect:/emps";
}
在application.properties配置文件中添加spring.mvc.hiddenmethod.filter.enabled=true
。(如果没加会进入接收post请求的方法,即新增员工的方法)
启动项目,修改员工信息
7. CRUD-删除员工
因为Rest风格,删除时需要发起delete请求,所以要在删除按钮外包一层form标签,用form标签替换掉原本的button标签。
<form th:action="@{/emp/}+${emp.id}" method="post">
<input type="hidden" name="_method" value="delete">
<button type="submit" class="btn btn-sm btn-danger">删除</button>
</form>
在EmployeeController添加:
//员工删除
@DeleteMapping("/emp/{id}")
public String deleteEmployee(@PathVariable("id") Integer id){
//删除员工
employeeDao.delete(id);
//重定向会list页面
return "redirect:/emps";
}
运行项目,实现员工删除
但是为每个删除按钮添加表单不好,而且样式也改变了。
解决办法是我们可以把表单放到main标签外,然后为删除按钮条件点击事件,实现一个表单多条数据共同使用。
<!--th:attr是thymeleaf中的设置自定义属性的属性-->
<button th:attr="del_uri=@{/emp/}+${emp.id}" type="submit" class="btn btn-sm btn-danger deleteBtn">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</main>
<form id="deleteEmpForm" method="post">
<input type="hidden" name="_method" value="delete">
</form>
然后在页面里加上删除按钮的点击事件
<!-- 删除按钮的单击事件 -->
<script>
$(".deleteBtn").click(function () {
//$(this).attr("del_uri")获取表单提交地址
//删除当前员工
$("#deleteEmpForm").attr("action", $(this).attr("del_uri")).submit();
return false;
});
</script>
重新运行项目,删除成功
8. 定制错误页面
在templates目录下新建一个error目录,并把404.html页面放入,当系统发生错误时,SpringBoot就会在error自动读取错误状态码相对应的我们设置的错误页面。
把MyMvcConfig中的addInterceptors先注释掉,然后运行项目,在浏览器输入一个不存在的地址,就会跳到我们制定的错误页面。
我们也可以在error目录下放置4xx.html和5xx.html页面,当发送错误时,SpringBoot会首先在静态资源目录下找error目录,不知道就用SpringBoot默认的错误页面,如果找的了就会在error目录里找错误状态码相对应的错误页面(如404错误就会找404.html),默认没有相对应的,就找开头相同的错误页面(如404错误,找不到404.html就找4xx.html),如果也没有,就会报错,而不会使用默认的错误页面。
例如,我们在error复制404.html,新建一个4xx.html,把4xx页面里的404改为4xx,然后在添加员工那,在生日栏里输入错误格式的数据,点击新建就会出现400错误,但是error目录里没有400页面,就会找对应的4xx页面。
如果我们把error的4开头的错误页面都删除了,当出现404或400错误时,因为我们已经创建了error目录,所以SpringBoot就不会使用默认的错误页面,但是我们error目录里又没有相对应的错误页面,那么程序就会报错。
页面能获取的错误信息:
- timestamp:时间戳
- status:状态码
- error:错误提示
- exception:异常对象
- message:异常消息
- errors:JSR303数据校验的所有错误
在4xx页面中添加:
<h1>status: [[${status}]]</h1>
<h2>timestamp: [[${timestamp}]]</h2>