1.过滤敏感词

我们想在某个网站上发布一些内容的时候,网站会对我们所发布的内容进行过滤,如果发现我的内容里包含一些色情、暴力等非法词汇,会把这些词汇隐去,即不显示或者打码,那么这种行为就叫过滤敏感词。从技术上的角度就是对输入的字符串进行过滤,调用某些api判断这些字符串是否合法,如jdk里面的replace方法,但是如果发布的是一篇文章,那么字符串的数量就会非常多,这个时候再使用原生的api会导致效率非常低,这个时候推荐使用前缀树这种数据结构,自己实现过滤敏感词的一种算法。

前缀树的根节点不包含任何字符,除了根节点以外的节点只包含一个字符,从根节点到某一个节点的路径上就是当前这个节点对应的字符串,每个节点所有的子节点包含的字符串不同。

如图所示,从根节点到叶子节点走过的路径,即为一个敏感词。
首先,需要定义好项目中的敏感词有哪些,根据这些敏感词构造一棵前缀树,检测用户输入的字符串是否有敏感词。
具体实现需要三个指针,一个指针指向前缀树,两个指针指向字符串(一前一后),同时需要一个新的空间存储结果。
resource下新建sensitive-words.txt,输入需要过滤的敏感词,util下新建SensitiveFilter

@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以后,在调用其构造器之后,这个方法就会被自动调用,即在服务器启动就会被初始化,用于筛选敏感词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);}}}/*** 过滤敏感词** @param text 带过滤的文本* @return 过滤后的文本*/public String filter(String text) {if (StringUtils.isBlank(text)) {return null;}//指针1TrieNode tempNode = rootNode;//指针2int begin = 0;//指针3int 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();}//判断是否为符号private boolean isSymbol(Character c) {// 0x2E80~0x9FFF 是东亚文字范围return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);}//前缀树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);}}
}

2.发布帖子

maven搜索fastjson,把相关的依赖添加进pom.xml

     <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.58</version></dependency>

在CommunityUtil类中实现获取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);}

有关ajax的小示例

    //ajax示例@RequestMapping(path = "/ajax", method = RequestMethod.POST)@ResponseBodypublic 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>AJAX</title>
</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); //将data转换为js格式console.log(typeof(data));console.log(data.code);console.log(data.msg);});}
</script>
</body>
</html>

DiscussPostMapper中新增一个发布帖子的方法
discusspost-mapper.xml中补充sql语句

    int insertDiscussPost(DiscussPost discussPost);
    <sql id="insertFields">user_id, title, content, type, status, create_time, comment_count, score</sql><insert id="insertDiscussPost" parameterType="DiscussPost" keyProperty="id">insert into discuss_post(<include refid="insertFields"></include>)values(#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})</insert>

DiscussPostService增加addDiscussPost()方法,用于转义HTML标记和过滤敏感词,并且调用insertDiscussPost()插入数据。

    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);}

新建DiscussPostController,添加add访问请求

@Controller
@RequestMapping("/discuss")
public class DiscussPostController {@Autowiredprivate DiscussPostService discussPostService;@Autowiredprivate HostHolder hostHolder; //获取当前用户@RequestMapping(path = "/add", method = RequestMethod.POST)@ResponseBodypublic 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, "发布成功!");}
}
    <!-- 内容 --><div class="main"><div class="container"><div class="position-relative"><!-- 筛选条件 --><ul class="nav nav-tabs mb-3"><li class="nav-item"><a th:class="|nav-link ${orderMode==0?'active':''}|" th:href="@{/index(orderMode=0)}">最新</a></li><li class="nav-item"><a th:class="|nav-link ${orderMode==1?'active':''}|" th:href="@{/index(orderMode=1)}">最热</a></li></ul><button type="button" class="btn btn-primary btn-sm position-absolute rt-0" data-toggle="modal" data-target="#publishModal" th:if="${loginUser!=null}">我要发布</button></div><!-- 弹出框 --><div class="modal fade" id="publishModal" tabindex="-1" role="dialog" aria-labelledby="publishModalLabel" aria-hidden="true"><div class="modal-dialog modal-lg" role="document"><div class="modal-content"><div class="modal-header"><h5 class="modal-title" id="publishModalLabel">新帖发布</h5><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button></div><div class="modal-body"><form><div class="form-group"><label for="recipient-name" class="col-form-label">标题:</label><input type="text" class="form-control" id="recipient-name"></div><div class="form-group"><label for="message-text" class="col-form-label">正文:</label><textarea class="form-control" id="message-text" rows="15"></textarea></div></form></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button><button type="button" class="btn btn-primary" id="publishBtn">发布</button></div></div></div></div><!-- 提示框 --><div class="modal fade" id="hintModal" tabindex="-1" role="dialog" aria-labelledby="hintModalLabel" aria-hidden="true"><div class="modal-dialog modal-lg" role="document"><div class="modal-content"><div class="modal-header"><h5 class="modal-title" id="hintModalLabel">提示</h5></div><div class="modal-body" id="hintBody">发布完毕!</div></div></div></div>

修改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);});
}

3.帖子详情

DiscussPostMapper中新增一个根据帖子id查询到帖子详情的方法

    DiscussPost selectDiscussPostById(int id);

