mirror of https://github.com/halo-dev/halo
feat:(add comment blacklist) (#465)
* feat:(add comment blacklist) * update enum name * update enum * fix:(update pom) * 优化相关的细节pull/499/head
parent
d867f4cca3
commit
11c32ffdca
|
@ -130,6 +130,7 @@ public class PostController {
|
|||
@ApiOperation("Comments a post")
|
||||
@CacheLock(autoDelete = false, traceRequest = true)
|
||||
public BaseCommentDTO comment(@RequestBody PostCommentParam postCommentParam) {
|
||||
postCommentService.validateCommentBlackListStatus();
|
||||
return postCommentService.convertTo(postCommentService.createBy(postCommentParam));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package run.halo.app.model.entity;
|
||||
|
||||
import lombok.*;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* comment_black_list
|
||||
*
|
||||
* @author Lei XinXin
|
||||
* @date 2020/1/3
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@Table(name = "comment_black_list")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CommentBlackList extends BaseEntity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "ip_address", columnDefinition = "VARCHAR(127) NOT NULL")
|
||||
private String ipAddress;
|
||||
|
||||
/**
|
||||
* 封禁时间
|
||||
*/
|
||||
@Column(name = "ban_time")
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date banTime;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package run.halo.app.model.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 封禁状态
|
||||
* @author Lei XinXin
|
||||
* @date 2020/1/5
|
||||
*/
|
||||
@Getter
|
||||
public enum BanStatusEnum {
|
||||
/**
|
||||
* 封禁状态
|
||||
*/
|
||||
NORMAL(0),;
|
||||
|
||||
private int status;
|
||||
BanStatusEnum(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package run.halo.app.model.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 评论违规类型枚举
|
||||
*
|
||||
* @author Lei XinXin
|
||||
* @date 2020/1/4
|
||||
*/
|
||||
@Getter
|
||||
public enum CommentViolationTypeEnum {
|
||||
/**
|
||||
* 评论违规类型
|
||||
*/
|
||||
NORMAL(0),
|
||||
/**
|
||||
* 频繁
|
||||
*/
|
||||
FREQUENTLY(1),
|
||||
;
|
||||
|
||||
private int type;
|
||||
CommentViolationTypeEnum(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
|
@ -25,7 +25,13 @@ public enum CommentProperties implements PropertyEnum {
|
|||
|
||||
CONTENT_PLACEHOLDER("comment_content_placeholder", String.class, ""),
|
||||
|
||||
INTERNAL_PLUGIN_JS("comment_internal_plugin_js", String.class, "//cdn.jsdelivr.net/gh/halo-dev/halo-comment@latest/dist/halo-comment.min.js");
|
||||
INTERNAL_PLUGIN_JS("comment_internal_plugin_js", String.class, "//cdn.jsdelivr.net/gh/halo-dev/halo-comment@latest/dist/halo-comment.min.js"),
|
||||
|
||||
COMMENT_BAN_TIME("comment_ban_time", Integer.class, "10"),
|
||||
|
||||
COMMENT_RANGE("comment_range", Integer.class, "30");
|
||||
|
||||
|
||||
|
||||
private final String value;
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package run.halo.app.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import run.halo.app.model.entity.CommentBlackList;
|
||||
import run.halo.app.repository.base.BaseRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 评论黑名单Repository
|
||||
*
|
||||
* @author Lei XinXin
|
||||
* @date 2020/1/3
|
||||
*/
|
||||
public interface CommentBlackListRepository extends BaseRepository<CommentBlackList, Long> {
|
||||
|
||||
/**
|
||||
* 根据IP地址获取数据
|
||||
*
|
||||
* @param ipAddress
|
||||
* @return
|
||||
*/
|
||||
Optional<CommentBlackList> findByIpAddress(String ipAddress);
|
||||
|
||||
/**
|
||||
* Update Comment BlackList By IPAddress
|
||||
*
|
||||
* @param commentBlackList
|
||||
* @return result
|
||||
*/
|
||||
@Modifying
|
||||
@Query("UPDATE CommentBlackList SET banTime=:#{#commentBlackList.banTime} WHERE ipAddress=:#{#commentBlackList.ipAddress}")
|
||||
int updateByIpAddress(@Param("commentBlackList") CommentBlackList commentBlackList);
|
||||
}
|
|
@ -7,7 +7,9 @@ import run.halo.app.model.projection.CommentChildrenCountProjection;
|
|||
import run.halo.app.model.projection.CommentCountProjection;
|
||||
import run.halo.app.repository.base.BaseCommentRepository;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -45,4 +47,15 @@ public interface PostCommentRepository extends BaseCommentRepository<PostComment
|
|||
@NonNull
|
||||
@Override
|
||||
List<CommentChildrenCountProjection> findDirectChildrenCount(@NonNull Collection<Long> commentIds);
|
||||
|
||||
/**
|
||||
* 根据时间范围和IP地址统计评论次数
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param startTime 起始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 评论次数
|
||||
*/
|
||||
@Query("SELECT COUNT(id) FROM PostComment WHERE ipAddress=?1 AND updateTime BETWEEN ?2 AND ?3 AND status <> 2")
|
||||
int countByIpAndTime(String ipAddress, Date startTime, Date endTime);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package run.halo.app.service;
|
||||
|
||||
import run.halo.app.model.enums.CommentViolationTypeEnum;
|
||||
|
||||
/**
|
||||
* Comment BlackList Service
|
||||
* @author Lei XinXin
|
||||
* @date 2020/1/3
|
||||
*/
|
||||
public interface CommentBlackListService {
|
||||
/**
|
||||
* 评论封禁状态
|
||||
*
|
||||
* @param ipAddress ip地址
|
||||
* @return boolean
|
||||
*/
|
||||
CommentViolationTypeEnum commentsBanStatus(String ipAddress);
|
||||
}
|
|
@ -49,4 +49,9 @@ public interface PostCommentService extends BaseCommentService<PostComment> {
|
|||
|
||||
@NonNull
|
||||
Page<PostCommentWithPostVO> pageTreeBy(@NonNull CommentQuery commentQuery, @NonNull Pageable pageable);
|
||||
|
||||
/**
|
||||
* Validate CommentBlackList Status
|
||||
*/
|
||||
void validateCommentBlackListStatus();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
package run.halo.app.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import run.halo.app.model.entity.CommentBlackList;
|
||||
import run.halo.app.model.enums.CommentViolationTypeEnum;
|
||||
import run.halo.app.model.properties.CommentProperties;
|
||||
import run.halo.app.repository.CommentBlackListRepository;
|
||||
import run.halo.app.repository.PostCommentRepository;
|
||||
import run.halo.app.service.CommentBlackListService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.base.AbstractCrudService;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Comment BlackList Service Implements
|
||||
*
|
||||
* @author Lei XinXin
|
||||
* @date 2020/1/3
|
||||
*/
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CommentBlackListServiceImpl extends AbstractCrudService<CommentBlackList, Long> implements CommentBlackListService {
|
||||
private static ZoneId zoneId = ZoneId.of("Asia/Shanghai");
|
||||
private final CommentBlackListRepository commentBlackListRepository;
|
||||
private final PostCommentRepository postCommentRepository;
|
||||
private final OptionService optionService;
|
||||
|
||||
|
||||
public CommentBlackListServiceImpl(CommentBlackListRepository commentBlackListRepository, PostCommentRepository postCommentRepository, OptionService optionService) {
|
||||
super(commentBlackListRepository);
|
||||
this.commentBlackListRepository = commentBlackListRepository;
|
||||
this.postCommentRepository = postCommentRepository;
|
||||
this.optionService = optionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentViolationTypeEnum commentsBanStatus(String ipAddress) {
|
||||
/*
|
||||
N=后期可配置
|
||||
1. 获取评论次数;
|
||||
2. 判断N分钟内,是否超过规定的次数限制,超过后需要每隔N分钟才能再次评论;
|
||||
3. 如果在时隔N分钟内,还有多次评论,可被认定为恶意攻击者;
|
||||
4. 对恶意攻击者进行N分钟的封禁;
|
||||
*/
|
||||
Optional<CommentBlackList> blackList = commentBlackListRepository.findByIpAddress(ipAddress);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
Date endTime = new Date(now.atZone(zoneId).toInstant().toEpochMilli());
|
||||
Integer banTime = optionService.getByPropertyOrDefault(CommentProperties.COMMENT_BAN_TIME, Integer.class, 10);
|
||||
Date startTime = new Date(now.minusMinutes(banTime)
|
||||
.atZone(zoneId).toInstant().toEpochMilli());
|
||||
Integer range = optionService.getByPropertyOrDefault(CommentProperties.COMMENT_RANGE, Integer.class, 30);
|
||||
boolean isPresent = postCommentRepository.countByIpAndTime(ipAddress, startTime, endTime) >= range;
|
||||
if (isPresent && blackList.isPresent()) {
|
||||
update(now, blackList.get(), banTime);
|
||||
return CommentViolationTypeEnum.FREQUENTLY;
|
||||
} else if (isPresent) {
|
||||
CommentBlackList commentBlackList = CommentBlackList
|
||||
.builder()
|
||||
.banTime(getBanTime(now, banTime))
|
||||
.ipAddress(ipAddress)
|
||||
.build();
|
||||
super.create(commentBlackList);
|
||||
return CommentViolationTypeEnum.FREQUENTLY;
|
||||
}
|
||||
return CommentViolationTypeEnum.NORMAL;
|
||||
}
|
||||
|
||||
private void update(LocalDateTime localDateTime, CommentBlackList blackList, Integer banTime) {
|
||||
blackList.setBanTime(getBanTime(localDateTime, banTime));
|
||||
int updateResult = commentBlackListRepository.updateByIpAddress(blackList);
|
||||
Optional.of(updateResult)
|
||||
.filter(result -> result <= 0).ifPresent(result -> log.error("更新评论封禁时间失败"));
|
||||
}
|
||||
|
||||
private Date getBanTime(LocalDateTime localDateTime, Integer banTime) {
|
||||
return new Date(localDateTime.plusMinutes(banTime).atZone(zoneId).toInstant().toEpochMilli());
|
||||
}
|
||||
}
|
|
@ -9,18 +9,23 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import run.halo.app.exception.BadRequestException;
|
||||
import run.halo.app.exception.ForbiddenException;
|
||||
import run.halo.app.exception.NotFoundException;
|
||||
import run.halo.app.model.dto.post.BasePostMinimalDTO;
|
||||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.PostComment;
|
||||
import run.halo.app.model.enums.CommentViolationTypeEnum;
|
||||
import run.halo.app.model.params.CommentQuery;
|
||||
import run.halo.app.model.properties.CommentProperties;
|
||||
import run.halo.app.model.vo.PostCommentWithPostVO;
|
||||
import run.halo.app.repository.PostCommentRepository;
|
||||
import run.halo.app.repository.PostRepository;
|
||||
import run.halo.app.service.CommentBlackListService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCommentService;
|
||||
import run.halo.app.service.UserService;
|
||||
import run.halo.app.utils.ServiceUtils;
|
||||
import run.halo.app.utils.ServletUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -43,14 +48,18 @@ public class PostCommentServiceImpl extends BaseCommentServiceImpl<PostComment>
|
|||
|
||||
private final PostRepository postRepository;
|
||||
|
||||
private final CommentBlackListService commentBlackListService;
|
||||
|
||||
public PostCommentServiceImpl(PostCommentRepository postCommentRepository,
|
||||
PostRepository postRepository,
|
||||
UserService userService,
|
||||
OptionService optionService,
|
||||
CommentBlackListService commentBlackListService,
|
||||
ApplicationEventPublisher eventPublisher) {
|
||||
super(postCommentRepository, optionService, userService, eventPublisher);
|
||||
this.postCommentRepository = postCommentRepository;
|
||||
this.postRepository = postRepository;
|
||||
this.commentBlackListService = commentBlackListService;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -110,4 +119,14 @@ public class PostCommentServiceImpl extends BaseCommentServiceImpl<PostComment>
|
|||
throw new BadRequestException("该文章已经被禁止评论").setErrorData(postId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateCommentBlackListStatus() {
|
||||
CommentViolationTypeEnum banStatus = commentBlackListService.commentsBanStatus(ServletUtils.getRequestIp());
|
||||
Integer banTime = optionService.getByPropertyOrDefault(CommentProperties.COMMENT_BAN_TIME, Integer.class, 10);
|
||||
if (banStatus == CommentViolationTypeEnum.FREQUENTLY) {
|
||||
throw new ForbiddenException(String.format("您的评论过于频繁,请%s分钟之后再试。", banTime));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue