Spring Boot Integration with Redis for Search History, Hot Search, and Sensitive Word Filtering
This guide demonstrates how to integrate Spring Boot with Redis to implement personal search history, hot search ranking, and sensitive word filtering using a DFA algorithm, including Maven dependencies, YAML configuration, utility classes, service implementations, and controller endpoints with full Java code examples.
This article explains how to combine Spring Boot and Redis to provide three main features: personal search history, hot search ranking, and sensitive word filtering.
First, add the required Maven dependencies for Spring Boot Web, Test, Data Redis, and Apache Commons Lang3:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-data-redis
2.7.0
org.apache.commons
commons-lang3
3.12.0Configure Redis in application.yml :
spring:
redis:
# database index
database: 0
host: 192.168.31.28
port: 6379
password: 123456
lettuce:
pool:
# max connections
max-active: 8
# max wait time (-1 means unlimited)
max-wait: -1
# max idle
max-idle: 8
# min idle
min-idle: 0
timeout: 10000Place the sensitive‑word list file word.txt under resources/static . The list can be sourced from public repositories.
1. Sensitive‑Word Filtering (DFA Algorithm)
The DFA algorithm builds a trie where each node stores whether it is the end of a sensitive word and a map of next characters.
1.1 Initialization
Read the word file and construct the trie:
/**
* @author shawn
* @version 1.0
* @ClassName SensitiveWordInit
* Description: Initialize sensitive‑word map
*/
@Configuration
@SuppressWarnings({"rawtypes", "unchecked"})
public class SensitiveWordInit {
private String ENCODING = "UTF-8";
public Map initKeyWord() throws IOException {
Set
wordSet = readSensitiveWordFile();
return addSensitiveWordToHashMap(wordSet);
}
private Set
readSensitiveWordFile() throws IOException {
Set
wordSet = null;
ClassPathResource classPathResource = new ClassPathResource("static/word.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
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
newMap = new HashMap
();
newMap.put("isEnd", "0");
nowMap.put(keyChar, newMap);
nowMap = newMap;
}
if (i == word.length() - 1) {
nowMap.put("isEnd", "1");
}
}
}
return wordMap;
}
}1.2 Filter Component
The filter provides methods to detect, retrieve, and replace sensitive words.
/**
* @author shawn
* @version 1.0
* @ClassName SensitiveFilter
* Description: Sensitive‑word filter using DFA
*/
@Component
public class SensitiveFilter {
private Map sensitiveWordMap = null;
public static int minMatchType = 1; // minimal match
public static int maxMatchType = 2; // maximal match
public static String placeHolder = "**";
private static SensitiveFilter instance = null;
private SensitiveFilter() throws IOException {
sensitiveWordMap = new SensitiveWordInit().initKeyWord();
}
public static SensitiveFilter getInstance() throws IOException {
if (instance == null) {
instance = new SensitiveFilter();
}
return instance;
}
public Set
getSensitiveWord(String txt, int matchType) {
Set
sensitiveWordList = new HashSet<>();
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) {
return replaceSensitiveWord(txt, minMatchType, placeHolder);
}
public String replaceSensitiveWord(String txt, int matchType) {
return replaceSensitiveWord(txt, matchType, placeHolder);
}
public String replaceSensitiveWord(String txt, int matchType, String replaceChar) {
String resultTxt = txt;
Set
set = getSensitiveWord(txt, matchType);
for (String word : set) {
String replaceString = getReplaceChars(replaceChar, word.length());
resultTxt = resultTxt.replaceAll(word, replaceString);
}
return resultTxt;
}
private String getReplaceChars(String replaceChar, int length) {
StringBuilder resultReplace = new StringBuilder(replaceChar);
for (int i = 1; i < length; i++) {
resultReplace.append(replaceChar);
}
return resultReplace.toString();
}
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 || SensitiveFilter.minMatchType == matchType) {
if (matchFlag < 2 || !flag) {
matchFlag = 0;
}
}
return matchFlag;
}
}2. Redis Utilities for Search History and Hot Search
Utility class to generate Redis keys:
public class RedisKeyUtils {
private static final String SPLIT = ":";
private static final String SEARCH = "search";
private static final String SEARCH_HISTORY = "search-history";
private static final String HOT_SEARCH = "hot-search";
private static final String SEARCH_TIME = "search-time";
public static String getSearchHistoryKey(String userId) {
return SEARCH + SPLIT + SEARCH_HISTORY + SPLIT + userId;
}
public static String getHotSearchKey() {
return SEARCH + SPLIT + HOT_SEARCH;
}
public static String getSearchTimeKey(String searchKey) {
return SEARCH + SPLIT + SEARCH_TIME + SPLIT + searchKey;
}
}Service layer RedisService implements:
Adding a user's search keyword to a hash (personal history).
Deleting a keyword from personal history.
Retrieving a user's search history list.
Fetching the top‑N hot search terms (default 9) based on ZSET scores and a one‑month time window.
Incrementing hot‑search scores when a term is searched.
@Service("redisService")
public class RedisService {
private Logger logger = LoggerFactory.getLogger(RedisService.class);
private static final Integer HOT_SEARCH_NUMBER = 9;
private static final Long HOT_SEARCH_TIME = 30L * 24 * 60 * 60 * 1000L; // 30 days
@Resource
private StringRedisTemplate redisSearchTemplate;
public Long addSearchHistoryByUserId(String userId, String searchKey) {
try {
String redisKey = RedisKeyUtils.getSearchHistoryKey(userId);
boolean exists = Boolean.TRUE.equals(redisSearchTemplate.hasKey(redisKey));
if (exists) {
Object hk = redisSearchTemplate.opsForHash().get(redisKey, searchKey);
if (hk != null) {
return 1L;
} else {
redisSearchTemplate.opsForHash().put(redisKey, searchKey, "1");
}
} else {
redisSearchTemplate.opsForHash().put(redisKey, searchKey, "1");
}
return 1L;
} catch (Exception e) {
logger.error("redis发生异常,异常原因:", e);
return 0L;
}
}
public Long delSearchHistoryByUserId(String userId, String searchKey) {
try {
String redisKey = RedisKeyUtils.getSearchHistoryKey(userId);
return redisSearchTemplate.opsForHash().delete(redisKey, searchKey);
} catch (Exception e) {
logger.error("redis发生异常,异常原因:", e);
return 0L;
}
}
public List
getSearchHistoryByUserId(String userId) {
try {
String redisKey = RedisKeyUtils.getSearchHistoryKey(userId);
if (Boolean.TRUE.equals(redisSearchTemplate.hasKey(redisKey))) {
List
list = new ArrayList<>();
Cursor
> cursor = redisSearchTemplate.opsForHash().scan(redisKey, ScanOptions.NONE);
while (cursor.hasNext()) {
Map.Entry
entry = cursor.next();
list.add(entry.getKey().toString());
}
return list;
}
return null;
} catch (Exception e) {
logger.error("redis发生异常,异常原因:", e);
return null;
}
}
public List
getHotList(String searchKey) {
try {
Long now = System.currentTimeMillis();
List
result = new ArrayList<>();
ZSetOperations
zSetOps = redisSearchTemplate.opsForZSet();
ValueOperations
valOps = redisSearchTemplate.opsForValue();
Set
all = zSetOps.reverseRangeByScore(RedisKeyUtils.getHotSearchKey(), 0, Double.MAX_VALUE);
if (StringUtils.isNotEmpty(searchKey)) {
for (String val : all) {
if (StringUtils.containsIgnoreCase(val, searchKey)) {
if (result.size() > HOT_SEARCH_NUMBER) break;
Long time = Long.valueOf(Objects.requireNonNull(valOps.get(val)));
if ((now - time) < HOT_SEARCH_TIME) {
result.add(val);
} else {
zSetOps.add(RedisKeyUtils.getHotSearchKey(), val, 0);
}
}
}
} else {
for (String val : all) {
if (result.size() > HOT_SEARCH_NUMBER) break;
Long time = Long.valueOf(Objects.requireNonNull(valOps.get(val)));
if ((now - time) < HOT_SEARCH_TIME) {
result.add(val);
} else {
zSetOps.add(RedisKeyUtils.getHotSearchKey(), val, 0);
}
}
}
return result;
} catch (Exception e) {
logger.error("redis发生异常,异常原因:", e);
return null;
}
}
public int incrementScoreByUserId(String searchKey) {
Long now = System.currentTimeMillis();
ZSetOperations
zSetOps = redisSearchTemplate.opsForZSet();
ValueOperations
valOps = redisSearchTemplate.opsForValue();
try {
if (zSetOps.score(RedisKeyUtils.getHotSearchKey(), searchKey) <= 0) {
zSetOps.add(RedisKeyUtils.getHotSearchKey(), searchKey, 0);
valOps.set(RedisKeyUtils.getSearchTimeKey(searchKey), String.valueOf(now));
}
} catch (Exception e) {
zSetOps.add(RedisKeyUtils.getHotSearchKey(), searchKey, 0);
valOps.set(RedisKeyUtils.getSearchTimeKey(searchKey), String.valueOf(now));
}
return 1;
}
public Long incrementScore(String searchKey) {
try {
Long now = System.currentTimeMillis();
ZSetOperations
zSetOps = redisSearchTemplate.opsForZSet();
ValueOperations
valOps = redisSearchTemplate.opsForValue();
zSetOps.incrementScore(RedisKeyUtils.getHotSearchKey(), searchKey, 1);
valOps.getAndSet(RedisKeyUtils.getSearchTimeKey(searchKey), String.valueOf(now));
return 1L;
} catch (Exception e) {
logger.error("redis发生异常,异常原因:", e);
return 0L;
}
}
}3. Controller Endpoints
Example controllers expose REST APIs for adding/searching history and hot‑search queries.
@RestController
public class SensitiveController {
@Autowired
SensitiveFilter sensitiveFilter;
@GetMapping("/sensitive")
public String sensitive(String keyword) {
return sensitiveFilter.replaceSensitiveWord(keyword);
}
public static void main(String[] args) throws IOException {
String searchKey = "傻逼h";
String placeholder = "***";
SensitiveFilter filter = SensitiveFilter.getInstance();
String s = filter.replaceSensitiveWord(searchKey, 1, placeholder);
System.out.println(s);
int n = filter.CheckSensitiveWord(searchKey, 0, 2);
if (n > 0) {
// log illegal input
}
}
}
@RestController
public class SearchHistoryController {
@Autowired
RedisService redisService;
@GetMapping("/add")
public String addSearchHistoryByUserId(String userId, String searchKey) {
redisService.addSearchHistoryByUserId(userId, searchKey);
redisService.incrementScore(searchKey);
return null;
}
@GetMapping("/del")
public Long delSearchHistoryByUserId(String userId, String searchKey) {
return redisService.delSearchHistoryByUserId(userId, searchKey);
}
@GetMapping("/getUser")
public List
getSearchHistoryByUserId(String userId) {
return redisService.getSearchHistoryByUserId(userId);
}
@GetMapping("/getHot")
public List
getHotList(String searchKey) {
return redisService.getHotList(searchKey);
}
}The article concludes with a brief note thanking readers and providing the original blog source.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.