在discusspost-mapper.xml中书写sql语句

    <select id="selectDiscussPostById" resultType="DiscussPost">select <include refid="selectFields"></include>from discuss_postwhere id = #{id}</select>

DiscussPostController新建一个方法,根据用户id获取帖子对象和用户对象,传入model中方便在页面进行调用,然后页面跳转至详情页

    @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";}}

修改index.html

            <!-- 帖子列表 --><ul class="list-unstyled"><li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}"><a th:href="@{|/user/profile/${map.user.id}|}"><img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;"></a><div class="media-body"><h6 class="mt-0 mb-3"><a th:href="@{|/discuss/detail/${map.post.id}|}" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</a><span class="badge badge-secondary bg-primary" th:if="${map.post.type==1}">置顶</span><span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华</span></h6><div class="text-muted font-size-12"><u class="mr-3" th:utext="${map.user.username}">寒江雪</u> 发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b><ul class="d-inline float-right"><li class="d-inline ml-2">赞 <span th:text="${map.likeCount}">11</span></li><li class="d-inline ml-2">|</li><li class="d-inline ml-2">回帖 <span th:text="${map.post.commentCount}">7</span></li></ul></div></div></li></ul>

修改discuss-detail.html

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous"><link rel="stylesheet" th:href="@{/css/global.css}" /><link rel="stylesheet" th:href="@{/css/discuss-detail.css}" /><title>牛客网-帖子详情</title>
</head>
<body>
<div class="nk-container"><!-- 头部 --><header class="bg-dark sticky-top" th:replace="index::header"><div class="container"><!-- 导航 --><nav class="navbar navbar-expand-lg navbar-dark"><!-- logo --><a class="navbar-brand" href="#"></a><button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><!-- 功能 --><div class="collapse navbar-collapse" id="navbarSupportedContent"><ul class="navbar-nav mr-auto"><li class="nav-item ml-3 btn-group-vertical"><a class="nav-link" href="../index.html">首页</a></li><li class="nav-item ml-3 btn-group-vertical"><a class="nav-link position-relative" href="letter.html">消息<span class="badge badge-danger">12</span></a></li><li class="nav-item ml-3 btn-group-vertical"><a class="nav-link" href="register.html">注册</a></li><li class="nav-item ml-3 btn-group-vertical"><a class="nav-link" href="login.html">登录</a></li><li class="nav-item ml-3 btn-group-vertical dropdown"><a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/></a><div class="dropdown-menu" aria-labelledby="navbarDropdown"><a class="dropdown-item text-center" href="profile.html">个人主页</a><a class="dropdown-item text-center" href="setting.html">账号设置</a><a class="dropdown-item text-center" href="login.html">退出登录</a><div class="dropdown-divider"></div><span class="dropdown-item text-center text-secondary">nowcoder</span></div></li></ul><!-- 搜索 --><form class="form-inline my-2 my-lg-0" action="search.html"><input class="form-control mr-sm-2" type="search" aria-label="Search" /><button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button></form></div></nav></div></header><!-- 内容 --><div class="main"><!-- 帖子详情 --><div class="container"><!-- 标题 --><h6 class="mb-4"><img src="http://static.nowcoder.com/images/img/icons/ico-discuss.png"/><span th:utext="${post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</span><div class="float-right"><input type="hidden" id="postId" th:value="${post.id}"><button type="button" class="btn btn-danger btn-sm" id="topBtn"th:disabled="${post.type==1}" sec:authorize="hasAnyAuthority('moderator')">置顶</button><button type="button" class="btn btn-danger btn-sm" id="wonderfulBtn"th:disabled="${post.status==1}" sec:authorize="hasAnyAuthority('moderator')">加精</button><button type="button" class="btn btn-danger btn-sm" id="deleteBtn"th:disabled="${post.status==2}" sec:authorize="hasAnyAuthority('admin')">删除</button></div></h6><!-- 作者 --><div class="media pb-3 border-bottom"><a href="profile.html"><img th:src="${user.headerUrl}" class="align-self-start mr-4 rounded-circle user-header" alt="用户头像" ></a><div class="media-body"><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:18</b><ul class="d-inline float-right"><li class="d-inline ml-2"><a href="javascript:;" th:onclick="|like(this,1,${post.id},${post.userId},${post.id});|" class="text-primary"><b th:text="${likeStatus==1?'已赞':'赞'}">赞</b> <i th:text="${likeCount}">11</i></a></li><li class="d-inline ml-2">|</li><li class="d-inline ml-2"><a href="#replyform" class="text-primary">回帖 <i th:text="${post.commentCount}">7</i></a></li></ul></div></div></div><!-- 正文 --><div class="mt-4 mb-3 content" th:utext="${post.content}">金三银四的金三已经到了,你还沉浸在过年的喜悦中吗?如果是,那我要让你清醒一下了:目前大部分公司已经开启了内推,正式网申也将在3月份陆续开始,金三银四,春招的求职黄金时期已经来啦!!!再不准备,作为19应届生的你可能就找不到工作了。。。作为20届实习生的你可能就找不到实习了。。。现阶段时间紧,任务重,能做到短时间内快速提升的也就只有算法了,那么算法要怎么复习?重点在哪里?常见笔试面试算法题型和解题思路以及最优代码是怎样的?跟左程云老师学算法,不仅能解决以上所有问题,还能在短时间内得到最大程度的提升!!!</div></div><!-- 回帖 --><div class="container mt-3"><!-- 回帖数量 --><div class="row"><div class="col-8"><h6><b class="square"></b> <i th:text="${post.commentCount}">30</i>条回帖</h6></div><div class="col-4 text-right"><a href="#replyform" class="btn btn-primary btn-sm">&nbsp;&nbsp;回&nbsp;&nbsp;帖&nbsp;&nbsp;</a></div></div><!-- 回帖列表 --><ul class="list-unstyled mt-4"><li class="media pb-3 pt-3 mb-3 border-bottom" th:each="cvo:${comments}"><a href="profile.html"><img th:src="${cvo.user.headerUrl}" class="align-self-start mr-4 rounded-circle user-header" alt="用户头像" ></a><div class="media-body"><div class="mt-0"><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}">1</i>#</span></div><div class="mt-2" th:utext="${cvo.comment.content}">这开课时间是不是有点晚啊。。。</div><div class="mt-4 text-muted font-size-12"><span>发布于 <b th:text="${#dates.format(cvo.comment.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b></span><ul class="d-inline float-right"><li class="d-inline ml-2"><a href="javascript:;" th:onclick="|like(this,2,${cvo.comment.id},${cvo.comment.userId},${post.id});|" class="text-primary"><b th:text="${cvo.likeStatus==1?'已赞':'赞'}">赞</b>(<i th:text="${cvo.likeCount}">1</i>)</a></li><li class="d-inline ml-2">|</li><li class="d-inline ml-2"><a href="#" class="text-primary">回复(<i th:text="${cvo.replyCount}">2</i>)</a></li></ul></div><!-- 回复列表 --><ul class="list-unstyled mt-4 bg-gray p-3 font-size-12 text-muted"><li class="pb-3 pt-3 mb-3 border-bottom" th:each="rvo:${cvo.replys}"><div><span th:if="${rvo.target==null}"><b class="text-info" th:text="${rvo.user.username}">寒江雪</b>:&nbsp;&nbsp;</span><span th:if="${rvo.target!=null}"><i class="text-info" th:text="${rvo.user.username}">Sissi</i> 回复<b class="text-info" th:text="${rvo.target.username}">寒江雪</b>:&nbsp;&nbsp;</span><span th:utext="${rvo.reply.content}">这个是直播时间哈,觉得晚的话可以直接看之前的完整录播的~</span></div><div class="mt-3"><span th:text="${#dates.format(rvo.reply.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</span><ul class="d-inline float-right"><li class="d-inline ml-2"><a href="javascript:;" th:onclick="|like(this,2,${rvo.reply.id},${rvo.reply.userId},${post.id});|" class="text-primary"><b th:text="${rvo.likeStatus==1?'已赞':'赞'}">赞</b>(<i th:text="${rvo.likeCount}">1</i>)</a></li><li class="d-inline ml-2">|</li><li class="d-inline ml-2"><a th:href="|#huifu-${rvoStat.count}|" data-toggle="collapse" class="text-primary">回复</a></li></ul><div th:id="|huifu-${rvoStat.count}|" class="mt-4 collapse"><form method="post" th:action="@{|/comment/add/${post.id}|}"><div><input type="text" class="input-size" name="content" th:placeholder="|回复${rvo.user.username}|"/><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}"></div><div class="text-right mt-2"><button type="submit" class="btn btn-primary btn-sm" onclick="#">&nbsp;&nbsp;回&nbsp;&nbsp;复&nbsp;&nbsp;</button></div></form></div></div></li><!-- 回复输入框 --><li class="pb-3 pt-3"><form method="post" th:action="@{|/comment/add/${post.id}|}"><div><input type="text" class="input-size" name="content" placeholder="请输入你的观点"/><input type="hidden" name="entityType" value="2"><input type="hidden" name="entityId" th:value="${cvo.comment.id}"></div><div class="text-right mt-2"><button type="submit" class="btn btn-primary btn-sm" onclick="#">&nbsp;&nbsp;回&nbsp;&nbsp;复&nbsp;&nbsp;</button></div></form></li></ul></div></li></ul><!-- 分页 --><nav class="mt-5" th:replace="index::pagination"><ul class="pagination justify-content-center"><li class="page-item"><a class="page-link" href="#">首页</a></li><li class="page-item disabled"><a class="page-link" href="#">上一页</a></li><li class="page-item active"><a class="page-link" href="#">1</a></li><li class="page-item"><a class="page-link" href="#">2</a></li><li class="page-item"><a class="page-link" href="#">3</a></li><li class="page-item"><a class="page-link" href="#">4</a></li><li class="page-item"><a class="page-link" href="#">5</a></li><li class="page-item"><a class="page-link" href="#">下一页</a></li><li class="page-item"><a class="page-link" href="#">末页</a></li></ul></nav></div><!-- 回帖输入 --><div class="container mt-3"><form class="replyform" method="post" th:action="@{|/comment/add/${post.id}|}"><p class="mt-3"><a name="replyform"></a><textarea placeholder="在这里畅所欲言你的看法吧!" name="content"></textarea><input type="hidden" name="entityType" value="1"><input type="hidden" name="entityId" th:value="${post.id}"></p><p class="text-right"><button type="submit" class="btn btn-primary btn-sm">&nbsp;&nbsp;回&nbsp;&nbsp;帖&nbsp;&nbsp;</button></p></form></div></div><!-- 尾部 --><footer class="bg-dark"><div class="container"><div class="row"><!-- 二维码 --><div class="col-4 qrcode"><img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" /></div><!-- 公司信息 --><div class="col-8 detail-info"><div class="row"><div class="col"><ul class="nav"><li class="nav-item"><a class="nav-link text-light" href="#">关于我们</a></li><li class="nav-item"><a class="nav-link text-light" href="#">加入我们</a></li><li class="nav-item"><a class="nav-link text-light" href="#">意见反馈</a></li><li class="nav-item"><a class="nav-link text-light" href="#">企业服务</a></li><li class="nav-item"><a class="nav-link text-light" href="#">联系我们</a></li><li class="nav-item"><a class="nav-link text-light" href="#">免责声明</a></li><li class="nav-item"><a class="nav-link text-light" href="#">友情链接</a></li></ul></div></div><div class="row"><div class="col"><ul class="nav btn-group-vertical company-info"><li class="nav-item text-white-50">公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司</li><li class="nav-item text-white-50">联系方式:010-60728802(电话)&nbsp;&nbsp;&nbsp;&nbsp;admin@nowcoder.com</li><li class="nav-item text-white-50">牛客科技©2018 All rights reserved</li><li class="nav-item text-white-50">京ICP备14055008号-4 &nbsp;&nbsp;&nbsp;&nbsp;<img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />京公网安备 11010502036488号</li></ul></div></div></div></div></div></footer>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script th:src="@{/js/global.js}"></script>
<script th:src="@{/js/discuss.js}"></script>
</body>
</html>

