Skip to content

定义实体 ​

在qk-entity包中定义一个实体类User.entity,示例代码如下:

java
package com.cghbh.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * 用户实体类
 * 对应数据库 user 表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
    /**
     * 主键ID
     */
    private Integer id;

    /**
     * 登录账号
     */
    private String username;

    /**
     * 登录密码
     */
    private String password;

    /**
     * 用户真实姓名
     */
    private String name;

    /**
     * 手机号码
     */
    private String phone;

    /**
     * 邮箱地址
     */
    private String email;

    /**
     * 性别:0-女,1-男,2-未知
     */
    private Integer gender;

    /**
     * 用户状态:0-禁用,1-正常
     */
    private Integer status;

    /**
     * 所属部门ID
     */
    private Integer deptId;

    /**
     * 角色ID
     */
    private Integer roleId;

    /**
     * 用户头像图片地址
     */
    private String image;

    /**
     * 备注信息
     */
    private String remark;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;

}

返回字段处理 ​

在之前的分页功能自定义联表查询的语句中, SQL语句查询部门名称deptName和角色名称roleName, 这两个字段需要在实体类中扩展才能够接收数据库返回的结果 , 且这两个字段不是user表字段,需要添加 @TableField(exist = false),需要新增的字段示例代码如下:

java
package com.cghbh.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * 用户实体类
 * 对应数据库 user 表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
    /**
     * 部门名称
     * 在数据库中排除这两个字段,但是实际返回的时候需要使用
     */
    @TableField(exist = false) 
    private String deptName; 

    /**
     * 角色名称
     * 在数据库中排除这两个字段,但是实际返回的时候需要使用
     */
    @TableField(exist = false) 
    private String roleName; 
}

密码加密处理 ​

在user表中,用户的密码目前还处于裸奔状态,因为我们需要进行加密处理。

密码加密的方式有很多,使用最为广泛的当属MD5,它是一种信息摘要算法,他可以根据我们提供的明文产生一个密文,特点如下:

  • **定长输出:**无论原始内容长短,最终结果固定为32位十六进制字符串
  • **单向不可逆:**只能将原文生成密文,无法从密文还原出原始数据
  • **雪崩效应:**原文微小改动,MD5 值会发生完全变化,差异极大

一般使用流程如下:

  1. 在往数据库保存用户信息的时候,要对明文的密码进行MD5加密,将加密后的密文存储再数据库表中
  2. 在用户登录的时候,前端输入的是明文密码,那么此时我们要将明文进行MD5加密,然后再跟数据库中查询出来的密文进行对比

但是如果直接使用MD5加密 , 如果用户密码设置的比较简单 , 可能会被穷举法破解 , 安全度不高, 为了提高用户密码的强度和安全性, 在使用MD5加密的时候一般会采用加盐的方式对密码进行加密

  • 盐 : 盐就是一个随机字符串, 在新增用户的时候生成一个指定位数的随机字符串作为盐值
  • 加盐加密 : 使用用户(明文密码 + 盐值)一起做MD5加密生成密文 , 将密文保存到数据库中
  • 登录比对 : 根据用户输入用户名在数据库中查询出用户信息(密码和盐) , 再使用用户输入的明文密码+盐进行MD5加密得到密文, 和数据库中查询出来的密文进行比对
    • 如果一致说明密码正确 , 登录成功
    • 如果不一致说明密码错误 , 登录失败

1.扩展数据库增加一个salt字段存储盐值 ​

sql
alter table user add salt char(10) comment '密码盐值'

2.实体类新增字段 ​

java
package com.cghbh.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * 用户实体类
 * 对应数据库 user 表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
    /**
     * 密码加密盐值
     */
    private String salt; 
}

3.新增用户生成盐值,密码加盐加密之后存储到数据库 ​

java
@Override 
public void addUser(User user) {
    // 获取用户密码,默认是用户名+123,这里仅做演示,生产勿用!!!
    String pwd = user.getUsername() + "123";
    // 生成随机盐值字符串
    String salt = RandomUtil.randomString(10);
    // 保存盐值
    user.setSalt(salt);
    // 使用盐做加盐加密生成密文
    String md5Hex = DigestUtil.md5Hex(pwd + salt);
    // 将密码和盐值加密之后存储到数据库
    user.setPassword(md5Hex);
    userMapper.insert(user);
}

4.测试加盐加密之后的密码比对 ​

java
package com.cghbh;

