当前位置:   article > 正文

SpringBoot+Redis 搞定搜索栏热搜、不雅文字过滤功能

springboot集成redissearch

开发者(KaiFaX)

面向全栈工程师的开发者
专注于前端、Java/Python/Go/PHP的技术社区

来源:blog.csdn.net/qq_25838777/article/details/109489767

使用java和redis实现一个简单的热搜功能,具备以下功能:

  1. 搜索栏展示当前登陆的个人用户的搜索历史记录,删除个人历史记录

  2. 用户在搜索栏输入某字符,则将该字符记录下来 以zset格式存储的redis中,记录该字符被搜索的个数以及当前的时间戳 (用了DFA算法,感兴趣的自己百度学习吧)

  3. 每当用户查询了已在redis存在了的字符时,则直接累加个数, 用来获取平台上最热查询的十条数据。(可以自己写接口或者直接在redis中添加一些预备好的关键词)

  4. 最后还要做不雅文字过滤功能。这个很重要不说了你懂的。

代码实现热搜与个人搜索记录功能,主要controller层下几个方法就行了 :

  1. 向redis 添加热搜词汇(添加的时候使用下面不雅文字过滤的方法来过滤下这个词汇,合法再去存储

  2. 每次点击给相关词热度 +1

  3. 根据key搜索相关最热的前十名

  4. 插入个人搜索记录

  5. 查询个人搜索记录

首先配置好redis数据源等等基础

最后贴上核心的 服务层的代码 :

  1. package com.****.****.****.user;
  2.  
  3. import com.jianlet.service.user.RedisService;
  4. import org.apache.commons.lang.StringUtils;
  5. import org.springframework.data.redis.core.*;
  6. import org.springframework.stereotype.Service;
  7. import javax.annotation.Resource;
  8. import java.util.*;
  9. import java.util.concurrent.TimeUnit;
  10.  
  11. /**
  12.  * @author: mrwanghc
  13.  * @date: 2020/5/13
  14.  * @description:
  15.  */
  16. @Transactional
  17. @Service("redisService")
  18. public class RedisServiceImpl implements RedisService {
  19.  
  20.     //导入数据源
  21.     @Resource(name = "redisSearchTemplate")
  22.     private StringRedisTemplate redisSearchTemplate;
  23.  
  24.  
  25.     //新增一条该userid用户在搜索栏的历史记录
  26.     //searchkey 代表输入的关键词
  27.     @Override
  28.     public int addSearchHistoryByUserId(String userid, String searchkey) {
  29.         String shistory = RedisKeyUtils.getSearchHistoryKey(userid);
  30.         boolean b = redisSearchTemplate.hasKey(shistory);
  31.         if (b) {
  32.             Object hk = redisSearchTemplate.opsForHash().get(shistory, searchkey);
  33.             if (hk != null) {
  34.                 return 1;
  35.             }else{
  36.                 redisSearchTemplate.opsForHash().put(shistory, searchkey, "1");
  37.             }
  38.         }else{
  39.             redisSearchTemplate.opsForHash().put(shistory, searchkey, "1");
  40.         }
  41.         return 1;
  42.     }
  43.  
  44.     //删除个人历史数据
  45.     @Override
  46.     public Long delSearchHistoryByUserId(String userid, String searchkey) {
  47.         String shistory = RedisKeyUtils.getSearchHistoryKey(userid);
  48.         return redisSearchTemplate.opsForHash().delete(shistory, searchkey);
  49.     }
  50.  
  51.     //获取个人历史数据列表
  52.     @Override
  53.     public List<String> getSearchHistoryByUserId(String userid) {
  54.         List<String> stringList = null;
  55.         String shistory = RedisKeyUtils.getSearchHistoryKey(userid);
  56.         boolean b = redisSearchTemplate.hasKey(shistory);
  57.         if(b){
  58.             Cursor<Map.Entry<Object, Object>> cursor = redisSearchTemplate.opsForHash().scan(shistory, ScanOptions.NONE);
  59.             while (cursor.hasNext()) {
  60.                 Map.Entry<Object, Object> map = cursor.next();
  61.                 String key = map.getKey().toString();
  62.                 stringList.add(key);
  63.             }
  64.             return stringList;
  65.         }
  66.         return null;
  67.     }
  68.  
  69.     //新增一条热词搜索记录,将用户输入的热词存储下来
  70.     @Override
  71.     public int incrementScoreByUserId(String searchkey) {
  72.         Long now = System.currentTimeMillis();
  73.         ZSetOperations zSetOperations = redisSearchTemplate.opsForZSet();
  74.         ValueOperations<String, String> valueOperations = redisSearchTemplate.opsForValue();
  75.         List<String> title = new ArrayList<>();
  76.         title.add(searchkey);
  77.         for (int i = 0, lengh = title.size(); i < lengh; i++) {
  78.             String tle = title.get(i);
  79.             try {
  80.                 if (zSetOperations.score("title", tle) <= 0) {
  81.                     zSetOperations.add("title", tle, 0);
  82.                     valueOperations.set(tle, String.valueOf(now));
  83.                 }
  84.             } catch (Exception e) {
  85.                 zSetOperations.add("title", tle, 0);
  86.                 valueOperations.set(tle, String.valueOf(now));
  87.             }
  88.         }
  89.         return 1;
  90.     }
  91.     
  92.     //根据searchkey搜索其相关最热的前十名 (如果searchkey为null空,则返回redis存储的前十最热词条)
  93.     @Override
  94.     public List<String> getHotList(String searchkey) {
  95.         String key = searchkey;
  96.         Long now = System.currentTimeMillis();
  97.         List<String> result = new ArrayList<>();
  98.         ZSetOperations zSetOperations = redisSearchTemplate.opsForZSet();
  99.         ValueOperations<String, String> valueOperations = redisSearchTemplate.opsForValue();
  100.         Set<String> value = zSetOperations.reverseRangeByScore("title"0, Double.MAX_VALUE);
  101.         //key不为空的时候 推荐相关的最热前十名
  102.         if(StringUtils.isNotEmpty(searchkey)){
  103.             for (String val : value) {
  104.                 if (StringUtils.containsIgnoreCase(val, key)) {
  105.                     if (result.size() > 9) {//只返回最热的前十名
  106.                         break;
  107.                     }
  108.                     Long time = Long.valueOf(valueOperations.get(val));
  109.                     if ((now - time) < 2592000000L) {//返回最近一个月的数据
  110.                         result.add(val);
  111.                     } else {//时间超过一个月没搜索就把这个词热度归0
  112.                         zSetOperations.add("title", val, 0);
  113.                     }
  114.                 }
  115.             }
  116.         }else{
  117.             for (String val : value) {
  118.                 if (result.size() > 9) {//只返回最热的前十名
  119.                     break;
  120.                 }
  121.                 Long time = Long.valueOf(valueOperations.get(val));
  122.                 if ((now - time) < 2592000000L) {//返回最近一个月的数据
  123.                     result.add(val);
  124.                 } else {//时间超过一个月没搜索就把这个词热度归0
  125.                     zSetOperations.add("title", val, 0);
  126.                 }
  127.             }
  128.         }
  129.         return result;
  130.     }
  131.  
  132.     //每次点击给相关词searchkey热度 +1
  133.     @Override
  134.     public int incrementScore(String searchkey) {
  135.         String key = searchkey;
  136.         Long now = System.currentTimeMillis();
  137.         ZSetOperations zSetOperations = redisSearchTemplate.opsForZSet();
  138.         ValueOperations<String, String> valueOperations = redisSearchTemplate.opsForValue();
  139.         zSetOperations.incrementScore("title", key, 1);
  140.         valueOperations.getAndSet(key, String.valueOf(now));
  141.         return 1;
  142.     }
  143.  
  144.  
  145. }

核心的部分写完了,剩下的需要你自己将如上方法融入到你自己的代码中就行了。

代码实现过滤不雅文字功能

在springboot 里面写一个配置类加上@Configuration注解,在项目启动的时候加载一下,代码如下:

  1. package com.***.***.interceptor;
  2.  
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.core.io.ClassPathResource;
  5. import java.io.*;
  6. import java.util.HashMap;
  7. import java.util.HashSet;
  8. import java.util.Map;
  9. import java.util.Set;
  10.  
  11.  
  12. //屏蔽敏感词初始化
  13. @Configuration
  14. @SuppressWarnings({ "rawtypes""unchecked" })
  15. public class SensitiveWordInit {
  16.     // 字符编码
  17.     private String ENCODING = "UTF-8";
  18.     // 初始化敏感字库
  19.     public Map initKeyWord() throws IOException {
  20.         // 读取敏感词库 ,存入Set中
  21.         Set<String> wordSet = readSensitiveWordFile();
  22.         // 将敏感词库加入到HashMap中//确定有穷自动机DFA
  23.         return addSensitiveWordToHashMap(wordSet);
  24.     }
  25.  
  26.     // 读取敏感词库 ,存入HashMap中
  27.     private Set<String> readSensitiveWordFile() throws IOException {
  28.     Set<String> wordSet = null;
  29.         ClassPathResource classPathResource = new ClassPathResource("static/censorword.txt");
  30.         InputStream inputStream = classPathResource.getInputStream();
  31.         //敏感词库
  32.         try {
  33.         // 读取文件输入流
  34.             InputStreamReader read = new InputStreamReader(inputStream, ENCODING);
  35.             // 文件是否是文件 和 是否存在
  36.             wordSet = new HashSet<String>();
  37.             // StringBuffer sb = new StringBuffer();
  38.             // BufferedReader是包装类,先把字符读到缓存里,到缓存满了,再读入内存,提高了读的效率。
  39.             BufferedReader br = new BufferedReader(read);
  40.             String txt = null;
  41.             // 读取文件,将文件内容放入到set中
  42.             while ((txt = br.readLine()) != null) {
  43.                 wordSet.add(txt);
  44.             }
  45.             br.close();
  46.             // 关闭文件流
  47.             read.close();
  48.         } catch (Exception e) {
  49.             e.printStackTrace();
  50.         }
  51.         return wordSet;
  52.     }
  53.     // 将HashSet中的敏感词,存入HashMap中
  54.     private Map addSensitiveWordToHashMap(Set<String> wordSet) {
  55.     // 初始化敏感词容器,减少扩容操作
  56.     Map wordMap = new HashMap(wordSet.size());
  57.         for (String word : wordSet) {
  58.             Map nowMap = wordMap;
  59.             for (int i = 0; i < word.length(); i++) {
  60.                 // 转换成char型
  61.                 char keyChar = word.charAt(i);
  62.                 // 获取
  63.                 Object tempMap = nowMap.get(keyChar);
  64.                 // 如果存在该key,直接赋值
  65.                 if (tempMap != null) {
  66.                     nowMap = (Map) tempMap;
  67.                 }
  68.                 // 不存在则,则构建一个map,同时将isEnd设置为0,因为他不是最后一个
  69.                 else {
  70.                     // 设置标志位
  71.                     Map<String, String> newMap = new HashMap<String, String>();
  72.                     newMap.put("isEnd""0");
  73.                     // 添加到集合
  74.                     nowMap.put(keyChar, newMap);
  75.                     nowMap = newMap;
  76.                 }
  77.                 // 最后一个
  78.                 if (i == word.length() - 1) {
  79.                     nowMap.put("isEnd""1");
  80.                 }
  81.             }
  82.         }
  83.         return wordMap;
  84.     }
  85. }

然后这是工具类代码 :

  1. package com.***.***.interceptor;
  2.  
  3. import java.io.IOException;
  4. import java.util.HashSet;
  5. import java.util.Iterator;
  6. import java.util.Map;
  7. import java.util.Set;
  8.  
  9. //敏感词过滤器:利用DFA算法  进行敏感词过滤
  10. public class SensitiveFilter {
  11.     //敏感词过滤器:利用DFA算法  进行敏感词过滤
  12.     private Map sensitiveWordMap = null;
  13.  
  14.     // 最小匹配规则
  15.     public static int minMatchType = 1;
  16.  
  17.     // 最大匹配规则
  18.     public static int maxMatchType = 2;
  19.  
  20.     // 单例
  21.     private static SensitiveFilter instance = null;
  22.  
  23.     // 构造函数,初始化敏感词库
  24.     private SensitiveFilter() throws IOException {
  25.         sensitiveWordMap = new SensitiveWordInit().initKeyWord();
  26.     }
  27.  
  28.     // 获取单例
  29.     public static SensitiveFilter getInstance() throws IOException {
  30.         if (null == instance) {
  31.             instance = new SensitiveFilter();
  32.         }
  33.         return instance;
  34.     }
  35.  
  36.     // 获取文字中的敏感词
  37.     public Set<String> getSensitiveWord(String txt, int matchType) {
  38.         Set<String> sensitiveWordList = new HashSet<String>();
  39.         for (int i = 0; i < txt.length(); i++) {
  40.             // 判断是否包含敏感字符
  41.             int length = CheckSensitiveWord(txt, i, matchType);
  42.             // 存在,加入list中
  43.             if (length > 0) {
  44.                 sensitiveWordList.add(txt.substring(i, i + length));
  45.                 // 减1的原因,是因为for会自增
  46.                 i = i + length - 1;
  47.             }
  48.         }
  49.         return sensitiveWordList;
  50.     }
  51.     // 替换敏感字字符
  52.     public String replaceSensitiveWord(String txt, int matchType,
  53.                                        String replaceChar) {
  54.         String resultTxt = txt;
  55.         // 获取所有的敏感词
  56.         Set<String> set = getSensitiveWord(txt, matchType);
  57.         Iterator<String> iterator = set.iterator();
  58.         String word = null;
  59.         String replaceString = null;
  60.         while (iterator.hasNext()) {
  61.             word = iterator.next();
  62.             replaceString = getReplaceChars(replaceChar, word.length());
  63.             resultTxt = resultTxt.replaceAll(word, replaceString);
  64.         }
  65.         return resultTxt;
  66.     }
  67.  
  68.     /**
  69.      * 获取替换字符串
  70.      *
  71.      * @param replaceChar
  72.      * @param length
  73.      * @return
  74.      */
  75.     private String getReplaceChars(String replaceChar, int length) {
  76.         String resultReplace = replaceChar;
  77.         for (int i = 1; i < length; i++) {
  78.             resultReplace += replaceChar;
  79.         }
  80.         return resultReplace;
  81.     }
  82.  
  83.     /**
  84.      * 检查文字中是否包含敏感字符,检查规则如下:<br>
  85.      * 如果存在,则返回敏感词字符的长度,不存在返回0
  86.      * @param txt
  87.      * @param beginIndex
  88.      * @param matchType
  89.      * @return
  90.      */
  91.     public int CheckSensitiveWord(String txt, int beginIndex, int matchType) {
  92.         // 敏感词结束标识位:用于敏感词只有1位的情况
  93.         boolean flag = false;
  94.         // 匹配标识数默认为0
  95.         int matchFlag = 0;
  96.         Map nowMap = sensitiveWordMap;
  97.         for (int i = beginIndex; i < txt.length(); i++) {
  98.             char word = txt.charAt(i);
  99.             // 获取指定key
  100.             nowMap = (Map) nowMap.get(word);
  101.             // 存在,则判断是否为最后一个
  102.             if (nowMap != null) {
  103.                 // 找到相应key,匹配标识+1
  104.                 matchFlag++;
  105.                 // 如果为最后一个匹配规则,结束循环,返回匹配标识数
  106.                 if ("1".equals(nowMap.get("isEnd"))) {
  107.                     // 结束标志位为true
  108.                     flag = true;
  109.                     // 最小规则,直接返回,最大规则还需继续查找
  110.                     if (SensitiveFilter.minMatchType == matchType) {
  111.                         break;
  112.                     }
  113.                 }
  114.             }
  115.             // 不存在,直接返回
  116.             else {
  117.                 break;
  118.             }
  119.         }
  120.  
  121.         if (SensitiveFilter.maxMatchType == matchType){
  122.             if(matchFlag < 2 || !flag){        //长度必须大于等于1,为词
  123.                 matchFlag = 0;
  124.             }
  125.         }
  126.         if (SensitiveFilter.minMatchType == matchType){
  127.             if(matchFlag < 2 && !flag){        //长度必须大于等于1,为词
  128.                 matchFlag = 0;
  129.             }
  130.         }
  131.         return matchFlag;
  132.     }
  133. }

在你代码的controller层直接调用方法判断即可:

  1. //非法敏感词汇判断
  2.         SensitiveFilter filter = SensitiveFilter.getInstance();
  3.         int n = filter.CheckSensitiveWord(searchkey,0,1);
  4.         if(n > 0){ //存在非法字符
  5.             logger.info("这个人输入了非法字符--> {},不知道他到底要查什么~ userid--> {}",searchkey,userid);
  6.             return null;
  7.         }

也可将敏感文字替换*等字符 :

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

最后刚才的 SensitiveWordInit.java 里面用到了 censorword.text 文件,放到你项目里面的 resources 目录下的 static 目录中,这个文件就是不雅文字大全,也需要您与时俱进的更新,项目启动的时候会加载该文件。

可以自己百度下载这个东西,很多的,而且与时俱进~~,我就不贴链接了。


1. 回复“m”可以查看历史记录;

2. 回复“h”或者“帮助”,查看帮助;

   开发者已开通多个技术群交流学习,请加若飞微信:1321113940  (暗号k)进开发群学习交流

  说明:我们都是开发者。视频或文章来源于网络,如涉及版权或有误,请您与若飞(1321113940)联系,将在第一时间删除或者修改,谢谢!

c9ad2b9e243c3ade8202e7b4ea6e6b00.png

开 发 者 : KaiFaX

面向全栈工程师的开发者
专注于前端、Java/Python/Go/PHP的技术社区

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/853030
推荐阅读
相关标签
  

闽ICP备14008679号