4.事务管理

事务是由N布数据库操作序列组成的逻辑执行单元,这些序列操作要么全执行,要么全放弃执行。
事务的特性(ACID):
原子性(Atomicity):事务是应用中不可再分的最小执行体。
一致性(Consistency):事务执行的结果,须使数据从一个一致性状态,变为另一个一致性状态。
隔离性(Isolation):各个事务的执行互不干扰,任何事务的内部操作对其他的事务都是隔离的。
持久性(Durability):事务一旦提交,对数据所做的任何改变都要记录到永久存储器中。

事务的隔离性是针对并发而言的,如果在多线程的环境下没有做事务隔离,每一个浏览器在访问服务器的时候,服务器就会创建一个线程去处理这个请求,在这个请求中如果要访问数据库,就可能会产生事务的操作,如果多个事务访问同一条数据,如果不做隔离性,就会出现一些问题。

常见的并发异常:
第一类丢失更新、第二类丢失更新
脏读、不可重复读、幻读

常见的隔离级别:
Read Uncommitted:读取未提交的数据
Read Committed:读取已提交的数据
Repeatable Read:可重复读
Serializable:串行化







实现机制
悲观锁(数据库)
共享锁(S锁)
事务A对某数据加了共享锁后,其他事务只能对该数据加共享锁,但不能加排他锁。
排他锁(X锁)
事务A对某数据加了排他锁后。其他事务对该数据既不能加共享锁,也不能加排他锁。
乐观锁(自定义)
版本号、时间戳等
在更新数据前,检查版本号是否发生变化。若变化则取消本次更新,否则就更新数据(版本号+1)。


