Build a Simple Hot Search Feature with Java, Redis, and Sensitive Word Filtering
This guide demonstrates how to implement a simple hot‑search feature and personal search‑history management using Java and Redis, covering ZSet‑based ranking, timestamp handling, and a DFA‑based sensitive‑word filter with Spring‑Boot configuration and reusable service methods.
Source: csdn.net/qq_25838777/article/details/109489767
Implement a Simple Hot Search Feature with Java and Redis
The application provides the following functions:
Display the logged‑in user's search history in the search bar and allow deletion of personal history.
When a user types a character, record it in Redis as a ZSet, storing the search count and timestamp (the DFA algorithm is used for word detection).
For queries that already exist in Redis, increment the count to obtain the platform's top ten hot queries.
Include a sensitive‑word filtering feature to block inappropriate content.
Key controller methods for hot search and personal search‑record management include:
Add a hot‑search term to Redis (filtering illegal words before storage).
Increment the hotness of a related term by 1 on each click.
Retrieve the top ten hot terms for a given key.
Insert a user's personal search record.
Query a user's personal search history.
Configure the Redis Data Source
The core service layer implementation is shown below:
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;
@Transactional
@Service("redisService")
public class RedisServiceImpl implements RedisService {
// Data source
@Resource(name = "redisSearchTemplate")
private StringRedisTemplate redisSearchTemplate;
// Add a search history record for a user
@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;
}
// Delete personal history
@Override
public Long delSearchHistoryByUserId(String userid, String searchkey) {
String shistory = RedisKeyUtils.getSearchHistoryKey(userid);
return redisSearchTemplate.opsForHash().delete(shistory, searchkey);
}
// Get personal history list
@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;
}
// Increment hot term count
@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;
}
// Get top ten hot list for a key
@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;
}
// Increment score for a term
@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;
}
}The core part is completed; integrate these methods into your own code as needed.
Implement Sensitive‑Word Filtering
Add a Spring‑Boot configuration class annotated with @Configuration to load the filter at startup:
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;
// Initialize sensitive words
@Configuration
@SuppressWarnings({ "rawtypes", "unchecked" })
public class SensitiveWordInit {
private String ENCODING = "UTF-8";
public Map initKeyWord() throws IOException {
Set<String> wordSet = readSensitiveWordFile();
return addSensitiveWordToHashMap(wordSet);
}
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<>();
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;
}
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;
}
}Utility class for filtering:
package com.***.***.interceptor;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
// Sensitive word filter using DFA algorithm
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;
}
}Use the filter in a controller to check or replace illegal words:
// Illegal word check
SensitiveFilter filter = SensitiveFilter.getInstance();
int n = filter.CheckSensitiveWord(searchkey, 0, 1);
if (n > 0) {
logger.info("User entered illegal characters -> {}, unknown query, userid -> {}", searchkey, userid);
return null;
}
// Replace with asterisks
String text = "敏感文字";
String x = filter.replaceSensitiveWord(text, 1, "*");Place the censorword.txt file containing prohibited terms in resources/static so the filter loads it at startup; keep the list updated as needed.
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.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
