**高并发、微服务 、性能调优实战案例100讲，所有案例均源于个人工作实战，均配合代码落地**

加我微信：itsoku，所有案例均提供在线答疑。

# 第23节 SpringBoot中数据脱敏优雅设计与实现

<span style="font-weight:bold; color:red">目前整个课程59块钱，100个案例，含所有源码 & 文档 & 技术支持，可点击左下角小黄车了解</span>。

## 本文内容

本文主要介绍 SpringBoot 中数据脱敏优雅的设计与实现，坚持看完，相信你会有所收获。



## 先带大家看下效果

接口代码

```java
@RestController
public class TestController {
    @GetMapping("/getUser")
    public Result<User> getUser() {
        User user = new User();
        user.setId("001");
        user.setName("路人");
        user.setPhone("18612345678");
        user.setEmail("likun_557@163.com");
        user.setIdCard("420123432112121332");
        user.setPassword("123456");
        user.setAddress("上海市闵行区漕河泾开发区");
        user.setBankCard("6226090211114567");
        return ResultUtils.success(user);
    }
}
```

访问接口输出如下，敏感信息自动按照某种格式脱敏了。

```json
{
  "success": true,
  "data": {
    "id": "001",
    "name": "路*",
    "phone": "186****5678",
    "email": "l********@163.com",
    "idCard": "4***************32",
    "password": "******",
    "address": "上海市闵********",
    "bankCard": "6226 **** **** 4567"
  },
  "msg": null,
  "code": null
}
```



## 如何实现？

Controler方法的返回值，会被SpringBoot统一处理，其内部会使用jackson将对象序列化为json字符，然后由SpringBoot输出到客户端。

所以，我们只需在jackson序列化这一步，将需要脱敏的数据统一处理就可以了。

我们可以自定义一个脱敏的注解，注解中指定脱敏的策略，这个策略其实就是一个字符串替换的函数，可以将这个注解标注在需要脱敏的字段上面。

然后自定义一个脱敏的jackson序列化器，这个序列化器会对标注了脱敏注解的字段进行处理，会将注解上的脱敏策略取出来，使用脱敏策略对原字符串进行替换，然后输出替换后的字符串。



## 源码

### 脱敏注解

> 如下，需要添加
>
> @JacksonAnnotationsInside
> @JsonSerialize(using = DesensitizationJsonSerializable.class)

```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizationJsonSerializable.class)
public @interface Desensitization {
    /**
     * 脱敏策略
     *
     * @return
     */
    DesensitizationStrategy value();
}
```

### 脱敏策略

> 如下，脱敏策略就是一个字符串替换函数，参数就是原本的字符串，返回值是脱敏后的字符串。
>
> 下面默认定义了 7 种，这里脱敏方法，使用到了hutool中自带的工具，这个工具内部已经提供了场景的一些数据脱敏的函数。
>
> 如果感觉不够用，也可以自己拓展。

```java
public enum DesensitizationStrategy {
    // 手机号脱敏策略，保留前三位和后四位
    PHONE(s -> DesensitizedUtil.desensitized(s, DesensitizedUtil.DesensitizedType.MOBILE_PHONE)),

    // 邮箱脱敏策略，保留邮箱用户名第一个字符和@符号前后部分
    EMAIL(s -> DesensitizedUtil.desensitized(s, DesensitizedUtil.DesensitizedType.EMAIL)),

    // 身份证号脱敏策略，保留前四位和后四位
    ID_CARD(s -> DesensitizedUtil.desensitized(s, DesensitizedUtil.DesensitizedType.ID_CARD)),

    // 地址脱敏策略，保留省市信息，其余部分脱敏为**
    ADDRESS(s -> DesensitizedUtil.desensitized(s, DesensitizedUtil.DesensitizedType.ADDRESS)),

    // 银行卡号脱敏策略，保留前四位和后三位
    BANK_CARD(s -> DesensitizedUtil.desensitized(s, DesensitizedUtil.DesensitizedType.BANK_CARD)),

    // 姓名脱敏策略，保留姓氏第一个字符，其余部分脱敏为**
    NAME(s -> DesensitizedUtil.desensitized(s, DesensitizedUtil.DesensitizedType.CHINESE_NAME)),

    // 密码脱敏策略，统一显示为******
    PASSWORD(s -> "******");

    private final Function<String, String> desensitization;

    DesensitizationStrategy(Function<String, String> desensitization) {
        this.desensitization = desensitization;
    }

    public Function<String, String> getDesensitization() {
        return desensitization;
    }
}
```

### jackson脱敏序列化器

```java
public class DesensitizationJsonSerializable extends JsonSerializer<String> implements ContextualSerializer {

    //脱敏策略
    private DesensitizationStrategy desensitizationStrategy;

    @Override
    public void serialize(String s, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        // 使用脱敏策略将字符串处理后序列化到json中
        gen.writeString(desensitizationStrategy.getDesensitization().apply(s));
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        // 获取属性上的 Desensitization 注解
        Desensitization annotation = property.getAnnotation(Desensitization.class);
        // 注解不为空 && 属性类型必须是字符串类型
        if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
            //设置脱敏策略
            this.desensitizationStrategy = annotation.value();
            return this;
        }
        // 返回默认的序列化器
        return prov.findValueSerializer(property.getType(), property);
    }
}
```

### 使用

