瑞吉外卖-day02

YangeIT大约 24 分钟瑞吉外卖Filter过滤器员工模块分页查询字段自动填充ThreadLocal

瑞吉外卖-day02

课程内容

  • 管理端
    • 完善登录功能
    • 新增员工
    • 员工信息分页查询
    • 启用/禁用员工账号
    • 编辑员工信息

1. 完善登录功能

1.1 问题分析

前面我们已经完成了后台系统的员工登录功能开发,但是目前还存在一个问题,接下来我们来说明一个这个问题, 以及如何处理。

1). 目前现状

用户如果不登录,直接访问系统首页面,照样可以正常访问。

image-20210727232226862
image-20210727232226862

2). 理想效果

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

image-20210727232747276

那么,具体应该怎么实现呢?

可以使用我们之前讲解过的 过滤器、拦截器来实现,在过滤器、拦截器中拦截前端发起的请求,判断用户是否已经完成登录,如果没有登录则返回提示信息,跳转到登录页面。

1.2 思路分析 🍐

image-20210727233554707

过滤器具体的处理逻辑如下:

A. 获取本次请求的 URI

B. 判断本次请求, 是否需要登录, 才可以访问

C. 如果不需要,则直接放行

D. 判断登录状态,如果已登录,则直接放行

E. 如果未登录, 则返回未登录结果

如果未登录,我们需要给前端返回什么样的结果呢? 这个时候, 我们可以去看看前端是如何处理的 ?

image-20210728001324901

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 的形式来跟踪一下代码执行的过程。

image-20210728000838992
image-20210728000838992

对于前端的代码, 也可以进行 debug 调试。

F12 打开浏览器的调试工具, 找到我们前面提到的 request.js, 在 request.js 的响应拦截器位置打上断点。

image-20210728001929657
image-20210728001929657

2. 新增员工

2.1 需求分析

后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。点击按钮跳转到新增页面,如下:

点击查看需求说明

当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。

2.2 数据模型

新增员工,其实就是将我们新增页面录入的员工数据插入到

点击查看数据模型图示

employee 表中的 image-20210728004144521

需要注意,employee 表中对 username 字段加入了唯一约束,因为 username 是员工的登录账号,必须是唯一的。

image-20210728004250254
image-20210728004250254

2.3 程序执行流程

点击查看程序的执行过程:
image-20210728005638224
image-20210728005638224

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.5 功能测试

代码编写完毕之后,需要将工程重启, 完毕之后直接访问管理系统首页, 点击 "员工管理" 页面中的 "添加员工" 按钮, 输入员工基本信息, 然后点击 "保存" 进行数据保存, 保存完毕后, 检查数据库中是否录入员工数据。

点击查看测试步鄹

当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现错误提示信息:

image-20210728010841569
image-20210728010841569

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

image-20210728010938086
image-20210728010938086

出现上述的错误, 主要就是因为在 employee 表结构中,我们针对于 username 字段,建立了唯一索引,添加重复的 username 数据时,违背该约束,就会报错。但是此时前端提示的信息并不具体,用户并不知道是因为什么原因造成的该异常,我们需要给用户提示详细的错误信息 。

思考:如果采用上述方式 2 实现保存用户,要不要进行异常处理尼?

2.6 全局异常处理 ✏️

2.6.1 思路分析

要想解决上述测试中存在的问题,我们需要对程序中可能出现的异常进行捕获,通常有两种处理方式:

点击查看 2 种异常处理方式

2 种异常处理方式

A. 在 Controller 方法中加入 try...catch 进行异常捕获

形式如下:

image-20210729094125294
image-20210729094125294

如果采用这种方式,虽然可以解决,但是存在弊端,需要我们在保存其他业务数据时,也需要在 Controller 方法中加上 try...catch 进行处理,代码冗余,不通用。

采用这种方式来实现,我们只需要在项目中定义

2.6.2 全局异常处理器

1️⃣ 在 controller 包中的新建 exception 包,中自定义一个全局异常处理器 GlobalExceptionHandler
2️⃣ 在全局异常处理器 GlobalExceptionHandler 加上注解,可以通过属性 annotations 指定拦截哪一类的 Controller 方法。
3️⃣ 在异常处理器的方法上加上注解 来指定拦截的是那一类型的异常
点击查看异常处理方法流程
  1. 指定捕获的异常类型为 SQLIntegrityConstraintViolationException image-20210729100232642

  2. 解析异常的提示信息, 获取出是那个值违背了唯一约束

  3. 组装错误信息并返回

/**
 * 全局异常处理
 * 说明:项目中无论哪个类 出了异常,都会被本类所捕获
 */

