赞
踩
目录
首先定义敏感词:在 resources 资源文件中新建文件 sensitive-words.txt:
- 赌博
- 嫖娼
- 吸毒
- 开票
实现上述算法(在 util 包下新建 SensitiveFilter 类)
- package com.example.demo.util;
- import org.springframework.stereotype.Component;
- import java.util.HashMap;
- import java.util.Map;
-
- @Component
- public class SensitiveFilter {
-
- //定义前缀树
- private class TrieNode {
- //对于关键词结束进行一个标识
- private boolean isKeywordEnd = false;
-
- public boolean isKeywordEnd() {
- return isKeywordEnd;
- }
-
- public void setKeywordEnd(boolean keywordEnd) {
- isKeywordEnd = keywordEnd;
- }
-
- //定义子节点(key 是下级字符、value 是下级节点)
- private Map<Character, TrieNode> subNodes = new HashMap<>();
-
- // 添加子节点
- public void addSubNode(Character c, TrieNode node) {
- subNodes.put(c,node);
- }
-
- // 获取子节点
- public TrieNode getSubNode(Character c) {
- return subNodes.get(c);
- }
- }
- }
在 SensitiveFilter 类添加:
- package com.example.demo.util;
- import org.apache.commons.lang3.CharUtils;
- import org.apache.commons.lang3.StringUtils;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.stereotype.Component;
-
- import javax.annotation.PostConstruct;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.util.HashMap;
- import java.util.Map;
-
- @Component
- public class SensitiveFilter {
-
- //定义日志
- private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);
-
- //定义常量:当我们检测到敏感词的时候,需要把敏感词替换成这个常量
- private static final String REPLACEMENT = "***";
-
- //初始化根节点
- private TrieNode rootNode = new TrieNode();
-
- //根据配置文件构造树型:在初次使用工具时自动初始化,添加注解 @PostConstruct
- // 当容器实例化 Bean 之后,在调用构造器之后,这个方法自动被调用
- @PostConstruct
- public void init() {
- try (
- InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");//字符流
- BufferedReader reader = new BufferedReader(new InputStreamReader(is));//字节流-》缓冲流
- ) {
- String keyword;
- while ((keyword = reader.readLine()) != null) {
- // 添加到前缀树
- this.addKeyword(keyword);
- }
- } catch (IOException e) {
- logger.error("加载敏感词文件失败: " + e.getMessage());
- }
- }
-
- // 将一个敏感词添加到前缀树中
- private void addKeyword(String keyword) {
- //创建一个节点默认指向根节点,后续会移动
- TrieNode tempNode = rootNode;
- for (int i = 0; i < keyword.length(); i++) {
- char c = keyword.charAt(i);
- //获取子节点
- TrieNode subNode = tempNode.getSubNode(c);
- //如果为空,初始化子节点,放在当前节点处
- if (subNode == null) {
- // 初始化子节点
- subNode = new TrieNode();
- tempNode.addSubNode(c, subNode);
- }
-
- // 完成之后,那个创建节点指向子节点,进入下一轮循环
- tempNode = subNode;
-
- // 设置结束标识
- if (i == keyword.length() - 1) {
- tempNode.setKeywordEnd(true);
- }
- }
- }
- }
在 SensitiveFilter 类添加:
- /**
- * 过滤敏感词
- *
- * @param text 待过滤的文本
- * @return 过滤后的文本
- */
- public String filter(String text) {
- if (StringUtils.isBlank(text)) {
- return null;
- }
-
- // 指针1
- TrieNode tempNode = rootNode;
- // 指针2
- int begin = 0;
- // 指针3
- int position = 0;
- // 定义变量记录最终结果,需要不断追加,使用 StringBuilder
- StringBuilder sb = new StringBuilder();
-
- //指针三需要遍历到结尾
- while (position < text.length()) {
- //得到某一个字符
- char c = text.charAt(position);
-
- // 跳过符号(会存在❤️敏❤️感❤️词,这种不连接的,如果识别不了是不可行的)
- if (isSymbol(c)) {
- // 若指针1处于根节点,将此符号计入结果,让指针2向下走一步
- if (tempNode == rootNode) {
- sb.append(c);
- begin++;
- }
- // 无论符号在开头或中间,指针3都向下走一步
- position++;
- continue;
- }
-
- // 检查下级节点
- //获取下级节点
- tempNode = tempNode.getSubNode(c);
- //若没有下级节点,则以begin开头的字符串不是敏感词,进入下一个位置;指针1重新指向根节点
- if (tempNode == null) {
- // 以begin开头的字符串不是敏感词
- sb.append(text.charAt(begin));
- // 进入下一个位置
- position = ++begin;
- // 重新指向根节点
- tempNode = rootNode;
- } else if (tempNode.isKeywordEnd()) {
- //若发现敏感词,将指针二和指针三之间字符串替换掉,并且使指针三进入下一个位置,
- //此时设置指针二和指针三位置相同,指针一指向根节点
- // 发现敏感词,将begin~position字符串替换掉
- sb.append(REPLACEMENT);
- // 进入下一个位置
- begin = ++position;
- // 重新指向根节点
- tempNode = rootNode;
- } else { //:检测途中没有检测完,也没有发现符号,需要继续往下执行,则指针三进入下一个位置
- // 检查下一个字符
- position++;
- }
- }
-
- //没有敏感词时 将最后一批字符计入结果
- sb.append(text.substring(begin));
-
- return sb.toString();
- }
-
- // 判断是否为符号
- private boolean isSymbol(Character c) {
- // 0x2E80~0x9FFF 是东亚文字范围
- //CharUtils.isAsciiAlphanumeric 判断是不是一个合法字符
- return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
- }
新建测试类:
- package com.example.demo;
-
- import com.example.demo.util.SensitiveFilter;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.test.context.ContextConfiguration;
- import org.springframework.test.context.junit4.SpringRunner;
-
- @RunWith(SpringRunner.class)
- @SpringBootTest
- @ContextConfiguration(classes = DemoApplication.class)
- public class SensitiveTests {
-
- @Autowired
- private SensitiveFilter sensitiveFilter;
-
- @Test
- public void testSensitiveFilter() {
- String text = "这里可以赌博,可以嫖娼,可以吸毒,可以开票";
- text = sensitiveFilter.filter(text);
- System.out.println(text);
-
- text = "这里可以☆赌☆博☆,可以☆嫖☆娼☆,可以☆吸☆毒☆,可以☆开☆票☆,哈哈哈!";
- text = sensitiveFilter.filter(text);
- System.out.println(text);
- }
- }
添加 AJAX 的 pom.xml:
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.58</version>
- </dependency>
在 util 包下的 Community 类下添加方法:
- //添加处理字符串的 JSON 方法:给浏览器返回编号、提示信息、业务数据
- public static String getJSONString(int code, String msg, Map<String, Object> map) {
- JSONObject json = new JSONObject();
- json.put("code", code);
- json.put("msg", msg);
- if (map != null) {
- for (String key : map.keySet()) {
- json.put(key, map.get(key));
- }
- }
- return json.toJSONString();
- }
-
- //可能只有编号,有可能没有提示信息或者业务数据,那么添加两个重载方法便于调用
- public static String getJSONString(int code, String msg) {
- return getJSONString(code, msg, null);
- }
-
- public static String getJSONString(int code) {
- return getJSONString(code, null, null);
- }
添加一个帖子的方法返回增加的行数,在 dao 包下的 DiscussPostMapper 类下追加一个方法:
- //添加一个帖子的方法
- int insertDiscussPost(DiscussPost discussPost);
在 resources 资源文件下 mapper 包下的 discusspost-mapper.xml 中实现方法:
- <sql id="insertFields">
- user_id, title, content, type, status, create_time, comment_count, score
- </sql>
-
- <insert id="insertDiscussPost" parameterType="DiscussPost">
- insert into discuss_post(<include refid="insertFields"></include>)
- values(#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})
- </insert>
业务层需要提供一个保存帖子的业务方法(并且使用敏感词的方法),在 service 包下的 DiscussPostService 类中追加一个方法:
- //注入敏感词
- @Autowired
- private SensitiveFilter sensitiveFilter;
-
- //添加一个保存帖子的业务方法,返回添加的行数
- public int addDiscussPost(DiscussPost post) {
- if (post == null) {
- throw new IllegalArgumentException("参数不能为空");
- }
- //处理标签:转义 HTML
- post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
- post.setContent(HtmlUtils.htmlEscape(post.getContent()));
-
- //过滤敏感词
- post.setTitle(sensitiveFilter.filter(post.getTitle()));
- post.setContent(sensitiveFilter.filter(post.getContent()));
-
- return discussPostMapper.insertDiscussPost(post);//实现插入数据,调用方法
- }
实现增加帖子功能,在 controller 包下新建 DiscussPostController 类(实现增加帖子功能):
- package com.example.demo.controller;
-
- import com.example.demo.entity.DiscussPost;
- import com.example.demo.entity.User;
- import com.example.demo.service.DiscussPostService;
- import com.example.demo.util.CommunityUtil;
- import com.example.demo.util.HostHolder;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.ResponseBody;
-
- import java.util.Date;
-
- /**
- * 实现增加帖子功能
- */
- @Controller
- @RequestMapping("/discuss")
- public class DiscussPostController {
- //实现功能,先注入 DiscussPostService 添加帖子的方法
- @Autowired
- private DiscussPostService discussPostService;
-
- @Autowired
- private HostHolder hostHolder;
-
- @RequestMapping(path = "/add", method = RequestMethod.POST)
- @ResponseBody //添加 @ResponseBody 表示返回字符串
- //添加方法,返回字符串,传入标题(title)和内容(context)
- public String addDiscussPost(String title, String content) {
- //发帖子前提是登陆:在 HostHolder 中取对象,如果取到的对象为空(未登录),给页面一个 403 提示(异步的,是 json 格式的数据)
- User user = hostHolder.getUser();
- if (user == null) {
- return CommunityUtil.getJSONString(403, "你还没有登陆");
- }
-
- //登陆成功之后需要调用 DiscussPostService 保存帖子:构造实体:创建 post 对象,传入 UserId、title、content、处理时间
- DiscussPost post = new DiscussPost();
- post.setUserId(user.getId());
- post.setTitle(title);
- post.setContent(content);
- post.setCreateTime(new Date());
- discussPostService.addDiscussPost(post);
-
- // 报错的情况,将来统一处理.
- return CommunityUtil.getJSONString(0, "发布成功!");
- }
- }
处理页面:
index.xml:
<button type="button" class="btn btn-primary btn-sm position-absolute rt-0" data-toggle="modal" data-target="#publishModal" th:if="${loginUser!=null}">我要发布</button>
js 中的 index.js:
- $(function(){
- $("#publishBtn").click(publish);
- });
-
- function publish() {
- $("#publishModal").modal("hide");
-
- // 获取标题和内容
- var title = $("#recipient-name").val();
- var content = $("#message-text").val();
- // 发送异步请求(POST)
- $.post(
- CONTEXT_PATH + "/discuss/add",
- {"title":title,"content":content},
- function(data) {
- data = $.parseJSON(data);
- // 在提示框中显示返回消息
- $("#hintBody").text(data.msg);
- // 显示提示框
- $("#hintModal").modal("show");
- // 2秒后,自动隐藏提示框
- setTimeout(function(){
- $("#hintModal").modal("hide");
- // 刷新页面
- if(data.code == 0) {
- window.location.reload();
- }
- }, 2000);
- }
- );
-
- }
增加查看帖子的方法,在 dao 包下的 DiscussPostMapper 类下追加一个方法:
- //根据帖子 id,查询帖子详情,返回一个帖子
- DiscussPost selectDiscussPostById(int id);
在 resources 资源文件下 mapper 包下的 discusspost-mapper.xml 中实现方法:
- <select id="selectDiscussPostById" resultType="DiscussPost">
- select <include refid="selectFields"></include>
- from discuss_post
- where id = #{id}
- </select>
增加查询方法,在 service 包下的 DiscussPostService 类中追加一个方法:
- //根据 id 查询 帖子方法
- public DiscussPost findDiscussPostById(int id) {
- return discussPostMapper.selectDiscussPostById(id);
- }
查询请求,在 controller 包下的DiscussPostController 类中新增处理查询请求:
- @Autowired
- private UserService userService;
-
- //查询请求方法
- @RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
- public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model) {
- // 帖子
- DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
- model.addAttribute("post", post);
- //第一种方法:在查询的时候使用关联查询 。优点:查询快;缺点:可能存在冗余、耦合
- //第二种方法:先查出帖子数据,根据 id 调用 Userservice 查询 User,再通过 Model 将 User 发送给 模板,
- // 这样模板得到了帖子,也得到了模板。优点:查询两次,没有冗余;缺点:查询慢
- //在这里使用第二种情况,查询慢可以使用 Redis 来优化
- // 作者
- User user = userService.findUserById(post.getUserId());
- // 把作者传给模板
- model.addAttribute("user", user);
-
- return "/site/discuss-detail";
- }
index.xml:在帖子标题上增加访问详情页面的链接
<a th:href="@{|/discuss/detail/${map.post.id}|}" th:utext="${map.post.title}">学习 Java</a>
disucss-detail.html:处理静态资源的访问路径、复用 index.xml 的header 区域、显示标题、作者、发布时间、帖子正文等内容
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。