二刷苍穹外卖 day02

发布于:2025-06-13 ⋅ 阅读:(16) ⋅ 点赞:(0)

新增员工

DTO

将前端传递的参数列表通过对应的实体类接收
当前端提交的数据和实体类中对应的属性差别较大时,使用DTO来封装数据
![[Pasted image 20250611183830.png]]

@Data
public class EmployeeDTO implements Serializable {

    private Long id;

    private String username;

    private String name;

    private String phone;

    private String sex;

    private String idNumber;

}

记得要implements Serializable,实现接口后,对象就具备了可序列化的能力

controller

/**  
 * 新增员工  
 */  
@PostMapping  
@ApiOperation("新增员工")  
public Result save(@RequestBody EmployeeDTO employeeDTO)  
{  
    log.info("新增员工:{}",employeeDTO);  
    employeeService.save(employeeDTO);  
    return Result.success();  
}

Result定义了后端的返回结果格式

Result

package com.sky.result;  
  
import lombok.Data;  
  
import java.io.Serializable;  
  
/**  
 * 后端统一返回结果  
 * @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;  
    }  
  
}

Result是一个通用的后端统一返回结果封装类,用于向前端返回统一格式的响应数据

泛型参数表示返回的数据类型,可以是任何对象
属性:
code 相应状态码,1表示成功,0或其他表示失败
msg:描述错误信息,成功则为空
data:封装返回给前端的具体数据,类型为泛型T

方法:
success()

只返回code=1
success(T object):静态方法,返回包含具体数据的成功响应(code=1,data=object)
error(String msg):返回错误信息和失败状态码

public static <T> Result<T>
是一个Java泛型方法的声明,前一个表明了泛型方法的类型参数声明部分
Result表示该方法返回一个Result类型的对象
static表示这是一个类方法,可通过类名调用

@Service

@Override  
public void save(EmployeeDTO employeeDTO) {  
    Employee employee = new Employee();  
    BeanUtils.copyProperties(employeeDTO,employee);  
    employee.setStatus(StatusConstant.ENABLE);  
    employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes());  
    employee.setCreateTime(LocalDateTime.now());  
    employee.setUpdateTime(LocalDateTime.now());  
      
	employee.setCreateUser(10L);  
	employee.setCreateUser(10L);
      
    employeeMapper.insert(employee);  
}

注意一下BeanUtils.copyProperties()方法,以后用的很多
用于将一个 JavaBean 对象(源对象)的属性值复制到另一个 JavaBean 对象(目标对象)中。这里它将 EmployeeDTO 对象 employeeDTO 的属性值复制到 Employee 对象 employee 中。通常,源对象和目标对象需有相同或兼容的属性名及类型,这样才能正确复制属性值,比如 EmployeeDTO 中的 name 属性若与 Employee 中的 name 属性类型匹配,就会将 employeeDTOname 值复制到 employeename 属性中 。

mapper

@Insert("insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user,status) " +  
        "values " +  
        "(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})")  
void insert(Employee employee);

上面的是的属性是数据库属性,下面的是后端
因为在application.yml中已经开启了驼峰命名,所以二者可以对应

mybatis:
  configuration:
    #开启驼峰命名
    map-underscore-to-camel-case: true

问题一 重名

使用全局异常处理器来解决问题

@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex)  
{  
 //Duplicate entry 'zhangsan' for key 'employee.idx_username'
    String message = ex.getMessage();  
    if(message.contains("Duplicate entry"))  
    {  
        String[] split = message.split(" ");  
        String username=split[2];  
        String msg=username+MessageConstant.ALREADY_EXISTS;  
        return Result.error(msg);  
    }  
    else  
    {  
        return Result.error(MessageConstant.UNKNOWN_ERROR);  
    }  
}

@ExceptionHandler 是一个注解,该方法用于处理特定类型的异常。比如在上述代码中,标记的方法 exceptionHandler 专门处理 SQLIntegrityConstraintViolationException 类型的异常。当程序执行过程中抛出此类型异常时,就会执行被 @ExceptionHandler 标记的方法,根据异常信息进行相应处理,比如提取异常信息中的用户名,返回特定的错误提示给前端,告知用户用户名已存在等。

![[Pasted image 20250611202502.png]]

问题二:

新增员工时,我们需要获得当前登录员工的id
使用JWT 令牌并相应给前端

当前端指令请求时,它所携带的JWT令牌可以解析出对应员工的登录id;

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);  
        //3、通过,放行  
        return true;  
    } catch (Exception ex) {  
        //4、不通过,响应401状态码  
        response.setStatus(401);  
        return false;  
    }  
}

在拦截器中已经解析出了员工的id

Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);  
        Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());  

第一行调用JwtUtil工具类的parseJWT方法,使用配置文件中的管理员密钥对token令牌进行解析,将解析后的对象(包括id)存储在claim对象中
通过JwtClaimsContant.EMP_ID这个常量作为键,获取对应的用户ID,得到字符串之后再转换为Long类型

ThreadLocal

ThreadLocal是为每一个线程提供的一个单独的存储空间,具有线程隔离的效果,其他线程无法访问

常用方法

public void set(T value)设置
public T get()获取
public void remove()

解决流程

**![[Pasted image 20250611203416.png]]**

在初始工程中已经封装了对应类

public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();  
public static void  setCurrentId(Long id)  
{  
    threadLocal.set(id);  
}  
public static Long getCurrentId() {  
    return threadLocal.get();  
}  
public static void removeCurrentId(){  
    threadLocal.remove();  
}

在一个多线程的 Web 应用中,不同的请求线程可能需要各自独立的用户 ID 标识。可以在某个请求线程开始处理业务时,调用setCurrentId方法设置该线程的用户 ID,在处理业务过程中通过getCurrentId方法随时获取该线程的用户 ID,处理完成后调用removeCurrentId方法清理资源。

在拦截器中存储empid

 try {
            //.................
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);
            /将用户id存储到ThreadLocal
            BaseContext.setCurrentId(empId);
            
            //3、通过,放行
            return true;

在service中获取局部变量的值

    public void save(EmployeeDTO employeeDTO) {
        //.............................

        //设置当前记录创建人id和修改人id
        employee.setCreateUser(BaseContext.getCurrentId());
        employee.setUpdateUser(BaseContext.getCurrentId());

        employeeMapper.insert(employee);
    }

员工分页查询

注意:
请求参数类型为Query,在路径后拼接/admin/employee/page?name=zhangsan

返回数据中records数组使用Employee实体类对属性进行了封装

Controller

  @GetMapping("/page")
    @ApiOperation("员工分页查询")
    public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
        log.info("员工分页查询,参数为:{}", employeePageQueryDTO);
        PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);//后续定义
        return Result.success(pageResult);
    }

方法返回值类型为Result<PageResult>Result是自定义的用于封装响应结果的类,PageResult则可能是包含分页数据的类。

PageResult是一个专用的数据载体
long total 总记录数
List records 当前页实际查询到的数据集合

Result
是泛型的具体类型实例,data字段的类型是PageResult

service

@Override  
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {  
    PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());  
    Page<Employee>page=employeeMapper.pageQuery(employeePageQueryDTO);  
    long total=page.getTotal();  
    List<Employee> records=page.getResult();  
    return new PageResult(total,records);  
}

mappe层

<select id="pageQuery" resultType="com.sky.entity.Employee">  
    select * from employee    <where>  
        <if test="name !=null and name !=''">  
            and name like concat('%',${name},'%');        </if>  
    </where>  
    order by create_time desc  
</select>

查询名字中间包含name的,不需要limit,分页由pagehelper插件进行处理

操作时间字段问题

在WebMvcConfiguration中扩展SpringMVC消息转换器
Spring Boot 默认使用 MappingJackson2HttpMessageConverter 处理 JSON 序列化 / 反序列化,但 Jackson 对日期类型的默认序列化规则会导致格式异常

  • 若实体类中是 DateLocalDateTime 等日期类型,Jackson 默认可能将其序列化为 时间戳拆分的数组(如 [年, 月, 日, 时, 分, 秒],与前端期望的格式(如 2022524112024)不匹配。
  • 将自定义的 MappingJackson2HttpMessageConverter 放到转换器列表的第一位,确保优先使用(覆盖默认的 Jackson 转换器)。
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {  
    log.info("扩展消息转换器...");  
    //创建一个消息转换器对象  
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();  
    //需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据  
    converter.setObjectMapper(new JacksonObjectMapper());  
    //将自己的消息转化器加入容器内  
    converters.add(0, converter);  
}

启用禁用员工账号

Controller

@PostMapping("/status/{status}")  
@ApiOperation("启用禁用员工账号")  
public Result startOrStop(@PathVariable Integer status,
 @RequestParam("id") Long id  // 显式声明id来自请求参数
)  
{  
    log.info("启用禁用员工账号");  
    employeeService.startOrStop(status,id);  
    return Result.success();  
}

注意讲义中当前方法中 Long id 参数未显式声明参数来源。在 Spring MVC 中,未标注@PathVariable/@RequestBody等注解的参数默认会尝试从请求参数(@RequestParam)获取,但如果请求中未传递id参数,会直接抛出MissingServletRequestParameterException(400 错误)。建议修改

编辑员工

回显员工信息

controller

/**  
 * 根据id查询员工信息  
 */  
