跟着狂神的SpringBoot网课做了一个小demo,功能有:登录、注销、拦截器、Restful CRUD、错误页面。不过完善了下put、delete的请求方式。
Demo
准备工作
使用Lombok插件
pom.xml
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
<!--thymeleaf依赖-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
pojo层
package com.rainszj.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
// 员工表
@Data
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
/**
* 0:女 1: 男
*/
private Integer gender;
private Date birth;
private Department department;
public Employee(Integer id, String lastName, String email, Integer gender, Department department) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
// 日期自动设置
this.birth = new Date();
this.department = department;
}
}
package com.rainszj.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
// 部门表
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
dao层
此demo没有使用数据库,使用Map模拟数据库
package com.rainszj.dao;
import com.rainszj.pojo.Department;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class DepartmentDao {
// 模拟数据库中的数据
private static Map<Integer, Department> departments = null;
static {
// 创建一个部门表
departments = new HashMap<Integer, Department>();
departments.put(101, new Department(101, "研发部"));
departments.put(102, new Department(102, "运营部"));
departments.put(103, new Department(103, "项目部"));
departments.put(104, new Department(104, "运维部"));
departments.put(105, new Department(105, "测试部"));
}
// 获取所有的部门
public Collection<Department> getDeparments() {
return departments.values();
}
// 根据 id 获取部门
public Department getDeparmentById(Integer id) {
return departments.get(id);
}
}
package com.rainszj.dao;
import com.rainszj.pojo.Department;
import com.rainszj.pojo.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@Repository
public class EmployeeDao {
@Autowired
private DepartmentDao departmentDao;
// 模拟数据库中的数据
private static Map<Integer, Employee> employees = null;
static {
// 创建一个员工表
employees = new HashMap<Integer, Employee>();
employees.put(1001, new Employee(1001, "白展堂", "1315480331@qq.com", 1, new Department(101, "研发部")));
employees.put(1002, new Employee(1002, "佟湘玉", "1315480331@qq.com", 0, new Department(102, "运营部")));
employees.put(1003, new Employee(1003, "吕轻侯", "1315480331@qq.com", 1, new Department(103, "项目部")));
employees.put(1004, new Employee(1004, "李大嘴", "1315480331@qq.com", 1, new Department(104, "运维部")));
employees.put(1005, new Employee(1005, "郭芙蓉", "1315480331@qq.com", 0, new Department(105, "测试部")));
}
// 主键自增
private static Integer initId = 1006;
/**
* 添加一个员工
*
* @param employee
*/
public void addEmployee(Employee employee) {
if (employee.getId() == null) {
employee.setId(initId++);
}
// ???
employee.setDepartment(departmentDao.getDeparmentById(employee.getDepartment().getId()));
employees.put(employee.getId(), employee);
}
/**
* 查询全部员工信息
*/
public Collection<Employee> getAllEmployee() {
return employees.values();
}
/**
* 根据 ID 查询员工
*/
public Employee getEmployeeById(Integer id) {
return employees.get(id);
}
/**
* 根据 ID删除员工
*/
public void removeEmployeeById(Integer id) {
employees.remove(id);
}
}
-
css、js等放在 static 文件夹下
-
html 放在 templates 文件夹下
登录+拦截器
templates下的页面只能通过Controller跳转实现,而META-INF/resources、resources、static、public下的页面是能直接被外界访问的
我们在登录的表单提交地址上写一个Controller:/user/login
<form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
//这里面的所有表单标签都需要加上一个name属性
</form>
去写对应的Controller
package com.rainszj.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.annotation.SessionScope;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
/**
* 登录
*/
@RequestMapping("/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Model model, HttpSession session) {
if (!StringUtils.isEmpty(username) && "123".equals(password)) {
// 登录成功
session.setAttribute("loginUser", username);
// 这里使用重定向,防止表单重复提交
return "redirect:/main.html";
} else {
// 登录失败
model.addAttribute("msg", "用户名或密码错误");
return "index";
}
}
}
这里说一下为什么不能使用转发,而是用重定向:
因为如果是转发的话,提交完表单(即使表单提交方式为post)后,再经由Controller转发,也会将表单中的参数携带过去,而此时的URL地址又不会发生变化,如果用户此时刷新的话,会造成表单的重复提交!
如图:
而重定向不同,重定向地址栏会发生变化。当表单提交到Controller后,经由Controller重定向后,表单的参数不会显示再地址栏,因为重定向是两次请求,可以防止表单的重复提交!
而重定向不同,重定向地址栏会发生变化,经由Controller重定向后,不会将表单的参数携带过去,可以防止表单的重复提交!
其实问题的本质还是转发和重定向的区别。
设置重定向后的视图:在MyMvcConfig
中addViewControllers()
添加
// 跳转到 dashboard
registry.addViewController("/main.html").setViewName("dashboard");
页面存在缓存,所以我们需要禁用模板引擎的缓存
#禁用模板缓存
spring.thymeleaf.cache=false
模板引擎修改后,想要实时生效!页面修改完毕后,IDEA小技巧 : Ctrl + F9 重新编译!
OK ,测试登录成功!如果模板出现500错误,参考处理连接:
https://blog.csdn.net/fengzyf/article/details/83341479
下面该是拦截器了!
如果不设置拦截器的话,用户可以不用登录进行身份验证,直接跳到后台!
package com.rainszj.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义拦截器
*/
public class LoginHandlerInterceptor implements HandlerInterceptor {
/**
* return true:放行,false:不放行
*/
@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;
}
}
}
然后将拦截器注册到我们的SpringMVC配置类(MyMvcConfig)当中!
/**
* 添加拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册拦截器,及拦截请求和要剔除哪些请求!
//我们还需要过滤静态资源文件,否则样式显示不出来
registry.addInterceptor(new LoginHandlerInterceptor())
// 对所有请求的所有路径进去拦截
.addPathPatterns("/**")
// 放行哪些路径
.excludePathPatterns("/index.html", "/", "/user/login", "/css/*", "/js/*", "/img/*");
}
我们可以在后台的导航栏显示用户的登录信息
th:text="${session.loginUser}"
或者
[[${session.loginUser}]]
下面进行正式的增删改查,也就是Restful 风格的CRUD
Restful CRUD
Restful架构
概念:REST指的是一组架构约束条件和原则,如果一个架构符合REST的约束条件和原则,就称之为RESTful架构。
restful不是一个专门的技术,他是一个规范。规范就是写写代码给类命名,给属性命名,创建包结构 等等都最好按一定的规则来进行。这样的话以后代码看起来更规范,更容易理解。好比以前写增删改查请求的路径。
优点:
-
可以方便的实现程序的前后台代码的分离
-
resutful要求每个请求都是无状态的
-
可以使请求的路径更规范
要求 : 我们需要使用 Restful风格实现我们的crud操作!
普通CRUD和Restful风格的CRUD对别,明显的区别在URL地址栏上!
看看一些具体的要求,就是我们小实验的架构;
我们先来实现第一个功能:
查询所有员工
给 a 连接添加请求
<a class="nav-link" href="#" th:href="@{/emps}">员工管理</a>
编写处理请求的Controller
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
//查询所有员工,返回列表页面
@GetMapping("/emps")
public String list(Model model){
Collection<Employee> employees = employeeDao.getAll();
//将结果放在请求中
model.addAttribute("emps",employees);
return "emp/list";
}
}
ok,跳转没有问题!我们只需将数据渲染进去即可。
但是发现一个问题,侧边栏和顶部都相同,我们是不是应该将它抽取出来呢?
Thymeleaf 公共页面元素抽取
1.抽取公共片段 th:fragment
定义模板名
2.引入公共片段 th:insert
插入模板名
我们来抽取一下,使用list列表做演示!我们要抽取头部导航栏和侧边栏
为了重用更清晰,我们建立一个commons
文件夹,专门存放公共页面;
然后我们在list页面中去引入,可以删掉原来的
模板名:会使用thymeleaf的前后缀配置规则进行解析
引入方式: ~{模板::标签名}
除了使用replace插入,还可以使用insert替换,或者include包含,三种方式会有一些小区别,可以见名知义;
我们使用replace替换,可以解决div多余的问题,可以查看thymeleaf的文档学习
还有一个问题:侧边栏激活的问题,它总是激活第一个;按理来说,这应该是动态的才对!
解决:
去修改对应的请求:
现在我们来遍历我们的员工信息!顺便美化一些页面,增加添加,修改,删除的按钮
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><a class="btn btn-sm btn-success" th:href="@{/emp}">添加员工</a></h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>性别</th>
<th>生日</th>
<th>部门</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp:${emps}">
<td th:text="${emp.id}"></td>
<td th:text="${emp.lastName}"></td>
<td th:text="${emp.email}"></td>
<td th:text="${emp.gender == 0 ? '女' : '男'}"></td>
<!--使用时间格式化工具,查阅thymeleaf文档得知-->
<td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm:ss')}"></td>
<td th:text="${emp.department.departmentName}"></td>
<td>
<a class="btn btn-sm btn-primary">修改</a>
<a class="btn btn-sm btn-danger">删除</a>
</td>
</tr>
</tbody>
</table>
</div>
</main>
添加员工
1. 将添加员工信息改为超链接
<!--添加员工按钮-->
<a class="btn btn-sm btn-success" href="emp" th:href="@{/emp}">添加员工</a>
2. 编写对应的controller
/**
* 跳转到添加员工页面
*/
@GetMapping("/emp")
public String toAddPage(Model model) {
Collection<Department> depts = departmentDao.getDeparments();
model.addAttribute("depts", depts);
return "emp/add";
}
3. 修改前端页面
<form class="form-horizontal" method="post">
<div class="form-group">
<label for="lastName" class="col-sm-2 control-label">姓名</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="lastName" name="lastName" placeholder="姓名">
</div>
</div>
<div class="form-group">
<label for="email" class="col-sm-2 control-label">邮箱</label>
<div class="col-sm-10">
<input type="text" name="email" class="form-control" id="email" placeholder="1315480331@qq.com">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">性别</label> <br/>
<div class="col-sm-8 control-label">
<input type="radio" name="gender" value="1">
<label class="form-check-label">男</label>
<input type="radio" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label for="birth" class="col-sm-2 control-label">生日</label>
<div class="col-sm-10">
<input type="text" name="birth" class="form-control" id="birth" placeholder="2020-1-1">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">部门</label>
<div class="col-sm-10">
<!--由于我们 Controller 接受的是一个 Employee对象,这里传递的 department.id -->
<select class="form-control" name="department.id">
<option value="" selected disabled>--请选择--</option>
<option th:each="dept:${depts}" th:value="${dept.id}" th:text="${dept.departmentName}"></option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">提交</button>
</div>
</div>
</form>
先测试是否能够跳转和页面的展示情况
1. 设置表单的提交地址
<form class="form-horizontal" th:action="@{/emp}" method="post">
2. 编写对应的Controller
/**
* 添加员工页面
* 接收前端传递的参数,自动封装成为对象
* [要求前端传递的参数名,和属性名一致]
*/
@PostMapping("/emp")
public String addEmp(Employee employee) {
System.out.println("[addEmp]=>" + employee);
employeeDao.addEmployee(employee);
return "redirect:/emps";
}
原理探究 : ThymeleafViewResolver
@Override
protected View createView(final String viewName, final Locale locale) throws Exception {
// First possible call to check "viewNames": before processing redirects and forwards
if (!this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
vrlogger.trace("[THYMELEAF] View "{}" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
return null;
}
// Process redirects (HTTP redirects)
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
vrlogger.trace("[THYMELEAF] View "{}" is a redirect, and will not be handled directly by ThymeleafViewResolver.", viewName);
final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length(), viewName.length());
final RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
}
// Process forwards (to JSP resources)
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
// The "forward:" prefix will actually create a Servlet/JSP view, and that's precisely its aim per the Spring
// documentation. See http://docs.spring.io/spring-framework/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-redirecting-forward-prefix
vrlogger.trace("[THYMELEAF] View "{}" is a forward, and will not be handled directly by ThymeleafViewResolver.", viewName);
final String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length(), viewName.length());
return new InternalResourceView(forwardUrl);
}
// Second possible call to check "viewNames": after processing redirects and forwards
if (this.alwaysProcessRedirectAndForward && !canHandle(viewName, locale)) {
vrlogger.trace("[THYMELEAF] View "{}" cannot be handled by ThymeleafViewResolver. Passing on to the next resolver in the chain.", viewName);
return null;
}
vrlogger.trace("[THYMELEAF] View {} will be handled by ThymeleafViewResolver and a " +
"{} instance will be created for it", viewName, getViewClass().getSimpleName());
return loadView(viewName, locale);
}
从源码中得知:如果是转发和重定向的话,不会走视图解析器,会走到我们的请求中!
在使用SpringBoot的时候,前端填写数据,注意时间格式问题!
SpringMVC会将页面提交的值转换为指定的类型,默认日期是按照 / 的方式提交 ; 比如将2019/01/01 转换为一个date对象。
那思考一个问题?我们能不能修改这个默认的格式呢?
我们去看WebMvcAutoConfiguration
;找到一个日期格式化的方法 mvcConversionService()
,我们可以看一下
@Bean
@Override
public FormattingConversionService mvcConversionService() {
WebConversionService conversionService = new WebConversionService(this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
点进:getDateFormat()
,这表示我们可以在配置文件中修改它默认的日期格式!
application.properties
# 自定义日期格式化
spring.mvc.date-format=yyyy-MM-dd
现在它可以支持 - 格式了,但是又不能支持 / 格式了。
员工修改功能
我们要实现员工修改功能,需要实现两步;
1. 点击修改按钮,去到编辑页面,我们可以直接使用添加员工的页面实现
2. 显示原数据,修改完毕后跳回列表页面!
我们去实现一下:首先修改跳转链接的位置;
<a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a>
编写对应的Controller
/**
* 跳转到修改页面
*/
@GetMapping("/emp/{id}")
public String toUpdatePage(@PathVariable("id") Integer id, Model model) {
Employee employee = employeeDao.getEmployeeById(id);
model.addAttribute("emp", employee);
Collection<Department> depts = departmentDao.getDeparments();
model.addAttribute("depts", depts);
return "emp/update";
}
我们需要在这里将add页面复制一份,改为update页面;需要修改页面,将我们后台查询数据回显
<form class="form-horizontal" method="post">
<!--隐藏域, 提交要修改的 id-->
<input type="hidden" name="id" th:value="${emp.id}">
<div class="form-group">
<label for="lastName" class="col-sm-2 control-label">姓名</label>
<div class="col-sm-10">
<input type="text" th:value="${emp.lastName}" class="form-control" id="lastName" name="lastName"
placeholder="姓名">
</div>
</div>
<div class="form-group">
<label for="email" class="col-sm-2 control-label">邮箱</label>
<div class="col-sm-10">
<input type="text" th:value="${emp.email}" name="email" class="form-control" id="email"
placeholder="1315480331@qq.com">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">性别</label> <br/>
<div class="col-sm-8 control-label">
<input type="radio" th:checked="${emp.gender == 1}" name="gender" value="1">
<label class="form-check-label">男</label>
<input type="radio" th:checked="${emp.gender == 0}" name="gender" value="0">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label for="birth" class="col-sm-2 control-label">生日</label>
<div class="col-sm-10">
<input type="text" th:value="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}" name="birth" class="form-control" id="birth" placeholder="2020-1-1">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">部门</label>
<div class="col-sm-10">
<!--由于我们 Controller 接受的是一个 Employee对象,这里传递的 department.id -->
<select class="form-control" name="department.id">
<option value="" selected disabled>--请选择--</option>
<option th:each="dept:${depts}" th:value="${dept.id}" th:text="${dept.departmentName}" th:selected="${dept.id == emp.department.id}"></option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">修改</button>
</div>
</div>
</form>
数据回显OK,我们继续完成数据修改问题!
修改表单提交的地址:
<form class="form-horizontal" th:action="@{/emp}" method="post">
编写对应的Controller
/**
* 修改员工
*/
@PutMapping("/emp")
public String updateEmp(Employee employee) {
employeeDao.addEmployee(employee);
return "redirect:/emps";
}
这里我们需要注意一点:修改时,使用隐藏域提交该员工的id
<input name="id" type="hidden" class="form-control" th:value="${emp.id}">
重启,修改信息测试OK!
在做Restful的删除操作之前,我们先来看一个类!,这个类可以将HTTP方法进行转换
HiddenHttpMethodFilter
当作删除操作的时候,不再通过超链接提交,通过form
表单提交,为了将请求方法从post
转成 DELETE
,否则可能会走到修改请求或者是删除请求,具体走那个看这两个请求定义的先后顺序了!
spring
针对这个问题为我们一个解决访问,我们只需要在web.xml
中配置一个转换请求方式的过滤器
但在SpringBoot
中已经我们自动装配了!
在mvc
的自动配置类中,有个hiddenHttpMethodFilter
类
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = true)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
点进OrderedHiddenHttpMethodFilter
类,发现他继承了HiddenHttpMethodFilter
类
/**
* 由于浏览器目前仅支持GET和POST,无法使用其他方法发送请求。
* 但该过滤器可以将发布的方法参数装换为相应的HTTP方法。
* 例如由Prototype库(JS的一个库)提供的,使用带有附加隐藏表单字段(_method),的普通POST方法来传递"真实" HTTP方法。
* 该过滤器读取该参数,隐藏域的name="_method",value仅允许使用delete、put、patch的HTTP方法
* 注意:如果是多部分POST请求,此过滤器需要在多部分处理之后运行,
* 因为它本身需要检查POST主体参数。
* 因此,通常,放置一个Spring的MultipartFilter}在您的web.xml过滤器链中的此HiddenHttpMethodFilter之前。
*/
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
// 允许的方法
private static final List<String> ALLOWED_METHODS =
Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
/** 默认的方法的参数 */
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = DEFAULT_METHOD_PARAM;
/**
* Set the parameter name to look for HTTP methods.
* @see #DEFAULT_METHOD_PARAM
*/
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
// 实际上将post方法装换成相应HTTP方法的操作
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
// 表单的method必须为post
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
// 获取_method的value
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
// 转换成大写
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
// HTTP方法转换完成
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
// 放行
filterChain.doFilter(requestToUse, response);
}
/**
* Http方法请求包装
*/
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
@Override
public String getMethod() {
return this.method;
}
}
}
删除员工
解决思路:
通过JS获取删除员工的a
标签的href
的值,将其赋值给form表单的action,form表单中通过隐藏域提交请求的方法,由JS来提交表单。
我们还需要保证两件事:
-
form的请求必须是POST
-
form中必须带一个_method的参数,代表要把POST请求转换为什么请求。
Come on!
编写删除的a链接
<a class="btn btn-sm btn-danger" th:href="@{/emp/} + ${emp.id}" id="deleteEmp">删除</a>
声明一个form,用来提交delete请求:
<form action="" id="toDelete" method="post">
<input type="hidden" name="_method" value="delete">
</form>
需要给删除超链接增加单击事件,而且还要阻止原来的超链接事件:
<script>
$(function () {
// 当删除按钮点击时
$("[id='deleteEmp']").click(
function () {
// 获取删除的超链接
let href = $(this).attr("href");
// 获取form表单
let form = $("#toDelete");
// 把form的action属性设置成href
form.attr("action", href);
// 提交表单
form.submit();
// 阻止原来的单击事件
return false;
}
);
});
</script>
编写Controller
/**
* 删除用户
*/
@DeleteMapping("/emp/{id}")
public String deleteEmp(@PathVariable("id") Integer id) {
employeeDao.removeEmployeeById(id);
return "redirect:/emps";
}
SpringBoot默认是开启的,如果我们关闭,就会405报错!
spring.mvc.hiddenmethod.filter.enabled=false
做完删除操作之后,想起来修改操作put也应该这样做!哈哈
只需要在 update.html 的 form表单中加上隐藏域即可
<input type="hidden" name="_method" value="put">
定制错误页面
我们只需要在模板目录下添加一个error文件夹,文件夹中存放我们相应的错误页面,比如404.html 或者 4xx.html 等等,SpringBoot就会帮我们自动使用了!
注销功能
a链接
<a class="nav-link" href="#" th:href="@{/user/loginOut}">Sign out</a>
对应的Controller
/**
* 注销
*/
@GetMapping("/user/logout")
public String logout(HttpSession session) {
// 将 Session 删除
session.invalidate();
return "redirect:/index.html";
}
学到这里,SpringBoot的基本开发就以及没有问题了,我们后面去学习一下SpringBoot如何操作数据库以及配置Mybatis;