瑞吉外卖-day02
瑞吉外卖-day02
课程内容
- 管理端
- 完善登录功能
- 新增员工
- 员工信息分页查询
- 启用/禁用员工账号
- 编辑员工信息
1. 完善登录功能
1.1 问题分析
前面我们已经完成了后台系统的员工登录功能开发,但是目前还存在一个问题,接下来我们来说明一个这个问题, 以及如何处理。
1). 目前现状
用户如果不登录,直接访问系统首页面,照样可以正常访问。

2). 理想效果
上述这种设计并不合理,我们希望看到的效果应该 是,只有登录成功后才可以访问系统中的页面,如果没有登录, 访问系统中的任何界面都直接跳转到登录页面。

那么,具体应该怎么实现呢?
可以使用我们之前讲解过的 过滤器、拦截器来实现,在过滤器、拦截器中拦截前端发起的请求,判断用户是否已经完成登录,如果没有登录则返回提示信息,跳转到登录页面。
1.2 思路分析 🍐

过滤器具体的处理逻辑如下:
A. 获取本次请求的 URI
B. 判断本次请求, 是否需要登录, 才可以访问
C. 如果不需要,则直接放行
D. 判断登录状态,如果已登录,则直接放行
E. 如果未登录, 则返回未登录结果
如果未登录,我们需要给前端返回什么样的结果呢? 这个时候, 我们可以去看看前端是如何处理的 ?

1.3 代码实现
1). 定义登录校验过滤器
自定义一个过滤器 LoginCheckFilter 并实现 Filter 接口, 在 doFilter 方法中完成校验的逻辑。 那么接下来, 我们就根据上述分析的步骤, 来完成具体的功能代码实现:
所属包: com.itheima.reggie.filter
点击查看代码
import com.alibaba.fastjson.JSON;
import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 检查用户是否已经完成登录
* filterName:过滤器在对象容器中的名字
* urlPatterns:匹配路径
*/
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter{
//路径匹配器,支持通配符
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request= (HttpServletRequest) servletRequest; //获取session中值
HttpServletResponse response= (HttpServletResponse) servletResponse; //重定向
//1、获取本次请求的URI
String requestURI = request.getRequestURI();
// {}占位符
log.info("接受到的请求路径:{}",requestURI);
//定义不需要处理的请求路径--白名单
String[] urls = new String[]{
"/employee/login", // 登陆页 直接放行
"/employee/logout",//注销页 直接放行
"/backend/**", //所有的pc端的 前端资源 放行
"/front/**" //所有的移动端 前端资源 放行
};
//2、判断本次请求是否需要处理
boolean check = check(urls, requestURI);
//3、如果不需要处理,则直接放行
if(check){
// filterChain.doFilter 放行
filterChain.doFilter(servletRequest,servletResponse);
// 直接返回该方法,下面的代码不要执行了
return;
}
//4、判断登录状态,如果已登录,则直接放行
HttpSession session = request.getSession();
Object employee = session.getAttribute("employee");
// 如果用户id不等于空,说明已经登陆过,直接放行
if (employee!=null){
log.info("用户已经登录,直接放行");
filterChain.doFilter(servletRequest,servletResponse);
// 直接返回该方法,下面的代码不要执行了
return;
}
log.info("用户未登录");
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
// JSON.toJSONString 将对象 转成 JSON字符串 给前端使用 返回的数据一定是NOTLOGIN 且code 为0
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
}
/**
* 路径匹配,检查本次请求是否需要放行
* @param urls
* @param requestURI
* @return
*/
public boolean check(String[] urls,String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if(match){
return true;
}
}
return false;
}
}
介绍: Spring 中提供的路径匹配器 ; 通配符规则:
符号 含义 ? 匹配一个字符 * 匹配 0 个或多个字符 ** 匹配 0 个或多个目录/字符
点击查看代码
/**
* 路径匹配,检查本次请求是否需要放行
* @param urls
* @param requestURI
* @return
*/
public boolean check(String[] urls,String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if(match){
return true;
}
}
return false;
}
2). 开启组件扫描
需要在引导类上, 加上 Servlet 组件扫描的注解, 来扫描过滤器配置的@WebFilter 注解, 扫描上之后, 过滤器在运行时就生效了。
点击查看代码
@Slf4j
@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class,args);
log.info("项目启动成功...");
}
}
在 SpringBoot 项目中, 在引导类/配置类上加了该注解后, 会自动扫描项目中(当前包及其子包下)的@WebServlet , @WebFilter , @WebListener 注解, 自动注册 Servlet 的相关组件 ;
1.4 功能测试
代码编写完毕之后,我们需要将工程重启一下,然后在浏览器地址栏直接输入系统管理后台首页,然后看看是否可以跳转到登录页面即可。我们也可以通过 debug 的形式来跟踪一下代码执行的过程。

