diff --git a/src/main/java/cn/allms/community/domain/model/post/Post.java b/src/main/java/cn/allms/community/domain/model/post/Post.java new file mode 100644 index 0000000..e62697d --- /dev/null +++ b/src/main/java/cn/allms/community/domain/model/post/Post.java @@ -0,0 +1,99 @@ +package cn.allms.community.domain.model.post; + +import cn.allms.community.domain.model.topic.TopicPost; +import cn.allms.community.infrastructure.constant.CommonConstants; +import cn.allms.community.infrastructure.constant.ExceptionCodeConstant; +import cn.allms.community.infrastructure.exception.BusinessException; +import lombok.Data; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** + * 帖子对象 + * @author xieYj + * @date 2021/5/11 11:50 + */ +@Data +public class Post { + /** + * 帖子ID + */ + private Long id; + + /** + * 帖子作者ID + */ + private Long authorId; + + /** + * 帖子标题 + */ + private String title; + + /** + * 帖子内容 + */ + private String sourceContent; + + /** + * 发帖时间戳 + */ + private Long postingTime; + + /** + * 最后修改时间 + */ + private Long lastModifyTime; + + /** + * 加入的话题 + */ + private Set topics = new HashSet<>(); + + /** + * 最多加入的主题数 + */ + public static final Integer MAX_JOINED_TOPICS_NUM = 5; + + /** + * 自定义的setter,优先会调用此set方法,而不是lombok自带的 + * @param authorId 作者Id + */ + public void setAuthorId(Long authorId) { + Assert.isTrue(authorId > 0, "帖子作者Id必须大于0"); + this.authorId = authorId; + } + + public void setSourceContent(String sourceContent) { + Assert.isTrue(Objects.nonNull(sourceContent), "帖子内容不能为空"); + Assert.isTrue(sourceContent.length() >= 16, "帖子内容必须大于16"); + this.sourceContent = sourceContent; + } + + /** + * 将帖子关联话题 | 加入话题 + * + * @param topicIds 话题集合 + */ + public void joinTopics(String topicIds) throws BusinessException { + if (Objects.isNull(topicIds)) { + return; + } + String[] topicIdArray = topicIds.split(CommonConstants.TOPIC_SPLIT_COMMA); + for (String s : topicIdArray) { + TopicPost topicPost = new TopicPost(Long.valueOf(s), this.getId()); + this.topics.add(topicPost); + if (topicSize() > MAX_JOINED_TOPICS_NUM) { + throw new BusinessException(ExceptionCodeConstant.ONE_POST_MOST_JOIN_INTO_FIVE_TOPICS); + } + } + } + + public int topicSize(){ + return this.topics.size(); + } +} diff --git a/src/main/java/cn/allms/community/domain/model/post/PostAuthor.java b/src/main/java/cn/allms/community/domain/model/post/PostAuthor.java new file mode 100644 index 0000000..4f02bbe --- /dev/null +++ b/src/main/java/cn/allms/community/domain/model/post/PostAuthor.java @@ -0,0 +1,52 @@ +package cn.allms.community.domain.model.post; + +import cn.allms.community.infrastructure.constant.ExceptionCodeConstant; +import cn.allms.community.infrastructure.exception.BusinessException; +import lombok.Data; + +/** + * 帖子作者对象 + * @author xieYj + * @date 2021/5/11 11:51 + */ +@Data +public class PostAuthor { + /** + * 帖子内容最小长度 + */ + private final static int MIN_LENGTH_POST_SOURCE_CONTENT = 16; + + /** + * 作者ID + */ + private Long id; + /** + * 作者账号 + */ + private String account; + /** + * 作者昵称 + */ + private String nickname; + + /** + * 发布帖子 + * + * @param title 帖子标题 + * @param sourceContent 帖子内容 + * @return + */ + public Post posting(String title, String sourceContent) throws BusinessException { + long currentTimeMillis = System.currentTimeMillis(); + if (sourceContent.length() < MIN_LENGTH_POST_SOURCE_CONTENT) { + throw new BusinessException(ExceptionCodeConstant.POST_SOURCE_CONTENT_AT_LEAST_SIXTEEN_WORDS); + } + Post post = new Post(); + post.setAuthorId(this.id); + post.setSourceContent(sourceContent); + post.setTitle(title); + post.setPostingTime(currentTimeMillis); + post.setLastModifyTime(currentTimeMillis); + return post; + } +} diff --git a/src/main/java/cn/allms/community/domain/model/topic/Topic.java b/src/main/java/cn/allms/community/domain/model/topic/Topic.java new file mode 100644 index 0000000..a912500 --- /dev/null +++ b/src/main/java/cn/allms/community/domain/model/topic/Topic.java @@ -0,0 +1,36 @@ +package cn.allms.community.domain.model.topic; + +import lombok.Data; + +import java.sql.Timestamp; +import java.util.Set; + +/** + * 话题对象 + * + * @author xieYj + * @date 2021/5/11 14:31 + */ +@Data +public class Topic { + /** + * 话题id + */ + private Long id; + /** + * 话题标题 + */ + private String title; + /** + * 话题描述 + */ + private String description; + /** + * 话题创建时间 + */ + private Timestamp createTime; + /** + * 话题下的帖子列表 + */ + private Set posts; +} diff --git a/src/main/java/cn/allms/community/domain/model/topic/TopicPost.java b/src/main/java/cn/allms/community/domain/model/topic/TopicPost.java new file mode 100644 index 0000000..aed723b --- /dev/null +++ b/src/main/java/cn/allms/community/domain/model/topic/TopicPost.java @@ -0,0 +1,17 @@ +package cn.allms.community.domain.model.topic; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * 话题文章对象 + * + * @author xieYj + * @date 2021/5/11 14:31 + */ +@Data +@AllArgsConstructor +public class TopicPost { + private Long topicId; + private Long postId; +} diff --git a/src/main/java/cn/allms/community/domain/service/contentfilter/ContentFilter.java b/src/main/java/cn/allms/community/domain/service/contentfilter/ContentFilter.java new file mode 100644 index 0000000..a9550a6 --- /dev/null +++ b/src/main/java/cn/allms/community/domain/service/contentfilter/ContentFilter.java @@ -0,0 +1,25 @@ +package cn.allms.community.domain.service.contentfilter; + +import lombok.Getter; +import lombok.Setter; + +/** + * 内容过滤器 + * @author xieYj + * @date 2021/5/11 15:09 + */ +@Setter +@Getter +public abstract class ContentFilter { + /** + * 下一个内容过滤器 + */ + private ContentFilter nextContentFilter; + + /** + * 过滤内容 + * @param object 过滤对象 + * @return + */ + public abstract boolean filterContent(Object object); +} diff --git a/src/main/java/cn/allms/community/domain/service/contentfilter/ImageContentFilter.java b/src/main/java/cn/allms/community/domain/service/contentfilter/ImageContentFilter.java new file mode 100644 index 0000000..b78309f --- /dev/null +++ b/src/main/java/cn/allms/community/domain/service/contentfilter/ImageContentFilter.java @@ -0,0 +1,14 @@ +package cn.allms.community.domain.service.contentfilter; + +/** + * 图片过滤器 + * @author xieYj + * @date 2021/5/11 15:17 + */ +public class ImageContentFilter extends ContentFilter{ + @Override + public boolean filterContent(Object object) { + // TODO 调用第三方接口实现图片过滤 + return false; + } +} diff --git a/src/main/java/cn/allms/community/domain/service/contentfilter/LocalTextContentFilter.java b/src/main/java/cn/allms/community/domain/service/contentfilter/LocalTextContentFilter.java new file mode 100644 index 0000000..d718371 --- /dev/null +++ b/src/main/java/cn/allms/community/domain/service/contentfilter/LocalTextContentFilter.java @@ -0,0 +1,35 @@ +package cn.allms.community.domain.service.contentfilter; + +import org.springframework.util.Assert; + +import java.util.HashSet; +import java.util.Set; + +/** + * 本地文本内容过滤器 + * @author xieYj + * @date 2021/5/11 15:24 + */ +public class LocalTextContentFilter extends TextContentFilter { + /** + * 本地敏感词 + */ + private Set sensitiveWords = new HashSet<>(); + + public LocalTextContentFilter() { + sensitiveWords.add("NND"); + sensitiveWords.add("奶奶个熊"); + } + + + @Override + public boolean filterContent(Object content) { + Assert.isTrue(content instanceof String, "本地文本过滤对象必须为 String 类型"); + for (String sensitiveWord : sensitiveWords) { + if (((String) content).contains(sensitiveWord)) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/cn/allms/community/domain/service/contentfilter/PostMainBodyContentFilterChain.java b/src/main/java/cn/allms/community/domain/service/contentfilter/PostMainBodyContentFilterChain.java new file mode 100644 index 0000000..d643551 --- /dev/null +++ b/src/main/java/cn/allms/community/domain/service/contentfilter/PostMainBodyContentFilterChain.java @@ -0,0 +1,46 @@ +package cn.allms.community.domain.service.contentfilter; + +import cn.allms.community.domain.model.post.Post; + +import java.util.Set; + +/** + * 帖子内容过滤:包含文本过滤,图片过滤 + * 责任链模式 + * + * @author xieYj + * @date 2021/5/11 15:33 + */ +public class PostMainBodyContentFilterChain { + /** + * 内容过滤器 + */ + private Set contentFilters; + + public PostMainBodyContentFilterChain() { + TextContentFilter localTextContentFilter = new LocalTextContentFilter(); + TextContentFilter remoteTextContentFilter = new RemoteTextContentFilter(); + ImageContentFilter imageContentFilter = new ImageContentFilter(); + // 优先校验本地的敏感词 + contentFilters.add(localTextContentFilter); + // 第三方文本敏感词过滤 + contentFilters.add(remoteTextContentFilter); + // 第三方图片过滤 + contentFilters.add(imageContentFilter); + } + + /** + * 过滤标题 + * + * @param post 帖子信息 + * @return true —— 通过, false —— 未通过 + */ + public boolean filterMainBody(Post post) { + for (ContentFilter contentFilter : contentFilters) { + if (!contentFilter.filterContent(post.getSourceContent())) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/cn/allms/community/domain/service/contentfilter/PostTitleContentFilterChain.java b/src/main/java/cn/allms/community/domain/service/contentfilter/PostTitleContentFilterChain.java new file mode 100644 index 0000000..cff9e4e --- /dev/null +++ b/src/main/java/cn/allms/community/domain/service/contentfilter/PostTitleContentFilterChain.java @@ -0,0 +1,44 @@ +package cn.allms.community.domain.service.contentfilter; + +import cn.allms.community.domain.model.post.Post; + +import java.util.Iterator; +import java.util.Set; + +/** + * 帖子标题过滤:文本过滤 + * 责任链模式 + * + * @author xieYj + * @date 2021/5/11 15:40 + */ +public class PostTitleContentFilterChain { + /** + * 内容过滤器 + */ + private Set contentFilters; + + public PostTitleContentFilterChain() { + LocalTextContentFilter localTextContentFilter = new LocalTextContentFilter(); + RemoteTextContentFilter remoteTextContentFilter = new RemoteTextContentFilter(); + // 本地文本敏感词校验 + contentFilters.add(localTextContentFilter); + // 第三方文本敏感词校验 + contentFilters.add(remoteTextContentFilter); + } + + /** + * 过滤标题 + * + * @param post 帖子信息 + * @return true —— 通过,false —— 未通过 + */ + public boolean filterTitle(Post post) { + for (ContentFilter contentFilter : contentFilters) { + if (!contentFilter.filterContent(post.getTitle())) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/cn/allms/community/domain/service/contentfilter/RemoteTextContentFilter.java b/src/main/java/cn/allms/community/domain/service/contentfilter/RemoteTextContentFilter.java new file mode 100644 index 0000000..7acf6eb --- /dev/null +++ b/src/main/java/cn/allms/community/domain/service/contentfilter/RemoteTextContentFilter.java @@ -0,0 +1,14 @@ +package cn.allms.community.domain.service.contentfilter; + +/** + * 远程文本内容过滤器 + * @author xieYj + * @date 2021/5/11 15:22 + */ +public class RemoteTextContentFilter extends TextContentFilter{ + @Override + public boolean filterContent(Object object) { + // TODO 需要调用第三方服务才能实现 + return false; + } +} diff --git a/src/main/java/cn/allms/community/domain/service/contentfilter/TextContentFilter.java b/src/main/java/cn/allms/community/domain/service/contentfilter/TextContentFilter.java new file mode 100644 index 0000000..83d7293 --- /dev/null +++ b/src/main/java/cn/allms/community/domain/service/contentfilter/TextContentFilter.java @@ -0,0 +1,16 @@ +package cn.allms.community.domain.service.contentfilter; + +/** + * 文本过滤器 + * @author xieYj + * @date 2021/5/11 15:18 + */ +public abstract class TextContentFilter extends ContentFilter{ + /** + * 文本过滤器,抽象 + * @param object 过滤对象 + * @return + */ + @Override + public abstract boolean filterContent(Object object); +} diff --git a/src/main/java/cn/allms/community/infrastructure/constant/CommonConstants.java b/src/main/java/cn/allms/community/infrastructure/constant/CommonConstants.java new file mode 100644 index 0000000..4332c68 --- /dev/null +++ b/src/main/java/cn/allms/community/infrastructure/constant/CommonConstants.java @@ -0,0 +1,10 @@ +package cn.allms.community.infrastructure.constant; + +/** + * 公共常量 + * @author xieYj + * @date 2021/5/11 14:56 + */ +public interface CommonConstants { + String TOPIC_SPLIT_COMMA = ","; +} diff --git a/src/main/java/cn/allms/community/infrastructure/constant/ExceptionCodeConstant.java b/src/main/java/cn/allms/community/infrastructure/constant/ExceptionCodeConstant.java new file mode 100644 index 0000000..6b0abad --- /dev/null +++ b/src/main/java/cn/allms/community/infrastructure/constant/ExceptionCodeConstant.java @@ -0,0 +1,12 @@ +package cn.allms.community.infrastructure.constant; + +/** + * 异常码状态常量 + * + * @author xieYj + * @date 2021/5/11 14:17 + */ +public interface ExceptionCodeConstant { + String POST_SOURCE_CONTENT_AT_LEAST_SIXTEEN_WORDS = "2001"; + String ONE_POST_MOST_JOIN_INTO_FIVE_TOPICS = "2002"; +} diff --git a/src/main/java/cn/allms/community/infrastructure/exception/BusinessException.java b/src/main/java/cn/allms/community/infrastructure/exception/BusinessException.java new file mode 100644 index 0000000..4c41736 --- /dev/null +++ b/src/main/java/cn/allms/community/infrastructure/exception/BusinessException.java @@ -0,0 +1,84 @@ +package cn.allms.community.infrastructure.exception; + +/** + * @author xieYj + * @date 2021/5/11 13:59 + */ +public class BusinessException extends Exception { + /** + * 业务异常码 + */ + private String code; + /** + * 业务异常参数 + */ + private Object[] arguments; + + public BusinessException() { + super(); + } + + public BusinessException(Throwable cause) { + super(cause); + } + + public BusinessException(String message) { + super(message); + } + + public BusinessException(String code, String message) { + super(message); + this.code = code; + } + + public BusinessException(String message, Object[] arguments) { + super(message); + this.arguments = arguments; + } + + public BusinessException(String code, String message, Object[] arguments) { + super(message); + this.code = code; + this.arguments = arguments; + } + + public BusinessException(String message, Object[] arguments, Throwable cause) { + super(message, cause); + this.arguments = arguments; + } + + public BusinessException(String code, String message, Object[] arguments, Throwable cause) { + super(message, cause); + this.code = code; + this.arguments = arguments; + } + + public BusinessException(String message, Throwable cause) { + super(message, cause); + } + + + public BusinessException(String code, String message, Throwable cause) { + super(message, cause); + this.code = code; + } + + + public BusinessException(String message, + Throwable cause, + boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + + public BusinessException(String code, + String message, + Throwable cause, + boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + this.code = code; + } + +}