手机号脱敏不能只靠@jsonserialize注解,因其只能绑定字段或类型,多个手机号字段需重复加注解且为string类型单独写序列化器,维护成本高;应结合serializerprovider与命名规则实现动态识别和全局脱敏。

手机号脱敏为什么不能只靠 @JsonSerialize 注解
因为 @JsonSerialize 只能绑定到字段或类型,一旦你有多个手机号字段(比如 phone、contactPhone、emergencyPhone),挨个加注解既重复又难维护;更麻烦的是,如果这些字段类型都是 String,你还得为每个字段单独写一个序列化器类——这不是在写脱敏,是在写样板代码。
真正可控又可持续的做法,是让 Jackson 知道:“只要遇到某个字段名匹配 phone 或值符合手机号格式的 String,就自动脱敏”。这需要结合自定义 SerializerProvider 和命名规则判断,而不是依赖注解硬绑定。
用 SimpleModule 注册全局手机号序列化器
Spring Boot 默认用 ObjectMapper 做 JSON 序列化,你可以通过 SimpleModule 注入一个针对 String 类型的通用处理器,在序列化时动态检查字段名和内容。
实操建议:
- 创建一个继承
StdSerializer<string></string>的类,比如PhoneMaskSerializer - 重写
serialize(String value, JsonGenerator gen, SerializerProvider serializers)方法,在里面判断:value是否为 11 位数字、是否以 1 开头、是否匹配正则"^1[3-9]\d{9}$" - 匹配成功则输出
value.substring(0, 3) + "****" + value.substring(7),否则原样输出 - 在
ObjectMapper配置阶段,用module.addSerializer(String.class, new PhoneMaskSerializer())注册
注意:这样会作用于所有 String 字段,所以判断逻辑必须严谨,否则日志 ID、订单号等也可能被误脱敏。
@JsonSerialize + @JsonProperty 临时绕过全局规则
有些接口需要返回明文手机号(比如内部管理后台导出 Excel),但全局序列化器已经生效,这时候不能删配置,也不能改字段类型。
可以局部覆盖:
- 在目标字段上加
@JsonSerialize(using = ToStringSerializer.class),强制走 Jackson 默认字符串序列化 - 或者用
@JsonProperty(access = JsonProperty.Access.READ_ONLY)配合 getter 返回明文,同时确保 setter 不参与反序列化 - 若字段名含
phone但内容不是手机号(如"phone": "暂无"),需在PhoneMaskSerializer中增加非数字字符快速过滤,避免正则执行开销
别忘了测试空值、null、空字符串、超长字符串这些边界情况——它们不会进正则匹配,但可能抛 StringIndexOutOfBoundsException。
Spring Boot 3+ 里 ObjectMapper 自动配置的坑
Spring Boot 3 默认启用 spring.jackson.deserialization.fail-on-unknown-properties=true,但它不影响序列化;真正容易掉坑的是:如果你手动 new 了一个 ObjectMapper 并注册了模块,却没把它声明为 @Bean,那 WebMvc 的 MappingJackson2HttpMessageConverter 依然用默认实例,你的脱敏器根本不会生效。
正确做法:
- 不要 new ObjectMapper,而是
@Autowired ObjectMapper objectMapper后调用registerModule(...) - 或者定义一个
@Bean方法,返回配置好的ObjectMapper,并加上@Primary - 确认启动日志里出现
Serializing String with com.example.PhoneMaskSerializer这类提示,否则说明模块没注册成功
还有个隐藏问题:Lombok 的 @Data 会生成 toString(),而某些监控埋点或日志框架会调用它——这时候脱敏逻辑不触发,手机号就直接打到日志里了。得单独给 toString() 做处理,不能只盯着 JSON。










