Skip to content

Commit 00d46bd

Browse files
yyyCodeclaude
andcommitted
feat(sensitive-word): add DFA-based sensitive word filtering
- Add com.github.houbb:sensitive-word:0.29.5 dependency - Create SensitiveWordFilter utility component with built-in 6W+ word bank - Integrate into ForumService (topic title+content, comment content) - Integrate into CommentService (article comment create + reply) - Returns 4003 error code when sensitive words detected Co-Authored-By: Claude <noreply@anthropic.com>
1 parent bf401d8 commit 00d46bd

4 files changed

Lines changed: 107 additions & 2 deletions

File tree

OpenBlog-business/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@
9797
<version>0.4.20</version>
9898
</dependency>
9999

100+
<!-- 敏感词过滤(DFA 算法) -->
101+
<dependency>
102+
<groupId>com.github.houbb</groupId>
103+
<artifactId>sensitive-word</artifactId>
104+
<version>0.29.5</version>
105+
</dependency>
106+
100107
<dependency>
101108
<groupId>io.minio</groupId>
102109
<artifactId>minio</artifactId>

OpenBlog-business/src/main/java/com/yqz/openblog/comment/service/CommentService.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.yqz.openblog.comment.repo.CommentMapper;
1818
import com.yqz.openblog.common.BizException;
1919
import com.yqz.openblog.common.PageResult;
20+
import com.yqz.openblog.forum.filter.SensitiveWordFilter;
2021
import com.yqz.openblog.user.entity.User;
2122
import com.yqz.openblog.user.repo.UserRepository;
2223
import org.springframework.stereotype.Service;
@@ -30,11 +31,14 @@ public class CommentService {
3031
private final CommentMapper commentMapper;
3132
private final ArticleMapper articleMapper;
3233
private final UserRepository userRepository;
34+
private final SensitiveWordFilter sensitiveWordFilter;
3335

34-
public CommentService(CommentMapper commentMapper, ArticleMapper articleMapper, UserRepository userRepository) {
36+
public CommentService(CommentMapper commentMapper, ArticleMapper articleMapper,
37+
UserRepository userRepository, SensitiveWordFilter sensitiveWordFilter) {
3538
this.commentMapper = commentMapper;
3639
this.articleMapper = articleMapper;
3740
this.userRepository = userRepository;
41+
this.sensitiveWordFilter = sensitiveWordFilter;
3842
}
3943

4044
public PageResult<CommentThreadResponse> listComments(Long articleId, int page, int size) {
@@ -163,6 +167,8 @@ private CommentUserResponse toUser(User u, Long uid) {
163167
}
164168

165169
public CommentThreadResponse createTopLevel(Long articleId, Long uid, CommentCreateRequest req) {
170+
checkSensitive(req.getContent());
171+
166172
LambdaQueryWrapper<Article> articleW = Wrappers.lambdaQuery();
167173
articleW.eq(Article::getId, articleId).eq(Article::getStatus, ArticleStatus.PUBLISHED);
168174
Article article = articleMapper.selectOne(articleW);
@@ -189,6 +195,7 @@ public CommentThreadResponse createTopLevel(Long articleId, Long uid, CommentCre
189195

190196
public CommentThreadResponse reply(Long commentId, Long uid, CommentCreateRequest req) {
191197
ensureUserActive(uid);
198+
checkSensitive(req.getContent());
192199

193200
Comment parent = commentMapper.selectOne(
194201
Wrappers.lambdaQuery(Comment.class)
@@ -303,6 +310,13 @@ public PageResult<CommentAdminResponse> listAllComments(int page, int size, Stri
303310
return new PageResult<>(items, page, size, p.getTotal());
304311
}
305312

313+
private void checkSensitive(String text) {
314+
if (text == null || text.isBlank()) return;
315+
if (sensitiveWordFilter.contains(text)) {
316+
throw new BizException(4003, "内容包含敏感词,请修改后重试");
317+
}
318+
}
319+
306320
private void ensureUserActive(Long uid) {
307321
User u = userRepository.findById(uid).orElseThrow(() -> new BizException(4041, "用户不存在"));
308322
if ("BANNED".equals(u.getStatus())) {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.yqz.openblog.forum.filter;
2+
3+
import com.github.houbb.sensitive.word.bs.SensitiveWordBs;
4+
import com.github.houbb.sensitive.word.support.allow.WordAllows;
5+
import com.github.houbb.sensitive.word.support.deny.WordDenys;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import org.springframework.stereotype.Component;
9+
10+
import java.util.List;
11+
12+
/**
13+
* 敏感词过滤器 — 基于 DFA 算法,使用 {@code com.github.houbb:sensitive-word}。
14+
* <p>
15+
* 主要用法:
16+
* <ul>
17+
* <li>{@link #contains(String)} — 是否包含敏感词</li>
18+
* <li>{@link #findAll(String)} — 返回所有命中的敏感词</li>
19+
* <li>{@link #replace(String)} — 用 {@code *} 替换敏感词</li>
20+
* </ul>
21+
*/
22+
@Component
23+
public class SensitiveWordFilter {
24+
25+
private static final Logger log = LoggerFactory.getLogger(SensitiveWordFilter.class);
26+
27+
private final SensitiveWordBs sw;
28+
29+
public SensitiveWordFilter() {
30+
// 使用内置词库(6W+),后续可扩展从 DB/文件加载自定义词库
31+
this.sw = SensitiveWordBs.newInstance()
32+
.wordDeny(WordDenys.defaults())
33+
.wordAllow(WordAllows.defaults())
34+
.ignoreCase(true)
35+
.ignoreWidth(true)
36+
.ignoreNumStyle(true)
37+
.ignoreChineseStyle(true)
38+
.ignoreEnglishStyle(true)
39+
.ignoreRepeat(false)
40+
.init();
41+
log.info("SensitiveWordFilter initialized");
42+
}
43+
44+
/**
45+
* 是否包含敏感词。
46+
*/
47+
public boolean contains(String text) {
48+
if (text == null || text.isBlank()) return false;
49+
return sw.contains(text);
50+
}
51+
52+
/**
53+
* 返回命中的所有敏感词。
54+
*/
55+
public List<String> findAll(String text) {
56+
if (text == null || text.isBlank()) return List.of();
57+
return sw.findAll(text);
58+
}
59+
60+
/**
61+
* 用 {@code *} 替换敏感词。
62+
*/
63+
public String replace(String text) {
64+
if (text == null || text.isBlank()) return text;
65+
return sw.replace(text);
66+
}
67+
}

OpenBlog-business/src/main/java/com/yqz/openblog/forum/service/ForumService.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
import com.yqz.openblog.forum.entity.ForumTopicStatus;
1717
import com.yqz.openblog.forum.repo.ForumCommentMapper;
1818
import com.yqz.openblog.forum.repo.ForumTopicMapper;
19+
import com.yqz.openblog.forum.filter.SensitiveWordFilter;
1920
import com.yqz.openblog.user.entity.User;
2021
import com.yqz.openblog.user.repo.UserRepository;
2122
import org.springframework.stereotype.Service;
2223

2324
import java.util.*;
25+
2426
import java.util.stream.Collectors;
2527

2628
@Service
@@ -29,11 +31,14 @@ public class ForumService {
2931
private final ForumTopicMapper forumTopicMapper;
3032
private final ForumCommentMapper forumCommentMapper;
3133
private final UserRepository userRepository;
34+
private final SensitiveWordFilter sensitiveWordFilter;
3235

33-
public ForumService(ForumTopicMapper forumTopicMapper, ForumCommentMapper forumCommentMapper, UserRepository userRepository) {
36+
public ForumService(ForumTopicMapper forumTopicMapper, ForumCommentMapper forumCommentMapper,
37+
UserRepository userRepository, SensitiveWordFilter sensitiveWordFilter) {
3438
this.forumTopicMapper = forumTopicMapper;
3539
this.forumCommentMapper = forumCommentMapper;
3640
this.userRepository = userRepository;
41+
this.sensitiveWordFilter = sensitiveWordFilter;
3742
}
3843

3944
/**
@@ -83,6 +88,7 @@ public ForumTopicResponse getTopic(Long topicId) {
8388
*/
8489
public ForumTopicResponse createTopic(Long uid, ForumTopicCreateRequest req) {
8590
ensureUserActive(uid);
91+
checkSensitive(req.getTitle(), req.getContent());
8692

8793
ForumTopic topic = new ForumTopic();
8894
topic.setTitle(req.getTitle());
@@ -146,6 +152,7 @@ public PageResult<ForumCommentResponse> listComments(Long topicId, int page, int
146152
*/
147153
public ForumCommentResponse createComment(Long topicId, Long uid, ForumCommentRequest req) {
148154
ensureUserActive(uid);
155+
checkSensitive(req.getContent());
149156

150157
ForumTopic topic = forumTopicMapper.selectById(topicId);
151158
if (topic == null || topic.getStatus() == ForumTopicStatus.HIDDEN) {
@@ -260,4 +267,14 @@ private void ensureUserActive(Long uid) {
260267
throw new BizException(4011, "账号已被封禁");
261268
}
262269
}
270+
271+
private void checkSensitive(String... texts) {
272+
for (String text : texts) {
273+
if (text == null || text.isBlank()) continue;
274+
if (sensitiveWordFilter.contains(text)) {
275+
List<String> words = sensitiveWordFilter.findAll(text);
276+
throw new BizException(4003, "内容包含敏感词,请修改后重试");
277+
}
278+
}
279+
}
263280
}

0 commit comments

Comments
 (0)