对于前端的代码, 也可以进行 debug 调试。
F12 打开浏览器的调试工具, 找到我们前面提到的 request.js, 在 request.js 的响应拦截器位置打上断点。

2. 新增员工
2.1 需求分析
后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。点击按钮跳转到新增页面,如下:
点击查看需求说明

当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。
2.2 数据模型
新增员工,其实就是将我们新增页面录入的员工数据插入到 。
点击查看数据模型图示
employee 表中的
需要注意,employee 表中对 username 字段加入了唯一约束,因为 username 是员工的登录账号,必须是唯一的。

2.3 程序执行流程
点击查看程序的执行过程:


A. 点击"保存"按钮, 页面发送 ajax 请求,将新增员工页面中输入的数据以提交到服务端, 请求方式 POST, 请求路径 /employee
B. 服务端 Controller 接收页面提交的数据并调用 Service 将数据进行保存
C. Service 调用 Mapper 操作数据库,保存数据
2.4 代码实现 ✏️
在 EmployeeController 中增加 save 方法, 用于保存用户员工信息。
步鄹
A. 在新增员工时, 按钮页面原型中的需求描述, 需要给员工设置初始默认密码 123456, 并对密码进行 MD5 加密(数据库中存储密文)。

B. 在组装员工信息时, 还需要封装(公共字段:每个表都有这些字段)。
简单实用
方式1:(用来方式用户名重复 的简单处理)类:com.itheima.controller.EmployeeController
/**
* 保存用户
* 逻辑修改:先查后增
* @param request
* @param employee
* @return
*/
@PostMapping
public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
log.info("用户的基本信息:{}",employee.toString());
// 0 先查后增
String username = employee.getUsername();
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.eq(username!=null,"username",username);
Employee one = employeeService.getOne(wrapper);
if (one!=null){
// 如果one不等于null 说明已经存在,提示用户
return R.error("添加用户失败,用户名:"+username+ "已存在");
}
// 1. 设置默认密码 123456 存密文 e10adc3949ba59abbe56e057f20f883e
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
// 2. 设置默认状态 为启动
employee.setStatus(1);
// 3. 创建时间 和修改时间 一致
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
// 4. 创建者 和修改者 一致
// 创建者id 已经在登陆的时候,存到Session,取出即可使用
HttpSession session = request.getSession();
employee.setUpdateUser((Long) session.getAttribute("employee"));
employee.setCreateUser((Long) session.getAttribute("employee"));
// 5. 执行保存用户的操作
boolean savestatus = employeeService.save(employee);
// 三元表达式: condition?条件为true执行:条件为false的时候执行
return savestatus?R.success("添加用户成功"):R.error("添加用户失败,请稍后再试");
}
能力提升
方式2:设置默认值,直接保存(异常统一处理)类:com.itheima.controller.EmployeeController
/**
* 保存用户
* @param request
* @param employee
* @return
*/
@PostMapping
public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
log.info("用户的基本信息:{}",employee.toString());
// 1. 设置默认密码 123456 存密文 e10adc3949ba59abbe56e057f20f883e
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
// 2. 设置默认状态 为启动
employee.setStatus(1);
// 3. 创建时间 和修改时间 一致
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
// 4. 创建者 和修改者 一致
// 创建者id 已经在登陆的时候,存到Session,取出即可使用
HttpSession session = request.getSession();
employee.setUpdateUser((Long) session.getAttribute("employee"));
employee.setCreateUser((Long) session.getAttribute("employee"));
// 5. 执行保存用户的操作
boolean savestatus = employeeService.save(employee);
// 三元表达式: condition?条件为true执行:条件为false的时候执行
return savestatus?R.success("添加用户成功"):R.error("添加用户失败,请稍后再试");
}
2.5 功能测试
代码编写完毕之后,需要将工程重启, 完毕之后直接访问管理系统首页, 点击 "员工管理" 页面中的 "添加员工" 按钮, 输入员工基本信息, 然后点击 "保存" 进行数据保存, 保存完毕后, 检查数据库中是否录入员工数据。
点击查看测试步鄹
当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现错误提示信息:

