news 2026/9/10 19:47:34

一个注解搞定接口返回数据脱敏...

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一个注解搞定接口返回数据脱敏...

01

背景

下午惬意时光,突然产品小姐姐走到我面前,打断我短暂的摸鱼 time,企图与我进行深入交流,还好我早有防备没有闪,打开瑞 star 的点单页面,暗示没有一杯 coffee 解决不了的需求。

需求是某些接口返回的信息,涉及到敏感数据的必须进行脱敏操作,我思考一反,表示某问题,马上安排。

02

思路

①要做成可配置多策略的脱敏操作,要不然一个个接口进行脱敏操作,重复的工作量太多,很显然违背了“多写一行算我输”的程序员规范。

思来想去,定义数据脱敏注解和数据脱敏逻辑的接口, 在返回类上,对需要进行脱敏的属性加上,并指定对应的脱敏策略操作。

②接下来我只需要拦截控制器返回的数据,找到带有脱敏注解的属性操作即可,一开始打算用 @ControllerAdvice 去实现,但发现需要自己去反射类获取注解。

当返回对象比较复杂,需要递归去反射,性能一下子就会降低,于是换种思路,我想到平时使用的 @JsonFormat,跟我现在的场景很类似,通过自定义注解跟字段解析器,对字段进行自定义解析,tql。

03

实现代码

①自定义数据注解,并可以配置数据脱敏策略:

@Target({ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataMasking { DataMaskingFunc maskFunc() default DataMaskingFunc.NO_MASK; }

②自定义 Serializer,参考 jackson 的 StringSerializer,下面的示例只针对 String 类型进行脱敏。

public interface DataMaskingOperation { String MASK_CHAR = "*"; String mask(String content, String maskChar); } publicenum DataMaskingFunc { /** * 脱敏转换器 */ NO_MASK((str, maskChar) -> { return str; }), ALL_MASK((str, maskChar) -> { if (StringUtils.hasLength(str)) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < str.length(); i++) { sb.append(StringUtils.hasLength(maskChar) ? maskChar : DataMaskingOperation.MASK_CHAR); } return sb.toString(); } else { return str; } }); privatefinal DataMaskingOperation operation; private DataMaskingFunc(DataMaskingOperation operation) { this.operation = operation; } public DataMaskingOperation operation() { returnthis.operation; } } publicfinalclass DataMaskingSerializer extends StdScalarSerializer<Object> { privatefinal DataMaskingOperation operation; public DataMaskingSerializer() { super(String.class, false); this.operation = null; } public DataMaskingSerializer(DataMaskingOperation operation) { super(String.class, false); this.operation = operation; } public boolean isEmpty(SerializerProvider prov, Object value) { String str = (String)value; return str.isEmpty(); } public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { if (Objects.isNull(operation)) { String content = DataMaskingFunc.ALL_MASK.operation().mask((String) value, null); gen.writeString(content); } else { String content = operation.mask((String) value, null); gen.writeString(content); } } public final void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSer) throws IOException { this.serialize(value, gen, provider); } public JsonNode getSchema(SerializerProvider provider, Type typeHint) { returnthis.createSchemaNode("string", true); } public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException { this.visitStringFormat(visitor, typeHint); } }

③自定义 AnnotationIntrospector,适配我们自定义注解返回相应的 Serializer。

@Slf4j public class DataMaskingAnnotationIntrospector extends NopAnnotationIntrospector { @Override public Object findSerializer(Annotated am) { DataMasking annotation = am.getAnnotation(DataMasking.class); if (annotation != null) { return new DataMaskingSerializer(annotation.maskFunc().operation()); } return null; } }

④覆盖 ObjectMapper:

@Configuration( proxyBeanMethods = false ) publicclass DataMaskConfiguration { @Configuration( proxyBeanMethods = false ) @ConditionalOnClass({Jackson2ObjectMapperBuilder.class}) staticclass JacksonObjectMapperConfiguration { JacksonObjectMapperConfiguration() { } @Bean @Primary ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build(); AnnotationIntrospector ai = objectMapper.getSerializationConfig().getAnnotationIntrospector(); AnnotationIntrospector newAi = AnnotationIntrospectorPair.pair(ai, new DataMaskingAnnotationIntrospector()); objectMapper.setAnnotationIntrospector(newAi); return objectMapper; } } }

⑤返回对象加上注解:

public class User implements Serializable { /** * 主键ID */ private Long id; /** * 姓名 */ @DataMasking(maskFunc = DataMaskingFunc.ALL_MASK) private String name; /** * 年龄 */ private Integer age; /** * 邮箱 */ @DataMasking(maskFunc = DataMaskingFunc.ALL_MASK) private String email; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/22 4:04:53

基于LangChain手工测试用例生成工具

在编写测试用例的过程中&#xff0c;测试工程师会通过需求文档&#xff0c;研发的概要设计等信息编写测试用例&#xff0c;测试用例的输出格式常常为思维导图或者excel等数据信息。 在以上的流程中&#xff0c;一个测试工程师可以根据比较详细的需求文档以及研发的概要设计输出…

作者头像 李华
网站建设 2026/9/10 10:59:59

接口性能优化的11个小技巧

接口性能优化 对于从事后端开发的同学来说&#xff0c;肯定再熟悉不过了&#xff0c;因为它是一个跟开发语言无关的公共问题。 该问题说简单也简单&#xff0c;说复杂也复杂。 有时候&#xff0c;只需加个索引就能解决问题。 有时候&#xff0c;需要做代码重构。 有时候&am…

作者头像 李华
网站建设 2026/8/29 2:30:03

git个人开发流程

1. 标准开发流程 (针对个人项目) 第一步&#xff1a;同步主分支 (保持起点最新) # 1. 切换回主分支 git checkout main# 2. 拉取远程最新代码 (防止你在这个设备开发时&#xff0c;在另一个设备Push过代码) git pull origin main 第二步&#xff1a;新建并切换分支 不要在 …

作者头像 李华
网站建设 2026/8/22 5:37:39

智能安防(有完整资料)

资料查找方式&#xff1a;特纳斯电子&#xff08;电子校园网&#xff09;&#xff1a;搜索下面编号即可编号&#xff1a;CP-51-2021-051设计简介&#xff1a;本设计是基于单片机的智能安防系统&#xff0c;主要实现以下功能&#xff1a;可通过LCD1602安防状态和是否有人&#x…

作者头像 李华
网站建设 2026/8/29 6:42:58

django基于大数据旅游数据分析与推荐系统的设计与实现

前言Django基于大数据的旅游数据分析与推荐系统是一个结合了Python编程语言、Django Web框架、大数据技术以及推荐算法的综合性平台&#xff0c;旨在通过收集、分析和利用旅游相关的数据&#xff0c;为用户提供个性化的旅游推荐服务&#xff0c;同时为旅游企业和管理部门提供决…

作者头像 李华
网站建设 2026/9/5 1:03:55

SSH登录异常频发:除了改端口,我们还能做什么?

封不完的IP&#xff1f;拦不住的爆破&#xff1f;这四招让黑客直接绝望&#xff01; 各位运维工程师、系统管理员&#xff0c;上面这些吐槽&#xff0c;是不是你们的日常&#xff1f;面对SSH暴||力破解&#xff0c;修改默认22端口就像是给家门换了一把没藏在垫子底下、但藏在花…

作者头像 李华