定义实体
在qk-entity包中定义一个实体类User.entity,示例代码如下:
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),需要新增的字段示例代码如下:
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 值会发生完全变化,差异极大
一般使用流程如下:
- 在往数据库保存用户信息的时候,要对明文的密码进行MD5加密,将加密后的密文存储再数据库表中
- 在用户登录的时候,前端输入的是明文密码,那么此时我们要将明文进行MD5加密,然后再跟数据库中查询出来的密文进行对比
但是如果直接使用MD5加密 , 如果用户密码设置的比较简单 , 可能会被穷举法破解 , 安全度不高, 为了提高用户密码的强度和安全性, 在使用MD5加密的时候一般会采用加盐的方式对密码进行加密
- 盐 : 盐就是一个随机字符串, 在新增用户的时候生成一个指定位数的随机字符串作为盐值
- 加盐加密 : 使用用户(明文密码 + 盐值)一起做MD5加密生成密文 , 将密文保存到数据库中
- 登录比对 : 根据用户输入用户名在数据库中查询出用户信息(密码和盐) , 再使用用户输入的明文密码+盐进行MD5加密得到密文, 和数据库中查询出来的密文进行比对
- 如果一致说明密码正确 , 登录成功
- 如果不一致说明密码错误 , 登录失败
1.扩展数据库增加一个salt字段存储盐值
alter table user add salt char(10) comment '密码盐值'2.实体类新增字段
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.新增用户生成盐值,密码加盐加密之后存储到数据库
@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.测试加盐加密之后的密码比对
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:新增和更新都填充
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 方法。。
⚠️ 注意点:
- 实体字段必须加
@TableField(fill = xxx),否则不触发填充 - strictFill:如果用户手动传了值,优先用用户值,不覆盖; 如果想用 MP 强制覆盖,不用 strict,用
setFieldValByName() - 填充处理器要交给 Spring 管理(加
@Component) - 类型要对应:Date / LocalDateTime
@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());
}
}填充有三种方式, 注意三种方式的区别
推荐使用第三种: 第三种的源码如下, 他是先判断有该属性才会填充, 底层调用依然是第二种, 在第二种方式的基础上加了健壮性的判断
default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
if (Objects.nonNull(fieldVal) && metaObject.hasSetter(fieldName)) {
metaObject.setValue(fieldName, fieldVal);
}
return this;
}3.真实案例
首先我们需要给我们的Role的实体类需要填充的字段添加注解
@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 编写填充逻辑:
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返回到前端的时候会自动忽略被标注的字段,代码示例如下:
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;
}