在resources下新建sensitive-word.txt,并在util下实现 SensitiveFilter
// 前缀树
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);
}
}
// 将一个敏感词添加到前缀树中
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);
}
}
}
@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());
}
}
/**
* 过滤敏感词
*
* @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 (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));
// 进入下一个位置
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();
}
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.58version>
dependency>
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);
}
⑤ 写个main方法简单测一下,不需要容器管理
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>();
map.put("name", "zhangsan");
map.put("age", 25);
System.out.println(getJSONString(0, "ok", map));
}
// ajax示例
@RequestMapping(path = "/ajax", method = RequestMethod.POST)
@ResponseBody
public String testAjax(String name, int age) {
System.out.println(name);
System.out.println(age);
return CommunityUtil.getJSONString(0, "操作成功!");
}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AJAXtitle>
head>
<body>
<p>
<input type="button" value="发送" onclick="send();">
p>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous">script>
<script>
function send() {
$.post(
"/community/alpha/ajax",
{"name":"张三","age":23},
function(data) {
console.log(typeof(data));
console.log(data);
data = $.parseJSON(data);
console.log(typeof(data));
console.log(data.code);
console.log(data.msg);
}
);
}
script>
body>
html>
int insertDiscussPost(DiscussPost discussPost);
<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>
@Autowired
private DiscussPostMapper discussPostMapper;
@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);
}
@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, "发布成功!");
}
$(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);
}
);
}
② 不登陆不显示发布的按钮 index.html,判断当前是否有用户登录即可
<button type="button" class="btn btn-primary btn-sm position-absolute rt-0" data-toggle="modal" data-target="#publishModal" th:if="${loginUser!=null}">我要发布button>
DiscussPost selectDiscussPostById(int id);
<select id="selectDiscussPostById" resultType="DiscussPost">
select <include refid="selectFields">include>
from discuss_post
where id = #{id}
select>
public DiscussPost findDiscussPostById(int id) {
return discussPostMapper.selectDiscussPostById(id);
}
在DisscussPostController下添加方法getDiscussPost
@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);
return "/site/discuss-detail";
}
<a th:href="@{|/discuss/detail/${map.post.id}|}" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!a>
<span th:utext="${post.title}">备战春招,面试刷题跟他复习,一个月全搞定!span>
⑥ 作者部分:头像和姓名
<img th:src="${user.headerUrl}" class="align-self-start mr-4 rounded-circle user-header" alt="用户头像" >
<div class="mt-0 text-warning" th:utext="${user.username}">寒江雪div>
⑦发帖时间
<div class="text-muted mt-3">
发布于 <b th:text="${#dates.format(post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18b>
div>
⑧ 正文部分
<div class="mt-4 mb-3 content" th:utext="${post.content}">
金三银四的金三已经到了,你还沉浸在过年的喜悦中吗?
如果是,那我要让你清醒一下了:目前大部分公司已经开启了内推,正式网申也将在3月份陆续开始,金三银四,春招的求职黄金时期已经来啦!!!
再不准备,作为19应届生的你可能就找不到工作了。。。作为20届实习生的你可能就找不到实习了。。。
现阶段时间紧,任务重,能做到短时间内快速提升的也就只有算法了,
那么算法要怎么复习?重点在哪里?常见笔试面试算法题型和解题思路以及最优代码是怎样的?
跟左程云老师学算法,不仅能解决以上所有问题,还能在短时间内得到最大程度的提升!!!
div>
entity_type:评论目标的类别(如:1代表帖子,2代表评论等)
entity_id:具体目标(帖子id 228,或者帖子 id 229)
target_id:指向某个人的评论
属性,getset方法,tostring
package com.nowcoder.community.entity;
import java.util.Date;
public class Comment {
private int id;
private int userId;
private int entityType;
private int entityId;
private int targetId;
private String content;
private int status;
private Date createTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getEntityType() {
return entityType;
}
public void setEntityType(int entityType) {
this.entityType = entityType;
}
public int getEntityId() {
return entityId;
}
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public int getTargetId() {
return targetId;
}
public void setTargetId(int targetId) {
this.targetId = targetId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "Comment{" +
"id=" + id +
", userId=" + userId +
", entityType=" + entityType +
", entityId=" + entityId +
", targetId=" + targetId +
", content='" + content + '\'' +
", status=" + status +
", createTime=" + createTime +
'}';
}
}
package com.nowcoder.community.dao;
import com.nowcoder.community.entity.Comment;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface CommentMapper {
List<Comment> selectCommentsByEntity(int entityType, int entityId, int offset, int limit);
int selectCountByEntity(int entityType, int entityId);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nowcoder.community.dao.CommentMapper">
<sql id="selectFields">
id, user_id, entity_type, entity_id, target_id, content, status, create_time
sql>
<select id="selectCommentsByEntity" resultType="Comment">
select <include refid="selectFields">include>
from comment
where status = 0
and entity_type = #{entityType}
and entity_id = #{entityId}
order by create_time asc
limit #{offset}, #{limit}
select>
<select id="selectCountByEntity" resultType="int">
select count(id)
from comment
where status = 0
and entity_type = #{entityType}
and entity_id = #{entityId}
select>
mapper>
service下实现 CommentService
package com.nowcoder.community.service;
import com.nowcoder.community.dao.CommentMapper;
import com.nowcoder.community.entity.Comment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CommentService implements CommunityConstant {
@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);
}
}
page.setLimit(5); page.setPath("/discuss/detail/" + discussPostId); page.setRows(post.getCommentCount());
List commentList
commentVo.put("comment", comment);
commentVo.put("user", userService.findUserById(comment.getUserId()));
List replyList
List
replyVo.put("reply", reply);
replyVo.put("user", userService.findUserById(reply.getUserId()));
replyVo.put("target", target);
commentVo.put("replys", replyVoList);
commentVo.put("replyCount", replyCount);
commentVoList.add(commentVo);
model.addAttribute("comments", commentVoList);
@Autowired
private CommentService commentService;
@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());
// 评论: 给帖子的评论
// 回复: 给评论的评论
// 评论列表
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<>();
// 评论
commentVo.put("comment", comment);
// 作者
commentVo.put("user", userService.findUserById(comment.getUserId()));
// 回复列表
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) {
Map<String, Object> replyVo = new HashMap<>();
// 回复
replyVo.put("reply", reply);
// 作者
replyVo.put("user", userService.findUserById(reply.getUserId()));
// 回复目标
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";
}
回帖数量
<li class="d-inline ml-2">回帖 <span th:text="${map.post.commentCount}">7span>li>
<h6><b class="square">b> <i th:text="${post.commentCount}">30i>条回帖h6>
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="cvo:${comments}">
<img th:src="${cvo.user.headerUrl}" class="align-self-start mr-4 rounded-circle user-header" alt="用户头像" >
<span class="font-size-12 text-success" th:utext="${cvo.user.username}">掉脑袋切切span>
<span class="badge badge-secondary float-right floor">
<i th:text="${page.offset + cvoStat.count}">1i>#
span>
<li class="pb-3 pt-3 mb-3 border-bottom" th:each="rvo:${cvo.replys}">
<span th:if="${rvo.target==null}">
<b class="text-info" th:text="${rvo.user.username}">寒江雪b>:
span>
<span th:if="${rvo.target!=null}">
<i class="text-info" th:text="${rvo.user.username}">Sissii> 回复
<b class="text-info" th:text="${rvo.target.username}">寒江雪b>:
span>
<span th:text="${#dates.format(rvo.reply.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18span>
<li class="d-inline ml-2"><a th:href="|#huifu-${rvoStat.count}|" data-toggle="collapse" class="text-primary">回复a>li>
<nav class="mt-5" th:if="${page.rows>0}" th:fragment="pagination">
……
nav>
当前页面的
<nav class="mt-5" th:replace="index::pagination">
……
nav>
int insertComment(Comment comment);
<sql id="insertFields">
user_id, entity_type, entity_id, target_id, content, status, create_time
sql>
<insert id="insertComment" parameterType="Comment">
insert into comment(<include refid="insertFields">include>)
values(#{userId},#{entityType},#{entityId},#{targetId},#{content},#{status},#{createTime})
insert>
int insertComment(Comment comment);
<sql id="insertFields">
user_id, entity_type, entity_id, target_id, content, status, create_time
sql>
<insert id="insertComment" parameterType="Comment">
insert into comment(<include refid="insertFields">include>)
values(#{userId},#{entityType},#{entityId},#{targetId},#{content},#{status},#{createTime})
insert>
int updateCommentCount(int id, int commentCount);
<update id="updateCommentCount">
update discuss_post set comment_count = #{commentCount} where id = #{id}
update>
public int updateCommentCount(int id, int commentCount) {
return discussPostMapper.updateCommentCount(id, commentCount);
}
@Autowired
private DiscussPostService discussPostService;
@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;
}
package com.nowcoder.community.controller;
import com.nowcoder.community.entity.Comment;
import com.nowcoder.community.service.CommentService;
import com.nowcoder.community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Date;
@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;
}
}
<form class="replyform" method="post" th:action="@{|/comment/add/${post.id}|}">
② 处理文本域,取名为content和实体属性对应
<textarea placeholder="在这里畅所欲言你的看法吧!" name="content">textarea>
③ 两个隐含条件entityType和entityId
<input type="hidden" name="entityType" value="1">
<input type="hidden" name="entityId" th:value="${post.id}">
<form method="post" th:action="@{|/comment/add/${post.id}|}">
……
form>
② 处理文本域,取名为content和实体属性对应
<input type="text" class="input-size" name="content" placeholder="请输入你的观点"/>
③ 两个隐含条件entityType和entityId
<input type="hidden" name="entityType" value="2">
<input type="hidden" name="entityId" th:value="${cvo.comment.id}">
<form method="post" th:action="@{|/comment/add/${post.id}|}">
……
form>
② 处理文本域,取名为content和实体属性对应
<input type="text" class="input-size" name="content" th:placeholder="|回复${rvo.user.username}|"/>
③ 两个隐含条件entityType和entityId和targetId
<input type="hidden" name="entityType" value="2">
<input type="hidden" name="entityId" th:value="${cvo.comment.id}">
<input type="hidden" name="targetId" th:value="${rvo.user.id}">
④ 显示回复给哪个人
<input type="text" class="input-size" name="content" th:placeholder="|回复${rvo.user.username}|"/>