Build a Java Redis-Powered Hot Search Feature with Sensitive Word Filtering
This article explains how to create a Java‑based hot‑search system using Redis ZSets, record and manage personal search histories, retrieve the top ten queries, and integrate a DFA‑driven sensitive‑word filter within a Spring Boot application, complete with full code examples.
Introduction
Use Java and Redis to implement a simple hot‑search function that supports displaying a logged‑in user’s search history, deleting personal history, recording each entered character in a Redis ZSet (using a DFA algorithm), incrementing hot‑search scores, retrieving the top ten hot queries, and filtering inappropriate words.
Hot‑search and Personal Search History API
Add a hot‑search term to Redis (filtering illegal words first).
Increase the hot‑search score by 1 each time the term is clicked.
Query the top ten hot terms for a given key.
Insert a personal search record.
Query personal search records.
Redis Service Implementation
package com.****.****.****.user;
import com.jianlet.service.user.RedisService;
import org.apache.commons.lang.StringUtils;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author: mrwanghc
* @date: 2022/5/13
* @description:
*/
@Transactional
@Service("redisService")
public class RedisServiceImpl implements RedisService {
//导入数据源
@Resource(name = "redisSearchTemplate")
private StringRedisTemplate redisSearchTemplate;
//新增一条该userid用户在搜索栏的历史记录
//searchkey 代表输入的关键词
@Override
public int addSearchHistoryByUserId(String userid, String searchkey) {
String shistory = RedisKeyUtils.getSearchHistoryKey(userid);
boolean b = redisSearchTemplate.hasKey(shistory);
if (b) {
Object hk = redisSearchTemplate.opsForHash().get(shistory, searchkey);
if (hk != null) {
return 1;
} else {
redisSearchTemplate.opsForHash().put(shistory, searchkey, "1");
}
} else {
redisSearchTemplate.opsForHash().put(shistory, searchkey, "1");
}
return 1;
}
//删除个人历史数据
@Override
public Long delSearchHistoryByUserId(String userid, String searchkey) {
String shistory = RedisKeyUtils.getSearchHistoryKey(userid);
return redisSearchTemplate.opsForHash().delete(shistory, searchkey);
}
//获取个人历史数据列表
@Override
public List<String> getSearchHistoryByUserId(String userid) {
List<String> stringList = null;
String shistory = RedisKeyUtils.getSearchHistoryKey(userid);
boolean b = redisSearchTemplate.hasKey(shistory);
if (b) {
Cursor<Map.Entry<Object, Object>> cursor = redisSearchTemplate.opsForHash().scan(shistory, ScanOptions.NONE);
while (cursor.hasNext()) {
Map.Entry<Object, Object> map = cursor.next();
String key = map.getKey().toString();
stringList.add(key);
}
return stringList;
}
return null;
}
//新增一条热词搜索记录,将用户输入的热词存储下来
@Override
public int incrementScoreByUserId(String searchkey) {
Long now = System.currentTimeMillis();
ZSetOperations zSetOperations = redisSearchTemplate.opsForZSet();
ValueOperations<String, String> valueOperations = redisSearchTemplate.opsForValue();
List<String> title = new ArrayList<>();
title.add(searchkey);
for (int i = 0, lengh = title.size(); i < lengh; i++) {
String tle = title.get(i);
try {
if (zSetOperations.score("title", tle) <= 0) {
zSetOperations.add("title", tle, 0);
valueOperations.set(tle, String.valueOf(now));
}
} catch (Exception e) {
zSetOperations.add("title", tle, 0);
valueOperations.set(tle, String.valueOf(now));
}
}
return 1;
}
//根据searchkey搜索其相关最热的前十名 (如果searchkey为null空,则返回redis存储的前十最热词条)
@Override
public List<String> getHotList(String searchkey) {
String key = searchkey;
Long now = System.currentTimeMillis();
List<String> result = new ArrayList<>();
ZSetOperations zSetOperations = redisSearchTemplate.opsForZSet();
ValueOperations<String, String> valueOperations = redisSearchTemplate.opsForValue();
Set<String> value = zSetOperations.reverseRangeByScore("title", 0, Double.MAX_VALUE);
if (StringUtils.isNotEmpty(searchkey)) {
for (String val : value) {
if (StringUtils.containsIgnoreCase(val, key)) {
if (result.size() > 9) {
break;
}
Long time = Long.valueOf(valueOperations.get(val));
if ((now - time) < 2592000000L) {
result.add(val);
} else {
zSetOperations.add("title", val, 0);
}
}
}
} else {
for (String val : value) {
if (result.size() > 9) {
break;
}
Long time = Long.valueOf(valueOperations.get(val));
if ((now - time) < 2592000000L) {
result.add(val);
} else {
zSetOperations.add("title", val, 0);
}
}
}
return result;
}
//每次点击给相关词searchkey热度 +1
@Override
public int incrementScore(String searchkey) {
String key = searchkey;
Long now = System.currentTimeMillis();
ZSetOperations zSetOperations = redisSearchTemplate.opsForZSet();
ValueOperations<String, String> valueOperations = redisSearchTemplate.opsForValue();
zSetOperations.incrementScore("title", key, 1);
valueOperations.getAndSet(key, String.valueOf(now));
return 1;
}
}Sensitive Word Filtering Configuration
Define a Spring Boot @Configuration class that loads a list of prohibited words from static/censorword.txt and builds a DFA‑based map for fast detection.
package com.***.***.interceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
//屏蔽敏感词初始化
@Configuration
@SuppressWarnings({ "rawtypes", "unchecked" })
public class SensitiveWordInit {
//字符编码
private String ENCODING = "UTF-8";
//初始化敏感字库
public Map initKeyWord() throws IOException {
//读取敏感词库 ,存入Set中
Set<String> wordSet = readSensitiveWordFile();
//将敏感词库加入到HashMap中//确定有穷自动机DFA
return addSensitiveWordToHashMap(wordSet);
}
//读取敏感词库 ,存入Set中
private Set<String> readSensitiveWordFile() throws IOException {
Set<String> wordSet = null;
ClassPathResource classPathResource = new ClassPathResource("static/censorword.txt");
InputStream inputStream = classPathResource.getInputStream();
try {
InputStreamReader read = new InputStreamReader(inputStream, ENCODING);
wordSet = new HashSet<String>();
BufferedReader br = new BufferedReader(read);
String txt = null;
while ((txt = br.readLine()) != null) {
wordSet.add(txt);
}
br.close();
read.close();
} catch (Exception e) {
e.printStackTrace();
}
return wordSet;
}
//将HashSet中的敏感词,存入HashMap中
private Map addSensitiveWordToHashMap(Set<String> wordSet) {
//初始化敏感词容器,减少扩容操作
Map wordMap = new HashMap(wordSet.size());
for (String word : wordSet) {
Map nowMap = wordMap;
for (int i = 0; i < word.length(); i++) {
char keyChar = word.charAt(i);
Object tempMap = nowMap.get(keyChar);
if (tempMap != null) {
nowMap = (Map) tempMap;
} else {
Map<String, String> newMap = new HashMap<String, String>();
newMap.put("isEnd", "0");
nowMap.put(keyChar, newMap);
nowMap = newMap;
}
if (i == word.length() - 1) {
nowMap.put("isEnd", "1");
}
}
}
return wordMap;
}
}The utility class SensitiveFilter provides methods to detect and replace sensitive words using the DFA map.
package com.***.***.interceptor;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
//敏感词过滤器:利用DFA算法 进行敏感词过滤
public class SensitiveFilter {
private Map sensitiveWordMap = null;
//最小匹配规则
public static int minMatchType = 1;
//最大匹配规则
public static int maxMatchType = 2;
//单例
private static SensitiveFilter instance = null;
//构造函数,初始化敏感词库
private SensitiveFilter() throws IOException {
sensitiveWordMap = new SensitiveWordInit().initKeyWord();
}
//获取单例
public static SensitiveFilter getInstance() throws IOException {
if (null == instance) {
instance = new SensitiveFilter();
}
return instance;
}
//获取文字中的敏感词
public Set<String> getSensitiveWord(String txt, int matchType) {
Set<String> sensitiveWordList = new HashSet<String>();
for (int i = 0; i < txt.length(); i++) {
int length = CheckSensitiveWord(txt, i, matchType);
if (length > 0) {
sensitiveWordList.add(txt.substring(i, i + length));
i = i + length - 1;
}
}
return sensitiveWordList;
}
//替换敏感字字符
public String replaceSensitiveWord(String txt, int matchType, String replaceChar) {
String resultTxt = txt;
Set<String> set = getSensitiveWord(txt, matchType);
Iterator<String> iterator = set.iterator();
String word = null;
String replaceString = null;
while (iterator.hasNext()) {
word = iterator.next();
replaceString = getReplaceChars(replaceChar, word.length());
resultTxt = resultTxt.replaceAll(word, replaceString);
}
return resultTxt;
}
private String getReplaceChars(String replaceChar, int length) {
String resultReplace = replaceChar;
for (int i = 1; i < length; i++) {
resultReplace += replaceChar;
}
return resultReplace;
}
public int CheckSensitiveWord(String txt, int beginIndex, int matchType) {
boolean flag = false;
int matchFlag = 0;
Map nowMap = sensitiveWordMap;
for (int i = beginIndex; i < txt.length(); i++) {
char word = txt.charAt(i);
nowMap = (Map) nowMap.get(word);
if (nowMap != null) {
matchFlag++;
if ("1".equals(nowMap.get("isEnd"))) {
flag = true;
if (SensitiveFilter.minMatchType == matchType) {
break;
}
}
} else {
break;
}
}
if (SensitiveFilter.maxMatchType == matchType) {
if (matchFlag < 2 || !flag) {
matchFlag = 0;
}
}
if (SensitiveFilter.minMatchType == matchType) {
if (matchFlag < 2 && !flag) {
matchFlag = 0;
}
}
return matchFlag;
}
}In a controller you can invoke the filter to reject or replace illegal characters before further processing:
//非法敏感词汇判断
SensitiveFilter filter = SensitiveFilter.getInstance();
int n = filter.CheckSensitiveWord(searchkey, 0, 1);
if (n > 0) {
logger.info("这个人输入了非法字符--> {},不知道他到底要查什么~ userid--> {}", searchkey, userid);
return null;
}
//或者进行替换
String safe = filter.replaceSensitiveWord(searchkey, 1, "*");Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
