意见箱
恒创运营部门将仔细参阅您的意见和建议,必要时将通过预留邮箱与您保持联络。感谢您的支持!
意见/建议
提交建议

第二章 开发社区核心功能(一)

来源:恒创科技 编辑:恒创科技编辑部
2022-09-09 14:05:24


第二章 开发社区核心功能(一)敏感词过滤

第二章 开发社区核心功能(一)_事务管理

第二章 开发社区核心功能(一)_帖子评论_02


第二章 开发社区核心功能(一)

工具类

包含敏感词的文件:sensitive-words.txt

赌博
嫖娼
吸毒
开票

敏感词过滤器:SensitiveFilter.java

/**
* 敏感词过滤器
*
* @author xiexu
* @create 2022-06-04 17:12
*/
@Component
public class SensitiveFilter {

private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);

// 敏感词替换符
private static final String REPLACEMENT = "***";

// 根节点
private TrieNode rootNode = new TrieNode();

// 前缀树
private class TrieNode {
// 关键词结束标识
private boolean isKeyWordEnd = false;

// 当前节点的子节点(key是下级字符,value是下级节点)
private Map<Character, TrieNode> subNodes = new HashMap<>();

public boolean isKeyWordEnd() {
return isKeyWordEnd;
}

public void setKeyWordEnd(boolean keyWordEnd) {
isKeyWordEnd = keyWordEnd;
}

// 添加子节点的方法
public void addSubNode(Character c, TrieNode node) {
subNodes.put(c, node);
}

// 获取子节点
public TrieNode getSubNode(Character c) {
return subNodes.get(c);
}
}

@PostConstruct // 表示这是一个初始化方法,在初始化当前类以后会调用该方法,而当前类在服务一启动的时候就会被初始化
public void init() {
InputStream is = null;
BufferedReader reader = null;
try {
// 通过类加载器获取该敏感词文件
is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
reader = new BufferedReader(new InputStreamReader(is));
String keyWord;
// 每次读取敏感词的一行
while ((keyWord = reader.readLine()) != null) {
// 把敏感词添加到前缀树
this.addKeyWord(keyWord);
}
} catch (Exception e) {
logger.error("加载敏感词文件失败:" + e.getMessage());
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}

// 将一个敏感词添加到前缀树中
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);
}
}
}

/**
* 过滤敏感词
*
* @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 sb = new StringBuilder();

while (begin < text.length()) {
if (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);
if (tempNode == null) {
// 以begin开头的字符串不是敏感词
sb.append(text.charAt(begin));
// 进入下一个位置
begin++;
position = begin;
// 重新指向根节点
tempNode = rootNode;
} else if (tempNode.isKeyWordEnd()) {
// 发现敏感词,将begin到position这一段的字符串替换掉
sb.append(REPLACEMENT);
// 进入下一个位置
position++;
begin = position;
// 重新指向根节点
tempNode = rootNode;
} else {
// 还没有发现敏感词,继续检查下一个字符
position++;
}
} else { // 将最后一批字符计入结果(指针3到终点,指针2还没有到终点)
// position遍历越界但仍未匹配到敏感词
sb.append(text.charAt(begin));
begin++;
position = begin;
// 重新指向根节点
tempNode = rootNode;
}
}

return sb.toString();
}

// 判断是否为符号
private boolean isSymbol(Character c) {
/**
* isAsciiAlphanumeric如果是特殊字符返回false,如果是abc这种普通字符就返回true
* 0x2E80 ~ 0x9FFF:代表东亚文字范围,(中文、日文、韩文这些就属于东亚文字)
* c < 0x2E80 || c > 0x9FFF 意思就是排除东亚文字范围的其他符号
*/
return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
}

}
测试过滤敏感词功能
/**
* 过滤敏感词功能测试
*/
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.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);
}

}

第二章 开发社区核心功能(一)_java_03

发布帖子

第二章 开发社区核心功能(一)_帖子评论_04

