(苍穹外卖 DAY2)新增员工功能、分页查询员工功能解读

写在前面

在学习苍穹外卖过程中,弹幕常有 “为什么我打不开?为什么我没有输出?”的疑问,针对这些我也在学习过程中同样遇到的问题,万分感激在弹幕中找到了答案,并作出这系列汇总。本文内容是基于弹幕对苍穹外卖项目的实施与补充,仅供学习与分享之用,如有侵权请联系删除~

2024-07-04

目录

添加员工功能

        添加员工功能需求分析和设计

                分析 => 查看产品原型

                设计 ==> 接口文档

                数据库如何设计员工表?

        设置EmployeeDTO封装前端传来的数据

                为什么要用DTO封装而不直接使用实体类?

        实现逻辑

                EmployeeController:save()

                EmployeeService & EmployeeServiceImpl :save()实现逻辑

                        EmployeeServiceImpl新增员工方法

                        状态常量类:

                EmployeeMapper

        ​​​​​​​测试:

                返回401状态码(未授权)

                        拦截器代码:

                ​​​​​​​        校验JWT步骤:

                解决测试401问题:

                ​​​​​​​        参数名称是啥?

                ​​​​​​​        参数值如何获取呢?

        完善代码

                1)用户名重复 ==> 处理异常

                ​​​​​​​        原因:

                ​​​​​​​        处理:

                ​​​​​​​        代码:

                ​​​​​​​        思考:为什么不在异常前先判断用户是否存在?

                2)动态设置创建记录人的id

                ​​​​​​​        思考:如何获取当前用户的id?

                ​​​​​​​        思考:获取到id后,在拦截器我们并没有调用service,如何将id传给service操作?

                什么是Threadlocal ?常用方法?

                ​​​​​​​        如何验证代码是否在同一线程执行?

                ​​​​​​​        BaseContext.java类

                ​​​​​​​        为什么要封装为BaseContext?

分页查询功能

        需求分析与设计

                分析 => 查看产品原型

                设计 => 接口文档

        设置EmployeePageQueryDTO封装前端传来的数据

        实现逻辑

                EmployeeController:page()

                        PageResult类

                        Result类 

                ​​​​​​​                ​​​​​​​为什么要定义该类?

                EmployeeService & EmployeeServiceImpl:pageQuery实现逻辑:

                ​​​​​​​        使用PageHelper插件导:在pom文佳导入

                ​​​​​​​        EmployeeServiceImpl代码

                ​​​​​​​        Page对象提供的方法:

                EmployeeMapper

                ​​​​​​​        mapper.java代码

                ​​​​​​​        EmployeeMapper.xml代码:

                ​​​​​​​        没有出现红、蓝小鸟如何解决?

        功能测试

                解决操作时间的格式问题:

                ​​​​​​​        解决方案

                ​​​​​​​        方案①:修改路径src/main/java/com/sky/entity/Employee.java

                ​​​​​​​        方案②:扩展SpringMVC框架的消息转换器

                ​​​​​​​        ​​​​​​​        WebMvcConfiguration.java相关代码​编辑

                ​​​​​​​        ​​​​​​​        有因为导包报错的 => 解决

                ​​​​​​​        ​​​​​​​        ​​​​​​​自定义的对象转换器JacksonObjectMapper.java



添加员工功能

添加员工功能需求分析和设计

分析 => 查看产品原型

思考:新增员工相当于单表插入一条数据,应该对数据进行何种限制?

设计 ==> 接口文档

思考:

用何种请求方式? ==> post请求 ==> 前端传json给后端处理;

前端数据传输格式? ==> json

 数据库如何设计员工表?

id字段 ==> 自增 (数据库自增ID、UUID(Universally Unique Identifier)、雪花算法)

用户名 ==> 唯一

状态 ==> 默认为1(正常为1,禁用为0)


设置EmployeeDTO封装前端传来的数据

为什么要用DTO封装而不直接使用实体类?
  • 数据封装与解耦:避免直接暴露内部数据结构,后端数据变化而不影响前端。
  • 安全性:可以去除不需要的字段,防止SQL注入攻击。

前端传输的数据与实体类的属性相差较大的时候,用DTO更安全。(@Data注解DAY1已提及)

@Data
public class EmployeeDTO implements Serializable {

    private Long id;

    private String username;

    private String name;

    private String phone;

    private String sex;

    private String idNumber;
}

属性字段与接口文档的body的值一一对应。

实现逻辑

EmployeeController:save()
  • @PostMapping注解:定义接口路径
  • 接收参数:接收json格式的DTO,要用@RequestBody注解
  • 调用service的save方法(稍后定义),将DTO作为参数传入
  • 返回值:封装为Result

