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")
|
@ApiOperation("Comments a post")
|
||||||
@CacheLock(autoDelete = false, traceRequest = true)
|
@CacheLock(autoDelete = false, traceRequest = true)
|
||||||
public BaseCommentDTO comment(@RequestBody PostCommentParam postCommentParam) {
|
public BaseCommentDTO comment(@RequestBody PostCommentParam postCommentParam) {
|
||||||
|
postCommentService.validateCommentBlackListStatus();
|
||||||
return postCommentService.convertTo(postCommentService.createBy(postCommentParam));
|
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, ""),
|
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;
|
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.model.projection.CommentCountProjection;
|
||||||
import run.halo.app.repository.base.BaseCommentRepository;
|
import run.halo.app.repository.base.BaseCommentRepository;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,4 +47,15 @@ public interface PostCommentRepository extends BaseCommentRepository<PostComment
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
List<CommentChildrenCountProjection> findDirectChildrenCount(@NonNull Collection<Long> commentIds);
|
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
|
@NonNull
|
||||||
Page<PostCommentWithPostVO> pageTreeBy(@NonNull CommentQuery commentQuery, @NonNull Pageable pageable);
|
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.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import run.halo.app.exception.BadRequestException;
|
import run.halo.app.exception.BadRequestException;
|
||||||
|
import run.halo.app.exception.ForbiddenException;
|
||||||
import run.halo.app.exception.NotFoundException;
|
import run.halo.app.exception.NotFoundException;
|
||||||
import run.halo.app.model.dto.post.BasePostMinimalDTO;
|
import run.halo.app.model.dto.post.BasePostMinimalDTO;
|
||||||
import run.halo.app.model.entity.Post;
|
import run.halo.app.model.entity.Post;
|
||||||
import run.halo.app.model.entity.PostComment;
|
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.params.CommentQuery;
|
||||||
|
import run.halo.app.model.properties.CommentProperties;
|
||||||
import run.halo.app.model.vo.PostCommentWithPostVO;
|
import run.halo.app.model.vo.PostCommentWithPostVO;
|
||||||
import run.halo.app.repository.PostCommentRepository;
|
import run.halo.app.repository.PostCommentRepository;
|
||||||
import run.halo.app.repository.PostRepository;
|
import run.halo.app.repository.PostRepository;
|
||||||
|
import run.halo.app.service.CommentBlackListService;
|
||||||
import run.halo.app.service.OptionService;
|
import run.halo.app.service.OptionService;
|
||||||
import run.halo.app.service.PostCommentService;
|
import run.halo.app.service.PostCommentService;
|
||||||
import run.halo.app.service.UserService;
|
import run.halo.app.service.UserService;
|
||||||
import run.halo.app.utils.ServiceUtils;
|
import run.halo.app.utils.ServiceUtils;
|
||||||
|
import run.halo.app.utils.ServletUtils;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -43,14 +48,18 @@ public class PostCommentServiceImpl extends BaseCommentServiceImpl<PostComment>
|
||||||
|
|
||||||
private final PostRepository postRepository;
|
private final PostRepository postRepository;
|
||||||
|
|
||||||
|
private final CommentBlackListService commentBlackListService;
|
||||||
|
|
||||||
public PostCommentServiceImpl(PostCommentRepository postCommentRepository,
|
public PostCommentServiceImpl(PostCommentRepository postCommentRepository,
|
||||||
PostRepository postRepository,
|
PostRepository postRepository,
|
||||||
UserService userService,
|
UserService userService,
|
||||||
OptionService optionService,
|
OptionService optionService,
|
||||||
|
CommentBlackListService commentBlackListService,
|
||||||
ApplicationEventPublisher eventPublisher) {
|
ApplicationEventPublisher eventPublisher) {
|
||||||
super(postCommentRepository, optionService, userService, eventPublisher);
|
super(postCommentRepository, optionService, userService, eventPublisher);
|
||||||
this.postCommentRepository = postCommentRepository;
|
this.postCommentRepository = postCommentRepository;
|
||||||
this.postRepository = postRepository;
|
this.postRepository = postRepository;
|
||||||
|
this.commentBlackListService = commentBlackListService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -110,4 +119,14 @@ public class PostCommentServiceImpl extends BaseCommentServiceImpl<PostComment>
|
||||||
throw new BadRequestException("该文章已经被禁止评论").setErrorData(postId);
|
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