```java
public class User {
    // id
    private String id;
    // 姓名
    @Desensitization(DesensitizationStrategy.NAME)
    private String name;
    // 手机号
    @Desensitization(DesensitizationStrategy.PHONE)
    private String phone;
    // 邮箱
    @Desensitization(DesensitizationStrategy.EMAIL)
    private String email;
    // 银行卡
    @Desensitization(DesensitizationStrategy.ID_CARD)
    private String idCard;
    // 密码
    @Desensitization(DesensitizationStrategy.PASSWORD)
    private String password;
    // 地址
    @Desensitization(DesensitizationStrategy.ADDRESS)
    private String address;

    @Desensitization(DesensitizationStrategy.BANK_CARD)
    private String backCard;

	//getter setter方法省略...
}
```



## 案例源码

源码同样是放在我的《高并发&微服务&性能调优实战案例100讲》的代码中（lesson023模块中），有兴趣的可以点击左下角的小黄车了解下，感谢大家的观看。



# 高并发 & 微服务 & 性能调优实战案例100讲

## 已更新 23 节课

<span style="font-weight:bold; color:red">目前整个课程59块钱，含所有源码 & 文档 & 技术支持，一杯咖啡的价格，还没下手的朋友，赶紧了，马上要涨价了</span>。

```java
1. 分片上传实战
2. 通用并发处理工具类实战
3. 实现一个好用接口性能压测工具类
4. 超卖问题的4种解决方案，也是防止并发修改数据出错的通用方案
5. Semaphore实现接口限流实战
6. 并行查询，优化接口响应速度实战
7. 接口性能优化之大事务优化
8. 通用的Excel动态导出功能实战
9. 手写线程池管理器，管理&监控所有线程池
10. 动态线程池
11. SpringBoot实现动态Job实战
12. 并行查询，性能优化利器，可能有坑
13. 幂等的4种解决方案，吃透幂等性问题
14. 接口通用返回值设计与实现
15. 接口太多，各种dto、vo不计其数，如何命名？
16. 一个业务太复杂了，方法太多，如何传参？
17. 接口报错，如何快速定位日志？
18. 线程数据共享必学的3个工具类：ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
19. 通过AOP统一打印请求链路日志，排错效率飞升
20. 大批量任务处理常见的方案（模拟余额宝发放收益）
21. 并发环境下，如何验证代码是否正常？
22. MySql和Redis数据一致性
23. SpringBoot数据脱敏优雅设计与实现
```



## 课程部分大纲，连载中。。。。

以下课程均来源于个人多年的实战，均提供原理讲解 && 源码落地

1. 分片上传实战
2. 通用并发处理工具类实战
3. 实现一个好用接口性能压测工具类
4. 超卖问题的4种解决方案，也是防止并发修改数据出错的通用方案
5. Semaphore实现接口限流实战
6. 并行查询，优化接口响应速度实战
7. 接口性能优化之大事务优化
8. 通用的Excel动态导出功能实战
9. 手写线程池管理器，管理&监控所有线程池
10. 动态线程池
11. SpringBoot实现动态Job实战
12. 并行查询，性能优化利器，可能有坑
13. 幂等的4种解决方案，吃透幂等性问题
14. 接口通用返回值设计与实现
15. 接口太多，各种dto、vo不计其数，如何命名？
16. 一个业务太复杂了，方法太多，如何传参？
17. 接口报错，如何快速定位日志？
18. 线程数据共享必学的3个工具类：ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
19. 通过AOP统一打印请求链路日志，排错效率飞升
20. 大批量任务处理常见的方案（模拟余额宝发放收益）
21. 并发环境下，如何验证代码是否正常？
22. MySql和Redis数据一致性
23. SpringBoot数据脱敏优雅设计与实现
24. 一行代码搞定操作日志
25. AOP实现MyBatis分页功能
26. SpringBoot读写分离实战
27. MQ专题：事务消息实战（防止消息丢失）
28. MQ专题：消息消息重试策略
29. MQ专题：消息幂等消费通用方案实战
30. MQ专题：延迟消息通用方案实战
31. MQ专题：顺序消息通用方案实战
32. MQ专题：消息积压问题
33. 分布式事务：事务消息实现事务最终一致性
34. 分布式事务：通用的TCC分布式事务生产级代码落地实战
35. 分布式锁案例实战
36. 微服务中如何传递上下文？实战
37. 微服务链路日志追踪实战（原理&代码落地）
38. SpringBoot实现租户数据隔离
39. MyBatis进阶：封装MyBatis，实现通用的无SQL版CRUD功能，架构师必备
40. MyBatis进阶：自己实现通用分表功能，架构师必备
41. MyBatis进阶：实现多租户隔离ORM框架
42. SpringBoot中实现自动监听PO的变化，自动生成表结构
43. 分布式专题：其他实战课程等
44. 性能调优：如何排查死锁？
45. 性能调优：如何排查内存溢出？
46. 性能调优：CPU被打满，如何排查？
47. 性能调优：生产代码没生效，如何定位？
48. 性能调优：接口太慢，如何定位？
49. 性能调优：如何查看生产上接口的入参和返回值？
50. 性能调优：远程debug
51. 生产上出现了各种故障，如何定位？
52. db和缓存一致性，常见的方案
53. Redis场景案例。。。
54. 系统资金账户设计案例（一些系统涉及到资金操作）
55. 其他等各种实战案例。。。