Log4j2 扩展日志脱敏插件
背景日志脱敏是常见的安全需求。为了金融交易的安全性,国家强制规定对于姓名、手机号、身份证号码、住址等信息需要数据脱敏。一般我们会采取注解的形式指定字段进行脱敏处理,但这样对代码的侵入性较高。假设公司有几十个系统要改造,或者采购了一些系统没有源代码,怎么办?还有,日志脱敏后,系统用户需要通过手机号找出对应的日志,怎么匹配? 目标实现一个零侵入性的日志插件,并能支持原始数据检索。 实现我们调研了 Github 一些开源项目,发现 https://github.com/houbb/sensitive 能够满足这个需求,它通过 Trie 类对字符进行解析处理,具体的处理逻辑大部分封装在 com.github.houbb.chars.scan.bs.CharsScanBs 这个类,我们只需要引入 CharsScanBs.scanAndReplace(text) 就可以实现对原始日志的脱敏处理,被脱敏的内容后面自动追加原始数据库的 MD5 信息,假设用户反馈系统问题,会提供他的手机号,我们将手机号生成 MD5,去脱敏日志文件匹配,就能定位到原始数据。 Log4j2 插件提供了两个扩展方式 A...
Spring Boot 实现可扩展的消息队列
背景在复杂的分布式系统中,消息队列(如 RocketMQ、Kafka、RabbitMQ)常用于优化系统性能。然而,直接在代码中引入这些消息队列的 API 会导致系统与特定消息队列的强耦合,后续难以切换其他消息队列组件。虽然 Spring Cloud Stream 提供了一种抽象层,但其引入了复杂的概念(如绑定器、通道、处理器等),且与低版本的 Spring Boot 不兼容。 为了解决这些问题,我们决定开发一个简洁、可切换的 MQ 组件,保留原生配置的同时,屏蔽底层 API 细节,并通过 Spring Boot 自动装配进行管理。 目标 封装通用接口:提供统一的接口,屏蔽底层消息队列的 API 细节。 保留原生配置:支持 RocketMQ、Kafka 等消息队列的原生配置。 做到开箱即用:通过 Spring Boot 的自动装配机制,简化配置和集成。 实现消息模型设计首先,定义一个通用的消息模型 Message,用于封装业务项目中生产和消费的消息内容。该模型兼容 RocketMQ 和 Kafka 的消息结构。 1234567891011121314151617181920212...
Spring Boot 实现自定义审计功能
背景在现代应用系统中,事件审计是一个至关重要的功能。通过记录用户的操作行为,我们可以追踪问题、分析用户行为,甚至在出现安全问题时提供关键证据。由于目前没有较好的事件审计框架,笔者决定实现一套可扩展的事件审计组件,要求对业务低侵入性,可以轻松获取前后变更的内容。 目标提供自定义注解给业务侧,实现开箱即用的事件审计存储功能。 实现审计我们定义了 @EventAuditor 注解,关键字段包括 operator(操作人)、content(操作内容模板)、bizScenario(事件发生的场景)等。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667@Documented@Inherited@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface EventAudit...
Spring 扩展 Bean 动态注册和销毁
背景Spring Boot Starters 的自动装配机制给我们提供了非常丰富的扩展点,但有时候需要动态开启或者关闭某些组件,只能修改配置,重启服务才能生效。例如,我们接入了 Arthas 工具,有时候需要关闭,有时候需要开启,但又不能随意重启服务。 目标监听 Spring Boot Starters 组件的配置变化,动态注册销毁 Bean。 实现以 Arthas Spring Boot Starter 组件为例。我们发现 Spring Cloud 组件提供了 EnvironmentChangeEvent 作为配置变更事件,传入 ApplicationListener 监听器可以实现配置变化的监听,代码片段如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051@RequiredArgsConstructor@Slf4jpublic class ArthasEnvironmentChangeListener implements Applic...
Mybatis 自动填充创建时间和更新时间字段
背景Spring JPA 提供了 @CreatedDate、@LastModifiedDate 注解,用于自动赋值实体类的创建时间和更新时间。但我们的团队主要使用 MyBatis-Plus 作为 ORM 框架,需要提供同类的机制支持。 目标为 MyBatis-Plus 提供自动填充功能。 实现MyBatis-Plus 也提供了自动填充功能,通过实现 com.baomidou.mybatisplus.core.handlers.MetaObjectHandler 接口来实现。笔者定了 AutofillMetaObjectHandler 类实现,如下。 123456789101112131415161718192021222324252627282930@RequiredArgsConstructorpublic class AutofillMetaObjectHandler implements MetaObjectHandler { private final String createdDateFieldName; private final String lastM...
Mybatis 拦截器解析原始 SQL 语句
背景MyBatis Plus 通过配置文件中设置 log-impl 属性来指定日志实现,以打印 SQL 语句。 1234567mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpllogging: level: org.ylzl.eden.demo.mapper: DEBUG 打印出来的 SQL 内容如下。 12345==> Preparing: SELECT id,login,email,activated,locked,lang_key,activation_key,reset_key,reset_date,created_by,created_date,last_modified_by,last_modified_date FROM demo_user WHERE id=?==> Parameters: 1(Long)<== Columns: ID, LOGIN, EMAIL, ACTIVATED, LOCKED, LANG_...
Sentinel 接入 Prometheus 监控
背景由于 Sentinel Dashboard 开源版本没有实现监控数据的持久化,只能查看 5 分钟的内存数据。在项目初期,我们没有足够的精力对 Sentinel 进行改造,因此,将 Sentinel 的监控数据集成到 Prometheus,并导入到 Grafana 可视化管理。 目标扩展 Sentinel,将监控数据作为 Spring Boot Actuator 暴露到 Prometheus,并展示到 Grafana 管理。 实现从官方的 PR 可以找到,Sentinel 提供了 MetricExtension 扩展点,允许您使用 SPI 扩展。 源码如下。 1234567891011121314151617181920package com.alibaba.csp.sentinel.metric.extension;import com.alibaba.csp.sentinel.slots.block.BlockException;public interface MetricExtension { void addPass(String resource, ...
Spring Boot 实现可扩展的分布式ID生成器
背景在复杂的分布式系统中,往往需要对大量的数据和消息进行唯一标识。美团技术团队的文章中介绍了 Leaf 分布式ID生成系统的两种方案:Leaf-snowflake 方案和 Leaf-segment 方案。 Leaf-snowflake 方案沿用 Twitter 开源的雪花算法,在原有的基础上使用 Zookeeper 持久顺序节点进行优化,如下图。 Leaf-segment 方案基于数据库实现,每次获取一批递增号段的值,用完之后再去数据库获取新的号段,可以生成趋势递增的 ID,同时 ID 号是可计算的,因此不适用于订单 ID 生成场景(容易被竞争对手算出一天的订单量),相关原理如下图。 由于 Leaf 分布式ID生成系统设计为独立部署,由各系统接入使用,但稍微维护不当,容易搞垮整个系统。笔者认为,可以将 Leaf 改造为插件化组件,托管到 Spring Boot Starter 自动装配。除此之外,业界开源的组件也有滴滴 TinyId、百度 UidGenerator 等,最好是设计为通用的 ID 生成器,底层实现任意切换,对业务无感知。 目标封装通用接口,屏蔽 API 细节,基...
Spring Boot 实现可扩展的分布式锁
背景在 Java 中分布式锁的实现框架主要包括基于 数据库、Redis 和 Zookeeper 的实现方式。使用 Redis 实现的组件可以选择 Jedis API 或者 Redisson API,使用 Zookeeper 实现的组件可以选择 Zookeeper API 或者 Curator API。笔者在项目中看到不少这种混用 API 的情况,维护性较差。 目标封装通用接口,屏蔽 API 细节,基于 Spring Boot 自动装配管理。 实现首先,定义分布式锁接口。 123456789101112131415161718192021222324252627282930313233public interface DistributedLock { /** * 锁类型 * * @return 锁类型 */ String lockType(); /** * 加锁(阻塞) * * @param key 锁对象 */ boolean lock(String key); /** * 加锁(阻塞直到超时) * * @param key 锁对象 ...
Spring 自定义 @RestControllerAdvice 返回结果
背景Spring 提供了 @RestControllerAdvice 用来实现 HTTP 协议的全局异常处理。在异常信息的处理上通常只返回特定的 Response 对象,如下。 123456789101112131415161718192021@Slf4j@RestControllerAdvicepublic class RestExceptionResolver { @ExceptionHandler(Exception.class) public ResponseEntity<?> processException(Exception ex) { BodyBuilder builder; Response response; ResponseStatus responseStatus = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class); if (responseStatus != null) { builder = Response...














