使用十一种设计模式实现 基于Jackson注解的 完美数据脱敏方案


前言

又是好长时间没有写分享了,对我来说,写博客消耗的心力太大了,只能偶尔心血来潮了,没法收回曾经立下的Flag了。


一、背景

最近有数据脱敏的需求,我三下五除二就解决了,就是太丑了,直接耦合在业务处理中。所以我一直在寻找更加优雅的方案,例如 Mybatis 插件了,工具类等等,但都感觉差点意思。终于 当我看到了这篇博文:自定义注解实现数据序列化时进行数据脱敏,这就是我想要的,使用 Jackson 注解。但当我看完后马上意识到,他的方式还不够灵活。
我 立刻就有了改造的灵感,下文就是 使用 外观 + 策略 + 命令 + 工厂 + 静态代理 + 装饰器 + 解释器 + 适配器 + 享元 + 桥接 + 模板(有点牵强了,就是为了硬凑贯口) 改造后的成品,提供更强的灵活性。

通过这个样例,可以学习三个事情

  1. 学习Jackson 这种提供扩展的方式,太方便了
  2. 设计模式只是参考,六大原则才是核心目标,没有必要太教条。
  3. lambda 相关技术太方便了, 尤其对设计模式的实现,如果可以尽量多用。

二、代码实现

2.1 对脱敏操作抽象出函数式接口

这个接口 是 策略 + 命令 结合后的抽象,并通过 default 函数实现装饰功能。使用注解Sensitive 实例来实现了命令模式 ,可以更灵活的控制脱敏执行。

@FunctionalInterface
public interface SensitiveDeal {
   
	String apply(String source, Sensitive sensitive);

	default SensitiveDeal compose(SensitiveDeal before) {
   
		Objects.requireNonNull(before);
		return (source, sensitive) -> apply(before.apply(source, sensitive), sensitive);
	}

	default SensitiveDeal andThen(SensitiveDeal after) {
   
		Objects.requireNonNull(after);
		return (source, sensitive) -> after.apply(apply(source, sensitive), sensitive);
	}
}

2.2 Jackson自定义注解

@Documented 表示该注解会生成到 javaDoc中
@Retention(RetentionPolicy.RUNTIME) 表示该注解在运行时生效
@Target(ElementType.FIELD) 表示注解作用于字段上
@JacksonAnnotationsInside 这个注解用来标记Jackson复合注解,当你使用多个Jackson注解组合成一个自定义注解时会用到它
@JsonSerialize(using = SensitiveJsonSerializer.class) 指定使用自定义的序列化器
参数说明见源码注释。

Sensitive 可以使用相同的一套参数来配置脱敏实现,可以说 当前的脱敏方案 提供了 统一的外观。
Strategy.CUSTOMER 策略提供了适配的支持,使用Sensitive.dealType 就可以将第三方实现嵌入当前框架。
重点注意一下 Strategy 继承了 SensitiveDeal,实现了静态代理,体会这种写法的便利。
Strategy 的实例化使用了 简单工厂,策略 + 命令模式,稍微有点复杂,但是对外暴露的时候,就是普通的策略,使用者并不关心策略本身是如何被创建的。
Sensitive.regular 正则表达式 使用了 解释器模式,这个使用有点过于划水了。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveJsonSerializer.class)
public @interface Sensitive {
   

	/**
	 * 脱敏策略,参考{@link Sensitive.Strategy}
	 */
	Strategy value();

	/**
	 * 有无参构造函数的{@link SensitiveDeal}实现类,用于完全自定义脱敏,参考
	 * {@link Sensitive.Strategy#CUSTOMER}
	 */
	Class<? extends SensitiveDeal> dealType() default SensitiveDeal.class;

	/**
	 * 正则表达式,参考具体策略的注释。
	 */
	String regular() default "";

	/**
	 * 替换后显示,与 {@link Sensitive#regular()}配合可以实现更方便的正则替换。<br>
	 * 剩余情况默认只取首位字符来填充。
	 */
	String replacement() default "*";

	/**
	 * 左侧需要保留几位明文字段,参考具体策略的注释。
	 */
	int maskStart() default 0;

	/**
	 * 右侧最多保留几位明文,参考具体策略的注释。
	 */
	int keepTail() default 0;

	/**
	 * 具体的脱敏策略实现
	 * 
	 * @author zkr-liuchunming
	 * @see SensitiveDeals
	 * @see SensitiveDeal
	 *
	 */
	enum Strategy implements SensitiveDeal {
   

		// 以下是 与业务字段相关的策略
		/**
		 * 性别
		 */
		SEX(SensitiveDeals::none),
		/**
		 * 用户名
		 */
		USERNAME(SensitiveDeals::userName),
		/**
		 * 密码
		 */
		PASSWORD(SensitiveDeals::none),
		/**
		 * 证件号码
		 */
		IDNO(SensitiveDeals.createWrap(6, 4)),
		/**
		 * 座机号
		 */
		FIXED_PHONE(SensitiveDeals.createSuffix(4)),
		/**
		 * 手机号
		 */
		MOBILE_PHONE(SensitiveDeals.createWrap(3, 4)),
		/**
		 * 邮箱
		 */
		EMAIL(SensitiveDeals::email),
		/**
		 * 银行账号
		 */
		BANK_ACCNO(SensitiveDeals.createWrap(6, 4)),
		/**
		 * 地址
		 */
		ADDRESS(SensitiveDeals.createRegular("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****")),

		// 以下是 与业务无关的通用策略,如果经常脱敏某种业务字段,建议抽成 业务字段策略,更有可读性
		/**
		 * 全部隐藏,例如密码之类
		 */
		NONE(SensitiveDeals::none),
		/**
		 * 只留结尾四个字符
		 */
		SUFFIX_4(SensitiveDeals.createSuffix(4)),
		/**
		 * 只保留结尾的通用脱敏,使用 {@link Sensitive#keepTail()}
		 */
		
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值