当前位置:   article > 正文

牛客网项目——项目开发(四):发布帖子,帖子详情,显示评论,添加评论_牛客论坛项目发布帖子

牛客论坛项目发布帖子

1. 过滤敏感词

在这里插入图片描述

1.1 前缀树

在这里插入图片描述

1.2 代码实现

在resources下新建sensitive-word.txt,并在util下实现 SensitiveFilter

  1. 定义前缀树结构
	// 前缀树
    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);
        }

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  1. 敏感词添加到前缀树方法
 	// 将一个敏感词添加到前缀树中
    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);
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  1. 初始化方法
	@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());
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  1. 过滤敏感词方法
	/**
     * 过滤敏感词
     *
     * @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();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

2. 发布帖子

在这里插入图片描述

2.1 编写工具类传输json数据

  1. 导入依赖fastjson
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.58</version>
		</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 在CommunityUtil下添加方法
    ① 服务器给浏览器返回编码(code),比如0代表什么意思,1代表什么意思
    ② 服务器给浏览器返回提示信息(msg),成果或失败
    ③ 还可能返回业务数据(map)
    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();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

④ 参数可能不是都有,重载

	public static String getJSONString(int code, String msg) {
        return getJSONString(code, msg, null);
    }

    public static String getJSONString(int code) {
        return getJSONString(code, null, null);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

⑤ 写个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));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这里插入图片描述

2.2 AJAX示例

  1. 在AlphaController里编写示例方法 testAjax
    // 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, "操作成功!");
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 编写一个静态网页测试
    ① 引入jquery
    ② 实现post请求
<!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);
                    console.log(typeof(data));
                    console.log(data.code);
                    console.log(data.msg);
                }
            );
        }
    </script>
</body>
</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

在这里插入图片描述
点击发送后
在这里插入图片描述

2.3 实现发布帖子功能

2.3.1 数据访问层

  1. 在DiscussPostMapper中增加插入帖子数据方法
int insertDiscussPost(DiscussPost discussPost);
  • 1
  1. 修改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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.3.2 业务层

  1. 在DiscussPostService添加方法addDiscussPost
    ① 判断内容空
    ② 发布的文字进行转义,包括标签和内容
    ③ 过滤敏感词
@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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

2.3.3 视图层

  1. 新建DiscussPostController,实现addDiscussPost
    ① 判断当前是否登录,没登陆返回code:403
    ② 创建post对象。存入信息,发布成功返回code:0
@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, "发布成功!");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  1. 处理页面
    ① 处理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);
	    }
	);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

② 不登陆不显示发布的按钮 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>
  • 1

3. 帖子详情

在这里插入图片描述

3.1 数据访问层

  1. 在DiscussPostMapper中添加查询帖子方法
DiscussPost selectDiscussPostById(int id);
  • 1
  1. 在discusspost-mapper.xml 下添加方法
<select id="selectDiscussPostById" resultType="DiscussPost">
    select <include refid="selectFields"></include>
    from discuss_post
    where id = #{id}
</select>
  • 1
  • 2
  • 3
  • 4
  • 5

3.2 业务层

  1. 在DiscussPostService添加方法
public DiscussPost findDiscussPostById(int id) {
    return discussPostMapper.selectDiscussPostById(id);
}
  • 1
  • 2
  • 3

3.3 表现层

在DisscussPostController下添加方法getDiscussPost

  1. 声明访问路径 path = "/detail/{discussPostId},请求方式 method = RequestMethod.GET
  2. 需要返回模板,不要写responsebody
  3. 方法中加参数接受变量,需要用到路径中的 “discussPostId”,查询的结果通过model携带数据
  4. 调用discussPostService.findDiscussPostById得到 DiscussPost 的id
  5. 用户id要处理。法一在mapper中使用关联查询,法二先查帖子数据,得到id再查用户名。前者效率肯定高,但是查询方法有些冗余耦合,不是所有业务需要。法二简单方便,后面通过redis提高效率。
  6. 评论的内容后面再写
@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";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3.4 前端页面

  1. 将每个帖子标题改成路径 index.html
<a th:href="@{|/discuss/detail/${map.post.id}|}" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</a>
  • 1
  1. 修改discuss-detail.html
    ① 声明thymeleaf模板
    ② 静态资源路径整理
    ③ 修改尾部js
    ④ 复用header
    ⑤ 标题部分,标题标签动态替换,使用utext
<span th:utext="${post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</span>
  • 1

⑥ 作者部分:头像和姓名

<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>
  • 1
  • 2
  • 3

⑦发帖时间

<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>
</div>
  • 1
  • 2
  • 3

⑧ 正文部分

<div class="mt-4 mb-3 content" th:utext="${post.content}">
	金三银四的金三已经到了,你还沉浸在过年的喜悦中吗?
	如果是,那我要让你清醒一下了:目前大部分公司已经开启了内推,正式网申也将在3月份陆续开始,金三银四,春招的求职黄金时期已经来啦!!!
	再不准备,作为19应届生的你可能就找不到工作了。。。作为20届实习生的你可能就找不到实习了。。。
	现阶段时间紧,任务重,能做到短时间内快速提升的也就只有算法了,
	那么算法要怎么复习?重点在哪里?常见笔试面试算法题型和解题思路以及最优代码是怎样的?
	跟左程云老师学算法,不仅能解决以上所有问题,还能在短时间内得到最大程度的提升!!!
</div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

5. 显示评论

在这里插入图片描述

5.1 数据访问层

5.1.1 数据库中的评论表

entity_type:评论目标的类别(如:1代表帖子,2代表评论等)
entity_id:具体目标(帖子id 228,或者帖子 id 229)
target_id:指向某个人的评论
在这里插入图片描述

5.1.2 entity下实现实体类 Comment

属性,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 +
                '}';
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

5.1.3 dao下实现接口 CommentMapper

  1. 查询评论,根据实体查询
  2. 返回评论的条目数
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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

5.1.4 mapper下实现comment-mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

5.2 业务层

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

5.3 表现层

5.3.1 补充DiscussPostController

  1. 注入CommentService
  2. 借助分页,参数添加page
  3. 设置评论分页信息,每页显示5条,设置路径,一共有多少评论数据
    page.setLimit(5); page.setPath("/discuss/detail/" + discussPostId); page.setRows(post.getCommentCount());
  4. 得到当前帖子的所有评论 List<Comment> commentList
  5. 遍历评论集合,创建评论VO列表,并放入map封装数据
    1. 放入评论内容 commentVo.put("comment", comment);
    2. 放入作者信息 commentVo.put("user", userService.findUserById(comment.getUserId()));
    3. 放入回复列表 List<Comment> replyList
    4. 创建回复的VO列表,还是map List<Map<String, Object>> replyVoList
      1. 放入回复内容 replyVo.put("reply", reply);
      2. 放入恢复者名字 replyVo.put("user", userService.findUserById(reply.getUserId()));
      3. 放入回复的目标(先判0)replyVo.put("target", target);
      4. replyVoList 放入commentVo commentVo.put("replys", replyVoList);
    5. 放入回复的数量 commentVo.put("replyCount", replyCount);
    6. commentVo 放入commentVoList commentVoList.add(commentVo);
  6. 最后comment翻入model 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";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

5.3.2 处理index.html

回帖数量

<li class="d-inline ml-2">回帖 <span th:text="${map.post.commentCount}">7</span></li>
  • 1

5.3.3 处理discuss-detail.html

  1. 回帖数量
<h6><b class="square"></b> <i th:text="${post.commentCount}">30</i>条回帖</h6>
  • 1
  1. 处理帖子展现,循环语句
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="cvo:${comments}">
  • 1
  1. 作者头像
<img th:src="${cvo.user.headerUrl}" class="align-self-start mr-4 rounded-circle user-header" alt="用户头像" >
  • 1
  1. 用户名字
<span class="font-size-12 text-success" th:utext="${cvo.user.username}">掉脑袋切切</span>
  • 1
  1. 一楼二楼
<span class="badge badge-secondary float-right floor">
	<i th:text="${page.offset + cvoStat.count}">1</i>#
</span>
  • 1
  • 2
  • 3
  1. 处理回复,遍历
<li class="pb-3 pt-3 mb-3 border-bottom" th:each="rvo:${cvo.replys}">
  • 1
  1. 无目标回复,就是显示用户名
<span th:if="${rvo.target==null}">
	<b class="text-info" th:text="${rvo.user.username}">寒江雪</b>:&nbsp;&nbsp;
</span>
  • 1
  • 2
  • 3
  1. 有目标回复,显示谁回复了谁
<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>
  • 1
  • 2
  • 3
  • 4
  1. 回复时间
<span th:text="${#dates.format(rvo.reply.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</span>
  • 1
  1. 点击回复时显示回复框
<li class="d-inline ml-2"><a th:href="|#huifu-${rvoStat.count}|" data-toggle="collapse" class="text-primary">回复</a></li>
  • 1
  1. 分页逻辑复用首页分页逻辑
    首页的
<nav class="mt-5" th:if="${page.rows>0}" th:fragment="pagination">
	……
</nav>
  • 1
  • 2
  • 3

当前页面的

<nav class="mt-5" th:replace="index::pagination">
	……
</nav>
  • 1
  • 2
  • 3

6. 添加评论

在这里插入图片描述

  1. 在 CommentMapper 增加方法 insertComment
int insertComment(Comment comment);
  • 1
  1. 在comment-mapper.xml 中实现语句
<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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

6.1 数据访问层

  1. 在 CommentMapper 增加方法 insertComment
int insertComment(Comment comment);
  • 1
  1. 在comment-mapper.xml 中实现语句
<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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 更新帖子评论数量,在DiscussPostMapper增加updateCommentCount
int updateCommentCount(int id, int commentCount);
  • 1
  1. 更新帖子评论数量,更新对应的xml文件
<update id="updateCommentCount">
    update discuss_post set comment_count = #{commentCount} where id = #{id}
</update>
  • 1
  • 2
  • 3

6.2 业务层

  1. 在DisscusPostService中添加方法updateCommentCount
public int updateCommentCount(int id, int commentCount) {
    return discussPostMapper.updateCommentCount(id, commentCount);
}
  • 1
  • 2
  • 3
  1. 在CommentService中添加addComment,事务隔离
@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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

6.3 表现层

6.3.1 新建CommentController

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

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

6.3.2 处理descuss-detail.xml

  1. 最底下针对帖子的评论
    ① 提交表单
<form class="replyform" method="post" th:action="@{|/comment/add/${post.id}|}">
  • 1

② 处理文本域,取名为content和实体属性对应

<textarea placeholder="在这里畅所欲言你的看法吧!" name="content"></textarea>
  • 1

③ 两个隐含条件entityType和entityId

<input type="hidden" name="entityType" value="1">
<input type="hidden" name="entityId" th:value="${post.id}">
  • 1
  • 2
  1. 给一个评论进行回复(不针对某个人)
    ① 完成表单
<form method="post" th:action="@{|/comment/add/${post.id}|}">
	……
</form>
  • 1
  • 2
  • 3

② 处理文本域,取名为content和实体属性对应

<input type="text" class="input-size" name="content" placeholder="请输入你的观点"/>
  • 1

③ 两个隐含条件entityType和entityId

<input type="hidden" name="entityType" value="2">
<input type="hidden" name="entityId" th:value="${cvo.comment.id}">
  • 1
  • 2
  1. 给一个评论进行回复(对某个人)
    ① 完成表单
<form method="post" th:action="@{|/comment/add/${post.id}|}">
	……
</form>
  • 1
  • 2
  • 3

② 处理文本域,取名为content和实体属性对应

<input type="text" class="input-size" name="content" th:placeholder="|回复${rvo.user.username}|"/>
  • 1

③ 两个隐含条件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}">
  • 1
  • 2
  • 3

④ 显示回复给哪个人

<input type="text" class="input-size" name="content" th:placeholder="|回复${rvo.user.username}|"/>
  • 1
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/572906
推荐阅读
相关标签
  

闽ICP备14008679号