柏竹 柏竹
首页
后端
前端
  • 应用推荐
关于
友链
  • 分类
  • 标签
  • 归档

柏竹

奋斗柏竹
首页
后端
前端
  • 应用推荐
关于
友链
  • 分类
  • 标签
  • 归档
  • Java基础

  • JavaWeb

  • 拓展技术

  • 框架技术

  • 数据库

  • 数据结构

  • Spring

  • SpringMVC

  • SpringBoot

  • SpringClound

  • Ruoyi-Vue-Plus

    • ruoyi-vue-plus-基础功能
    • ruoyi-vue-plus-权限控制
    • ruoyi-vue-plus-表格操作
    • ruoyi-vue-plus-缓存功能
    • ruoyi-vue-plus-日志功能
    • ruoyi-vue-plus-线程相关
    • ruoyi-vue-plus-OSS功能
    • ruoyi-vue-plus-代码生成功能
    • ruoyi-vue-plus-多数据源
    • ruoyi-vue-plus-任务调度
    • ruoyi-vue-plus-监控功能
    • ruoyi-vue-plus-国际化
    • ruoyi-vue-plus-XSS功能
    • ruoyi-vue-plus-防重幂&限流 功能
    • ruoyi-vue-plus-推送功能
    • ruoyi-vue-plus-序列化功能
      • 序列化功能
      • 翻译功能
        • 应用
        • 映射翻译
        • 直接翻译
        • 字典翻译
        • 自定义翻译
        • Contorller层应用
        • 实现原理
        • 源码
        • 核心源码
        • 翻译代码
      • 数据脱敏功能
        • 应用
        • Controller层应用
        • 自定义脱敏
        • 实现原理
        • 源码
    • ruoyi-vue-plus-数据加密
    • ruoyi-vue-plus-单元测试
    • ruoyi-vue-plus-前端插件
    • ruoyi-vue-plus-前端工具篇
    • ruoyi-vue-plus-部署篇
    • ruoyi-vue-plus-前端篇
    • ruoyi-vue-plus-后端工具篇
    • ruoyi-vue-plus-框架篇
    • ruoyi-vue-plus-问题解决
  • 后端
  • Ruoyi-Vue-Plus
柏竹
2024-04-15
目录

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 : 翻译处理器key
  • mapper : 映射属性名 (按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 : 翻译处理器key
  • other : 字典映射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值进行处理)

翻译序列化处理过程 :

  1. 启动初始化时 (TranslationConfig#init())

    1. 循环所有翻译转化的实现类Bean (1-3)
    2. 判断反射判断是否实现有 TranslationInterface转化接口
    3. Map存储 , 实现有则进行对其对象存储 , key为注解中的配置
    4. 存储所有Baen的Map放到翻译处理器类的常量中
    5. 设置序列化器 , 设置为 TranslationBeanSerializerModifier类 自己的处理器
  2. 首次序列化应用时 (TranslationBeanSerializerModifier#changeProperties())

    1. 回调获取注解信息 (TranslationHandler#createContextual()) . 对每个 @JsonSerialize(using = TranslationHandler.class)注解 的属性进行获取注解信息

    2. @Translation注解判断 . 遍历序列化对象的所有属性 判断是否引用有 @JsonSerialize(using = TranslationHandler.class)

      PS : @Translation注解引用有 @JsonSerialize(using = TranslationHandler.class)注解

    3. Null值序列化器 . 对象属性使用有注解 , 那么对该 对象属性进行 设置Null序列化器处理(TranslationHandler类处理)

  3. 序列化处理时 (TranslationHandler#serialize())

    1. Map集中根据上下文key获取翻译处理的Bean
    2. 判空 翻译处理Bean , 空则直接返回 , 否则 翻译处理
    3. 映射翻译处理 , 根据注解mapper属性控制 , 通过对象的属性名反射Get对应属性值并设到 value 中
    4. 判空处理 , 如果 value 为null , 那么直接设置null , 跳出翻译处理 (意味着该属性无任何翻译处理 . 该属性无默认值、无映射属性)
    5. 直接&字典 翻译处理 , 翻译处理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;
    }
}

OssUrlTranslationImplOSS翻译实现

点击展开
@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;
});

# 实现原理

数据脱敏处理过程 :

  1. 首次序列化应用时 (SensitiveJsonSerializer#createContextual())
    1. 回调获取注解信息 . 获取每个 @JsonSerialize(using = SensitiveJsonSerializer.class)注解 有关的属性
    2. 判断有效 . 判断 Sensitive注解是否存在 且 注解指定的类型为脱敏类型
    3. 设值 . 该注解是有效时设置该注解值为上下文处理 , 否则使用默认序列化
  2. 序列化处理时 (SensitiveJsonSerializer#serialize())
    1. 获取脱敏服务 . SensitiveService类 的Bean (目前服务仅有判断管理员脱敏处理)
    2. 判断是否脱敏 . 脱敏服务存在且不是管理员 , 进行脱敏处理 , 否则跳过脱敏处理

提示

脱敏服务仅做了管理员处理 , 其他业务自行增加

# 源码

类&注解 说明
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();
    }

}
#ruoyi-vue-plus#序列化#Jackson

← ruoyi-vue-plus-推送功能 ruoyi-vue-plus-数据加密→

最近更新
01
HTTPS自动续签
10-21
02
博客搭建-简化版(脚本)
10-20
03
ruoyi-vue-plus-部署篇
07-13
更多文章>
Theme by Vdoing | Copyright © 2019-2024 | 桂ICP备2022009417号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式