@PostMapping注解:在EmployeeController类上已定义过,所以此处不必定义

(tips:自动生成注释 ==> 在方法上方输入/** + 回车)

EmployeeService & EmployeeServiceImpl :save()实现逻辑
  1. 过来的数据是DTO,但在持久层建议使用实体类 ==> 创建Employee实体
  2. 用BeanUtils.copyProperties(EmployeeDTO,实体) ==>  将属性名一致的完成拷贝
  3. 完善实体中EmployeeDTO缺失的属性
  4. 账号状态 ==> 默认正常状态
  5. 设置密码 ==> 默认密码 
  6. 添加/修改时间 ==> 暂时硬写 (系统时间)
  7. 设置记录创建人 ==> 暂时硬写 (设置为10L)
EmployeeServiceImpl新增员工方法

状态常量类:

路径:src/main/java/com/sky/constant/StatusConstant.java

定义常量类目的:

EmployeeMapper

简单的sql语句用注解

如果在敲SQL语句时idea没有提示,参考如下方法设置SQL方言

代码开发部分完成(建议是跟着视频敲,本文的截图仅作补充,有些代码不全)


测试:

使用swagger测试接口

返回401状态码(未授权)

原因:请求被拦截器拦截,校验JWT令牌,未通过返回401

拦截器代码:

路径:src/main/java/com/sky/interceptor/JwtTokenAdminInterceptor.java

import com.sky.constant.JwtClaimsConstant;
import com.sky.context.BaseContext;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
                  Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);
            BaseContext.setCurrentId(empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

实现了HandlerInterceptor 接口,自定义拦截器,在MVC注册,实现拦截指定路径的请求

校验JWT步骤:
  • 解析JWT:
    • 将其分解为头部、载荷(Payload)和签名三部分。
  • 验证头部:
    • 确认 JWT 的头部(Header)部分是否符合预期,
    • 通常包括算法(例如 HS256、RS256 等)和令牌类型(如 "JWT")等信息。
  • 验证签名:
    • 如果 JWT 使用了签名(如 HMAC 或 RSA 签名),需要使用相应的密钥来验证签名的有效性。
    •  具体步骤包括:
      • 使用头部中指定的算法解码签名部分。
      • 头部和载荷组合起来,并使用相同的算法和密钥重新计算签名
      • 将计算出的签名与 JWT 中的签名部分进行比较,确认它们是否匹配。
      • 验证有效期:检查 JWT 中的有效期(exp)是否过期。
解决测试401问题:

在swagger设置全局参数

参数名称是啥?

根据连招可得,参数名为 token

参数值如何获取呢?

通过员工登录的返回值即可获取,复制粘贴确认即可

测试成功,新增员工代码初步完成!!!


完善代码

1)用户名重复 ==> 处理异常
原因:
  • 需要完善的原因,如果用户名重复,按照上述逻辑处理,控制台会抛SQL异常,程序中止
处理:
  1. 定义全局异常GlobalExceptionHandler类
  2. 捕获SQL异常,当异常信息包含Duplicate entry字段时,认定为用户名重复异常
  3. 输出用户名重复信息
  4. 异常处理后不会终止程序。
代码:

思考:为什么不在异常前先判断用户是否存在?

做过多假定,假如新增用户数量多时,每次都先查再增,效率低。

2)动态设置创建记录人的id
思考:如何获取当前用户的id?

基于JWT令牌认证的流程

每次请求都会在请求头携带token

在生成token时用到了用户id

所以登录成功后,在后续的请求中,可以反向取出id,在拦截器解析token,获取id

思考:获取到id后,在拦截器我们并没有调用service,如何将id传给service操作?

这时请出我们的 Threadlocal 闪亮登场!!!

什么是Threadlocal ?常用方法?

 Threadlocal 主要作用在同一线程内,那么

如何验证代码是否在同一线程执行?

在 拦截器、EmployeeController、EmployeeServiceImpl中输出Thread.currentThread.getId()查验

查看控制台发现一次请求输出的数值相同,每次请求的数值不同。

在开发中,使用Threadlocal 时往往封装为一个工具类

BaseContext.java类

路径:src/main/java/com/sky/context/BaseContext.java

为什么要封装为BaseContext?

简化代码:避免在多个类中代码冗余。

减少开销:该类都用static修饰符,在类加载时对象、方法就已经被初始化,且在整个生命周期中是单例的,所以ThreadLocal 实例不会重复创建,减少开销。

扩展性:ThreadLocal 的操作进行扩展或修改,可以集中在一个地方进行。例如,可以在 BaseContext 中添加日志记录、性能监控等功能,而不需要修改使用 ThreadLocal 的每一个地方。

新增员工功能实现、补充知识完结~


分页查询功能

需求分析与设计

分析 => 查看产品原型

思考:原型展示用到了什么参数?

分页查询常用的3个参数 :页码、每页展示数、(关键字)

设计 => 接口文档

思考:

用何种请求方式? ==> get请求 ==> 后端返回json数据展示;

前端数据传输格式? ==> query ==> 在路径上会有参数体现

返回参数:data{ total + 实体数组 } ==> 必须返回

设置EmployeePageQueryDTO封装前端传来的数据

实现逻辑

EmployeeController:page()
  • @GetMapping注解:定义接口路径,补上类缺失的 " /page "
  • 接收参数:接收query格式的DTO,不用@RequestBody注解
  • 调用service的pageQuery方法(稍后定义),将DTO作为参数传入
  • 返回值:封装的Result<PageResult>

PageResult类

records :当前页返回的 员工实体列表 (employeeList)


Result类 

类声明:​​​​​​​实现 Serializable 接口,使该类可以被序列化。序列化是将对象转换为字节流,以便保存到文件或通过网络传输。

属性:状态码code、错误信息msg、数据data(泛型-传进来是List就是List)

3个重载的success方法

无参:只返回状态码,一般用于post请求;

(T object):将传进来的对象赋值给data,返回result对象

(String msg):将传入的错误信息 msg 赋值给 msg 属性,返回该实例。

为什么要定义该类?
  • 统一响应格式:使得所有 API 的响应结构/格式一致,便于前端处理。
  • 支持泛型:通过泛型支持不同类型的数据,使这个类具有广泛的适用性。
  • 便捷方法:提供静态方法方便地构建成功或失败的响应,减少重复代码。
/**
 * 后端统一返回结果
 * @param <T>
 */
@Data
public class Result<T> implements Serializable {

    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //错误信息
    private T data; //数据

    public static <T> Result<T> success() {
        Result<T> result = new Result<T>();
        result.code = 1;
        return result;
    }
    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<T>();
        result.data = object;
        result.code = 1;
        return result;
    }

    public static <T> Result<T> error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }
}
EmployeeService & EmployeeServiceImpl:pageQuery实现逻辑:

