五、功能实现
1、登陆功能
简单实现
前提描述
实现输入用户名及密码来进行跳转页面,若是用户名不为空及密码正确进行跳转页面,否则回到初始页面并显示错误信息!!!
核心部分
html中使用到thymleaf中的几个表达式
表单url:th:action="@{/user/login}"
接收请求域中参数:th:text="${msg}"
使用其自带工具类strings(不为空即显示):th:if="#!strings.isEmpty(msg)"
LoginController:
@Controller public class LoginController { @RequestMapping("/user/login") public String hello(@RequestParam("email")String email, @RequestParam("password")String password, Model model) { //判断是否接收到用户名及密码是否正确 if(!StringUtils.isEmpty(email) && "123456".equals(password)){ return "redirect:/main.html"; }else{ model.addAttribute("msg","用户名或密码有错误!"); return "index"; } } }
注意唯一要说明的是用户名不为空以及密码正确进行的是重定向,改main.html是不存在的,通过自定义配置类中设置跳转路径进行跳转。通过这种形式可以给出一种感觉提交表单是跳转到main.html页面
配置类:
@Configuration public class MyMvcConfig implements WebMvcConfigurer{ @Override public void addViewControllers(ViewControllerRegistry registry) { ... //以这种形式来进行跳转 registry.addViewController("/main.html").setViewName("dashboard"); } //自定义的国际化组件就生效了!自动注入到spring容器中 @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); } }
效果:
成功跳转情况:
失败的跳转情况:
添加拦截器
拦截器添加的作用是什么呢?
我们在上面进行简单实现登录,通过简单校验中账号密码,如通过跳转路径/main.html则会直接跳转到/dashboard.html了。中间会经过一个main.html作为跳板(并不是一个真实页面)。此时就有个问题,若是直接访问main.html不就直接跳转过去到登录后的界面了嘛。
解决措施:通过添加拦截器进行拦截,若是直接访问这个main.html,则要进行session校验,若存在直接放行,不存在拦截
首先做一个前提条件:在登陆成功跳转之时创建session并传给浏览器。
//只需要在对应控制器中添加HttpSession参数,并执行方法生成session即可 session.setAttribute("loginUser",email);
自定义拦截器类:
public class MyInterceptor implements HandlerInterceptor { //访问指定请求前进行拦截判断,return true放行,return false不放行 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser = request.getSession().getAttribute("loginUser"); if(loginUser!=null){//若是有该session,通过 return true; }else{ request.setAttribute("msg","没有权限,无法登录"); request.getRequestDispatcher("/index.html").forward(request,response); return false; } } }
自定义拦截器生成之后我们则回到自定义mvc配置类中重写方法:
@Configuration public class MyMvcConfig implements WebMvcConfigurer{ ... @Override public void addInterceptors(InterceptorRegistry registry) { //使用registry来添加刚刚自定义的拦截器,并且添加拦截路径(addPathPatterns),不拦截的资源(excludePathPatterns) registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**") .excludePathPatterns("/index.html","/","/user/login","/css/**","/image/**","/js/**"); } }
由于上面登陆采用跳板形式,所以仅仅不拦截/user/login即可,拦截main.html时进行拦截校验!!!
效果:
2、展示员工列表
首先说明这部分需要完成的内容
将头部部分、侧边栏部分进行组件化,单独抽取一个页面(通过使用th:flagment="①"以及th:replace="~(组件页面前缀::①)"来获取组件)
跳转页面侧边栏对应部分高亮(通过在th:replace中添加请求参数,在组件部分中使用三元运算符中处理高亮)
编写一个controller来返回数据到页面中,应当跳转视图到list.html
使用th:each来遍历列表内容,并使用${单个实体}来展示数据,其中数字表示性别使用三元运算符解决,其中的日期使用thymeleaf模板中的例如${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')
组件化思路:
抽取对应重复使用的单独模块到公共页面(该页面通常放置到commons目录中),指定模块使用th:fragment="headBar"表示,在其他页面如图表页面、展示列表页面使用组件时,例如:th:replace="~{/commons/commons::sideBarMenu}"即可使用组件!!!
标签高亮的思路:
对于bootstap高亮只需要在其class中添加active即可产生高亮。基于上面组件化,我们在如th:replace="...(key=value)"中添加请求参数,紧接着在公共页面的指定标签如a标签部分的th:class其中进行运算符判断是否有改参数,有的话高亮,无的话不高亮。
Controller部分:
@Controller public class EmployeeController { @Autowired private EmployeeDao employeeDao;//这里为了简便直接调用dao @RequestMapping("/employee/emps") public String queryAll(Model model){ Collection<Employee> employees = employeeDao.queryAllEmployee();//该方法只是通过获取模拟数据来进行返回 model.addAttribute("emps",employees); return "list"; } }
页面遍历列表:
<tr th:each="emp:${emps}" > <td th:text="${emp.getId()}"></td> <td th:text="${emp.getLastName()}"></td> <td th:text="${emp.getEmail()}"></td> <!-- 三元运算符 --> <td th:text="${emp.getGender()}==0?'男':'女'"></td> <td th:text="${emp.getDepartment().getDepartmentName()}"></td> <!-- 使用自带的dates类进行格式化 --> <td th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td> <td> <button class="btn btn-sm btn-primary">编辑</button> <button class="btn btn-sm btn-danger">删除</button> </td> </tr>
效果:
3、添加员工
首先大致介绍流程
需要创建一个新的添加页面add.html,并且在list.html基础上添加一个按钮用来跳转页面
整体是需要两个controller的,第一个是用来进行跳转到add.html,其中需要controller原因是添加页面中需要选择部门,其中的遍历所有部门需要在controller中查询并返回到add.html。
第二个controller是为了填写完add.html页面时,再次点击提交,controller中会执行保存该员工的信息到虚拟map数据结构中,进行重定向到员工列表展示页面(也经过了一个controller重新查询并跳转)。
list.html中的添加按钮:
<!-- 添加一个添加成员按钮--> <div> <a th:href="@{/empadd}" class="btn btn-sm btn-success">添加</a> </div>
add.html中的form表单:
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4"> <!-- 添加一个表单 --> <form style="margin-top: 10px" method="post" th:action="@{/empadd}"> <!-- 账号邮箱 --> <div class="mb-3"> <label for="exampleInputEmail1" class="form-label">LastName</label> <input th:name="lastName" type="text" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"> </div> <div class="mb-3"> <label for="exampleInputPassword1" class="form-label">Email</label> <input th:name="email" type="email" class="form-control" id="exampleInputPassword1"> </div> <!-- 单选框 --> <div class="mb-3"> <label for="exampleInputPassword1" class="form-label">Gender</label><br> <div class="form-check form-check-inline"> <input th:name="gender" class="form-check-input" type="radio" id="inlineCheckbox1" value="0"> <label class="form-check-label" for="inlineCheckbox1">男</label> </div> <div class="form-check form-check-inline"> <input th:name="gender" class="form-check-input" type="radio" id="inlineCheckbox2" value="1"> <label class="form-check-label" for="inlineCheckbox2">女</label> </div> </div> <!-- 选择框 --> <div class="mb-3"> <label for="exampleInputPassword1" class="form-label">department</label><br> <select class="form-select" aria-label="Default select example" th:name="department.id"> <!-- 进行遍历所有部门 --> <option th:each="dep:${deps}" th:text="${dep.getDepartmentName()}" th:value="${dep.getId()}">Open this select menu</option> </select> </div> <!-- 生日日期--> <div class="mb-3"> <label for="exampleInputPassword1" class="form-label">Birth</label><br> <input th:name="birth" type="text" class="form-control" aria-describedby="emailHelp"> </div> <button type="submit" class="btn btn-primary">提交</button> </form>
两个controller:控制跳转页面及重定向(保存employee)
@Controller public class EmployeeController { @Autowired private EmployeeDao employeeDao; @Autowired private DepartmentDao departmentDao; ... //跳转到添加页面 @GetMapping("/empadd") public String toAddPage(Model model){ //查询部门信息,并保存到request域中,跳转视图 Collection<Department> departments = departmentDao.queryAllDepartment(); model.addAttribute("deps",departments); return "emp/add"; } //执行添加员工操作,并且重新加载并展示所有员工信息 @PostMapping("/empadd") public String addEmployee(Employee employee){ employeeDao.addEmployee(employee); System.out.println("employee=>"+employeeDao); //重定向 return "redirect:/emps"; } }
效果:
额外注意点
若是提交表单中,对于日期格式是很严格的,springboot中默认为yyyy/MM/dd,若是填写了如1998-07-20就会报错
解决方案:
yaml配置文件中进行配置指定的日期格式即可
# 提交表单中默认日期格式 spring.mvc.format.date=yyyy-MM-dd
4、修改员工
前提描述
在list.html页面中点击指定员工的编辑按钮跳转到update.html中,其中经过一个controller,该controller根据请求参数id来查询到指定员工的信息以及查询所有的部门信息,返回到update.html中
根据传来的员工信息及部门信息,预先填写好指定的表单,点击修改按钮前,注意添加一个隐藏域用来传递员工的id
因为我们模拟数据的是HashMap,添加员工使用put方法,若是key不变,重复put达到一个覆盖效果,也就是更新的效果,秒妙妙!!!,我们复用一个controller中的添加方法即可!!!
list.html中的编辑按钮:rest风格
<!-- rest风格请求,例如:http://localhost:8080/changlu/emp/101 --> <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.getId()}">编辑</a>
Controller编写:
@Controller public class EmployeeController { @Autowired private EmployeeDao employeeDao; @Autowired private DepartmentDao departmentDao; ... //跳转update页面 rest风格,获取id值 @GetMapping("/emp/{id}") public String toUpdatePage(@PathVariable("id")Integer id,Model model){ //根据参数id查询指定的员工信息 Employee employee = employeeDao.queryEmployeeById(id); model.addAttribute("emp",employee); //查询所有的部门信息,之后需要遍历部门的名称 Collection<Department> departments = departmentDao.queryAllDepartment(); model.addAttribute("deps",departments); return "emp/update"; } }
update.html:添加一个隐藏域以及实现自动填写表单
<!-- 添加一个隐藏域,用于传输id, --> <input type="hidden" name="id" th:value="${emp.getId()}"> <!-- 覆盖lastName --> <input th:value="${emp.getLastName()}" th:name="lastName" ...> <!-- 覆盖单选框,使用到判断 --> <input th:checked="${emp.getGender()==0}" th:name="gender" <!-- 覆盖选择框,使用到判断 --> <select class="form-select" aria-label="Default select example" th:name="department.id"> <option th:selected="${emp.getDepartment().getId()==dep.getId()}" th:each="dep:${deps}" ...> <!-- 覆盖日期框,使用到判断 --> <input th:value="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}" th:name="birth" ...>
效果:
5、删除员工
前提描述
在list.html中添加一个按钮即可,使用jquery来添加一个确定事件
发送请求到controller进行删除即可,最后重定向到列表展示页面
list.html
<!-- 绑定弹窗事件,用于删除的确认与否 --> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script> <script> $(function () { $("a[class='btn btn-sm btn-danger']").click(function () { return confirm("确定删除吗?"); }); }); </script> <!-- 同样是rest风格进行请求 --> <a class="btn btn-sm btn-danger" th:href="@{/delemp/}+${emp.getId()}">删除</a>
Controller类:
@Controller public class EmployeeController { @Autowired private EmployeeDao employeeDao; @Autowired private DepartmentDao departmentDao; ... //删除员工 @GetMapping("/delemp/{id}") public String delEmp(@PathVariable("id") Integer id){ employeeDao.deleteEmployeeById(id); //重定向到展示列表controller并跳转展示页 return "redirect:/emps"; } }
效果:
错误页
在springboot中,我们可以将错误页放置到template目录中的error目录里,当访问出现对应异常时会跳转到指定错误页面!!!
效果:
注销登录
前面我们在登录成功时生成session,后面又使用了拦截器对部分页面进行拦截。那么我们再添加一个注销的功能
/template/commons/commons.html <!-- 这是headBar部分的标签 --> <a class="nav-link" th:href="@{/logout}">Sign out</a>
loginController:使用invalidate()立即销毁session即可
@RequestMapping("/logout") public String logout(HttpSession session){ //立即销毁session session.invalidate(); return "redirect:/index.html"; }
效果:点击的是右上角的sign out
注意点
controller中重定向如:return “redirect:/emps” :后需要有/,会自动调整虚拟目录的名称(根据template的目录跳转而不是请求)
若只是普通跳转视图层:return "emps" ,不需要加/
href的话,thymeleaf中th:href="@{/emps}",加不加/都没关系,自动定位到虚拟目录后
ModelAndView视图使用setViewName("error/error")设置。