Halo博客项目源码学习
看了一些Halo博客的源码,觉得十分规范和优雅,从中学习了不少新的技术、业务的实现方式、代码编写规范等,在此记录一下笔记。
敏感信息的处理
通常手机号、邮箱等信息不会随意发送到前端,用户看到的一般是110****8980这样的形式,比较直接的解决方式是在VO的get方法中不返回真实的信息,在Halo中,定义了@SensitiveConceal注解和处理该注解的切面,可以作为一个参考。
@Aspect
@Component
public class SensitiveConcealAspect {
@Pointcut("within(run.halo.app.repository..*) "
+ "&& @annotation(run.halo.app.annotation.SensitiveConceal)")
public void pointCut() {
}
private Object sensitiveMask(Object comment) {
if (comment instanceof BaseComment) {
((BaseComment) comment).setEmail("");
((BaseComment) comment).setIpAddress("");
}
return comment;
}
@Around("pointCut()")
public Object mask(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
if (SecurityContextHolder.getContext().isAuthenticated()) {
return result;
}
if (result instanceof Iterable) {
((Iterable<?>) result).forEach(this::sensitiveMask);
}
return sensitiveMask(result);
}
}
缓存的实现
项目中缓存的实现应该是参考了SpringCache,没有SpringCache的复杂度,又能满足项目需求,缓存的实现全部在run.app.halo.cache包下。和SpringCache源码对比了一下,发现了一些问题。
在SpringCache的@Caheable注解中有sync属性,用于避免多线程环境下相同的请求同时去访问缓存,而缓存又刚好未命中,给数据库造成压力。
SpringCache中,对于sync为true的方法进行了特殊处理,如下所示,在org.springframework.cache.interceptor.CacheAspectSupport.execute方法中
// Special handling of synchronized invocation
if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try {
return wrapCacheValue(method, handleSynchronizedGet(invoker, key, cache));
}
catch (Cache.ValueRetrievalException ex) {
// Directly propagate ThrowableWrapper from the invoker,
// or potentially also an IllegalArgumentException etc.
ReflectionUtils.rethrowRuntimeException(ex.getCause());
}
}
else {
// No caching required, only call the underlying method
return invokeOperation(invoker);
}
}
在handleSynchronizedGet方法中会限制线程并发访问缓存,但具体还是依赖于Cache的实现。
- 基于ConcurrentHashMap的Cache,因为map本就是线程安全的,在ConcurrenMapCache中,Spring对各种操作没有作任何限制。
- 在RedisCache中,存在一个私有的synchronized修饰的getSynchronized方法。
在Halo中,以下两点让人疑惑
- InMemoryCache使用ConcurrentHashMap实现,get方法中使用了可重入锁。
- RedisCache没有作任何限制。
参照SpringSecurity的认证实现
SpringSecurity是一个比较重量级的框架,且保持有前后端不分离的风格,提供的功能多且复杂,学习成本也较高。Halo仅需要用到一小部分功能,参照框架自实现了一套认证流程,在security包下。
TransactionalEventListener
这篇博客讲得很不错,TransactionalEventListener使用场景以及实现原理。
Halo项目的应用场景是评论通知。
Q.E.D.