使用springboot集成redis實現(xiàn)一個簡單的熱搜功能。
- 搜索欄展示當(dāng)前登錄的個人用戶的搜索歷史記錄;
- 刪除個人用戶的搜索歷史記錄;
- 插入個人用戶的搜索歷史記錄;
- 用戶在搜索欄輸入某字符,則將該字符記錄下來以zset格式存儲在redis中,記錄該字符被搜索的個數(shù);
- 當(dāng)用戶再次查詢了已在redis存儲了的字符時,則直接累加個數(shù);
- 搜索相關(guān)最熱的前十條數(shù)據(jù);
實例
@Transactional
@Service("redisService")
public class RedisService {
@Resource
private StringRedisTemplate redisSearchTemplate;
/**
* 新增一條該userId用戶在搜索欄的歷史記錄,searchKey代表輸入的關(guān)鍵詞
*
* @param userId
* @param searchKey
* @return
*/
public int addSearchHistoryByUserId(String userId, String searchKey) {
String searchHistoryKey = RedisKeyUtil.getSearchHistoryKey(userId);
boolean flag = redisSearchTemplate.hasKey(searchHistoryKey);
if (flag) {
Object hk = redisSearchTemplate.opsForHash().get(searchHistoryKey, searchKey);
if (hk != null) {
return 1;
} else {
redisSearchTemplate.opsForHash().put(searchHistoryKey, searchKey, "1");
}
} else {
redisSearchTemplate.opsForHash().put(searchHistoryKey, searchKey, "1");
}
return 1;
}
/**
* 刪除個人歷史數(shù)據(jù)
*
* @param userId
* @param searchKey
* @return
*/
public long delSearchHistoryByUserId(String userId, String searchKey) {
String searchHistoryKey = RedisKeyUtil.getSearchHistoryKey(userId);
return redisSearchTemplate.opsForHash().delete(searchHistoryKey, searchKey);
}
/**
* 獲取個人歷史數(shù)據(jù)列表
*
* @param userId
* @return
*/
public List<String> getSearchHistoryByUserId(String userId) {
List<String> history = new ArrayList<>();
String searchHistoryKey = RedisKeyUtil.getSearchHistoryKey(userId);
boolean flag = redisSearchTemplate.hasKey(searchHistoryKey);
if (flag) {
Cursor<Map.Entry<Object, Object>> cursor = redisSearchTemplate.opsForHash().scan(searchHistoryKey, ScanOptions.NONE);
while (cursor.hasNext()) {
Map.Entry<Object, Object> map = cursor.next();
String key = map.getKey().toString();
history.add(key);
}
return history;
}
return null;
}
/**
* 新增一條熱詞搜索記錄,將用戶輸入的熱詞存儲下來
*
* @param searchKey
* @return
*/
public int addHot(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, length = title.size(); i < length; 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;
}
/**
* 根據(jù)searchKey搜索其相關(guān)最熱的前十名 (如果searchKey為null空,則返回redis存儲的前十最熱詞條)
*
* @param searchKey
* @return
*/
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);
//key不為空的時候 推薦相關(guān)的最熱前十名
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) {
//返回最近一個月的數(shù)據(jù)
result.add(val);
} else {
//時間超過一個月沒搜索就把這個詞熱度歸0
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) {
//返回最近一個月的數(shù)據(jù)
result.add(val);
} else {
//時間超過一個月沒搜索就把這個詞熱度歸0
zSetOperations.add("title", val, 0);
}
}
}
return result;
}
/**
* 每次點擊給相關(guān)詞searchKey熱度 +1
*
* @param searchKey
* @return
*/
public int incrementHot(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;
}
}
在向redis添加搜索詞匯時需要過濾不雅文字,合法時再去存儲到redis中,下面是過濾不雅文字的過濾器。
public class SensitiveFilter {
/**
* 敏感詞庫
*/
private Map sensitiveWordMap = null;
/**
* 最小匹配規(guī)則
*/
public static int minMatchType = 1;
/**
* 最大匹配規(guī)則
*/
public static int maxMatchType = 2;
/**
* 單例
*/
private static SensitiveFilter instance = null;
/**
* 構(gòu)造函數(shù),初始化敏感詞庫
*
* @throws IOException
*/
private SensitiveFilter() throws IOException {
sensitiveWordMap = new SensitiveWordInit().initKeyWord();
}
/**
* 獲取單例
*
* @return
* @throws IOException
*/
public static SensitiveFilter getInstance() throws IOException {
if (null == instance) {
instance = new SensitiveFilter();
}
return instance;
}
/**
* 獲取文字中的敏感詞
*
* @param txt
* @param matchType
* @return
*/
public Set<String> getSensitiveWord(String txt, int matchType) {
Set<String> sensitiveWordList = new HashSet<>();
for (int i = 0; i < txt.length(); i++) {
// 判斷是否包含敏感字符
int length = checkSensitiveWord(txt, i, matchType);
// 存在,加入list中
if (length > 0) {
sensitiveWordList.add(txt.substring(i, i + length));
// 減1的原因,是因為for會自增
i = i + length - 1;
}
}
return sensitiveWordList;
}
/**
* 替換敏感字字符
*
* @param txt
* @param matchType
* @param replaceChar
* @return
*/
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;
}
/**
* 獲取替換字符串
*
* @param replaceChar
* @param length
* @return
*/
private String getReplaceChars(String replaceChar, int length) {
String resultReplace = replaceChar;
for (int i = 1; i < length; i++) {
resultReplace += replaceChar;
}
return resultReplace;
}
/**
* 檢查文字中是否包含敏感字符,檢查規(guī)則如下:<br>
* 如果存在,則返回敏感詞字符的長度,不存在返回0
*
* @param txt
* @param beginIndex
* @param matchType
* @return
*/
public int checkSensitiveWord(String txt, int beginIndex, int matchType) {
// 敏感詞結(jié)束標(biāo)識位:用于敏感詞只有1位的情況
boolean flag = false;
// 匹配標(biāo)識數(shù)默認(rèn)為0
int matchFlag = 0;
Map nowMap = sensitiveWordMap;
for (int i = beginIndex; i < txt.length(); i++) {
char word = txt.charAt(i);
// 獲取指定key
nowMap = (Map) nowMap.get(word);
// 存在,則判斷是否為最后一個
if (nowMap != null) {
// 找到相應(yīng)key,匹配標(biāo)識+1
matchFlag++;
// 如果為最后一個匹配規(guī)則,結(jié)束循環(huán),返回匹配標(biāo)識數(shù)
if ("1".equals(nowMap.get("isEnd"))) {
// 結(jié)束標(biāo)志位為true
flag = true;
// 最小規(guī)則,直接返回,最大規(guī)則還需繼續(xù)查找
if (SensitiveFilter.minMatchType == matchType) {
break;
}
}
}
// 不存在,直接返回
else {
break;
}
}
if (SensitiveFilter.maxMatchType == matchType) {
//長度必須大于等于1,為詞
if (matchFlag < 2 || !flag) {
matchFlag = 0;
}
}
if (SensitiveFilter.minMatchType == matchType) {
//長度必須大于等于1,為詞
if (matchFlag < 2 && !flag) {
matchFlag = 0;
}
}
return matchFlag;
}
}
@Configuration
@SuppressWarnings({"rawtypes", "unchecked"})
public class SensitiveWordInit {
/**
* 字符編碼
*/
private String ENCODING = "UTF-8";
/**
* 初始化敏感字庫
*
* @return
* @throws IOException
*/
public Map initKeyWord() throws IOException {
// 讀取敏感詞庫,存入Set中
Set<String> wordSet = readSensitiveWordFile();
// 將敏感詞庫加入到HashMap中
return addSensitiveWordToHashMap(wordSet);
}
/**
* 讀取敏感詞庫 ,存入HashMap中
*
* @return
* @throws IOException
*/
private Set<String> readSensitiveWordFile() throws IOException {
Set<String> wordSet = null;
ClassPathResource classPathResource = new ClassPathResource("static/sensitiveWord.txt");
InputStream inputStream = classPathResource.getInputStream();
// 敏感詞庫
try {
// 讀取文件輸入流
InputStreamReader read = new InputStreamReader(inputStream, ENCODING);
// 文件是否是文件 和 是否存在
wordSet = new HashSet<>();
// BufferedReader是包裝類,先把字符讀到緩存里,到緩存滿了,再讀入內(nèi)存,提高了讀的效率。
BufferedReader br = new BufferedReader(read);
String txt = null;
// 讀取文件,將文件內(nèi)容放入到set中
while ((txt = br.readLine()) != null) {
wordSet.add(txt);
}
br.close();
// 關(guān)閉文件流
read.close();
} catch (Exception e) {
e.printStackTrace();
}
return wordSet;
}
/**
* 將HashSet中的敏感詞,存入HashMap中
*
* @param wordSet
* @return
*/
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++) {
// 轉(zhuǎn)換成char型
char keyChar = word.charAt(i);
// 獲取
Object tempMap = nowMap.get(keyChar);
// 如果存在該key,直接賦值
if (tempMap != null) {
nowMap = (Map) tempMap;
}
// 不存在則,則構(gòu)建一個map,同時將isEnd設(shè)置為0,因為他不是最后一個
else {
// 設(shè)置標(biāo)志位
Map<String, String> newMap = new HashMap<>();
newMap.put("isEnd", "0");
// 添加到集合
nowMap.put(keyChar, newMap);
nowMap = newMap;
}
// 最后一個
if (i == word.length() - 1) {
nowMap.put("isEnd", "1");
}
}
}
return wordMap;
}
}
其中用到的sensitiveWord.txt文件在resources目錄下的static目錄中,這個文件是不雅文字大全,需要與時俱進,不斷進步的。
測試
@GetMapping("/add")
public Object add() {
int num = redisService.addSearchHistoryByUserId("001", "hello");
return num;
}
@GetMapping("/delete")
public Object delete() {
long num = redisService.delSearchHistoryByUserId("001", "hello");
return num;
}
@GetMapping("/get")
public Object get() {
List<String> history = redisService.getSearchHistoryByUserId("001");
return history;
}
@GetMapping("/incrementHot")
public Object incrementHot() {
int num = redisService.addHot("母親節(jié)禮物");
return num;
}
@GetMapping("/getHotList")
public Object getHotList() {
List<String> hotList = redisService.getHotList("母親節(jié)禮物");
return hotList;
}
@GetMapping("/incrementScore")
public Object incrementScore() {
int num = redisService.incrementHot("母親節(jié)禮物");
return num;
}
@GetMapping("/sensitive")
public Object sensitive() throws IOException {
//非法敏感詞匯判斷
SensitiveFilter filter = SensitiveFilter.getInstance();
int n = filter.checkSensitiveWord("hello", 0, 1);
if (n > 0) {
//存在非法字符
System.out.printf("這個人輸入了非法字符--> %s,不知道他到底要查什么~ userid--> %s","hello","001");
return "exist sensitive word";
}
return "ok";
}
到此這篇關(guān)于springboot+redis實現(xiàn)熱搜的文章就介紹到這了,更多相關(guān)springboot redis熱搜內(nèi)容請搜索html5模板網(wǎng)以前的文章希望大家以后多多支持html5模板網(wǎng)!
【網(wǎng)站聲明】本站部分內(nèi)容來源于互聯(lián)網(wǎng),旨在幫助大家更快的解決問題,如果有圖片或者內(nèi)容侵犯了您的權(quán)益,請聯(lián)系我們刪除處理,感謝您的支持!