文章目录
- 一、前言
- 二、拦截器简介
- 三、代码目录结构简介
- 四、核心代码讲解
- 4.1 application.yml文件
- 4.2 自定义注解
- 4.2.1 SensitiveEntity
- 4.2.2 SensitiveData
- 4.2.3 MaskedEntity
- 4.2.4 MaskedField
- 4.2.5 MaskedMethod
- 4.3 Mybatis-Plus 拦截器数据自动加密
- 4.4 Mybatis 打印完整sql的拦截器
- 4.4.1 打印结果示例
- 4.5 Mybatis-Plus 配置类
- 4.6 CustomHandlerMethodReturnValueHandler
- 4.7 敏感数据类型枚举
- 4.8 Mvc拦截器配置
- 五、测试结果
- 5.1 数据库中
- 5.2 查询结果脱敏
一、前言
看完本文你将能学到什么?
- 自定义mybatis-plus拦截器,对指定数据更新时自动加密处理;
- 自定义mybatis拦截器,打印完整sql;
- 自定义springboot-starter;
- 自定义注解;
- 自定义 HandlerMethodReturnValueHandler 处理接口响应结果,我这里是使用它对需要的数据进行拦截处理,解密/脱敏;
- mybatis-plus 基本的增删改查api操作;
文章对应的完整代码仓库:
https://gitee.com/fengsoshuai/mybatis-plus-interceptor-demo
二、拦截器简介
Mybatis Plus 的拦截器终极奥义是使用了 Mybatis 的拦截器。
只是在原先的基础上,划分的更加细致了。缺点也很明确,没有处理响应结果的钩子方法。
Mybatis Plus 中的拦截器的定义是:
@Intercepts({@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class, Integer.class}
), @Signature(type = StatementHandler.class,method = "getBoundSql",args = {}
), @Signature(type = Executor.class,method = "update",args = {MappedStatement.class, Object.class}
), @Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})
public class MybatisPlusInterceptor implements Interceptor {// 省略全部代码...// mybatis-plus 的拦截器集合private List<InnerInterceptor> interceptors = new ArrayList();}
可以看到Mybatis Plus 拦截器的处理器, 其实现了Interceptor
,在内部遍历interceptors
,处理sql执行前的数据。
一般可以用作打印sql,或者按照某些条件拼接sql的条件(比如数据权限分离)。
三、代码目录结构简介
四、核心代码讲解
4.1 application.yml文件
额外定义了mybatis拦截器配置,主要是配置是否启用打印sql,或数据加密等拦截器。
# 数据源
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mp_interceptor_db?useUnicode=true&serverTimezone=UTCusername: rootpassword: roottype: com.alibaba.druid.pool.DruidDataSource# mybatis plus
mybatis-plus:# xml扫描,多个目录用逗号或者分号分隔(告诉mapper所对应的xml文件位置)mapper-locations: classpath*:mapper/**Mapper.xml# 以下配置均有默认值global-config:db-config:#主键类型 auto:"数据库ID自增" 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";id-type: auto# 全局逻辑删除的实体字段名logic-delete-field: deleted# 逻辑已删除值(默认为 1)logic-delete-value: 1# 逻辑未删除值(默认为 0)logic-not-delete-value: 0configuration:# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射map-underscore-to-camel-case: true# 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段call-setters-on-nulls: true# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 扫描实体type-aliases-package: org.feng.entity# 自定义mybatis拦截器配置
mybatis:interceptor:property:enable-sensitive: trueenable-illegal-sql: trueenable-optimistic-locker: trueprint-sql: true
4.2 自定义注解
4.2.1 SensitiveEntity
标注一个实体类,是否包含需要加密的字段。比如User类中有属性 phone,需要加密存储,则可以在User类上使用该注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SensitiveEntity {
}
4.2.2 SensitiveData
在标注有SensitiveEntity注解的实体中,使用本注解标注某个字段,表示该字段是加密的,并且指定加密类型(以哪种加密算法加密的)。
本项目中,重点在于代码设计,加密算法就使用了最简单的 Base64转码的方式,不喜勿喷!
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SensitiveData {/*** 指定加解密类型** @return 类型,{@link AbstractSensitive}*/String sensitiveType() default "";
}
4.2.3 MaskedEntity
标注一个实体是需要脱敏处理的。不一定会真正执行,需要和 MaskedMethod注解搭配使用。
比如本项目中,需要对 UserVO
进行脱敏处理。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MaskedEntity {
}@Data
@MaskedEntity
public class UserVO implements Response {/*** 用户名*/private String username;/*** 年龄*/private Integer age;/*** 邮箱*/@MaskedField(type = SensitiveDataTypeEnum.EMAIL)private String email;/*** 电话*/@MaskedField(type = SensitiveDataTypeEnum.PHONE, sensitiveType = Base64Sensitive.SENSITIVE_TYPE_CODE)private String phone;/*** 创建时间*/private LocalDateTime createTime;/*** 更新时间*/private LocalDateTime updateTime;public UserVO copyFieldByUser(@NonNull User user) {this.setUsername(user.getUsername());this.setAge(user.getAge());this.setPhone(user.getPhone());this.setEmail(user.getEmail());this.setCreateTime(user.getCreateTime());this.setUpdateTime(user.getUpdateTime());return this;}
}
4.2.4 MaskedField
用于在标注了MaskedEntity的实体中的单个字段上,表示该字段需要脱敏处理。
必须同时指定脱敏数据类型,比如是手机脱敏,还是邮箱脱敏等。
加密类型可以不指定,在指定时会进行解密处理,不指定则当做明文来操作,只进行脱敏数据。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MaskedField {/*** 指定脱敏数据类型** @return 脱敏数据类型*/SensitiveDataTypeEnum type();/*** 加密类型** @return 加密类型编码*/String sensitiveType() default "";
}
4.2.5 MaskedMethod
用于标注在Controller内的带有 ResponseBody的方法上。
表示该方法的返回值需要进行数据脱敏处理。
内部使用gson序列化为json,最终返回给调用方。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MaskedMethod {
}
比如在Controller中定义:
@MaskedMethod
@GetMapping("/list")
public ResponseEntity<List<UserVO>> list() {return new ResponseEntity<>("查询用户数据成功", "200", userService.listUser());
}
4.3 Mybatis-Plus 拦截器数据自动加密
对应的类是:MybatisPlusSensitiveInterceptor
具体实现如下:
package org.feng.interceptor;import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import lombok.Builder;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.feng.annotions.SensitiveData;
import org.feng.sensitive.AbstractSensitive;
import org.feng.util.SensitiveUtil;
import org.springframework.util.StringUtils;import java.lang.reflect.Field;
import java.util.Objects;/*** 处理密文拦截器** @version v1.0* @author: fengjinsong* @date: 2023年08月24日 23时07分*/
@Slf4j
@Builder
@Accessors(chain = true)
public class MybatisPlusSensitiveInterceptor implements InnerInterceptor {/*** 全局的加/解密处理*/private AbstractSensitive sensitive;@Overridepublic void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {// 执行加密操作executeSensitive(parameter);}private void executeSensitive(Object parameter) {Class<?> entityClass = parameter.getClass();// 当前实体类有标注了SensitiveEntity注解if (SensitiveUtil.isSensitiveEntity(parameter)) {Field[] fields = entityClass.getDeclaredFields();for (Field field : fields) {if (field.isAnnotationPresent(SensitiveData.class)) {SensitiveData sensitiveData = field.getAnnotation(SensitiveData.class);String sensitiveType = sensitiveData.sensitiveType();// 字段注解传的sensitiveType有值if (StringUtils.hasLength(sensitiveType)) {// 获取缓存中的实例AbstractSensitive sensitiveByType = AbstractSensitive.getSensitiveByType(sensitiveType);if (Objects.isNull(sensitiveByType)) {throw new RuntimeException("加解密类型设置错误,类型不存在");}// 重置变量的值SensitiveUtil.encryptFieldValue(field, parameter, sensitiveByType);continue;} else if (Objects.nonNull(sensitive)) {// 重置变量的值SensitiveUtil.encryptFieldValue(field, parameter, sensitive);continue;}throw new RuntimeException("未指定加、解密类型");}}}}
}
4.4 Mybatis 打印完整sql的拦截器
拦截Executor的查询和更新的方法,拼接sql语句并打印。
package org.feng.interceptor;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.feng.util.TimeUtil;
import org.springframework.util.CollectionUtils;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Objects;/*** mybatis拦截器拦截处理查询、更新的方法,mybatis-plus拦截器见:{@link MybatisPlusInterceptor}** @version v1.0* @author: fengjinsong* @date: 2023年08月25日 23时23分*/@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
})
@Slf4j
public class MybatisPrintSqlInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 获取语句映射对象Object[] invocationArgs = invocation.getArgs();MappedStatement mappedStatement = (MappedStatement) invocationArgs[0];// 获取参数(条件)Object paramObject = null;// 2个以上的入参,也就是有额外的查询或更新条件if (invocationArgs.length > 1) {paramObject = invocationArgs[1];}BoundSql boundSql = mappedStatement.getBoundSql(paramObject);Configuration configuration = mappedStatement.getConfiguration();String mappedStatementId = mappedStatement.getId();// 开始执行时间long start = System.currentTimeMillis();// 执行方法Object returnValue = invocation.proceed();// 执行耗时long executeTime = System.currentTimeMillis() - start;// 拼接sql,参数注入String sql = concatSql(configuration, boundSql);// 打印sqllogs(executeTime, sql, mappedStatementId);return returnValue;}private String concatSql(Configuration configuration, BoundSql boundSql) {Object parameterObject = boundSql.getParameterObject();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//替换空格、换行、tab缩进等String sql = boundSql.getSql().replaceAll("[\\s]+", " ");if (!CollectionUtils.isEmpty(parameterMappings) && Objects.nonNull(parameterObject)) {TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);for (ParameterMapping parameterMapping : parameterMappings) {String propertyName = parameterMapping.getProperty();if (metaObject.hasGetter(propertyName)) {Object obj = metaObject.getValue(propertyName);sql = sql.replaceFirst("\\?", getParameterValue(obj));} else if (boundSql.hasAdditionalParameter(propertyName)) {Object obj = boundSql.getAdditionalParameter(propertyName);sql = sql.replaceFirst("\\?", getParameterValue(obj));}}}}return sql;}private String getParameterValue(Object obj) {String value;if (obj instanceof String) {value = "'" + obj + "'";} else if (obj instanceof Date) {value = "'" + TimeUtil.defaultFormat(((Date) obj).toInstant()) + "'";} else if (obj instanceof LocalDateTime) {value = "'" + TimeUtil.defaultFormat((LocalDateTime) obj) + "'";} else if (obj instanceof LocalDate) {value = "'" + TimeUtil.defaultFormat((LocalDate) obj) + "'";} else {if (obj != null) {value = obj.toString();} else {value = "";}}return value.replace("$", "\\$");}private void logs(long time, String sql, String sqlId) {log.info("\r\n执行SQL:{} \r\n执行耗时:{}ms, 执行方法:{}", sql, time, sqlId);}@Overridepublic Object plugin(Object target) {// 如果是Executor(执行增删改查操作),则拦截下来if (target instanceof Executor) {return Plugin.wrap(target, this);}return target;}
}
4.4.1 打印结果示例
2023-08-26 17:11:40.192 INFO 21248 --- [nio-8080-exec-1] o.f.i.MybatisPrintSqlInterceptor :
执行SQL:SELECT id,username,age,email,phone,create_time,update_time,deleted FROM mp_user WHERE deleted=0 AND (username = '牛大山')
执行耗时:221ms, 执行方法:org.feng.mapper.UserMapper.selectList
4.5 Mybatis-Plus 配置类
添加拦截器。
package org.feng.config;import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.IllegalSQLInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.feng.interceptor.MybatisPlusSensitiveInterceptor;
import org.feng.interceptor.MybatisPrintSqlInterceptor;
import org.feng.properties.MybatisInterceptorProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;import javax.annotation.PostConstruct;
import javax.annotation.Resource;/*** Mybatis-Plus插件之拦截器的自动配置类<br>* <a href="https://baomidou.com/pages/2976a3/#mybatisplusinterceptor">插件官网链接</a>** @version v1.0* @author: fengjinsong* @date: 2023年08月24日 22时01分*/
@Slf4j
@AutoConfiguration
public class MybatisInterceptorConfiguration {@Resourceprivate MybatisInterceptorProperties mybatisInterceptorProperties;/*** 配置拦截器** @return 拦截器*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 自定义拦截:密文处理if (mybatisInterceptorProperties.isEnableSensitive()) {log.info("mybatis注册拦截器:密文处理");interceptor.addInnerInterceptor(MybatisPlusSensitiveInterceptor.builder().build());}// sql性能规范if (mybatisInterceptorProperties.isEnableIllegalSql()) {log.info("mybatis注册拦截器:SQL性能规范检查");interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());}// 乐观锁if (mybatisInterceptorProperties.isEnableOptimisticLocker()) {log.info("mybatis注册拦截器:乐观锁");interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());}return interceptor;}@Beanpublic ConfigurationCustomizer configurationCustomizer() {return configuration -> {// SQL 打印拦截if (mybatisInterceptorProperties.isPrintSql()) {log.info("mybatis注册拦截器:打印sql");configuration.addInterceptor(new MybatisPrintSqlInterceptor());}};}@PostConstructprivate void init() {log.info("Mybatis-Plus插件之拦截器的自动配置类 init");}
}
4.6 CustomHandlerMethodReturnValueHandler
自定义方法返回值处理器,用做数据脱敏处理。
package org.feng.common;import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandler;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;/*** 自定义方法返回结果处理器** @version v1.0* @author: fengjinsong* @date: 2023年08月26日 14时15分*/
@Slf4j
public class CustomHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler, AsyncHandlerMethodReturnValueHandler {@Overridepublic boolean supportsReturnType(MethodParameter returnType) {// 方法上标注了ResponseBody,就处理return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class))// 当前方法返回值需要数据脱敏&& (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), MaskedMethod.class) || returnType.hasMethodAnnotation(MaskedMethod.class));}@Overridepublic void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {mavContainer.setRequestHandled(true);HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);assert response != null;response.setContentType("application/json;charset=utf-8");if (returnValue instanceof ResponseEntity) {ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnValue;Object data = responseEntity.getData();// 响应结果是集合if (data instanceof List) {List<?> dataList = (List<?>) data;// 集合为空,直接返回if (CollectionUtils.isEmpty(dataList)) {response.getWriter().write(GsonUtil.toJsonWithNull(returnValue));return;}// 处理集合结果集Class<?> singleDataClass = dataList.get(0).getClass();// 是否标注为脱敏数据实体boolean maskedEntity = singleDataClass.isAnnotationPresent(MaskedEntity.class);// 不是脱敏实体,不用处理if (!maskedEntity) {response.getWriter().write(GsonUtil.toJsonWithNull(returnValue));return;}// 处理数据脱敏for (Object singleData : dataList) {Field[] fields = singleDataClass.getDeclaredFields();for (Field field : fields) {doMaskedField(field, singleData);}}response.getWriter().write(GsonUtil.toJsonWithNull(returnValue));return;} else {// 非集合响应结果处理Class<?> dataClass = data.getClass();// 是否标注为脱敏数据实体boolean maskedEntity = dataClass.isAnnotationPresent(MaskedEntity.class);// 不是脱敏实体,不用处理if (!maskedEntity) {response.getWriter().write(GsonUtil.toJsonWithNull(returnValue));return;}// 脱敏数据Field[] fields = dataClass.getDeclaredFields();for (Field field : fields) {doMaskedField(field, data);}}}// 序列化响应response.getWriter().write(GsonUtil.toJsonWithNull(returnValue));}/*** 脱敏数据处理:暂不支持复杂对象(嵌套)** @param field 属性对象* @param object 对象本身*/private void doMaskedField(Field field, Object object) {// 标注了MaskedFieldboolean maskedField = field.isAnnotationPresent(MaskedField.class);if (maskedField) {field.setAccessible(true);MaskedField maskedFieldAnnotation = field.getAnnotation(MaskedField.class);// 脱敏数据try {SensitiveDataTypeEnum dataTypeEnum = maskedFieldAnnotation.type();Object fieldValue = field.get(object);if (Objects.nonNull(fieldValue)) {field.set(object, dataTypeEnum.doDecryptAndMaskedField(fieldValue.toString(), maskedFieldAnnotation.sensitiveType()));}} catch (IllegalAccessException e) {log.error("脱敏失败", e);throw new RuntimeException("脱敏失败");}field.setAccessible(false);}}@Overridepublic boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {return supportsReturnType(returnType);}
}
4.7 敏感数据类型枚举
将对应的类型和脱敏方法,加解密规则进行绑定。
package org.feng.common;import lombok.AllArgsConstructor;
import lombok.Getter;
import org.feng.sensitive.AbstractSensitive;
import org.feng.util.SensitiveUtil;import java.util.Objects;/*** 敏感数据类型** @version v1.0* @author: fengjinsong* @date: 2023年08月26日 00时20分*/
@Getter
@AllArgsConstructor
public enum SensitiveDataTypeEnum {/*** 手机类型*/PHONE() {@OverrideString doSensitive(String originalData) {return SensitiveUtil.maskedPhone(originalData);}@OverrideString decrypt(String encryptData, String sensitiveType) {AbstractSensitive sensitive = AbstractSensitive.getSensitiveByType(sensitiveType);if (Objects.isNull(sensitive)) {return encryptData;}return sensitive.decrypt(encryptData);}},ID_CARD,EMAIL(){@OverrideString doSensitive(String originalData) {return SensitiveUtil.maskedEmail(originalData);}@OverrideString decrypt(String encryptData, String sensitiveType) {AbstractSensitive sensitive = AbstractSensitive.getSensitiveByType(sensitiveType);if (Objects.isNull(sensitive)) {return encryptData;}return sensitive.decrypt(encryptData);}},BANK_CARD,ADDRESS,CUSTOM;/*** 脱敏数据** @param originalData 原数据* @return 脱敏后的数据*/String doSensitive(String originalData) {throw new RuntimeException(this.name() + " 暂不支持脱敏数据");}String decrypt(String encryptData, String sensitiveType) {throw new RuntimeException(this.name() + " 暂不支持解密数据");}/*** 解密数据并脱敏** @param encryptData 密文数据* @param sensitiveType 加密类型* @return 解密并脱敏后的数据*/String doDecryptAndMaskedField(String encryptData, String sensitiveType) {String decryptText = decrypt(encryptData, sensitiveType);return doSensitive(decryptText);}
}
4.8 Mvc拦截器配置
package org.feng.config;import org.feng.common.CustomHandlerMethodReturnValueHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.util.List;/*** MVC配置** @version v1.0* @author: fengjinsong* @date: 2023年08月26日 14时06分*/
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {@Overridepublic void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {returnValueHandlers.add(new CustomHandlerMethodReturnValueHandler());}
}
五、测试结果
5.1 数据库中
插入数据时,没有手动调用base64转码,手机号自动调用加密方法,转为base64
5.2 查询结果脱敏
在UserVO中使用注解(这里省略其他字段):
@Data
@MaskedEntity
public class UserVO implements Response {/*** 邮箱*/@MaskedField(type = SensitiveDataTypeEnum.EMAIL)private String email;/*** 电话*/@MaskedField(type = SensitiveDataTypeEnum.PHONE, sensitiveType = Base64Sensitive.SENSITIVE_TYPE_CODE)private String phone;
}
以上注解表示对该类对象进行脱敏数据处理,并指定了email字段的脱敏规则,phone的解密规则和脱敏规则。
可以看到响应结果中,手机号是解密了的,并且进行了数据脱敏。
邮箱因为没有加密存储,使用注解标注后,也进行了数据脱敏。
{"data": [{"username": "小冯","age": 27,"email": null,"phone": null,"createTime": "2023-08-24 23:45:59","updateTime": "2023-08-24 23:45:59"},{"username": "小李","age": 25,"email": null,"phone": null,"createTime": "2023-08-24 23:46:16","updateTime": "2023-08-24 23:46:16"},{"username": "小刘","age": 32,"email": null,"phone": null,"createTime": "2023-08-24 23:46:26","updateTime": "2023-08-24 23:46:26"},{"username": "牛山","age": 22,"email": "f***g@163.com","phone": "181****5213","createTime": "2023-08-25 21:22:02","updateTime": "2023-08-25 21:22:02"},{"username": "牛大山","age": 23,"email": "f***g@163.com","phone": "181****5213","createTime": "2023-08-25 22:49:02","updateTime": "2023-08-25 22:49:02"}],"message": "查询用户数据成功","code": "200"
}