而此时,服务端已经报错了, 报错信息如下:

出现上述的错误, 主要就是因为在 employee 表结构中,我们针对于 username 字段,建立了唯一索引,添加重复的 username 数据时,违背该约束,就会报错。但是此时前端提示的信息并不具体,用户并不知道是因为什么原因造成的该异常,我们需要给用户提示详细的错误信息 。
思考:如果采用上述方式 2 实现保存用户,要不要进行异常处理尼?
2.6 全局异常处理 ✏️
2.6.1 思路分析
要想解决上述测试中存在的问题,我们需要对程序中可能出现的异常进行捕获,通常有两种处理方式:
点击查看 2 种异常处理方式
2 种异常处理方式
A. 在 Controller 方法中加入 try...catch 进行异常捕获
形式如下:

如果采用这种方式,虽然可以解决,但是存在弊端,需要我们在保存其他业务数据时,也需要在 Controller 方法中加上 try...catch 进行处理,代码冗余,不通用。
采用这种方式来实现,我们只需要在项目中定义。
2.6.2 全局异常处理器
1️⃣ 在 controller 包中的新建 exception 包,中自定义一个全局异常处理器 GlobalExceptionHandler
2️⃣ 在全局异常处理器 GlobalExceptionHandler 加上注解,可以通过属性 annotations 指定拦截哪一类的 Controller 方法。
3️⃣ 在异常处理器的方法上加上注解 来指定拦截的是那一类型的异常
点击查看异常处理方法流程
指定捕获的异常类型为 SQLIntegrityConstraintViolationException
解析异常的提示信息, 获取出是那个值违背了唯一约束
组装错误信息并返回
/**
* 全局异常处理
* 说明:项目中无论哪个类 出了异常,都会被本类所捕获
*/
//思想:面向切面编程---对原有的代码进行增强 并且不会改变原有的代码
//第一个注解以及值:
//所有的调用一般都是从Controller开始的,因此异常最后会抛到Controller ,只需要接受Controller异常即可
@ControllerAdvice(annotations = {RestController.class, Controller.class})
//异常处理后,需要返回信息给前端,将信息返回到 响应体中
@ResponseBody
//打日志使用 如:log.info()
@Slf4j
public class GlobalExceptionHandler {
/**
* 定一个一个专门处理的异常
* 用来提示用户名重复
* 因为是Spring框架,因此会先抛DuplicateKeyException异常,然后在抛SQLIntegrityConstraintViolationException异常,
* 所以需要方法中先捕获DuplicateKeyException异常
*/
@ExceptionHandler(DuplicateKeyException.class)
public R<String> doDuplicateKeyExceptionExceptionHandler(DuplicateKeyException ex){
log.error("异常信息:{}",ex.getMessage());
// : Duplicate entry 'guest' for key 'idx_username'
// 1.判断 错误消息 是否包含 Duplicate entry 字符串
boolean duplicate_entry = ex.getMessage().contains("Duplicate entry");
// 如果包含,则进行切割字符串
if (duplicate_entry){
String[] s = ex.getMessage().split(" ");
// Arrays.toString(数组) 能将数组 转化成字符串
log.info("数组对象:{}",s);
log.info("数组:{}", Arrays.toString(s));
return R.error("用户名:"+s[9]+"重复");
}
return R.error("未知错误");
}
/**
* 异常处理方法
* @ExceptionHandler(Exception.class)
* 代表 可以接受Exception以及其子类的所有异常
* @return
*/
@ExceptionHandler(Exception.class)
public R<String> doOtherExceptionHandler(Exception ex){
log.error(ex.getMessage());
return R.error("服务器正忙,请稍后");
}
/**
* 接受CategoryServiceImpl 中抛出的自定义异常
* @param exception
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> doCustomExceptionHandler(CustomException exception){
return R.error(exception.getMessage());
}
}
注解说明:
全局异常处理器上使用了的两个注解 @ControllerAdvice , @ResponseBody , 他们的作用分别为:
- @ControllerAdvice : 指定拦截那些类型的控制器;
- @ResponseBody: 将方法的返回值 R 对象转换为 json 格式的数据, 响应给页面;
上述使用的两个注解, 也可以合并成为一个注解 @RestControllerAdvice
2.6.3 测试
全局异常处理器编写完毕之后,我们需要将项目重启, 完毕之后直接访问管理系统首页, 点击 "员工管理" 页面中的 "添加员工" 按钮。当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现如下错误提示信息:

3. 员工分页查询
3.1 需求分析
系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。而在我们的分页查询页面中, 除了分页条件以外,还有一个查询条件 "员工姓名"。

请求参数
搜索条件: 员工姓名(模糊查询)
分页条件: 每页展示条数 , 页码
响应数据
总记录数
结果列表
3.2 程序执行流程
3.2.1 页面流程分析
在开发代码之前,需要梳理一下整个程序的执行过程。
A. 点击菜单,打开员工管理页面时,执行查询:
B. 搜索栏输入员工姓名,回车,执行查询:

执行流程
1). 页面发送 ajax 请求,将分页查询参数(page、pageSize、name)提交到服务端
2). 服务端 Controller 接收页面提交的数据, 并组装条件调用 Service 查询数据
3). Service 调用 Mapper 操作数据库,查询分页数据
4). Controller 将查询到的分页数据, 响应给前端页面
5). 页面接收到分页数据, 并通过 ElementUI 的 Table 组件展示到页面上
3.2.2 前端代码介绍(了解) 🚀 🚀
点击查看前端代码
1). 访问员工列表页面/member/list.html 时, 会触发 Vuejs 中的钩子方法, 在页面初始化时调用 created 方法

从上述的前端代码中我们可以看到, 执行完分页查询, 我们需要给前端返回的信息中需要包含两项 : records 中封装结果列表, total 中封装总记录数 。
而在组装请求参数时 , page、pageSize 都是前端分页插件渲染时的参数;

2). 在 getMemberList 方法中, 通过 axios 发起异步请求

axios 发起的异步请求会被声明在 request.js 中的 request 拦截器拦截, 在其中对 get 请求进行进一步的封装处理

最终发送给服务端的请求为 : GET 请求 , 请求链接 /employee/page?page=1&pageSize=10&name=xxx
3.3 代码实现 ✏️
3.3.1 分页插件配置
分页查询功能,在 MybatisPlus 要实现分页功能,就需要用到 MybatisPlus 中提供的
所属包: com.itheima.reggie.config
/**
* 配置MP的分页插件
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// 分页拦截器:1. 在sql后面加limit 2.增加一条统计所有数据条数的 语句 SELECT COUNT(*) FROM employee
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
3.3.2 分页查询实现
1️⃣ 页面在进行分页查询时, 具体的请求信息如下:
请求 | 说明 |
---|---|
请求方式 | GET |
请求路径 | /employee/page |
请求参数 | page(当前页) , pageSize(每页多少条) , name(检索词) |
2️⃣ 查询完毕后需要给前端的结果(如下图):

3️⃣ 具体的逻辑如下:
A. 构造分页条件
B. 构建搜索条件 - name 进行模糊匹配--like
C. 构建排序条件 - 更新时间倒序排序 --order by xx desc
D. 执行查询
E. 组装结果并返回
4️⃣ 具体的代码实现如下:
点击查看代码
/**
* 查询分页 笔记中的 3.3
* A. 构造分页条件
* B. 构建搜索条件 - name进行模糊匹配--like
* C. 构建排序条件 - 更新时间倒序排序 --order by xx desc
* D. 执行查询
* E. 组装结果并返回
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page<Employee>> page(Integer page, Integer pageSize, String name){
log.info("当前页:{} ,每页:{},检索词:{}",page,pageSize,name);
// A. 构造分页条件 page 当前页 pageSize 每页多少条
Page<Employee> employeePage = new Page<>(page, pageSize);
// B. 构建搜索条件 - name进行模糊匹配--like %name%
QueryWrapper<Employee> ewrapper = new QueryWrapper<>();
ewrapper.like(name!=null,"username",name); //condition 为true 拼接语句 否则 不拼接
//C. 构建排序条件 - 更新时间倒序排序 --order by xx desc
ewrapper.orderByDesc("update_time");
// D. 执行查询
employeeService.page(employeePage,ewrapper);
// E. 组装结果并返回
return R.success(employeePage);
}
3.4 功能测试
代码编写完毕之后,我们需要将工程重启, 完毕之后直接访问管理系统首页, 默认就会打开员工管理的列表页面, 我们可以查看列表数据是否可以正常展示, 也可以通过分页插件来测试分页功能, 及员工姓名的模糊查询功能。
在进行测试时,可以使用浏览器的监控工具查看页面和服务端的数据交互细节。 并借助于 debug 的形式, 根据服务端参数接收及逻辑执行情况。

测试过程中可以发现,对于员工状态字段(status)服务端返回的是状态码(1 或者 0),但是页面上显示的则是“正常”或者“已禁用”,这是因为页面中在展示数据时进行了处理。

4. 启用/禁用员工账号
4.1 需求分析
在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。如果某个员工账号状态为正常,则按钮显示为 "禁用",如果员工账号状态为已禁用,则按钮显示为"启用"。
需要注意,只有管理员(admin 用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示。
A. admin 管理员登录

B. 普通用户登录

4.2 程序执行流程
4.2.1 页面按钮动态展示
在上述的需求中,我们提到需要实现的效果是 : 只有管理员(admin 用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示 , 页面中是怎么做到只有管理员 admin 能够看到启用、禁用按钮的?
点击查看解析
1). 在列表页面(list.html)加载时, 触发钩子函数 created, 在钩子函数中, 会从 localStorage 中获取到用户登录信息, 然后获取到用户名

2). 在页面中, 通过 Vue 指令 v-if 进行判断,如果登录用户为 admin 将展示 启用/禁用 按钮, 否则不展示

4.2.2 执行流程分析
点击查看执行流程分析
1). 当管理员 admin 点击 "启用" 或 "禁用" 按钮时, 调用方法 statusHandle

scope.row : 获取到的是这一行的数据信息 ;
2). statusHandle
方法中进行二次确认, 然后发起 ajax 请求, 传递 id
status
参数


最终发起异步请求, 请求服务端, 请求信息如下:
请求 | 说明 |
---|---|
请求方式 | PUT |
请求路径 | /employee |
请求参数 | {"id":xxx,"status":xxx} |
{...params} : 三点是 ES6 中出现的扩展运算符。作用是遍历当前使用的对象能够访问到的所有属性,并将属性放入当前对象中。
4.3 代码实现
执行过程
1). 页面发送 ajax 请求,将参数(id、status)提交到服务端
2). 服务端 Controller 接收页面提交的数据并调用 Service 更新数据
3). Service 调用 Mapper 操作数据库
启用、禁用员工账号,本质上就是一个更新操作,也就是对 status 状态字段进行操作。 在 Controller 中创建 update 方法,此方法是一个通用的修改员工信息的方法。
点击查看代码
/** 禁用启用员工
* status 0 ---禁用
* status 1 ---启用
* 问题:前端获取Long类型id 会出现精度丢失 如1545285566273286145 -->1545285566273286000
* 解决步鄹:
* 1. 4.5.3笔记 引入JacksonObjectMapper--->config包
* 2. 在WebMvcConfig内中,添加JacksonObjectMapper 需要重写的方法是extendMessageConverters
* @param request
* @param employee
* @return
*/
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
Object employee_id = request.getSession().getAttribute("employee");
//设置修改时间
employee.setUpdateTime(LocalDateTime.now());
//如果id不等于空,设置修改人
if (employee_id!=null){
employee.setUpdateUser((Long) employee_id);
}
// 修改返回状态 false 修改失败
boolean b = employeeService.updateById(employee);
return b?R.success("操作成功"):R.error("操作失败");
}
4.4 功能测试
点击查看测试步鄹
代码编写完毕之后,我们需要将工程重启。 然后访问前端页面, 进行 "启用" 或 "禁用" 的测试。

