原文网址:halo博客--解决恶意刷评论的问题_IT利刃出鞘的博客-CSDN博客
简介
本文介绍halo博客如何通过设置评论次数来解决恶意刷评论的问题。
评论功能要设置频率的限制,否则可能被人一直刷评论,然后数据库存的垃圾评论越来越多,手动删除很麻烦。
halo博客设置评论频率的地方很隐蔽,这里我来介绍。
打开开发者模式
1.连续点击halo后台的图标
2.进入开发者设置
修改评论频率的设置
有两个设置与评论有关:
- comment_ban_time:时间阈值(单位:分钟,默认是 10)
- comment_range:评论数量(单位:次,默认是30)
默认配置为:如果 10 分钟内,当前 IP 的评论数达到30,就禁止再次评论。
修改配置的方法如下:
测试
我将评论设置为:12个小时(720分钟)只能评论两次
- comment_ban_time:720
- comment_range:2
结果
第三次评论时报错:
代码分析
点击 "评论" 按钮后,触发 api/content/posts/comments 请求:
该请求由 PostController 中的 comment 方法处理:
@PostMapping("comments")
@ApiOperation("Comments a post")
@CacheLock(autoDelete = false, traceRequest = true)
public BaseCommentDTO comment(@RequestBody PostCommentParam postCommentParam) {// 验证当前 IP 是否处于封禁状态postCommentService.validateCommentBlackListStatus();// 对评论的内容进行转义// Escape contentpostCommentParam.setContent(HtmlUtils.htmlEscape(postCommentParam.getContent(), StandardCharsets.UTF_8.displayName()));// 创建评论return postCommentService.convertTo(postCommentService.createBy(postCommentParam));
}
comment 方法首先会检查当前发送评论的 IP 是否处于封禁状态,如果未处于封禁状态,那么系统会对评论的内容进行 HTML 转义,转义完成后创建该评论。首先介绍一下 Halo 的 "封禁评论" 机制,封禁的目的是防止恶意 IP 抢占和浪费博客系统的资源。进入validateCommentBlackListStatus 方法,查看验证 IP 的具体过程:
public void validateCommentBlackListStatus() {// 查看当前 IP 的封禁状态CommentViolationTypeEnum banStatus =commentBlackListService.commentsBanStatus(ServletUtils.getRequestIp());// 获取系统设置的封禁时间Integer banTime = optionService.getByPropertyOrDefault(CommentProperties.COMMENT_BAN_TIME, Integer.class, 10);// 如果当前 IP 处于封禁状态, 提示用户稍后重试if (banStatus == CommentViolationTypeEnum.FREQUENTLY) {throw new ForbiddenException(String.format("您的评论过于频繁,请%s分钟之后再试。", banTime));}
}
上述代码中,服务器首先查询当前 IP 的封禁状态,如果状态为 FREQUENTLY,那么就认为当前 IP 的评论过于频繁,然后提示用户稍后重试。该过程是一种 "限流" 机制,其重点在于如何设计 "频繁评论" 的评判标准,直白一点就是如何 "限流"?限流的方式有很多种,如利用缓存或内存队列等。Halo 中使用数据库来实现限流策略,这个设计思路也是非常值得学习的,commentsBanStatus 方法的处理逻辑如下:
public CommentViolationTypeEnum commentsBanStatus(String ipAddress) {/*N=后期可配置1. 获取评论次数;2. 判断N分钟内,是否超过规定的次数限制,超过后需要每隔N分钟才能再次评论;3. 如果在时隔N分钟内,还有多次评论,可被认定为恶意攻击者;4. 对恶意攻击者进行N分钟的封禁;*/// 发送评论的 ip 在封禁是否在封禁名单中Optional<CommentBlackList> blackList =commentBlackListRepository.findByIpAddress(ipAddress);LocalDateTime now = LocalDateTime.now();Date endTime = new Date(DateTimeUtils.toEpochMilli(now));// 封禁的时间间隔, 也是评估是否需要封禁的时间间隔, 默认 10 分钟Integer banTime = optionService.getByPropertyOrDefault(CommentProperties.COMMENT_BAN_TIME, Integer.class, 10);// now - 时间间隔Date startTime = new Date(DateTimeUtils.toEpochMilli(now.minusMinutes(banTime)));// 评论数阈值, 默认为 30 个Integer range = optionService.getByPropertyOrDefault(CommentProperties.COMMENT_RANGE, Integer.class, 30);// 指定时间间隔内, 当前 ip 的评论数是否超过评论数阈值boolean isPresent =postCommentRepository.countByIpAndTime(ipAddress, startTime, endTime) >= range;if (isPresent && blackList.isPresent()) {// 设置当前 IP 的解禁时间为 banTime 分钟后update(now, blackList.get(), banTime);return CommentViolationTypeEnum.FREQUENTLY;} else if (isPresent) {// 构建 CommentBlackList 对象, 设置当前 IP 的解禁时间为 banTime 分钟后CommentBlackList commentBlackList = CommentBlackList.builder().banTime(getBanTime(now, banTime)).ipAddress(ipAddress).build();super.create(commentBlackList);return CommentViolationTypeEnum.FREQUENTLY;}return CommentViolationTypeEnum.NORMAL;
}
- 查询当前 IP 是否处于封禁黑名单(comment_black_list 表)中。
- 查询系统设置的时间阈值 banTime(默认是 10 分钟),并判断从 banTime 分钟前到现在,当前 IP 的评论数是否超过了评论数阈值 range(默认是 30 个),如果超过了,那么就需要对当前 IP 实施封禁措施。换句话说,如果 banTime 分钟内,当前 IP 的评论数达到指定阈值,就对当前 IP 进行限流,这里 banTime 是评估封禁的参数,也可以称为时间阈值。
- 达到限流条件后,如果当前 IP 存在于封禁黑名单,那么更新 comment_black_list 表,将其解禁时间设置为 banTime 分钟后。
虽然 comment_black_list 表中的属性 ban_time 在项目中被称为封禁时间,但结合代码可以发现它的真实含义是解禁时间。如果当前 IP 不在封禁黑名单,那么创建一条新的记录,IP 为当前请求的 IP,解禁时间为 banTime 分钟后。实际上,封禁黑名单的业务含义设置的并不严谨,它的作用仅仅是在数据表中创建或更新一条记录,且记录的解禁时间也只是一个参考值,因为评估 "限流" 的依据是 banTime 分钟前到现在的总评论数,与黑名单中的时间并无关联。Halo 中的 "限流" 机制类似于一个优先队列,队列的容量为 range,元素的属性包括 IP 和入队时间,如果元素入队的时间与当前时间的间隔达到 banTime,那么该元素出队,如果队列已满,那么实施 "限流",一旦队列恢复出至少一个空闲位置,那么用户便可再次发表评论。 - 达到限流条件后返回封禁状态 FREQUENTLY,否则返回 NORMAL。