在实际项目开发中,内容安全审核是一个非常重要的功能。本文将详细介绍如何在SpringBoot项目中实现一个功能完善的敏感词过滤系统。我们将从项目搭建、核心代码实现到实际应用场景逐步展开讲解。
一、项目准备
首先,创建一个SpringBoot项目,在pom.xml
中添加必要的依赖:
-
Spring Web:用于构建 RESTful API。
-
Lombok:简化 JavaBean 的编写,这里主要用于日志记录注解。
-
Apache Commons Lang3:提供了一些常用的工具类,如字符串处理等。
-
Spring Data Redis:如果要将敏感词存储在 Redis 中,用于操作 Redis 数据库。
<dependencies>
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Commons Lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
二、核心代码实现
1、敏感词过滤器实现类
@Component
@Slf4j
public class SensitiveWordFilter {
// 敏感词字典树,用于存储敏感词
private Map<Character, Map> sensitiveWordMap;
// 最小匹配规则,表示找到第一个匹配到的敏感词就返回
public static final int MATCH_TYPE_MIN = 1;
// 最大匹配规则,表示找到最长的匹配敏感词再返回
public static final int MATCH_TYPE_MAX = 2;
// 注入Redis模板,用于从Redis中加载敏感词
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 初始化敏感词过滤器,包括从Redis加载敏感词
*/
@PostConstruct
public void init() {
sensitiveWordMap = new HashMap<>();
// 从Redis加载敏感词并初始化字典树
loadSensitiveWordsFromRedis();
}
/**
* 从Redis加载敏感词并初始化到字典树中
*/
private void loadSensitiveWordsFromRedis() {
try {
// 从Redis的sensitive_words集合中获取所有敏感词
Set<String> words = stringRedisTemplate.opsForSet().members("sensitive_words");
if (words != null) {
// 遍历敏感词集合,将每个敏感词添加到字典树中
words.forEach(this::addWordToMap);
}
} catch (Exception e) {
// 记录加载敏感词失败日志
log.error("Failed to load sensitive words from Redis", e);
}
}
/**
* 将敏感词添加到字典树中
* @param word 要添加的敏感词
*/
public void addWordToMap(String word) {
Map<Character, Map> currentMap = sensitiveWordMap;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
Map<Character, Map> subMap = currentMap.get(c);
if (subMap == null) {
// 如果当前字符不存在于字典树中,则创建新的节点
subMap = new HashMap<>();
currentMap.put(c, subMap);
}
currentMap = subMap;
// 如果是敏感词的最后一个字符,则标记为敏感词结束
if (i == word.length() - 1) {
currentMap.put('$', null);
}
}
}
/**
* 检查文本中是否包含敏感词(最小匹配规则)
* @param text 要检查的文本
* @return 如果包含敏感词则返回true,否则返回false
*/
public boolean containsSensitiveWord(String text) {
if (StringUtils.isBlank(text)) {
return false;
}
// 遍历文本中的每个字符,检查是否存在敏感词
for (int i = 0; i < text.length(); i++) {
int length = checkSensitiveWord(text, i, MATCH_TYPE_MIN);
if (length > 0) {
return true;
}
}
return false;
}
/**
* 在文本中找到所有敏感词(最大匹配规则)
* @param text 要查找的文本
* @return 包含所有找到的敏感词的集合
*/
public Set<String> findSensitiveWords(String text) {
Set<String> sensitiveWords = new HashSet<>();
if (StringUtils.isBlank(text)) {
return sensitiveWords;
}
// 遍历文本中的每个字符,查找敏感词并添加到集合中
for (int i = 0; i < text.length(); i++) {
int length = checkSensitiveWord(text, i, MATCH_TYPE_MAX);
if (length > 0) {
sensitiveWords.add(text.substring(i, i + length));
// 更新索引以跳过已找到的敏感词
i = i + length - 1;
}
}
return sensitiveWords;
}
/**
* 过滤文本中的敏感词,用'*'替换
* @param text 要过滤的文本
* @return 过滤后的文本
*/
public String filter(String text) {
if (StringUtils.isBlank(text)) {
return text;
}
StringBuilder result = new StringBuilder(text);
Set<String> words = findSensitiveWords(text);
// 遍历找到的敏感词,用'*'替换
for (String word : words) {
int index = result.indexOf(word);
while (index != -1) {
for (int i = index; i < index + word.length(); i++) {
result.setCharAt(i, '*');
}
index = result.indexOf(word, index + 1);
}
}
return result.toString();
}
/**
* 检查文本中从指定索引开始的子串是否包含敏感词
* @param text 要检查的文本
* @param beginIndex 开始检查的索引
* @param matchType 匹配规则(最小匹配或最大匹配)
* @return 找到的敏感词长度,如果没有找到则返回0
*/
private int checkSensitiveWord(String text, int beginIndex, int matchType) {
Map<Character, Map> currentMap = sensitiveWordMap;
int wordLength = 0;
int maxLength = 0;
// 从指定索引开始遍历文本字符
for (int i = beginIndex; i < text.length(); i++) {
char c = text.charAt(i);
currentMap = currentMap.get(c);
if (currentMap == null) {
break;
}
wordLength++;
// 如果当前节点标记为敏感词结束
if (currentMap.containsKey('$')) {
if (matchType == MATCH_TYPE_MIN) {
// 最小匹配规则,返回当前敏感词长度
return wordLength;
} else {
// 最大匹配规则,记录当前敏感词长度,继续检查是否还有更长的敏感词
maxLength = wordLength;
}
}
}
// 最大匹配规则下返回最长敏感词长度
return maxLength;
}
}
2. 配置类
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.context.annotation.Bean;
@Configuration
public class RedisConfig {
/**
* 创建并配置StringRedisTemplate的Bean实例
*
* @param connectionFactory Redis连接工厂实例,用于创建与Redis服务器的连接
* @return 配置好的StringRedisTemplate实例,可用于在Spring应用中方便地操作Redis中的字符串数据类型
*/
@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(connectionFactory);
return template;
}
}
3. Controller实现
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Resource;
import java.util.Set;
@RestController
@RequestMapping("/api/sensitive")
@Slf4j
public class SensitiveWordController {
@Resource
private SensitiveWordFilter sensitiveWordFilter;
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 处理添加敏感词的请求
*
* @param word 要添加的敏感词内容,通过请求体传入
* @return 返回添加结果的响应实体,添加成功则返回状态码200及"添加成功"消息,添加失败则返回状态码500及"添加失败"消息
*/
@PostMapping("/word")
public ResponseEntity<String> addSensitiveWord(@RequestBody String word) {
try {
// 将敏感词添加到Redis的集合中
stringRedisTemplate.opsForSet().add("sensitive_words", word);
// 将敏感词添加到敏感词过滤器的字典树中
sensitiveWordFilter.addWordToMap(word);
return ResponseEntity.ok("添加成功");
} catch (Exception e) {
log.error("添加敏感词失败", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("添加失败");
}
}
/**
* 处理检查文本是否包含敏感词的请求
*
* @param text 要检查的文本内容,通过请求体传入
* @return 返回检查结果的响应实体,包含文本是否包含敏感词的布尔值,状态码为200
*/
@PostMapping("/check")
public ResponseEntity<Boolean> checkText(@RequestBody String text) {
return ResponseEntity.ok(sensitiveWordFilter.containsSensitiveWord(text));
}
/**
* 处理过滤文本中敏感词的请求
*
* @param text 要过滤的文本内容,通过请求体传入
* @return 返回过滤后的文本内容的响应实体,状态码为200
*/
@PostMapping("/filter")
public ResponseEntity<String> filterText(@RequestBody String text) {
return ResponseEntity.ok(sensitiveWordFilter.filter(text));
}
/**
* 处理获取文本中所有敏感词的请求
*
* @param text 要获取敏感词的文本内容,通过请求体传入
* @return 返回包含文本中所有敏感词的集合的响应实体,状态码为200
*/
@PostMapping("/find")
public ResponseEntity<Set<String>> findSensitiveWords(@RequestBody String text) {
return ResponseEntity.ok(sensitiveWordFilter.findSensitiveWords(text));
}
}
三、配置文件
在application.yml
中添加Redis配置:
spring:
redis:
host: localhost
port: 6379
database: 0
timeout: 10000
四、使用示例
1. 添加敏感词
curl -X POST http://localhost:8080/api/sensitive/word -d "敏感词1"
2. 检查文本
curl -X POST http://localhost:8080/api/sensitive/check -d "这是一段包含敏感词1的文本"
3. 过滤文本
curl -X POST http://localhost:8080/api/sensitive/filter -d "这是一段包含敏感词1的文本"
五、性能优化建议
1、Redis优化
使用Redis集群提高可用性
通过Redis Cluster模式,将数据分散存储在多个节点上,实现水平扩展和负载均衡。这不仅可以提高并发处理能力,还能在主节点故障时,通过自动的故障转移机制保证系统的可用性。
实现Redis缓存预热
在系统上线前或低峰期,提前将热点数据加载到Redis缓存中,以减少用户请求时的数据库查询压力,提高响应速度。可以通过分析历史访问记录,确定热点数据,并使用脚本程序触发数据预热过程。
添加Redis连接池配置
使用连接池来管理Redis的连接,可以减少频繁创建和关闭连接的开销,提高连接复用率。配置连接池时,需要根据实际业务需求和服务器资源情况,合理设置最大连接数、最大空闲连接数等参数。
并发处理
Redis本身支持高并发处理,但可以通过进一步优化配置和使用相关技术来提高并发性能。例如,使用Redis的Pipeline功能将多个命令打包并一次性执行,减少网络通信的开销;使用分布式锁和乐观锁等技术来协调多个客户端之间的并发访问。
使用本地缓存减少Redis访问
在应用服务器本地设置一层缓存(如Guava Cache或Caffeine等),用于存储一些访问频率高且变化不频繁的数据。当数据在本地缓存中命中时,可以直接返回结果,减少对Redis的访问压力。
2、功能扩展
支持敏感词分级
根据敏感词的严重程度或影响范围,将其分为不同的级别。在过滤敏感词时,可以根据级别采取不同的处理措施(如替换、删除或警告等)。
实现自定义替换规则
提供一种机制,允许用户根据自己的需求定义敏感词的替换规则。例如,可以将某些敏感词替换为指定的占位符或符号,以避免直接显示敏感内容。
添加白名单机制优化
对于一些特定的用户或内容,可以将其添加到白名单中,以允许其包含敏感词而不被过滤。这可以用于一些特殊的场景,如内部测试或特定用户的权限管理。
实现敏感词的定时更新机制
通过设置定时任务或使用Redis的发布订阅系统,定期从外部数据源(如数据库、文件或远程服务器等)获取最新的敏感词列表,并更新到Redis缓存中。这可以确保敏感词过滤的准确性和及时性。
添加并发控制
在处理敏感词过滤等操作时,需要考虑并发控制的问题。可以使用Redis的分布式锁或其他并发控制技术来确保多个客户端之间不会同时修改敏感词列表或缓存数据,从而避免数据不一致的问题。
六、总结
本文介绍了如何在SpringBoot中实现敏感词过滤功能,主要特点包括:
-
使用DFA算法实现高效的敏感词匹配
-
结合Redis实现敏感词的持久化存储
-
提供完整的REST API接口
-
支持敏感词的动态添加和更新
-
考虑了实际应用场景和性能优化