测试过程中没有报错,但是功能并没有实现,查看数据库中的数据也没有变化。但是从控制台输出的日志, 可以看出确实没有更新成功。

而在我们的数据库表结构中, 并不存在该 ID, 数据库中 风清扬 对应的 ID 为 1420038345634918401

4.5 代码修复 ⚠️
4.5.1 原因分析

通过观察控制台输出的 SQL 发现页面传递过来的员工 id 的值和数据库中的 id 值不一致,这是怎么回事呢?
在分页查询时,服务端会将返回的 R 对象进行 json 序列化,转换为 json 格式的数据,而员工的 ID 是一个 Long 类型的数据,而且是一个长度为 19 位的长整型数据, 该数据返回给前端是没有问题的。

那么具体的问题出现在哪儿呢?
问题实际上, 就出现在前端 JS 中, js 在对长度较长的长整型数据进行处理时, 会损失精度, 从而导致提交的 id 和数据库中的 id 不一致。 这里,我们也可以做一个简单的测试,代码如下:
点击查看测试代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
<script>
alert(1420038345634918401);
</script>
</head>
<body></body>
</html>
4.5.2 后端代码修改 ✏️ 👈
1️⃣ 添加 JacksonObjectMapper 类
包名:com.itheima.reggie.common
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
2️⃣ 修改 WebMvcConfig 配置类的方法
包名 :com.itheima.reggie.config;
package com.itheima.reggie.config;
import java.util.List;
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 设置静态资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始进行静态资源映射...");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
/**
*
* 就是将Long的数据 转化成字符串
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);
}
}
5. 编辑员工信息 ✏️
5.1 回显数据效果和接口
1️⃣ 回显效果

2️⃣ 回显接口

经过上述的分析,我们看到,在根据 ID 查询员工信息时,请求信息如下:
请求 | 说明 |
---|---|
请求方式 | GET |
请求路径 | /employee/{id} |
5.2 代码实现
类:com.itheima.reggie.controller.EmployeeController
/**
* 通过id获得员工信息
* @param id
* @return
*/
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
Employee byId = employeeService.getById(id);
return R.success(byId);
}
5.3 测试

点击
保存
直接调用之前禁用启用
的修改接口👈,直接修改