有关事务的示例:

@Service
//@Scope("prototype") //被Spring容器管理的bean默认是单例的,只会实例化一次,如果想多个实例,需要用scope注解,加上prototype
public class AlphaService {@Autowiredprivate AlphaDao alphaDao;@Autowiredprivate UserMapper userMapper;@Autowiredprivate DiscussPostMapper discussPostMapper;public AlphaService() {System.out.println("实例化AlphaService");}@PostConstruct //方法会在构造器之后调用public void init() {System.out.println("初始化AlphaService");}@PreDestroy //在销毁对象之前调用,如果对象被销毁就没法调用了public void destroy() {System.out.println("销毁AlphaService");}public String find() {return alphaDao.select();}// REQUIRED: 支持当前事务(外部事务),如果不存在则创建新事务.// REQUIRES_NEW: 创建一个新事务,并且暂停当前事务(外部事务).// NESTED: 如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),否则就会REQUIRED一样// 第二个参数为事务的传播机制,业务方法A可能会调用业务方法B,业务方法A和B可能都会加上@Transactional注解,当A调用B的时候,解决B的事务以哪个为基准的问题@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)public Object save1() {//新增用户User user = new User();user.setUsername("alpha");user.setSalt(CommunityUtil.generateUUID().substring(0, 5));user.setPassword(CommunityUtil.md5("123" + user.getSalt()));user.setEmail("alpha@qq.com");user.setHeaderUrl("http://image.nowcoder.com/head/99t.png");user.setCreateTime(new Date());userMapper.insertUser(user);//新增帖子DiscussPost post = new DiscussPost();post.setUserId(user.getId());post.setTitle("Hello");post.setContent("新人报道!");post.setCreateTime(new Date());discussPostMapper.insertDiscussPost(post);Integer.valueOf("abc"); //报个错,是看否能回滚return "ok";}public Object save2() {transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);return transactionTemplate.execute(new TransactionCallback<Object>() {@Overridepublic Object doInTransaction(TransactionStatus status) {//新增用户User user = new User();user.setUsername("beta");user.setSalt(CommunityUtil.generateUUID().substring(0, 5));user.setPassword(CommunityUtil.md5("123" + user.getSalt()));user.setEmail("beta@qq.com");user.setHeaderUrl("http://image.nowcoder.com/head/999.png");user.setCreateTime(new Date());userMapper.insertUser(user);//新增帖子DiscussPost post = new DiscussPost();post.setUserId(user.getId());post.setTitle("你好");post.setContent("我是新人!");post.setCreateTime(new Date());discussPostMapper.insertDiscussPost(post);Integer.valueOf("abc");return "ok";}});}// 让该方法在多线程的环境下,被异步的调用@Asyncpublic void execute1() {logger.debug("execute1");}/*    @Scheduled(initialDelay = 10000, fixedRate = 1000)*/public void execute2() {logger.debug("execute2");}
}

