Spring Boot + Redis 搞定搜尋欄熱搜、不雅文字過濾功能

語言: CN / TW / HK

theme: fancy highlight: androidstudio


使用java和redis實現一個簡單的熱搜功能,具備以下功能:

  1. 搜尋欄展示當前登陸的個人使用者的搜尋歷史記錄,刪除個人歷史記錄
  2. 使用者在搜尋欄輸入某字元,則將該字元記錄下來 以zset格式儲存的redis中,記錄該字元被搜尋的個數以及當前的時間戳 (用了DFA演算法,感興趣的自己百度學習吧)
  3. 每當使用者查詢了已在redis存在了的字元時,則直接累加個數, 用來獲取平臺上最熱查詢的十條資料。(可以自己寫介面或者直接在redis中新增一些預備好的關鍵詞)
  4. 最後還要做不雅文字過濾功能。這個很重要不說了你懂的。

程式碼實現熱搜與個人搜尋記錄功能,主要controller層下幾個方法就行了 :

  1. 向redis 新增熱搜詞彙(新增的時候使用下面不雅文字過濾的方法來過濾下這個詞彙,合法再去儲存
  2. 每次點選給相關詞熱度 +1
  3. 根據key搜尋相關最熱的前十名
  4. 插入個人搜尋記錄
  5. 查詢個人搜尋記錄

首先配置好redis資料來源等等基礎

最後貼上核心的 服務層的程式碼 :

java 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: wxd  * @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);         //key不為空的時候 推薦相關的最熱前十名         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 {//時間超過一個月沒搜尋就把這個詞熱度歸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) {//返回最近一個月的資料                     result.add(val);                 } else {//時間超過一個月沒搜尋就把這個詞熱度歸0                     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;     }     }

核心的部分寫完了,剩下的需要你自己將如上方法融入到你自己的程式碼中就行了。

程式碼實現過濾不雅文字功能

在springboot 裡面寫一個配置類加上@Configuration註解,在專案啟動的時候載入一下,程式碼如下:

java 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);     }       // 讀取敏感詞庫 ,存入HashMap中     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>();             // StringBuffer sb = new StringBuffer();             // BufferedReader是包裝類,先把字元讀到快取裡,到快取滿了,再讀入記憶體,提高了讀的效率。             BufferedReader br = new BufferedReader(read);             String txt = null;             // 讀取檔案,將檔案內容放入到set中             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型                 char keyChar = word.charAt(i);                 // 獲取                 Object tempMap = nowMap.get(keyChar);                 // 如果存在該key,直接賦值                 if (tempMap != null) {                     nowMap = (Map) tempMap;                 }                 // 不存在則,則構建一個map,同時將isEnd設定為0,因為他不是最後一個                 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;     } }

然後這是工具類程式碼 :

java 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 {     //敏感詞過濾器:利用DFA演算法  進行敏感詞過濾     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);             // 存在,加入list中             if (length > 0) {                 sensitiveWordList.add(txt.substring(i, i + length));                 // 減1的原因,是因為for會自增                 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;     }       /**      * 獲取替換字串      *      * @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;     }       /**      * 檢查文字中是否包含敏感字元,檢查規則如下:<br>      * 如果存在,則返回敏感詞字元的長度,不存在返回0      * @param txt      * @param beginIndex      * @param matchType      * @return      */     public int CheckSensitiveWord(String txt, int beginIndex, int matchType) {         // 敏感詞結束標識位:用於敏感詞只有1位的情況         boolean flag = false;         // 匹配標識數預設為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) {                 // 找到相應key,匹配標識+1                 matchFlag++;                 // 如果為最後一個匹配規則,結束迴圈,返回匹配標識數                 if ("1".equals(nowMap.get("isEnd"))) {                     // 結束標誌位為true                     flag = true;                     // 最小規則,直接返回,最大規則還需繼續查詢                     if (SensitiveFilter.minMatchType == matchType) {                         break;                     }                 }             }             // 不存在,直接返回             else {                 break;             }         }           if (SensitiveFilter.maxMatchType == matchType){             if(matchFlag < 2 || !flag){        //長度必須大於等於1,為詞                 matchFlag = 0;             }         }         if (SensitiveFilter.minMatchType == matchType){             if(matchFlag < 2 && !flag){        //長度必須大於等於1,為詞                 matchFlag = 0;             }         }         return matchFlag;     } }

在你程式碼的controller層直接呼叫方法判斷即可:

java //非法敏感詞彙判斷 SensitiveFilter filter = SensitiveFilter.getInstance(); int n = filter.CheckSensitiveWord(searchkey,0,1); if(n > 0){ //存在非法字元     logger.info("這個人輸入了非法字元--> {},不知道他到底要查什麼~ userid--> {}",searchkey,userid);     return null; }

也可將敏感文字替換*等字元 :

java SensitiveFilter filter = SensitiveFilter.getInstance(); String text = "敏感文字"; String x = filter.replaceSensitiveWord(text, 1, "*");

最後剛才的 SensitiveWordInit.java 裡面用到了 censorword.text 檔案,放到你專案裡面的 resources 目錄下的 static 目錄中,這個檔案就是不雅文字大全,也需要您與時俱進的更新,專案啟動的時候會載入該檔案。