大家好,我是孙嵓,大家经常翻看公众号文章左下角是不是有个显示了多少人阅读,那这个是怎么实现的呢,本文带你进行揭秘,当然微信研发可能用的不是我这种方式但是大同小异吗,我也是最近把这个功能放到了自己的线上项目,最近在单位太闲了自己给自己找点事干,总不能一直刷知乎吧,卷起来!!!
项目需求:
实现例如微信公众号左下角阅读多少次类似功能。
实现思路
结合aop,实现after方法,采用redis存储网页浏览次数,每调用一次接对redis值进行+1操作(注意这个接口只有在浏览此网页的时候有调用才行)。
其实还有另一种思路那就是通过拦截器去实现HandlerInterceptor也可以实现浏览次数,拦截一次就加1,大同小异,后续会出一个详细教程,废话少说看代码!!!
代码实现:
注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BrowseTimes {
/**
* 浏览类型(这里用的是个枚举)
*/
String type() default BrowseTimesType.COMMON;
}
Aspect
@Aspect
@Component
@Order(2)
public class BrowseTimeAspect {
@Autowired
RedisService redisService;
@Autowired
TokenService tokenService;
@Autowired
IPositionService positionService;
private static Logger logger = LoggerFactory.getLogger(BrowseTimeAspect.class);
/**
* 切入点
*/
@Pointcut("@annotation(com.fuwai.hr.common.annotation.BrowseTimes)")
public void checkPoint() {
}
/**
* 环绕获取请求参数
*
* @param joinPoint
* @return
*/
@After("checkPoint()")
public void afterMethod(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取被代理的方法
Method method = signature.getMethod();
//获取方法上的注解
BrowseTimes browseTimes = method.getAnnotation(BrowseTimes.class);
//获取方法的参数
Object[] args = joinPoint.getArgs()
//获取注解类型;业务上目前就这一种类型所以就没有分情况讨论
StringBuilder type = new StringBuilder(browseTimes.type());
//拼装redis的key,由于方法参数就这一个我就直接这么写了,这个参数是个id后边代码会写到
type.append(args[0]);
redisService.judgeBrowseTimes(type.toString());
}
}
判断浏览次数是否达到最大值方法
public synchronized void judgeBrowseTimes(String key) {
//获取key值,值为null插入值
String value = redisCache.getCacheObject(key);
if (StringUtils.isEmpty(value)) {
//不存在更新,存在不进行操作
redisCache.setIfAbsent(key, "1");
return;
}
Integer intValue = Integer.parseInt(value) + 1;
//存在即更新,不存在不进行操作;
redisCache.setIfPresent(key, String.valueOf(intValue));
}
最后将其挂载到方法上即可
@GetMapping(value = "/test")
@BrowseTimes(type = BrowseTimesType.TEST)
public AjaxResult test(@RequestParam("testId") String testId)
{
return testService.test(testId);
}
存在的问题及如何改进:
- 我定义了BrowseTimesType这个枚举按道理说应该支持多种类型的浏览次数,因为不同种类的业务逻辑可能不同需要单独处理扩充,用switch
case显得太臃肿这里可以考虑策略或者简易的模板方法设计模式去设计,增强代码的延展性和可维护性。 - 我的redis的key存活时间都设置了永久,那是我业务有些操作会把这个key给删掉的所以我就没加过期时间。其实正确的方法是应该将redis设置过期时间,可以写个定时任务将这些数据存到数据库中,如果redis不存在了就去查库,重新放入redis添加过期时间,写一段伪代码吧,当然如果你的用户访问量很多的话建议防止一下缓存穿透、击穿,雪崩啊等等。
if(ObjectUtil.isEmpty(redisCache.getKey(key))){
//查库
String num = sqlService.selectBrowseTime(key);
//设置了七天
redisCache.setIfAbsent(key, num , 7, TimeUnit.DAYS);
}
- 并发问题 其实判断浏览次数的方法是有并发问题的这里我分两种情况讨论
单体应用
直接在方法上加synchronized关键字;或者采用ReentrantLock在方法一开始进行lock操作,最后别忘了unlock
分布式应用
这里建议采用分布式锁,可以引入redisson,操作同ReentrantLock类似都是lock和unlock这里不做过多介绍,有兴趣的可以看一下
以上就是本文的全部内容了,能力有限,理性对待
如果感觉还不错的话,欢迎点赞和关注🦋
分享经验,贴近项目,crud永不为奴!!!
欢迎大家关注我的公众号,公众号也会实时发布Java项目相关的文章!!!