1. Spring Boot + Redis 搞定搜索欄熱搜、不雅文字過濾功能
使用java和redis實現一個簡單的熱搜功能,具備以下功能:
搜索欄展示當前登陸的個人用戶的搜索歷史記錄,刪除個人歷史記錄
用戶在搜索欄輸入某字元,則將該字元記錄下來 以zset格式存儲的redis中,記錄該字元被搜索的個數以及當前的時間戳 (用了DFA演算法,感興趣的自己網路學習吧)
每當用戶查詢了已在redis存在了的字元時,則直接累加個數, 用來獲取平台上最熱查詢的十條數據。(可以自己寫介面或者直接在redis中添加一些預備好的關鍵詞)
最後還要做不雅文字過濾功能。這個很重要不說了你懂的。
代碼實現熱搜與個人搜索記錄功能,主要controller層下幾個方法就行了 :
向redis 添加熱搜詞彙(添加的時候使用下面不雅文字過濾的方法來過濾下這個詞彙,合法再去存儲
每次點擊給相關詞熱度 +1
根據key搜索相關最熱的前十名
插入個人搜索記錄
查詢個人搜索記錄
首先配置好redis數據源等等基礎最後貼上核心的 服務層的代碼 :
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 代表輸入的關鍵詞@Overridepublic 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;} //刪除個人歷史數據@Overridepublic Long delSearchHistoryByUserId(String userid, String searchkey) {String shistory = RedisKeyUtils.getSearchHistoryKey(userid);return redisSearchTemplate.opsForHash().delete(shistory, searchkey);} //獲取個人歷史數據列表@Overridepublic 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;} //新增一條熱詞搜索記錄,將用戶輸入的熱詞存儲下來@Overridepublic 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存儲的前十最熱詞條)@Overridepublic 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 {//時間超過一個月沒搜索就把這個詞熱度歸0zSetOperations.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 {//時間超過一個月沒搜索就把這個詞熱度歸0zSetOperations.add("title", val, 0);}}}return result;} //每次點擊給相關詞searchkey熱度 +1@Overridepublic 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註解,在項目啟動的時候載入一下,代碼如下:
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中//確定有窮自動機DFAreturn 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;}}然後這是工具類代碼 :
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;// 匹配標識數默認為0int matchFlag = 0;Map nowMap = sensitiveWordMap;for (int i = beginIndex; i < txt.length(); i++) {char word = txt.charAt(i);// 獲取指定keynowMap = (Map) nowMap.get(word);// 存在,則判斷是否為最後一個if (nowMap != null) {// 找到相應key,匹配標識+1matchFlag++;// 如果為最後一個匹配規則,結束循環,返回匹配標識數if ("1".equals(nowMap.get("isEnd"))) {// 結束標志位為trueflag = 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層直接調用方法判斷即可:
//非法敏感詞彙判斷SensitiveFilter filter = SensitiveFilter.getInstance();int n = filter.CheckSensitiveWord(searchkey,0,1);if(n > 0){ //存在非法字元logger.info("這個人輸入了非法字元--> {},不知道他到底要查什麼~ userid--> {}",searchkey,userid);return null;}也可將敏感文字替換*等字元 :
SensitiveFilter filter = SensitiveFilter.getInstance();String text = "敏感文字";String x = filter.replaceSensitiveWord(text, 1, "*");最後剛才的?SensitiveWordInit.java?裡面用到了?censorword.text?文件,放到你項目裡面的 resources 目錄下的 static 目錄中,這個文件就是不雅文字大全,也需要您與時俱進的更新,項目啟動的時候會載入該文件。
原文:https://juejin.cn/post/710159440821827996