5.显示评论

可以针对帖子评论,也可以回复帖子的评论,所以表中有entity_type和entity_id两个字段

entity包下新建Comment实体类

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;}@Overridepublic String toString() {return "Comment{" +"id=" + id +", userId=" + userId +", entityType=" + entityType +", entityId=" + entityId +", targetId=" + targetId +", content='" + content + '\'' +", status=" + status +", createTime=" + createTime +'}';}
}

dao层下新建CommentMapper,添加显示评论及数量的方法,使用@Mapper注解,并修改index.html

@Controller
@RequestMapping("/discuss")
public class DiscussPostController implements CommunityConstant {@Autowiredprivate DiscussPostService discussPostService;@Autowiredprivate HostHolder hostHolder; //获取当前用户@Autowiredprivate UserService userService;@Autowiredprivate 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);// 点赞数量long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, discussPostId);model.addAttribute("likeCount", likeCount);// 点赞状态int likeStatus = hostHolder.getUser() == null ? 0 :likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_POST, discussPostId);model.addAttribute("likeStatus", likeStatus);// 评论分页信息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) {// 评论VOMap<String, Object> commentVo = new HashMap<>();// 评论commentVo.put("comment", comment);// 作者commentVo.put("user", userService.findUserById(comment.getUserId()));// 点赞数量likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, comment.getId());commentVo.put("likeCount", likeCount);// 点赞状态likeStatus = hostHolder.getUser() == null ? 0 :likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, comment.getId());commentVo.put("likeStatus", likeStatus);// 回复列表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);// 点赞数量likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, reply.getId());replyVo.put("likeCount", likeCount);// 点赞状态likeStatus = hostHolder.getUser() == null ? 0 :likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, reply.getId());replyVo.put("likeStatus", likeStatus);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";}

6.添加评论(事务管理)

Dao层:增加评论数据,CommentMapper下新增一个添加评论的方法
更新评论数量,DiscussPostMapper中新增一个更新数量的方法
在对应的mapper.xml文件中书写sql语句

@Mapper
public interface CommentMapper {List<Comment> selectCommentsByEntity(int entityType, int entityId, int offset, int limit);int selectCountByEntity(int entityType, int entityId);int insertComment(Comment comment);
}

comment-mapper中书写sql语句

    <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>

DiscussPostMapper中新增一个更新数量的方法

@Mapper
public interface DiscussPostMapper {List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit, int orderMode); //orderMode为排序模式,如果为0,就按照原先的顺序来排,如果为1 ,就按照热度的顺序来排// @Param注解用于给参数取别名,// 如果只有一个参数,并且在<if>里使用,则必须加别名.int selectDiscussPostRows(@Param("userId") int userId);int insertDiscussPost(DiscussPost discussPost);DiscussPost selectDiscussPostById(int id);int updateCommentCount(int id, int commentCount);
}

discusspost-mapper.xml中书写sql语句

    <update id="updateCommentCount">update discuss_post set comment_count = #{commentCount} where id = #{id}</update>

Service层:
1)处理添加评论的功能:
CommentService中新增addComment方法,此方法包含两次DML操作,对其进行事务管理,保持其在一个事务范围之内,要么全成功,要么全不成功,这里使用声明式事务

在DiscussPostService先书写更新帖子数量,然后再CommentService里面书写帖子个数。

    public int updateCommentCount(int id, int commentCount) {return discussPostMapper.updateCommentCount(id, commentCount);}
@Service
public class CommentService implements CommunityConstant {@Autowiredprivate CommentMapper commentMapper;@Autowiredprivate SensitiveFilter sensitiveFilter;@Autowiredprivate DiscussPostService discussPostService;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);}@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;}
}

Controller层:
处理添加评论数据的请求:书写方法:addComment

@Controller
@RequestMapping("/comment")
public class CommentController implements CommunityConstant {@Autowiredprivate CommentService commentService;@Autowiredprivate 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;}
}

更新discuss-detail.html

7.私信列表


私信指的是我和某些人之间的会话,我和任何人之间的多次会话为一个会话,所以能看到的一定是不同的人,针对某个会话点进去,才能看到详细的私信,对应community.message表,表中包含的字段有from_id消息的发送人,to_id消息的接收者,content内容,状态status,0表示未读,1表示已读,2表示删除,create_time表示创建时间,conversation_id表示会话id

Entity包下新建一个实体类Message

