原文网址: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 content
postCommentParam.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。