工具类CommunityUtil.java
// 将数据封装成json格式的字符串
public static String getJSONString(int code, String msg, Map<String, Object> map) {
// 创建一个json对象
JSONObject json = new JSONObject();
json.put("code", code);
json.put("msg", msg);
if (map != null) {
// 遍历map中的每一个key
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);
}
业务层DiscussPostService.java
// 插入帖子
public int addDiscussPost(DiscussPost post) {
if (post == null) {
throw new IllegalArgumentException("参数不能为空!");
}
// 转义HTML标记,如果标题中含有类似于<script>xxx</script>,会将标题转成普通文本而不会破坏网页显示内容
post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
post.setContent(HtmlUtils.htmlEscape(post.getContent()));
// 过滤敏感词
post.setTitle(sensitiveFilter.filter(post.getTitle()));
post.setContent(sensitiveFilter.filter(post.getContent()));
post.setStatus(0);
post.setType(0);
return discussPostMapper.insertDiscussPost(post);
}
控制层DiscussPostController.java
@Controller
@RequestMapping("/discuss")
public class DiscussPostController {

@Autowired
private DiscussPostService discussPostService;

@Autowired
private HostHolder hostHolder;

@RequestMapping(path = "/add", method = RequestMethod.POST)
@ResponseBody
public String addDiscussPost(String title, String content) {
User user = hostHolder.getUser();
if (user == null) {
return CommunityUtil.getJSONString(403, "您还没有登录哦!");
}
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, "发布成功!");
}

}
帖子详情

第二章 开发社区核心功能(一)_敏感词过滤_05

业务层DiscussPostService.java
/**
* 根据id查询帖子
*
* @param id
* @return
*/
public DiscussPost findDiscusspostById(int id) {
return discussPostMapper.selectDiscussPostById(id);
}
控制层DiscussPostController.java
@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);
// 作者
User user = userService.findUserById(post.getUserId());
model.addAttribute("user", user);
return "/site/discuss-detail";
}
事务管理

第二章 开发社区核心功能(一)_java_06

事务的隔离性

第二章 开发社区核心功能(一)_事务管理_07

常见的并发异常

第二章 开发社区核心功能(一)_帖子评论_08

第二章 开发社区核心功能(一)_帖子评论_09

第二章 开发社区核心功能(一)_帖子评论_10

第二章 开发社区核心功能(一)_项目_11

第二章 开发社区核心功能(一)_java_12

事务隔离级别

第二章 开发社区核心功能(一)_帖子评论_13

实现机制

第二章 开发社区核心功能(一)_事务管理_14

Spring 事务管理

第二章 开发社区核心功能(一)_敏感词过滤_15

显示评论

第二章 开发社区核心功能(一)_java_16

业务层CommentService.java
@Service
public class CommentService {

@Autowired
private CommentMapper commentMapper;

public List<Comment> findCommentsByEntity(int entityType, int entityId, int offset, int limit) {
return commentMapper.selectCommentsByEntity(entityType, entityId, offset, limit);
}

public int findCommentCount(int entityType, int entityId) {
return commentMapper.selectCountByEntity(entityType, entityId);
}

}
DiscussPostService.java
/**
* 根据id查询帖子
*
* @param id
* @return
*/
public DiscussPost findDiscusspostById(int id) {
return discussPostMapper.selectDiscussPostById(id);
}
控制层DiscussPostController.java
@Controller
@RequestMapping("/discuss")
public class DiscussPostController implements CommunityConstant {

@Autowired
private DiscussPostService discussPostService;

@Autowired
private HostHolder hostHolder;

@Autowired
private UserService userService;

@Autowired
private CommentService commentService;

@RequestMapping(path = "/add", method = RequestMethod.POST)
@ResponseBody
public String addDiscussPost(String title, String content) {
User user = hostHolder.getUser();
if (user == null) {
return CommunityUtil.getJSONString(403, "您还没有登录哦!");
}
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, "发布成功!");
}

@RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
// 查询帖子
DiscussPost post = discussPostService.findDiscusspostById(discussPostId);
model.addAttribute("post", post);
// 作者
User user = userService.findUserById(post.getUserId());
model.addAttribute("user", user);

// 查询评论信息
// 评论分页信息
page.setLimit(5);
page.setPath("/discuss/detail/" + discussPostId);
page.setRows(post.getCommentCount());

/**
* 说明:
* 评论:给帖子的评论
* 回复:给评论的评论
* vo:view object
*/
// 查询当前帖子的所有评论,下面这个commentList是评论列表
List<Comment> commentList = commentService.findCommentsByEntity(ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());

// 评论的vo列表
List<Map<String, Object>> commentVoList = new ArrayList<>();
if (commentList != null) {
for (Comment comment : commentList) {
// 一个评论的vo
Map<String, Object> commentVo = new HashMap<>();
// 往vo里添加评论
commentVo.put("comment", comment);
// 往vo里添加评论的作者
commentVo.put("user", userService.findUserById(comment.getUserId()));

// 下面这个replyList是回复列表,(offset:0,limit:Integer.MAX_VALUE 表示有多少评论就查多少评论,不分页)
List<Comment> replyList = commentService.findCommentsByEntity(ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);

// 回复的vo列表
List<Map<String, Object>> replyVoList = new ArrayList<>();
if (replyList != null) {
for (Comment reply : replyList) {
// 一个回复的vo
Map<String, Object> replyVo = new HashMap<>();
// 往vo里添加回复
replyVo.put("reply", reply);
// 往vo里添加回复的作者
replyVo.put("user", userService.findUserById(reply.getUserId()));
/**
* 往vo里添加回复的目标
* target:0表示普通回复
* target:非0 表示回复作者的评论
*/
User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());
replyVo.put("target", target);

replyVoList.add(replyVo);
}
}
commentVo.put("replys", replyVoList);

// 回复的数量
int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
commentVo.put("replyCount", replyCount);

commentVoList.add(commentVo);
}
}

model.addAttribute("comments", commentVoList);

return "/site/discuss-detail";
}

}
添加评论

第二章 开发社区核心功能(一)_项目_17

业务层DiscussPostService.java
/**
* 更新帖子评论数量
*
* @param id
* @param commentCount
* @return
*/
public int updateCommentCount(int id, int commentCount) {
return discussPostMapper.updateCommentCount(id, commentCount);
}
CommentService.java
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public int addComment(Comment comment) {
if (comment == null) {
throw new IllegalArgumentException("参数不能为空!");
}
// 添加评论
// 对评论内容的特殊字符进行转义
comment.setContent(HtmlUtils.htmlEscape(comment.getContent()));
// 对评论内容进行敏感词过滤
comment.setContent(sensitiveFilter.filter(comment.getContent()));
int rows = commentMapper.insertComment(comment);

// 更新帖子的评论数量
if (comment.getEntityType() == ENTITY_TYPE_POST) {
int count = commentMapper.selectCountByEntity(comment.getEntityType(), comment.getEntityId());
discussPostService.updateCommentCount(comment.getEntityId(), count);
}

return rows;
}
控制层CommentController.java
@Controller
@RequestMapping("/comment")
public class CommentController {

@Autowired
private CommentService commentService;

@Autowired
private HostHolder hostHolder;

@RequestMapping(path = "/add/{discussPostId}", method = RequestMethod.POST)
public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) {
comment.setUserId(hostHolder.getUser().getId());
comment.setStatus(0);
comment.setCreateTime(new Date());
commentService.addComment(comment);

// 重定向到帖子详情页
return "redirect:/discuss/detail/" + discussPostId;
}

}


上一篇: 租用美国服务器:潜在的风险与应对策略。 下一篇: MongoDB 5.0 扩展开源文档数据库操作