public class Message {private int id;private int fromId;private int toId;private String conversationId;private String content;private int status;private Date createTime;public int getId() {return id;}public void setId(int id) {this.id = id;}public int getFromId() {return fromId;}public void setFromId(int fromId) {this.fromId = fromId;}public int getToId() {return toId;}public void setToId(int toId) {this.toId = toId;}public String getConversationId() {return conversationId;}public void setConversationId(String conversationId) {this.conversationId = conversationId;}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;}@Overridepublic String toString() {return "Message{" +"id=" + id +", fromId=" + fromId +", toId=" + toId +", conversationId='" + conversationId + '\'' +", content='" + content + '\'' +", status=" + status +", createTime=" + createTime +'}';}
}

dao层下添加MessageMapper类。

@Mapper
public interface MessageMapper {//查询当前用户的会话列表,针对每个会话只返回一条最新的私信List<Message> selectConversations(int userId, int offset, int limit);//查询当前用户的会话数量int selectConversationCount(int userId);//查询某个会话所包含的私信列表List<Message> selectLetters(String conversationId, int offset, int limit);//查询某个会话所包含的私信数量int selectLetterCount(String conversationId);//查询未读私信的数量int selectLetterUnreadCount(int userId, String converationId);
}

message-mapper.xml中书写sql语句

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nowcoder.community.dao.MessageMapper"><sql id="selectFields">id, from_id, to_id, conversation_id, content, status, create_time</sql><select id="selectConversations" resultType="Message">select<include refid="selectFields"></include>from messagewhere id in (select max(id) from messagewhere status != 2and from_id != 1and (from_id = #{userId} or to_id = #{userId})group by conversation_id)order by id desclimit #{offset}, #{limit}</select><select id="selectConversationCount" resultType="int">select count(m.maxid)from (select max(id) as maxidfrom messagewhere status != 2and from_id != 1and (from_id = #{userId}or to_id = #{userId})group by conversation_id) as m</select><select id="selectLetters" resultType="Message">select<include refid="selectFields"></include>from messagewhere status != 2and from_id != 1and conversation_id = #{conversationId}order by id desclimit #{offset}, #{limit}</select><select id="selectLetterCount" resultType="int">select count(id)from messagewhere status != 2and from_id != 1and conversation_id = #{conversationId}</select><select id="selectLetterUnreadCount" resultType="int">select count(id)from messagewhere status = 0and from_id != 1and to_id = #{userId}<if test="conversationId!=null">and conversation_id = #{conversationId}</if></select>
</mapper>

service层,添加MessageService类

@Service
public class MessageService {@Autowiredprivate MessageMapper messageMapper;public List<Message> findConversations(int userId, int offset, int limit) {return messageMapper.selectConversations(userId, offset, limit);}public int findConversationCount(int userId) {return messageMapper.selectConversationCount(userId);}public List<Message> findLetters(String conversationId, int offset, int limit) {return messageMapper.selectLetters(conversationId, offset, limit);}public int findLetterCount(String conversationId) {return messageMapper.selectLetterCount(conversationId);}public int findLetterUnreadCount(int userId, String conversationId) {return messageMapper.selectLetterUnreadCount(userId, conversationId);}
}

Controller层,添加MessageController类,并修改letter.html


