前面我们实现了用户进入页面增加浏览量,评论时增加评论数等操作,但是当项目第一次启动时,Redis中没有统计数据,导致查询攻略时拿到的统计数据(从Redis中获取)和数据库不一致。因此,在项目启动时,从数据库中查询文章的统计数据,将其写入Redis中。

我们需要在项目初始化完成后,执行统计数据初始化操作,可以通过继承 ApplicationListener 监听器实现

ApplicationEvent以及Listener是Spring为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。

在模块:trip-article-server中新建com.swx.article.listener包,创建 RedisStatDataInitListener 类,实现ApplicationListener 接口,重写其中的 onApplicationEvent 方法,当上下文event.getApplicationContext()AnnotationConfigServletWebServerApplicationContext时,Spring容器启动完成,这个时候可以开始我们的数据初始化工作。

当数据量过大时,可以考虑分批次查询,每批次交给一个线程去异步执行

也可以写一个初始化的接口,url可以设置为一个随机字符串,防止接口被滥用,为保证初始化接口只能使用一次,可以将状态保存到 Redis 中,当 Redis 存在该状态时,表面已经初始化过,直接返回404.

RedisStatDataInitListener
@Component
public class RedisStatDataInitListener implements ApplicationListener<ContextRefreshedEvent> {

private final StrategyService strategyService;
private final RedisService redisService;

public RedisStatDataInitListener(StrategyService strategyService, RedisService redisService) {
this.strategyService = strategyService;
this.redisService = redisService;
}

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext ctx = event.getApplicationContext();
System.out.println(ctx.getClass());
if (AnnotationConfigServletWebServerApplicationContext.class == ctx.getClass()) {
System.out.println("-------------- 容器启动完成,执行初始化数据 --------------");
// 查询所有攻略数据
// TODO: 不能一次加载所有数据
List<Strategy> strategies = strategyService.list();
System.out.println("[攻略统计数据初始化]");
System.out.println("攻略数:" + strategies.size());
int count = 0;
for (Strategy strategy : strategies) {
String fullKey = StrategyRedisKeyPrefix.STRATEGIES_STAT_DATA_MAP.fullKey(strategy.getId() + "");
Boolean exists = redisService.hasKey(fullKey);
if (!exists) {
// 不存在,将数据存入 Redis
HashMap<String, Object> map = new HashMap<>();
map.put("viewnum", strategy.getViewnum());
map.put("thumbsupnum", strategy.getThumbsupnum());
map.put("replynum", strategy.getReplynum());
map.put("favornum", strategy.getFavornum());
map.put("sharenum", strategy.getSharenum());
redisService.setCacheMap(fullKey, map);
count++;
}
}
System.out.println("初始化:" + count);
// 遍历攻略列表,判断当前对象在 Redis 中是否存在
System.out.println("-------------- 数据初始化完成 --------------");
}
}
}

这里容器启动时会导致循环依赖,需要修改 StrategyServiceImpl 类,在 UserInfoFeignService 前添加@Lazy注解。

StrategyServiceImpl
public StrategyServiceImpl(@Lazy UserInfoFeignService userInfoFeignService) {
this.userInfoFeignService = userInfoFeignService;
}