@GetMapping("/{id}")  
@ApiOperation("根据id查询员工信息")  
public Result<Employee> getById(@PathVariable Long id) {  
    Employee employee = employeeService.getById(id);  
    return Result.success(employee);  
}

service层

@Override  
public Employee getById(Long id) {  
    Employee employee = employeeMapper.getById(id);  
    employee.setPassword("****");  
    return employee;  
}

密码主动修改很细节

修改信息

service

注意这里不能用builder(),因为builder只能是创建对象的时候使用

public void update(EmployeeDTO employeeDTO) {  
    Employee employee = new Employee();  
    BeanUtils.copyProperties(employeeDTO, employee);  
    employee.setUpdateTime(LocalDateTime.now());  
    employee.setUpdateUser(BaseContext.getCurrentId());  
    employeeMapper.update(employee);  
  
}

在上一个功能中已经实现了可泛用的mapper,这里不需要再写了

导入代码

CategoryMapper.xml文件中注意到一个点
select 语句中使用where if test时需要"and",表示&
在update语句中set里不需要写表示| ,有就满足

<select id="pageQuery" resultType="com.sky.entity.Category">  
    select * from category    <where>  
        <if test="name != null and name != ''">  
            and name like concat('%',#{name},'%')        </if>  
        <if test="type != null">  
            and type = #{type}        </if>  
    </where>  
    order by sort asc , create_time desc</select>  
  
<update id="update" parameterType="Category">  
    update category    <set>  
        <if test="type != null">  
            type = #{type},        </if>  
        <if test="name != null">  
            name = #{name},        </if>  
        <if test="sort != null">  
            sort = #{sort},        </if>  
        <if test="status != null">  
            status = #{status},        </if>  
        <if test="updateTime != null">  
            update_time = #{updateTime},        </if>  
        <if test="updateUser != null">  
            update_user = #{updateUser}        </if>  
    </set>  
    where id = #{id}</update>

总结

1.DTO:

用来接受前端提交的数据以及不同层之间传递
当与实体类差别较大时使用

2.Result

通用后端统一返回结果封装类,向前端返回统一格式的响应数据

3.public static Result

是一个Java泛型方法的声明,前一个表明了泛型方法的类型参数声明部分
Result表示该方法返回一个Result类型的对象
static表示这是一个类方法,可通过类名调用

4.BeanUtils.copyProperties()

一般将DTO赋值给对应的对象

5.重名

ExceptionHandler注解,当程序抛出对应异常时,会执行被@ExceptionHandler标记的方法

6.JWT令牌

通过JwTUtil.parseJWT可以解析令牌

7.ThreadLocal

仅本线程可用存储空间
set get remove

8.分页查询

PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
第一个参数employeePageQueryDTO.getPage()获取到的是要查询的页码,第二个参数employeePageQueryDTO.getPageSize()获取到的是每页显示的记录数

方法返回值类型为Result
PageResult是专门的数据载体,包括List records,即当前页查询到的数据集合
records

9.操作时间字段

更换自定义的转换器覆盖原有

10.启用禁用员工账号

未表明数据来源会默认从请求参数(@RequestParam)获取,即带有查询参数(Query Parameters)的 URL 链接?xx=xx

11.xml文件 if条件

在select时要保证全满足,即&,所以if 后要加"and"
在update时满足一个修改一个,即 |,所以If 后不用加