```java
@Controller
public class MessageController {@Autowiredprivate MessageService messageService;@Autowiredprivate HostHolder hostHolder;// 私信列表@RequestMapping(path = "/letter/list", method = RequestMethod.GET)public String getLetterList(Model model, Page page) {//        Integer.valueOf("abc");User user = hostHolder.getUser();// 分页信息page.setLimit(5);page.setPath("/letter/list");page.setRows(messageService.findConversationCount(user.getId()));//会话列表List<Message> conversationList = messageService.findConversations(user.getId(), page.getOffset(), page.getLimit());List<Map<String, Object>> conversations = new ArrayList<>();if (conversations != null) {for (Message message : conversationList) {Map<String, Object> map = new HashMap<>();map.put("conversation", message);map.put("letterCount", messageService.findLetterCount(message.getConversationId()));map.put("unreadCount", messageService.findLetterUnreadCount(user.getId(), message.getConversationId()));int targetId = user.getId() == message.getFromId() ? message.getToId() : message.getFromId();map.put("target", userService.findUserById(targetId));conversations.add(map);}}model.addAttribute("conversations", conversations);//查询未读消息数量int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null);model.addAttribute("LetterUnreadCount", letterUnreadCount);int noticeUnreadCount = messageService.findNoticeUnreadCount(user.getId(), null);model.addAttribute("noticeUnreadCount", noticeUnreadCount);return "/site/letter";}@RequestMapping(path = "/letter/detail/{conversationId}", method = RequestMethod.GET)public String getLetterDetail(@PathVariable("conversationId") String conversationId, Page page, Model model) {//分页信息page.setLimit(5);page.setPath("/letter/detail/" + conversationId);page.setRows(messageService.findLetterCount(conversationId));//私信列表List<Message> letterList = messageService.findLetters(conversationId, page.getOffset(), page.getLimit());List<Map<String, Object>> letters = new ArrayList<>();if (letterList != null) {for (Message message : letterList) {Map<String, Object> map = new HashMap<>();map.put("letter", message);map.put("fromUser", userService.findUserById(message.getFromId()));letters.add(map);}}model.addAttribute("letters", letters);//私信目标model.addAttribute("target", getLetterTarget(conversationId));//设置已读List<Integer> ids = getLetterIds(letterList);if (!ids.isEmpty()) {messageService.readMessage(ids);}return "/site/letter-detail";}private User getLetterTarget(String conversationId) {String[] ids = conversationId.split("_");int id0 = Integer.parseInt(ids[0]);int id1 = Integer.parseInt(ids[1]);if (hostHolder.getUser().getId() == id0) {return userService.findUserById(id1);} else {return userService.findUserById(id0);}}
}

修改letter-detail.html。

8.发送私信


发私信是指在消息页面,点击发送私信,在弹出框里填写发给谁,以及发送的内容,用异步请求的发生发送给服务器,服务器返回结果以后,判断成功还是失败,最终给一个提示就好了,除此之外,我们在进入详情页面的时候,点击给Ta私信,要自动带上当前对话对方的名字,填写内容,点击发送就可以了。

数据访问层:MessageMapper中新增发送私信及修改消息状态的方法(设置为已读)。

    //新增消息int insertMessage(Message message);//修改消息的状态int updateStatus(List<Integer> ids, int status);

message-mapper.xml中实现sql语句

    <sql id="insertFields">from_id, to_id, conversation_id, content, status, create_time</sql><insert id="insertMessage" parameterType="Message" keyProperty="id">insert into message(<include refid="insertFields"></include>)values (#{fromId},#{toId},#{conversationId},#{content},#{status},#{createTime})</insert><update id="updateStatus">update message set status = #{status}where id in<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach></update>

业务层:MessageService中新增addMessage方法和readMessage方法

@Service
public class MessageService {@Autowiredprivate MessageMapper messageMapper;@Autowiredprivate SensitiveFilter sensitiveFilter;public List<Message> findConversations(int userId, int offset, int limit) {return messageMapper.selectConversations(userId, offset, limit);}public int findConversationCount(int userId) {return messageMapper.selectConversationCount(userId);}public List<Message> findLetters(String conversationId, int offset, int limit) {return messageMapper.selectLetters(conversationId, offset, limit);}public int findLetterCount(String conversationId) {return messageMapper.selectLetterCount(conversationId);}public int findLetterUnreadCount(int userId, String conversationId) {return messageMapper.selectLetterUnreadCount(userId, conversationId);}public int addMessage(Message message) {message.setContent(HtmlUtils.htmlEscape(message.getContent()));message.setContent(sensitiveFilter.filter(message.getContent()));return messageMapper.insertMessage(message);}public int readMessage(List<Integer> ids) {return messageMapper.updateStatus(ids, 1);}
}

userService层增加一个根据用户名查找的方法

    public User findUserByName(String username) {return userMapper.selectByName(username);}

表现层,messageController新增sendLetter方法,处理用户点击发送私信的请求
接收前端传来的用户名和内容,拼接conversationId的时候把小的拼在前面

@RequestMapping(path = "/letter/send",method = RequestMethod.POST)@ResponseBodypublic String sendLetter(String toName,String content){User target=userService.findUserByName(toName);if (target==null){return CommunityUtil.getJSONString(1,"目标用户不存在");}Message message=new Message();message.setFromId(hostHolder.getUser().getId());message.setToId(target.getId());if(message.getFromId()<message.getToId()){message.setConversationId(message.getFromId()+"_"+message.getToId());}else {message.setConversationId(message.getToId()+"_"+message.getFromId());}message.setContent(content);message.setCreateTime(new Date());messageService.addMessage(message);return CommunityUtil.getJSONString(0);}

然后实现把未读消息改成已读。
在之前写的加载消息列表的方法里补充一下。首先写一个方法获取所有未读消息的id。

private List<Integer> getLetterIds(List<Message> letterList){List<Integer> ids=new ArrayList<>();if(letterList!=null){for (Message message:letterList){if(hostHolder.getUser().getId()==message.getToId() && message.getStatus()==0){ids.add(message.getId());}}}return ids;}

然后在getLetterDetail方法里加上

        //设置已读List<Integer> ids =getLetterIds(letterList);if (!ids.isEmpty()){messageService.readMessage(ids);}

修改letter.html、letter-detail.html及letter.js

9.统一处理异常


在controller层中添加advice包,添加ExceptionAdvice类 HomeController中添加getErrorPage方法

@ControllerAdvice(annotations = Controller.class)
public class ExceptionAdvice {private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class);@ExceptionHandler({Exception.class})public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException {logger.error("服务器发生异常:" + e.getMessage());for (StackTraceElement element : e.getStackTrace()) {logger.error(element.toString());}String xRequestedWith = request.getHeader("x-requested-with");if ("XMLHttpRequest".equals(xRequestedWith)) {response.setContentType("application/plain;charset=utf-8");PrintWriter writer = response.getWriter();writer.write(CommunityUtil.getJSONString(1, "服务器异常"));} else {response.sendRedirect(request.getContextPath() + "/error");}}
}

10.统一记录日志


现在已经开发了几个模块,将来系统可能越来越完善,模块会越来越多,就会有越来越多的Service,就比如说现在会有一个需求,我想对所有的Service记录日志,既然通知和拦截器解决不了问题,那么使用传统的方式也可以,就是把记录日志的代码封装到组件里,然后在不同的Service方法里去调用就可以了,如果将来有一天系统需求发生了变化,比如说我想在后面记录日志,而不是在前面记录日志,那么这种情况下,有多少Service就要改多少次系统需求,那么这个时候就可以使用AOP来代替传统的OOP。




在controller层中添加aspect包,创建ServiceLogAspect类

@Component
@Aspect
public class ServiceLogAspect {private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class);@Pointcut("execution(* com.nowcoder.community.service.*.*(..))")public void pointcut() {}@Before("pointcut()")public void before(JoinPoint joinPoint) {// 用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()].ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (attributes == null) {return;}HttpServletRequest request = attributes.getRequest();String ip = request.getRemoteHost();String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target));}
}

仿牛客网社区开发--核心功能模块相关推荐

  1. 仿牛客网社区项目 全栈总结

    学习仿牛客网社区项目 代码&资源 各章节总结 第一章 第二章 第三章 第四章 第五章 第六章 第七章 第八章 争取让每个知识点都有链接可点 项目总结 网站架构图 常见面试题 MySQL Red ...

  2. 从零开始—仿牛客网讨论社区项目(一)

    主要技术架构: SpringBoot Spring SpringMVC MyBatis Redis Kakfa Elasticsearch Spring Security Spring Actator ...

  3. 从零开始—仿牛客网讨论社区项目(六)

    主要技术架构: SpringBoot Spring SpringMVC MyBatis Redis Kakfa Elasticsearch Spring Security Spring Actator ...

  4. 仿牛客网讨论社区项目—优化网站性能

    性能优化: 1.考虑加入缓存优化 优化热门帖子列表 GitHub中搜索caffeine 在Maven Repository搜索caffeine配置文件,在resources文件包内的pom.xml文件 ...

  5. 2021-04-10 仿牛客网第六章

    一.Elasticsearch入门 仿牛客网 内容部分引用至 https://blog.csdn.net/weixin_44406146 目录 一.Elasticsearch入门 Elasticsea ...

  6. Java牛客网社区项目——知识点面试题

    Java牛客网社区项目--知识点&面试题 持续更新中(ง •̀_•́)ง 文章目录 Java牛客网社区项目--知识点&面试题 请简要介绍一下你的项目? 什么是Spring框架? 对Sp ...

  7. 云服务器上部署仿牛客网项目

    云服务器上部署仿牛客网项目 安装JRE 安装Maven 安装MySQL 给mysql导入数据 安装Redis 安装kafka 安装ElasticSearch Wkhtmltopdf 安装tomcat ...

  8. 仿牛客网项目第二章:开发社区登录模块(详细步骤和思路)

    目录 1. 发送邮件 1.0 三步走 1.1 邮箱设置 1.2 Spring Email 1.3 模板引擎 1.4 发送邮件的过程 1.5 检验发送邮件的过程 2. 开发注册功能 2.0 注册功能的步 ...

  9. 仿牛客网项目第五,六章:异步消息系统和分布式搜索引擎(详细步骤和思路)

    目录 1. Kafka:构建TB级异步消息系统 1.0 同步/异步消息的区别 1.1 项目的目的 1. 2 阻塞队列实现异步消息系统 1.4 Kafka入门 1.5 Spring整合Kafka 1.6 ...

最新文章

  1. 力扣(LeetCode)刷题,简单题(第23期)
  2. linux 系统lv扩展_linux 扩展lv
  3. ng: Can't bind to 'ngModel' since it isn't a known property of 'input'. - Angular 6
  4. “玩转课堂”基本构想
  5. 解决SVN提交代码时的错误:“Could not execute PROPPATCH”
  6. Oracle数户库、表导入导出
  7. 51CTO的企业文化——水文化
  8. 冰点还原精灵是怎么用的
  9. 几个多序列比对软件:Muscle,ClustalW和T-coffee的简单比较
  10. Python爬虫 使用Selenium爬取腾讯招聘信息
  11. 7月26日 select单表查询基础语句
  12. [错误分析][Error]no match for ‘operator<<‘无匹配的左移运算符
  13. H5音乐播放器(包含源码与示例)
  14. Invariance Matters: Exemplar Memory for Domain Adaptive Person Re-identification
  15. 用html实现满屋花的网页
  16. Facebook投放广告总被拒?教你搞定FB广告投放
  17. 谷歌创始人拉里·佩奇和谢尔盖·布林发表了论文 The Pagerank Citation Rank :Bringing Order to the Web...
  18. 进qq魔域显示无法连接服务器,为什么qq魔域更新时提示连接不到服务器
  19. SMART目标定义原则
  20. 一篇入门物联网大数据:TDengine时序数据库

热门文章

  1. ef增删改查的四种方式
  2. 甘书计算机,甘文生,暨南大学
  3. 线程通信中wait和sleep区别
  4. mysql db 100万行 大小_插入100万行数据
  5. 基于全局信息的人脸识别总结
  6. java/php/net/python校园课程教学资源共享设计
  7. 液压控制系列之活塞位置测量(带原点标定功能)
  8. 见山还是山,见水还是水,见程序还是程序
  9. 年底爆款 外星人m15 r6怎么样?
  10. Java代码实现解压文件包和压缩文件的工具类