ruoyi-vue-plus-序列化功能
# 序列化功能
ruoyi-vue-plus 采用序列化处理对象 , 通过 注解 + jackson序列化处理器层面 进行动态处理属性 . ruoyi-vue-plus采用序列化处理功能 :
# 翻译功能
ruoyi-vue-plus 翻译功能 是根据对象中的某一个明确的属性值翻译成预期的值 , 只需在对象属性中添加翻译的注解即可实现
翻译方式 :
- 映射翻译 (根据另一个字段翻译保存)
- 直接翻译 (根据字段值替换. 属性类型也会替换)
- 字典翻译 (根据other条件, 自定义使用)
# 应用
以下应用基于 源码翻译实现类 实现
ruoyi-vue-plus翻译功能应用文档 : https://plus-doc.dromara.org/ (opens new window)
# 映射翻译
注解必填
type
: 翻译处理器keymapper
: 映射属性名 (按userId翻译填充username)
@Data
public class TestUser implements Serializable {
private Long userId;
@Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "userId")
private String username;
}
# 直接翻译
只需填写 翻译处理器key , 应用本身的值进行翻译填充 (能够无视类型填充)
@Data
public class TestUser implements Serializable {
@Translation(type = TransConstant.OSS_ID_TO_URL)
private String ossids;
}
# 字典翻译
注解必填
type
: 翻译处理器keyother
: 字典映射key
@Data
public class TestUser implements Serializable {
@Translation(type = TransConstant.DICT_TYPE_TO_LABEL, other = "sys_user_sex")
private String sex;
}
# 自定义翻译
创建翻译器实现类
@AllArgsConstructor
@TranslationType(type = TransConstant.DIY_KEY)
public class DiyTranslationImpl implements TranslationInterface<String> {
// 业务实现类
private final XxxService xxxService;
/**
* 翻译处理过程
* @param key 映射属性的值 / 属性本身的值
* @param other 拓展处理标识
* @return 处理结果的值 (任意类型)
*/
@Override
public String translation(Object key, String other) {
// 处理过程...
return null;
}
}
实现类赋予常量key
public interface TransConstant {
// ....
String DIY_KEY = "diy_key";
}
对象属性 引入 @Translation
注解
# Contorller层应用
点击展开
@Resource
public ObjectMapper objectMapper;
@GetMapping("/test")
public void test() throws JsonProcessingException {
TestUser user = new TestUser();
user.setUserId(1L);
user.setDeptIds("100");
user.setSex("1");
/**
* 编程式模拟序列化过程 , 采用翻译采用的是 Jackson序列化
*/
// 序列化
String json = objectMapper.writeValueAsString(user);
System.out.println("json = " + json);
}
@Data
static class TestUser {
private Long userId;
@Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "userId")
private String username;
@Translation(type = TransConstant.OSS_ID_TO_URL)
private String ossids;
@Translation(type = TransConstant.DEPT_ID_TO_NAME)
private String deptIds;
@Translation(type =TransConstant.DICT_TYPE_TO_LABEL, other = "sys_user_sex")
private String sex;
}
# 实现原理
实现方案 : jackson序列化 , 在属性修饰符处理程序分配 自己的Null值处理器 (默认情况下的序列化不会对null值进行处理)
翻译序列化处理过程 :
启动初始化时 (TranslationConfig#init())
- 循环所有翻译转化的实现类Bean (1-3)
- 判断反射判断是否实现有
TranslationInterface
转化接口 - Map存储 , 实现有则进行对其对象存储 , key为注解中的配置
- 存储所有Baen的Map放到翻译处理器类的常量中
- 设置序列化器 , 设置为
TranslationBeanSerializerModifier
类 自己的处理器
首次序列化应用时 (TranslationBeanSerializerModifier#changeProperties())
回调获取注解信息 (TranslationHandler#createContextual()) . 对每个
@JsonSerialize(using = TranslationHandler.class)
注解 的属性进行获取注解信息@Translation
注解判断 . 遍历序列化对象的所有属性 判断是否引用有@JsonSerialize(using = TranslationHandler.class)
PS :
@Translation
注解引用有@JsonSerialize(using = TranslationHandler.class)
注解Null值序列化器 . 对象属性使用有注解 , 那么对该 对象属性进行 设置Null序列化器处理(
TranslationHandler
类处理)
序列化处理时 (TranslationHandler#serialize())
- Map集中根据上下文key获取翻译处理的Bean
- 判空 翻译处理Bean , 空则直接返回 , 否则 翻译处理
- 映射翻译处理 , 根据注解
mapper
属性控制 , 通过对象的属性名反射Get对应属性值并设到 value 中 - 判空处理 , 如果 value 为null , 那么直接设置null , 跳出翻译处理 (意味着该属性无任何翻译处理 . 该属性无默认值、无映射属性)
- 直接&字典 翻译处理 , 翻译处理Bean 翻译处理 (参数中的other是按字典key处理)
提示
翻译转化实现类 必须要 TranslationType注解 和 TranslationInterface接口 一同使用
# 源码
对象定义时对对象进行翻译处理
核心
类&注解 | 说明 |
---|---|
TranslationConfig | 翻译功能配置类 |
TranslationType | 翻译类型注解 |
TranslationInterface<T> | 翻译实现类接口 |
TranslationHandler | 翻译处理器 |
TranslationBeanSerializerModifier | Bean序列化配置修改类 |
翻译处理类
类 | 说明 |
---|---|
DeptNameTranslationImpl | 部门翻译 |
DictTypeTranslationImpl | 字典翻译 |
OssUrlTranslationImpl | OSS翻译 |
UserNameTranslationImpl | 用户名翻译 |
# 核心源码
TranslationConfig
翻译模块配置类
点击展开
@Slf4j
@AutoConfiguration
public class TranslationConfig {
@Autowired
private List<TranslationInterface<?>> list;
@Autowired
private ObjectMapper objectMapper;
@PostConstruct
public void init() {
Map<String, TranslationInterface<?>> map = new HashMap<>(list.size());
for (TranslationInterface<?> trans : list) {
if (trans.getClass().isAnnotationPresent(TranslationType.class)) {
TranslationType annotation = trans.getClass().getAnnotation(TranslationType.class);
map.put(annotation.type(), trans);
} else {
log.warn(trans.getClass().getName() + " 翻译实现类未标注 TranslationType 注解!");
}
}
TranslationHandler.TRANSLATION_MAPPER.putAll(map);
// 设置 Bean 序列化修改器
objectMapper.setSerializerFactory(
objectMapper.getSerializerFactory()
.withSerializerModifier(new TranslationBeanSerializerModifier()));
}
}
TranslationType
翻译类型注解
点击展开
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface TranslationType {
/**
* 类型
*/
String type();
}
TranslationInterface<T>
翻译接口
点击展开
public interface TranslationInterface<T> {
/**
* 翻译
*
* @param key 需要被翻译的键(不为空)
* @param other 其他参数
* @return 返回键对应的值
*/
T translation(Object key, String other);
}
TranslationHandler
翻译处理器
点击展开
@Slf4j
public class TranslationHandler extends JsonSerializer<Object> implements ContextualSerializer {
/**
* 全局翻译实现类映射器
*/
public static final Map<String, TranslationInterface<?>> TRANSLATION_MAPPER = new ConcurrentHashMap<>();
private Translation translation;
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
TranslationInterface<?> trans = TRANSLATION_MAPPER.get(translation.type());
if (ObjectUtil.isNotNull(trans)) {
// 如果映射字段不为空 则取映射字段的值
if (StringUtils.isNotBlank(translation.mapper())) {
value = ReflectUtils.invokeGetter(gen.getCurrentValue(), translation.mapper());
}
// 如果为 null 直接写出
if (ObjectUtil.isNull(value)) {
gen.writeNull();
return;
}
Object result = trans.translation(value, translation.other());
gen.writeObject(result);
} else {
gen.writeObject(value);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
Translation translation = property.getAnnotation(Translation.class);
if (Objects.nonNull(translation)) {
this.translation = translation;
return this;
}
return prov.findValueSerializer(property.getType(), property);
}
}
TranslationBeanSerializerModifier
序列化修改器
null值 属性序列化处理器
点击展开
public class TranslationBeanSerializerModifier extends BeanSerializerModifier {
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
for (BeanPropertyWriter writer : beanProperties) {
// 如果序列化器为 TranslationHandler 的话 将 Null 值也交给他处理
if (writer.getSerializer() instanceof TranslationHandler serializer) {
writer.assignNullSerializer(serializer);
}
}
return beanProperties;
}
}
# 翻译代码
DeptNameTranslationImpl
部门翻译实现类
点击展开
@AllArgsConstructor
@TranslationType(type = TransConstant.DEPT_ID_TO_NAME)
public class DeptNameTranslationImpl implements TranslationInterface<String> {
private final DeptService deptService;
@Override
public String translation(Object key, String other) {
if (key instanceof String ids) {
return deptService.selectDeptNameByIds(ids);
} else if (key instanceof Long id) {
return deptService.selectDeptNameByIds(id.toString());
}
return null;
}
}
DictTypeTranslationImpl
字典翻译实现
点击展开
@AllArgsConstructor
@TranslationType(type = TransConstant.DICT_TYPE_TO_LABEL)
public class DictTypeTranslationImpl implements TranslationInterface<String> {
private final DictService dictService;
@Override
public String translation(Object key, String other) {
if (key instanceof String dictValue && StringUtils.isNotBlank(other)) {
return dictService.getDictLabel(other, dictValue);
}
return null;
}
}
OssUrlTranslationImpl
OSS翻译实现
点击展开
@AllArgsConstructor
@TranslationType(type = TransConstant.OSS_ID_TO_URL)
public class OssUrlTranslationImpl implements TranslationInterface<String> {
private final OssService ossService;
@Override
public String translation(Object key, String other) {
if (key instanceof String ids) {
return ossService.selectUrlByIds(ids);
} else if (key instanceof Long id) {
return ossService.selectUrlByIds(id.toString());
}
return null;
}
}
UserNameTranslationImpl
用户名翻译实现
点击展开
@AllArgsConstructor
@TranslationType(type = TransConstant.USER_ID_TO_NAME)
public class UserNameTranslationImpl implements TranslationInterface<String> {
private final UserService userService;
@Override
public String translation(Object key, String other) {
if (key instanceof Long id) {
return userService.selectUserNameById(id);
}
return null;
}
}
# 数据脱敏功能
脱敏功能是将 敏感的隐私信息进行加工处理 , 转化为能够公开的信息 . ruoyi-vue-plus框架 采用 Jackson
序列化 + Sensitive
注解 + Hutool工具 实现脱敏工作
Hutool文档 : https://hutool.cn/docs/ (opens new window)
ruoyi-vue-plus数据脱敏应用文档 :https://plus-doc.dromara.org/ (opens new window)
# 应用
在对象的属性中添加 SensitiveStrategy
注解 , 注解的参数写上脱敏类型即可实现
# Controller层应用
点击展开
/**
* 测试数据脱敏
*/
@GetMapping("/test")
public void test() throws JsonProcessingException {
TestSensitive testSensitive = new TestSensitive();
testSensitive.setIdCard("210397198608215431");
testSensitive.setPhone("17640125371");
testSensitive.setAddress("北京市朝阳区某某四合院1203室");
testSensitive.setEmail("17640125371@163.com");
testSensitive.setBankCard("6226456952351452853");
// 手动序列化处理 . 采用 jackson序列化处理
ObjectMapper objectMapper = SpringUtils.getBean(ObjectMapper.class);
String json = objectMapper.writeValueAsString(testSensitive);
System.out.println("json = " + json);
}
/* 脱敏结果
json = {
"idCard": "210***********5431",
"phone": "176****5371",
"address": "北京市朝阳区某某********",
"email": "1**********@163.com",
"bankCard": "6226 **** **** **** 853"
}
*/
// 实体类
@Data
static class TestSensitive {
/**
* 身份证
*/
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
private String idCard;
/**
* 电话
*/
@Sensitive(strategy = SensitiveStrategy.PHONE)
private String phone;
/**
* 地址
*/
@Sensitive(strategy = SensitiveStrategy.ADDRESS)
private String address;
/**
* 邮箱
*/
@Sensitive(strategy = SensitiveStrategy.EMAIL)
private String email;
/**
* 银行卡
*/
@Sensitive(strategy = SensitiveStrategy.BANK_CARD)
private String bankCard;
}
# 自定义脱敏
脱敏仅保留头和位其他全部脱敏 , 脱敏 : 181220323 => 1*****3
SensitiveStrategy
类
追加脱敏方案 . 追加后在属性添加注解与 DIY_DESENSITIZATION
类型标识即可
/**
* 自定义脱敏
*/
DIY_DESENSITIZATION(str -> {
int len = str.length();
if (len >= 2) str = StrUtil.hide(str, 1, len);
return str;
});
# 实现原理
数据脱敏处理过程 :
- 首次序列化应用时 (SensitiveJsonSerializer#createContextual())
- 回调获取注解信息 . 获取每个
@JsonSerialize(using = SensitiveJsonSerializer.class)
注解 有关的属性 - 判断有效 . 判断
Sensitive
注解是否存在 且 注解指定的类型为脱敏类型 - 设值 . 该注解是有效时设置该注解值为上下文处理 , 否则使用默认序列化
- 回调获取注解信息 . 获取每个
- 序列化处理时 (SensitiveJsonSerializer#serialize())
- 获取脱敏服务 . SensitiveService类 的Bean (目前服务仅有判断管理员脱敏处理)
- 判断是否脱敏 . 脱敏服务存在且不是管理员 , 进行脱敏处理 , 否则跳过脱敏处理
提示
脱敏服务仅做了管理员处理 , 其他业务自行增加
# 源码
类&注解 | 说明 |
---|---|
SensitiveJsonSerializer | 脱敏序列化处理 |
SensitiveStrategy | 脱敏标识注解 |
SensitiveService | 脱敏业务接口 |
SysSensitiveServiceImpl | 脱敏业务实现类 |
SensitiveJsonSerializer
脱敏序列化处理
点击展开
@Slf4j
public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveStrategy strategy;
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
try {
SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class);
if (ObjectUtil.isNotNull(sensitiveService) && sensitiveService.isSensitive()) {
gen.writeString(strategy.desensitizer().apply(value));
} else {
// 管理员跳过脱敏
gen.writeString(value);
}
} catch (BeansException e) {
log.error("脱敏实现不存在, 采用默认处理 => {}", e.getMessage());
gen.writeString(value);
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
// 根据注解赋予指定脱敏策略
this.strategy = annotation.strategy();
return this;
}
return prov.findValueSerializer(property.getType(), property);
}
}
SensitiveStrategy
脱敏标识注解
点击展开
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive {
SensitiveStrategy strategy();
}
SensitiveService
脱敏业务接口
点击展开
public interface SensitiveService {
/**
* 是否脱敏
*/
boolean isSensitive();
}
SysSensitiveServiceImpl
脱敏业务实现类
点击展开
@Service
public class SysSensitiveServiceImpl implements SensitiveService {
/**
* 是否脱敏
*/
@Override
public boolean isSensitive() {
return !LoginHelper.isAdmin();
}
}