分页:将数据库中的数据按指定的 页码,每页展示数 计算出来的数据拼接

  • 使用PageHelper插件,传入 页码、每页展示数 => 在每次查询SQL中拼接 limit...)

        (使用Mybatis框架提供的PageHelper插件 ==> 简化代码)

  • 调用mapper,根据DTO查询数据库,返回Page<实例>(使用PageHelper固定返回该对象)
    • 如果DTO的name属性为空:查询整表;
    • 如果DTO的name属性非空:根据name模糊查询;
  • 将page封装为自定义的PageResult对象返回
使用PageHelper插件导:在pom文佳导入

EmployeeServiceImpl代码

​​​​​​​

PageResult.total:page.getTotal()

PageResult.records:page.getResult()

Page对象提供的方法:

返回一个long ==> 总数据数目

返回一个List<E> ==> 实体列表

EmployeeMapper
mapper.java代码

注意这个红色小鸟,点击跳转xml文件

因为该SQL语句动态且较为复杂,所以不用上述的注解方法,在xml文件中编写。

EmployeeMapper.xml代码:

  • 如果DTO的name属性为空:查询整表;
  • 如果DTO的name属性非空:根据name模糊查询;
  • 根据创建时间排序
没有出现红、蓝小鸟如何解决?

点击setting => 安装插件MybatisX

功能测试

  1. 启动项目、ngnix、Mysql数据库
  2. 登录 => 配置全局参数 JWT令牌
  3. 使用swagger生成的接口文档测试

解决操作时间的格式问题:
解决方案

方案①:修改路径src/main/java/com/sky/entity/Employee.java

因为返回的是Page<Employee> ,所以对应在Employee.java中添加注解

如果使用这种方式,在每次返回时间数据时,都要对应加上该注解,代码冗余、不易维护。

方案②:扩展SpringMVC框架的消息转换器
WebMvcConfiguration.java相关代码
  1. 将自定义的转换器(Java对象 互相转换 json)设置为消息转换器
  2. 将这个消息装唤起注入MVC容器中,初始排最末尾,设置为0优先使用
有因为导包报错的 => 解决

该类用到的包:

自定义的对象转换器JacksonObjectMapper.java

路径:src/main/java/com/sky/json/JacksonObjectMapper.java

​​​​​​​

​​​​​​​

​​​​​​​​​​​​​

  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值