//思想:面向切面编程---对原有的代码进行增强 并且不会改变原有的代码
//第一个注解以及值:
//所有的调用一般都是从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 , 他们的作用分别为:

  1. @ControllerAdvice : 指定拦截那些类型的控制器;
  2. @ResponseBody: 将方法的返回值 R 对象转换为 json 格式的数据, 响应给页面;

上述使用的两个注解, 也可以合并成为一个注解 @RestControllerAdvice image-20210729100052940

2.6.3 测试

全局异常处理器编写完毕之后,我们需要将项目重启, 完毕之后直接访问管理系统首页, 点击 "员工管理" 页面中的 "添加员工" 按钮。当我们在测试中,添加用户时, 输入了一个已存在的用户名时,前端界面出现如下错误提示信息:

image-20210729102220135
image-20210729102220135

3. 员工分页查询

3.1 需求分析

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

image-20210729134904625
image-20210729134904625
  • 请求参数

    • 搜索条件: 员工姓名(模糊查询)

    • 分页条件: 每页展示条数 , 页码

  • 响应数据

    • 总记录数

    • 结果列表

3.2 程序执行流程

3.2.1 页面流程分析

在开发代码之前,需要梳理一下整个程序的执行过程。

A. 点击菜单,打开员工管理页面时,执行查询: image-20210729163400772

B. 搜索栏输入员工姓名,回车,执行查询:

image-20210729164259997
image-20210729164259997

执行流程

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 方法

image-20210729231639034
image-20210729231639034

从上述的前端代码中我们可以看到, 执行完分页查询, 我们需要给前端返回的信息中需要包含两项 : records 中封装结果列表, total 中封装总记录数 。

而在组装请求参数时 , page、pageSize 都是前端分页插件渲染时的参数;

image-20210729232916380
image-20210729232916380

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

image-20210729231745143
image-20210729231745143

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

image-20210729232036767
image-20210729232036767

最终发送给服务端的请求为 : 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️⃣ 查询完毕后需要给前端的结果(如下图):
image-20210729235403154
image-20210729235403154
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 的形式, 根据服务端参数接收及逻辑执行情况。

image-20210730000855072
image-20210730000855072

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

image-20210730010606005
image-20210730010606005

4. 启用/禁用员工账号

4.1 需求分析

在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。如果某个员工账号状态为正常,则按钮显示为 "禁用",如果员工账号状态为已禁用,则按钮显示为"启用"。

需要注意,只有管理员(admin 用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示。

A. admin 管理员登录

image-20210730010858705
image-20210730010858705

B. 普通用户登录

image-20210730010941399
image-20210730010941399

4.2 程序执行流程

4.2.1 页面按钮动态展示

在上述的需求中,我们提到需要实现的效果是 : 只有管理员(admin 用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示 , 页面中是怎么做到只有管理员 admin 能够看到启用、禁用按钮的?

点击查看解析

1). 在列表页面(list.html)加载时, 触发钩子函数 created, 在钩子函数中, 会从 localStorage 中获取到用户登录信息, 然后获取到用户名

image-20210730012044171
image-20210730012044171

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

image-20210730012256779
image-20210730012256779

4.2.2 执行流程分析

点击查看执行流程分析

1). 当管理员 admin 点击 "启用" 或 "禁用" 按钮时, 调用方法 statusHandle

image-20210730012723560
image-20210730012723560

scope.row : 获取到的是这一行的数据信息 ;

2). statusHandle 方法中进行二次确认, 然后发起 ajax 请求, 传递 id status 参数

image-20210730013011861
image-20210730013011861
image-20210730013210948
image-20210730013210948

最终发起异步请求, 请求服务端, 请求信息如下:

请求说明
请求方式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 功能测试

点击查看测试步鄹

代码编写完毕之后,我们需要将工程重启。 然后访问前端页面, 进行 "启用" 或 "禁用" 的测试。

image-20210730123213103
image-20210730123213103

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

image-20210730123307452
image-20210730123307452

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

image-20210730123519468
image-20210730123519468

4.5 代码修复 ⚠️

4.5.1 原因分析

image-20210730123833129
image-20210730123833129

通过观察控制台输出的 SQL 发现页面传递过来的员工 id 的值和数据库中的 id 值不一致,这是怎么回事呢?

在分页查询时,服务端会将返回的 R 对象进行 json 序列化,转换为 json 格式的数据,而员工的 ID 是一个 Long 类型的数据,而且是一个长度为 19 位的长整型数据, 该数据返回给前端是没有问题的。

image-20210730124036415
image-20210730124036415

那么具体的问题出现在哪儿呢?

问题实际上, 就出现在前端 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);
    }
}

5. 编辑员工信息 ✏️

5.1 回显数据效果和接口

1️⃣ 回显效果

image-20210730181733784
image-20210730181733784

2️⃣ 回显接口

image-20210730181733784
image-20210730181733784

经过上述的分析,我们看到,在根据 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 测试

image-202181733784
image-202181733784

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