SpringBoot聚合项目:达内知道(十)-开发新增评论功能
我们已经完成了讲师回复和显示讲师回答列表的功能,首先明确问题-->回答-->评论的关系:
-
一个问题可以有多个回答
-
一个回答可以有多个评论
-
评论直接关联回答id,和问题没有关系
1.1 为comment表添加用户昵称列
我们可以通过sql语句在不删除当前表的前提下为表新增列,comment表中没有用户昵称列,这样显示数据非常不方便,甚至需要连表查询,所以我们新增用户昵称列,sql语句如下:
-- 为指定表新增列的代码
ALTER TABLE comment
ADD COLUMN user_nick_name VARCHAR(255)
AFTER user_id
-- 编写修改语句,将用户对应的昵称赋值
UPDATE comment c SET user_nick_name=(
SELECT nickname FROM user u WHERE
u.id=c.user_id
)
经过上面操作,我们的comment表中就新增了user_nick_name列,并赋上了对应的昵称值。
数据库列变化了,那么对应数据库的实体类也已经要新增一个属性。
Comment实体类新增属性如下:
/**
* 用户昵称
*/
1.2 编写新增评论的控制层
先在Vo包创建一个新增评论用的Vo类CommentVo,代码如下:
编写控制器方法来接收表单提交的信息:
1.3 编写页面的绑定
在detail_teacher.html页面修改代码:352行附近
<p class="text-left text-dark">
<a class="btn btn-primary mx-2"
href="#">采纳答案</a>
<a class="btn btn-outline-primary"
data-toggle="collapse" href="#collapseExample1"
role="button" aria-expanded="false"
aria-controls="collapseExample"
:href="'#addComment'+answer.id" ><!--修改href-->
<i class="fa fa-edit"></i>添加评论
</a>
</p>
<div class="collapse" id="collapseExample1"
:id="'addComment'+answer.id"><!--修改id-->
<div class="card card-body border-light">
<form action="#" method="post" class="needs-validation"
novalidate @submit.prevent="postComment(answer.id)"><!--阻止原有表单提交效果-->
<div class="form-group">
<textarea class="form-control" name="content" rows="3" required></textarea>
<div class="invalid-feedback">
评论内容不能为空!
</div>
</div>
<button type="submit" class="btn btn-primary my-1 float-right">提交评论</button>
</form>
</div>
</div>
1.4 编写提交评论的js代码
在question_detail.js文件中的answersApp对象中新增一个方法:
postComment:function(answerId){
if(!answerId){
return
}
console.log(answerId);
//获得输入框对象
let textarea=$("#addComment"+answerId+" textarea");//textarea前面一定要有空格:子孙后代选择器
let content=textarea.val();
if(!content){
alert("必须编写评论内容");
return;
}
let form=new FormData();
form.append("answerId",answerId);
form.append("content",content);
axios({
url:"/v1/comments",
method:"post",
data:form
}).then(function(response){
console.log(response.data);
})
}
1.5 开发用户添加评论的业务逻辑层
上次课完成了用户添加评论的控制层、页面和提交信息到java的Controller,现在开发业务逻辑层,以便控制层调用。
在接口ICommentService中添加方法:
public interface ICommentService extends IService<Comment> {
//用户新增评论的业务逻辑层方法:因为增加评论后要显示在评论列表,所以要返回Comment进行调用,避免再次查询数据库
Comment saveComment(CommentVo commentVo,String username);
}
在CommentServiceImpl类中实现业务逻辑层方法:
1.6 完善控制器方法
重启服务,尝试添加评论,观察数据库是否添加成功!
(1)未修改前,添加评论存在粘连问题,点开一个评论时,所有评论窗口都打开,关闭时都关闭
(2)修改后解决了粘连问题,但是不能显示在评论列表上
(3)数据库中新增了评论数据
注意:此时数据只是添加到数据库中,并不能进行显示。
1.7 显示每个回答的评论列表
现在我们已经成功新增了评论, 但是并不能把评论显示在页面上,每个评论应该显示在对应回答的评论列表下。
利用关联查询获得评论列表
要想查询出每个回答的评论信息,我们需要从数据库中查询该回答相关的评论信息,那么就有多种思路实现,主流两种思路如下图:
-
方案一(左侧)
多次查询,每次查询一个回答对应的所有评论,这样做的好处是逻辑和业务简单,但是效率低下。
-
方案二(右侧)
一次查询,连同回答和回答对应的评论全部查询出来,这样做的好处是效率高,但是编写代码比较复杂。
我们本次查询采用右侧的方式解决,首先明确一次查询要执行的sql语句如下:由于两个表进行关联查询时存在多个重名字段,所以使用别名加以区分(注意检查代码是否正确)。
SELECT
a.id,
a.content,
a.like_count,
a.user_id,
a.user_nick_name,
a.quest_id,
a.createtime,
a.accept_status,
c.id comment_id,
c.user_id comment_user_id,
c.answer_id comment_answer_id,
c.user_nick_name comment_user_nick_name,
c.content comment_content,
c.createtime comment_createtime
FROM answer a LEFT JOIN comment c
ON a.id=c.answer_id
WHERE a.quest_id=157
ORDER BY a.createtime
查询结果:
补充:数据库连接查询
数据库连接查询分内连接和外连接
-
内连接关键字: [inner] join inner可以省略
-
内连接特征:两张关联表必须有对应关系才能出现在查询结果中
-
-
外连接又分左连接和右连接
-
左连接 left [outer] join outer可以省略
-
右连接 right [outer] join outer可以省略
-
外连接特征
-
查询结果示意图:
上面是查询结果的结构示意图,意思是我们查询出的回答会保存在一个List<Answer>中,而这个List中的每一个Answer对象中又可能包含多个Comment,所以我们现在必须在Answer实体类中添加一个List<Comment>类型的属性comments来保存评论,代码如下:
/**
* 问答包含的评论集合
*/
下面开始利用Mybatis框架提供的解决方案实现我们的关联查询:
-
创建resource/mapper文件夹
-
将java中mapper/xml中的AnswerMapper.xml复制到resource/mapper文件路径下
-
Rebuild Module "knows-portal"
修改代码示意图如下:
最终修改后代码如下:注意复制后在Answer、AnswerMapper上Ctrl+鼠标左键点击能跳转到对应的文件才行
AnswerMapper中编写findAnswersByQuestionId方法:
由于涉及sql代码及多处修改,一定要测试一下,测试代码如下:
package cn.tedu.knows.portal;
import cn.tedu.knows.portal.mapper.AnswerMapper;
import cn.tedu.knows.portal.model.Answer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
测试结果如下:
注意检查输出结果内容是否为空,是否都完整,如果有null时,检查代码是否编写正确。注意问答没有讨论时commets=[ ],有讨论时,后面对应每个评论。如果测试正常,则继续编写下面的代码。
1.8 完成查询评论列表的业务逻辑层
上面的章节完成了查询评论列表的数据访问层,下面我们完善一下已经编写好的业务逻辑层代码。
将AnswerServiceImpl类中的getAnswersByQuestionId修改如下:
控制层不用修改,甚至js文件都不修改,只需要修改vue绑定即可。
1.9 Vue绑定显示所有评论
在detail_teacher.html页面的309行附近:
<div class="card-footer">
<p class="text-success fa fa-comment"><!--修改评论条数-->
<span v-text="answer.comments.length">1</span>条评论
</p>
<ul class="list-unstyled mt-3">
<li class="media my-2"
v-for="comment in answer.comments"><!--遍历评论-->
<img style="width: 50px;height: 50px;border-radius: 50%;"
src="../img/user.jpg" class="mr-3"
alt="...">
<div class="media-body">
<h6 class="mt-0 mb-1"><!--修改评论者昵称-->
<span v-text="comment.userNickName">李四</span>:
</h6>
<p class="text-dark">
<span class="text-monospace"
v-text="comment.content"><!--修改评论内容-->
明白了,谢谢老师!
</span>
<!-- 其它代码略 -->
</div>
启动服务进行测试,添加评论,发现添加评论后评论不能立即显示到问答下,需要刷新才能加载出来。
1.10 立即显示评论内容
上面虽然可以显示回答以及其评论内容了,但是并不能在添加评论时立即将评论内容显示在页面上,在question_detail.js的answersApp定义的Vue中的postComment方法中.then的内容修改如下:
.then(function(response){
console.log(response.data);
//立即将新增的评论显示在页面上
let comment=response.data;
let answers=answersApp.answers;
//遍历当前所有的回答对象
for(let i=0;i<answers.length;i++){
//遍历问题id=当前问题id
if(answers[i].id==answerId){
//给问题添加评论
answers[i].comments.push(comment);
break;
}
}
//清空文本域中的内容
textarea.val("");
})
补充:常用js操作数组的API
push() 向数组的末尾添加一个或更多元素,并返回新的长度
pop() 删除并返回数组的最后一个元素
unshift() 向数组的开头添加一个或更多元素,并返回新的长度
shift() 删除并返回数组的第一个元素
splice(index, howmany) 从index位置删除howmany个数组元素
splice(index, howmany, item) 从index位置删除howmany个数据元素并添加item元素
sort() 对数组的元素进行排序
reverse() 颠倒数组中元素的顺序
concat() 连接两个或更多的数组,并返回结果
join() 把数组的所有元素放入一个字符串,元素通过指定的分隔符进行分隔
重新启动服务进行测试,发现新增评论后,立即显示在该问答所对应的评论列表中。