评论回复功能设计
2019-08-29 本文已影响0人
盼旺
以评论为主的显示模式,类似于下面的CSDN的评论显示模式
最终效果图
效果图将评论拆分为评论表和回复表,评论挂在各种主题下面,而回复挂在评论下面
我是采用的Jpa建表,所以只需要把实体对象写好就行
回复表
先上项目结构图
项目结构图说一下我的思路
1.建立俩张表,回复表回复的id分为回复评论还是回复,用一个int标志判断。
2.想像树状那样显示出来,这里就采取链表的形式存储,一条评论下可能有多人回复,所以存储下一个对象我们使用List来存储,开始的List初始化为private List<ReplayNode> replays = new ArrayList<>();
不然replays.add()
的时候会报空指针.
3.因为评论和回复是分开建表的,所以我们还需要单独设置一个评论节点,分别对应上面项目结构图的TopicNode
和ReplayNode
4.插入链表和遍历链表都是采用递归方式,有更好的方式欢迎指教。
5.thymeleaf写递归方式和java一样的思路,就是改变了语法而已
每个文件源码和注释
Topic
package com.wg.springdemo.model;
import javax.persistence.*;
@Entity
@Table(name = "topic")
public class Topic {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long tid;//主键ID
private Long uid;//用户ID
@Column(length = 50)
private String uname;//用户名
@Column(length = 255)
private String uheadurl;//用户头像
@Column(length = 800)
private String tcontent;//评论内容
@Column(length = 20)
private String ttime;//评论时间
public Topic() {
}
public Topic(Long uid, String uname, String uheadurl, String tcontent, String ttime) {
this.uid = uid;
this.uname = uname;
this.uheadurl = uheadurl;
this.tcontent = tcontent;
this.ttime = ttime;
}
public Long getTid() {
return tid;
}
public void setTid(Long tid) {
this.tid = tid;
}
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getUheadurl() {
return uheadurl;
}
public void setUheadurl(String uheadurl) {
this.uheadurl = uheadurl;
}
public String getTcontent() {
return tcontent;
}
public void setTcontent(String tcontent) {
this.tcontent = tcontent;
}
public String getTtime() {
return ttime;
}
public void setTtime(String ttime) {
this.ttime = ttime;
}
@Override
public String toString() {
return "Topic{" +
"tid=" + tid +
", uid=" + uid +
", uname='" + uname + '\'' +
", uheadurl='" + uheadurl + '\'' +
", tcontent='" + tcontent + '\'' +
", ttime='" + ttime + '\'' +
'}';
}
}
Replay
package com.wg.springdemo.model;
import javax.persistence.*;
@Entity
@Table(name = "replay")
public class Replay {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long rid;
private Long torrid;//评论id或者回复id 就是有可能是回复评论,也可能是给回复的回复
private int torr;//用来判断上面的哪种情况:1表示回复评论 0表示回复回复
private Long uid;//用户ID
private Long touid;//目标用户id 给谁回复
@Column(length = 50)
private String uname;//用户名
@Column(length = 50)
private String touname;//目标用户名
@Column(length = 255)
private String uheadurl;//用户头像
@Column(length = 800)
private String rcontent;//回复内容
@Column(length = 20)
private String rtime;//回复时间
public Replay() {
}
public Replay(Long torrid, int torr, Long uid, Long touid, String uname, String touname, String uheadurl, String rcontent, String rtime) {
this.torrid = torrid;
this.torr = torr;
this.uid = uid;
this.touid = touid;
this.uname = uname;
this.touname = touname;
this.uheadurl = uheadurl;
this.rcontent = rcontent;
this.rtime = rtime;
}
public Long getRid() {
return rid;
}
public void setRid(Long rid) {
this.rid = rid;
}
public Long getTorrid() {
return torrid;
}
public void setTorrid(Long torrid) {
this.torrid = torrid;
}
public int getTorr() {
return torr;
}
public void setTorr(int torr) {
this.torr = torr;
}
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public Long getTouid() {
return touid;
}
public void setTouid(Long touid) {
this.touid = touid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getTouname() {
return touname;
}
public void setTouname(String touname) {
this.touname = touname;
}
public String getUheadurl() {
return uheadurl;
}
public void setUheadurl(String uheadurl) {
this.uheadurl = uheadurl;
}
public String getRcontent() {
return rcontent;
}
public void setRcontent(String rcontent) {
this.rcontent = rcontent;
}
public String getRtime() {
return rtime;
}
public void setRtime(String rtime) {
this.rtime = rtime;
}
@Override
public String toString() {
return "Replay{" +
"rid=" + rid +
", torrid=" + torrid +
", torr=" + torr +
", uid=" + uid +
", touid=" + touid +
", uname='" + uname + '\'' +
", touname='" + touname + '\'' +
", uheadurl='" + uheadurl + '\'' +
", rcontent='" + rcontent + '\'' +
", rtime='" + rtime + '\'' +
'}';
}
}
TopicDao
package com.wg.springdemo.dao;
import com.wg.springdemo.model.Topic;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TopicDao extends JpaRepository<Topic,Long> {
}
ReplayDao
package com.wg.springdemo.dao;
import com.wg.springdemo.model.Replay;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ReplayDao extends JpaRepository<Replay,Long> {
//找到所有对某评论的回复 或者对 某回复 的所有回复
List<Replay> findByTorridAndTorr(Long torrid,int tr);
}
TopicService
package com.wg.springdemo.service;
import com.wg.springdemo.dao.TopicDao;
import com.wg.springdemo.model.Topic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class TopicService {
@Autowired
TopicDao topicDao;
//存入一条评论
public Topic InsertOneTopic(Topic t){
return topicDao.save(t);
}
//查找所有评论
public List<Topic> FindAllTopic(){
return topicDao.findAll();
}
}
ReplayService
package com.wg.springdemo.service;
import com.wg.springdemo.dao.ReplayDao;
import com.wg.springdemo.model.Replay;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ReplayService {
@Autowired
ReplayDao replayDao;
//插入一条回复
public Replay InsertOneReplay(Replay r){
return replayDao.save(r);
}
//找到对某个评论的所有回复
public List<Replay> FindAllByTid(Long tid){
return replayDao.findByTorridAndTorr(tid,1);
}
//找到对某个回复的所有回复
public List<Replay> FindAllByRid(Long rid){
return replayDao.findByTorridAndTorr(rid,0);
}
}
TopicNode
package com.wg.springdemo.util;
import com.wg.springdemo.model.Replay;
import com.wg.springdemo.model.Topic;
import java.util.ArrayList;
import java.util.List;
public class TopicNode {
private Topic topic;
private List<ReplayNode> replays = new ArrayList<>();
public TopicNode() {
}
public Topic getTopic() {
return topic;
}
public void setTopic(Topic topic) {
this.topic = topic;
}
public List<ReplayNode> getReplays() {
return replays;
}
public void setReplays(List<ReplayNode> replays) {
this.replays = replays;
}
}
ReplayNode
package com.wg.springdemo.util;
import com.wg.springdemo.model.Replay;
import java.util.ArrayList;
import java.util.List;
public class ReplayNode {
private Replay replay;
private List<ReplayNode> listreplay = new ArrayList<>();
public ReplayNode() {
}
public Replay getReplay() {
return replay;
}
public void setReplay(Replay replay) {
this.replay = replay;
}
public List<ReplayNode> getListreplay() {
return listreplay;
}
public void setListreplay(List<ReplayNode> listreplay) {
this.listreplay = listreplay;
}
}
添加测试数据
package com.wg.springdemo;
import com.wg.springdemo.model.Replay;
import com.wg.springdemo.model.Topic;
import com.wg.springdemo.service.ReplayService;
import com.wg.springdemo.service.TopicService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringdemoApplicationTests {
@Autowired
ReplayService replayService;
@Autowired
TopicService topicService;
@Test
public void contextLoads() {
topicService.InsertOneTopic(new Topic( 1L,"小红","1.jpg","这是第一个评论","2018-01-02"));
topicService.InsertOneTopic(new Topic( 2L,"小黄","1.jpg","这是第二个评论","2018-01-02"));
topicService.InsertOneTopic(new Topic( 3L,"小绿","1.jpg","这是第三个评论","2018-01-02"));
topicService.InsertOneTopic(new Topic( 4L,"小黑","1.jpg","这是第四个评论","2018-01-02"));
topicService.InsertOneTopic(new Topic( 1L,"小红","1.jpg","这是第五个评论","2018-01-02"));
replayService.InsertOneReplay(new Replay(1L,1,2L,1L,"小黄","小红","1.jpg","这是第一条回复","2018-01-02"));
replayService.InsertOneReplay(new Replay(1L,1,3L,1L,"小绿","小红","1.jpg","这是第二条回复","2018-01-02"));
replayService.InsertOneReplay(new Replay(1L,0,4L,2L,"小黑","小黄","1.jpg","这是第一.一条回复","2018-01-02"));
replayService.InsertOneReplay(new Replay(3L,0,2L,4L,"小黄","小黑","1.jpg","这是第一.一.一条回复","2018-01-02"));
replayService.InsertOneReplay(new Replay(4L,0,4L,2L,"小黑","小黄","1.jpg","这是第一.一.一.一条回复","2018-01-02"));
replayService.InsertOneReplay(new Replay(4L,0,1L,1L,"小红","小黄","1.jpg","这也是第一.一.一.一条回复","2018-01-02"));
replayService.InsertOneReplay(new Replay(3L,1,2L,3L,"小黄","小绿","1.jpg","这是第三条回复","2018-01-02"));
}
}
HomeController
package com.wg.springdemo.controller;
import com.wg.springdemo.model.Replay;
import com.wg.springdemo.model.Topic;
import com.wg.springdemo.service.ReplayService;
import com.wg.springdemo.service.TopicService;
import com.wg.springdemo.util.ReplayNode;
import com.wg.springdemo.util.TopicNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.ArrayList;
import java.util.List;
@Controller
public class HomeController {
@Autowired
ReplayService replayService;
@Autowired
TopicService topicService;
//插入链表 参数分别代表需要待插入的节点list 和这些节点的父亲是谁
public boolean AddReplayNode(List<Replay> relists,ReplayNode freplay){
//为空就直接返回
if(relists.size()==0)return false;
//挨个遍历list中的节点信息,然后如果节点还有孩子就继续递归
for(Replay re:relists){
ReplayNode newreplaynode = new ReplayNode();
newreplaynode.setReplay(re);
freplay.getListreplay().add(newreplaynode);
List<Replay> replayList = new ArrayList<>();
replayList = replayService.FindAllByRid(re.getRid());
//有孩子就继续递归,有没有孩子这里是统一进入递归才判断,也可以来个if else
AddReplayNode(replayList,newreplaynode);
}
return false;
}
//展示出来 参数表示需要展示的节点list
public void ShowReplayNodes(List<ReplayNode> replayNodes){
if(replayNodes.size()==0)return;
for(ReplayNode temp: replayNodes){
System.out.println(temp.getReplay().toString());
ShowReplayNodes(temp.getListreplay());
}
}
@RequestMapping(value = "/", method = RequestMethod.GET)
public String getHomePostsByPage(Model model) {
List<Topic> topics = new ArrayList<>();
topics = topicService.FindAllTopic();//得到所有评论
List<TopicNode> topicNodes = new ArrayList<>();//装下所有评论的List
for(Topic temp : topics){
TopicNode topicNode = new TopicNode();
topicNode.setTopic(temp);//把每个Topic变成TopicNode
Long tid = temp.getTid();
//找到是这个评论的所有回复
List<Replay> thisreplays = new ArrayList<>();
thisreplays = replayService.FindAllByTid(tid);
//遍历每个第一层回复
for(Replay re:thisreplays){
ReplayNode replayNode = new ReplayNode();
replayNode.setReplay(re);
topicNode.getReplays().add(replayNode);
//得到回复的回复
List<Replay> replayList = new ArrayList<>();
replayList = replayService.FindAllByRid(re.getRid());
//递归
AddReplayNode(replayList,replayNode);
}
topicNodes.add(topicNode);
}
//输出
for(TopicNode tnode:topicNodes){
//得到评论
System.out.println(tnode.getTopic().toString());
ShowReplayNodes(tnode.getReplays());
}
model.addAttribute("topics",topicNodes);
return "index";
}
}
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Index</title>
<link rel="stylesheet" href="/css/index.css">
</head>
<body>
<div class="onetopic" th:each="tp : ${topics}">
<img src="/imgs/1.jpg" class="userhead" th:src="|/imgs/${tp.getTopic().getUheadurl()}|">
<span class="name" th:text="${tp.getTopic().getUname()}">小虎</span><span class="tr-time" th:text="${tp.getTopic().getTtime()}">2018-01-02</span>
<p class="tr-content" th:text="${tp.getTopic().getTcontent()}">这是留言内容</p>
<div th:include="digui::digui(${tp.getReplays()},1)">
<!--<div class="onereplay" th:each="tr : ${tp.getReplays()}">-->
<!--<img src="/imgs/1.jpg" class="userhead">-->
<!--<span class="name">小灰</span><span class="tr-time">2018-01-02</span>-->
<!--<p class="tr-content">回复 小虎 :这是回复内容</p>-->
<!--<div class="onereplay">-->
<!--<img src="/imgs/1.jpg" class="userhead">-->
<!--<span class="name">小灰</span><span class="tr-time">2018-01-02</span>-->
<!--<p class="tr-content">回复 小虎 :这是回复内容</p>-->
<!--</div>-->
<!--</div>-->
</div>
<!--<div class="onereplay">-->
<!--<img src="/imgs/1.jpg" class="userhead">-->
<!--<span class="name">小灰</span><span class="tr-time">2018-01-02</span>-->
<!--<p class="tr-content">回复 小虎 :这是回复内容</p>-->
<!--</div>-->
</div>
</body>
</html>
digui.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div th:fragment="digui(lists,level)">
<div class="onereplay" th:each="tr : ${lists}">
<img src="/imgs/1.jpg" class="userhead" th:src="|/imgs/${tr.getReplay().getUheadurl()}|">
<span class="name" th:text="${tr.getReplay().getUname()}">小灰</span><span class="tr-time" th:text="${tr.getReplay().getRtime()}">2018-01-02</span>
<p class="tr-content" th:text="'回复 '+${tr.getReplay().getTouname()}+' :'+${tr.getReplay().getRcontent()}">回复 小虎 :这是回复内容</p>
<div th:unless="${tr.getListreplay().size()==0}" th:include="this::digui(${tr.getListreplay()},${level+1})"></div>
</div>
</div>
</body>
</html>
index.css
.onetopic{
margin: 10px 0;
}
.userhead{
width: 50px;
border: 1px solid #e8cfcf;
border-radius: 30px;
}
.name{
position: relative;
top: -18px;
}
.tr-time{
position: relative;
top: -18px;
left: 18px;
font-size: 12px;
}
.tr-content{
padding: 0 0 20px 0;
border-bottom: 1px solid #b1a5a5;
margin: 0;
margin-left:52px;
}
.onereplay{
margin: 10px 0 10px 40px;
}