import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.itheima.entity.User;
import com.itheima.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class UserLoginTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    void testLogin() {
        String username = "xiaoli";
        String password = "xiaoli1234";
        // 1. 根据用户名查询用户信息(包含密文密码和盐)
        User user = userMapper.selectOne(Wrappers.lambdaQuery(User.class).eq(User::getUsername, username));
        if (user == null) {
            throw new RuntimeException("用户名不存在");
        }
        // 2. 使用用户输入的密码 + 盐进行MD5加密
        String md5Hex = DigestUtil.md5Hex(password + user.getSalt());
        // 3. 比对数据库中存储的密码和加密的密码是否相同
        if (user.getPassword().equals(md5Hex)) {
            System.out.println("登录成功");
        } else {
            System.out.println("登录失败");
        }
    }
}

自动填充功能通过实现 com.baomidou.mybatisplus.core.handlers.MetaObjectHandler 接口来实现。你需要创建一个类来实现这个接口,并在其中定义插入和更新时的填充逻辑。

字段的自动填充 ​

1.定义实体类 ​

在实体类中,你需要使用 @TableField 注解来标记哪些字段需要自动填充,并指定填充的策略。

FieldFill 可选值:

  • FieldFill.INSERT:仅新增填充
  • FieldFill.UPDATE:仅更新填充
  • FieldFill.INSERT_UPDATE:新增和更新都填充
java
package com.cghbh.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * 用户实体类
 * 对应数据库 user 表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
    /**
     * 创建时间
     * 新增时自动填充
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新时间
     * 新增和修改时自动填充
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

2.实现 MetaObjectHandler ​

创建一个类来实现 MetaObjectHandler 接口,并重写 insertFill 和 updateFill 方法。。

⚠️ 注意点:

  1. 实体字段必须加 @TableField(fill = xxx),否则不触发填充
  2. strictFill:如果用户手动传了值,优先用用户值,不覆盖; 如果想用 MP 强制覆盖,不用 strict,用 setFieldValByName()
  3. 填充处理器要交给 Spring 管理(加@Component)
  4. 类型要对应:Date / LocalDateTime
java
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("开始插入填充...");
        // 第一种填充方式 ===> 如果目标属性有值不会填充
        this.strictInsertFill(metaObject, "createUserId", Long.class, 123456L);
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        // 第二种填充方式 ===> 目标不管该属性是否有值, 都会被填充
        metaObject.setValue("createTime", LocalDateTime.now());
        // 第三种填充方式 ===> 在填充前会判断是否有这个属性, 如果有才会填充,推荐使用这个
        this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
        
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("开始更新填充...");
        this.strictInsertFill(metaObject, "updateUserId", Long.class, 123456L);
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

填充有三种方式, 注意三种方式的区别

推荐使用第三种: 第三种的源码如下, 他是先判断有该属性才会填充, 底层调用依然是第二种, 在第二种方式的基础上加了健壮性的判断

Java
default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
    if (Objects.nonNull(fieldVal) && metaObject.hasSetter(fieldName)) {
        metaObject.setValue(fieldName, fieldVal);
    }
    return this;
}

3.真实案例 ​

首先我们需要给我们的Role的实体类需要填充的字段添加注解

Java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {
    private Integer id; //部门编号
    private String name; //部门名称
    private String label; //部门标签
    private String remark; //部门描述
    @TableField(fill =  FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime; //创建时间
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime; //更新时间
}

我们可以对之前的Role表的操作进行优化, 首先需要定义MetaObjectHandler 编写填充逻辑:

java
package com.cghbh.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("开始插入填充...");
        // 方式一 : 如果字段有值就不填充, 没有值才填充
        // this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        // 方式二 : 不管字段是否有值都要填充为新值 , 如果字段不存在会报错
        // metaObject.setValue("createTime",LocalDateTime.now());
        // 方式三 : 判断字段是否存在, 如果字段存在就填充(不管之前有没有值)
        this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("开始更新填充...");
        // 如果字段有值就不填充, 没有值才填充
        // this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
    }
}

这样后续和createTime和updateTime有关的数据插入则会自动完成了。

实体类的字段忽略 ​

之前在对密码进行处理的时候,像密码、盐值这些信息比较敏感, 查询用户详情返回到前端的时候应该把密码、盐值信息屏蔽掉,只需要在实体类密码属性上添加一个@JsonIgnore注解即可 , 在将查询到的实体类对象转化为JSON返回到前端的时候会自动忽略被标注的字段,代码示例如下:

java
package com.cghbh.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * 用户实体类
 * 对应数据库 user 表
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
    /**
     * 登录密码
     * 序列化JSON时忽略该字段,防止密码返回前端
     */
    @JsonIgnore
    private String password;

    /**
     * 密码加密盐值
     * 序列化JSON时忽略该字段,不返回前端
     */
    @JsonIgnore
    private String salt;
}