续 查询所有评论
编写AnswerMapper.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">
<!--
namespace(命名空间)
指定当前xml文件对应的Mapper接口
必须指定一个存在的Mapper接口,当前xml文件才能正常运作
-->
<mapper namespace="cn.tedu.knows.portal.mapper.AnswerMapper">
<!-- 回答包含评论集合的映射结果 -->
<resultMap id="answerCommentMap" type="cn.tedu.knows.portal.model.Answer">
<id column="id" property="id" />
<result column="content" property="content" />
<result column="like_count" property="likeCount" />
<result column="user_id" property="userId" />
<result column="user_nick_name" property="userNickName" />
<result column="quest_id" property="questId" />
<result column="createtime" property="createtime" />
<result column="accept_status" property="acceptStatus" />
<!--
配置collection标签,
property属性指定Answer类中评论集合的属性名
也就是将查询结果中所有评论相关的数据,都要保存在评论集合中
ofType指定集合泛型的类型,这里是List<Comment>所以是Comment的全类名
-->
<collection property="comments"
ofType="cn.tedu.knows.portal.model.Comment">
<id column="comment_id" property="id" />
<result column="comment_user_id" property="userId" />
<result column="comment_user_nick_name" property="userNickName" />
<result column="comment_answer_id" property="answerId" />
<result column="comment_content" property="content" />
<result column="comment_createtime" property="createtime" />
</collection>
</resultMap>
<!--
select标签,表示一个查询方法,id和AnswerMapper中的一个方法名一致
注意不建议在标签内打注释,如果一定要打,写数据库sql语句注释,写xml注释会报错
我们需要关注的是这个查询的返回结果,反射需要自定义映射的查询结果都要编写resultMap属性
answerCommentMap是映射结果的名称,需要我们自己编写
-->
<select id="findAnswersByQuestionId" resultMap="answerCommentMap">
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.user_nick_name comment_user_nick_name,
c.answer_id comment_answer_id,
c.content comment_content,
c.createtime comment_createtime
FROM answer a
LEFT JOIN comment c ON a.id=c.answer_id
WHERE quest_id=#{id}
ORDER BY a.id
</select>
</mapper>
AnswerMapper接口对应代码:
@Repository
public interface AnswerMapper extends BaseMapper<Answer> {
// 对应AnswerMapper.xml文件中的内容
// 根据问题id查询所有回答以及回答包含的评论的方法
// 方法名必须和xml文件中<select>标签的id一致
List<Answer> findAnswersByQuestionId(Integer questionId);
}
上面的编写需要进行测试才能认定正确
测试代码如下
@Autowired
private AnswerMapper answerMapper;
@Test
public void testAnswer(){
// 根据问题id查询所有回答,包含所有评论
List<Answer> answers=
answerMapper.findAnswersByQuestionId(149);
for (Answer a: answers){
System.out.println(a);
}
}
修改业务逻辑层
上面的操作实际上是完成了数据访问层
实现根据问题id查询这个问题的所有回答和回答包含的所有评论
下面我们要在业务逻辑层中调用它
但是业务逻辑层中根据问题id查询所有回答的业务之前编写过了
所以这里是个修改
我们找到AnswerServiceImpl类中的getAnswersByQuestionId方法
修改如下
@Override
public List<Answer> getAnswersByQuestionId(Integer id) {
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 执行关联查询,获得包含所有评论的回答列表
List<Answer> answers=answerMapper
.findAnswersByQuestionId(id);
// 别忘了返回
return answers;
}
启动portal项目
浏览器发送同步请求localhost:8080/v1/answers/question/149
检查查询出来的回答列表中是否包含应该含有的评论内容
修改评论列表的Vue绑定
同步发送中包含了所有的评论信息
页面上要显示这些评论信息的话
需要添加页面上的绑定,不需要修改axios的调用
detail_teacher.html
310行附近
<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>
重启服务,观察问题详情页中是否能够显示所有回答和回答对应的所有评论
新增评论立即显示在列表中
我们在开发讲师新增回复时,遇到过类似的问题
也开发了讲师新增回复时立即显示在回复列表中的效果
但是现在的功能是新增评论,和新增回复有些许不同
现在我们新增一个评论也需要刷新网页才能显示在评论列表中
我们不想刷新页面,就想将新增的评论显示在评论列表中
新增回答时,全页面只有一个回答列表即answers
但是新增评论,全页面会有多个评论列表,因为每一个answer都包含一个comments
我们需要确定新增的评论要添加到哪个评论列表中
依据是新增的评论的回答id和回答列表中的回答对象的id相等
在postComment方法的then中添加代码如下
.then(function(response){
// 将新增成功的评论显示在页面中
let comment=response.data;
// 获得当前所有回答的集合
let answers=answersApp.answers;
// 遍历所有回答的集合
for(let answer of answers){
// 判断当前回答是不是新增评论的回答
if(answer.id==answerId){
// 如果确认一致,就将当前新增成功的评论对象
// 添加到这个回答的评论列表中
answer.comments.push(comment);
// 清空输入框的内容
textarea.val("");
break;
}
}
})
重启服务,测试新增评论立即显示的效果
开发删除评论的功能
删除评论的功能思路分析
删除评论对用户来说是一个"不可逆"的操作
所以为了减少用户误删除的情况,所有不可逆操作都会有二次确认或者是类似的提示
最简单的做法是使用浏览器给的确认弹框(confirm),但是不友好
我们达内知道删除评论的策略设计为
点击删除链接后弹出一个二次确认图标
还有另外我们需要注意的业务逻辑判断
- 讲师用户能删除任何人的评论
- 学生用户只能删除自己发表的评论
完成二次确认效果
detail_teacher.html页面的335行附近
<!--老师角色或者属于本用户的评论可以删除该评论-->
<a class="ml-2 fa fa-close " style="font-size: small"
data-toggle="collapse" role="button"
aria-expanded="false" aria-controls="collapseExample"
onclick="$(this).next().toggle(300)">
删除
</a>
<a class="badge badge-pill badge-danger text-white"
style="display: none;cursor: pointer"
@click="removeComment(comment.id)">
<i class="fa fa-close"></i>
</a>
在调用删除评论的方法时,传入删除评论的集合和删除评论的索引(下标)值
完成数组元素的删除操作
具体操作
detail.teacher.html的314行附近 li标签的v-for
修改代码
<li class="media my-2"
v-for="(comment,index) in answer.comments">
再修改调用删除方法,传入需要的参数
<a class="badge badge-pill badge-danger text-white"
style="display: none;cursor: pointer"
@click="removeComment(comment.id,
index,answer.comments)">
<!-- ↑↑↑↑↑ ↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
<i class="fa fa-close"></i>
</a>
参数中的index就是当前删除的评论在数组中的下标
answer.comments就是删除评论所在的数组对象
最后执行删除的操作还是编写在js代码中
// ↓↓↓↓↓ ↓↓↓↓↓↓
removeComment:function(commentId , index , comments){
axios({
url:"/v1/comments/"+commentId+"/delete",
method:"get"
}).then(function(response){
if(response.data!="ok"){
alert(response.data);
// ↓↓↓↓↓↓
return;
}
// 如果返回的response.data是"ok",才能运行下面代码
// 利用js代码从数组中删除元素的api删除元素即可
// splice实现删除数组中的元素,参数([删除下标的起始值],[删除的个数])
// ↓↓↓↓↓↓
comments.splice(index,1);
})
}
重启服务,测试删除效果
开发修改评论的功能
实现点击编辑链接展开表单的效果
现在页面中点击评论的编辑链接
会出现和之前点击添加评论按钮相同的bug
页面中所有评论编辑的表单都会展开
这个bug出现的原因和解决方式和之前一致
只不过这次需要使用comment.id来区分它们
detail_teacher.html页面的330行附近
<a class="text-primary ml-2"
style="font-size: small" data-toggle="collapse" href="#editCommemt1"
role="button" aria-expanded="false" aria-controls="collapseExample"
:href="'#editComment'+comment.id">
<!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑-->
<i class="fa fa-edit"></i>编辑
</a>
detail_teacher.html页面的351行附近
<div class="collapse" id="editCommemt1"
:id="'editComment'+comment.id">
<!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑-->
<div class="card card-body border-light">
<form action="" method="post" class="needs-validation" novalidate
@submit.prevent="updateComment(comment.id,index,answer)" >
<!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑-->
<div class="form-group">
<textarea class="form-control"
id="textareaComment1" name="content" rows="4"
required
v-text="comment.content"></textarea>
<!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑-->
<div class="invalid-feedback">
内容不能为空!
</div>
</div>
<button type="submit" class="btn btn-primary my-1 float-right">提交修改</button>
</form>
</div>
</div>
重启服务
测试展开评论表单的bug是否修改,还有编辑表单中是否有默认评论内容
编写修改评论的js代码
继续在question_detail.js文件的answersApp对象中添加方法
目标是将修改评论的信息发送给控制器方法
updateComment:function(commentId,index,answer){
// 获得用户修改后的评论内容,还使用jQuery的后代选择器
let textarea=$("#editComment"+commentId+" textarea");
let content=textarea.val();
// 判断内容是否为空,如果是空终止修改
if(!content){
return;
}
// 为了启动服务器端的SpringValidation验证
// 我们仍然提交CommentVO对象,answerId要传值,但无实际作用
let form=new FormData();
form.append("answerId",answer.id);
form.append("content",content);
axios({
url:"/v1/comments/"+commentId+"/update",
method:"post",
data:form
}).then(function (response){
// 暂时空置
})
}
// 按id进行评论内容修改的方法
// /v1/comments/20/update
@PostMapping("/{id}/update")
public Comment updateComment(
@PathVariable Integer id,
@Validated CommentVO commentVO,
BindingResult result,
@AuthenticationPrincipal UserDetails user){
log.debug("表单信息:{}",commentVO);
log.debug("要修改的评论id:{}",id);
if(result.hasErrors()){
String msg=result.getFieldError().getDefaultMessage();
throw new ServiceException(msg);
}
// 调用业务逻辑层
// 暂时返回null
return null;
}
重启服务测试
观察idea控制台中输出的信息是否正常
页面无任何效果
编写业务逻辑层
我们仍然可以直接使用MybatisPlus提供的修改功能
跳过数据访问层直接编写业务逻辑层接口方法
// 按id修改评论的业务逻辑层方法
Comment updateComment(Integer commentId,
CommentVO commentVO,String username);
CommentServiceImpl实现代码如下
@Override
@Transactional
public Comment updateComment(Integer commentId, CommentVO commentVO, String username) {
User user=userMapper.findUserByUsername(username);
// 修改之前需要先将要修改的评论对象查询出来
Comment comment=commentMapper.selectById(commentId);
// 如果是讲师或者是评论的发布者,可以修改评论
if(user.getType().equals(1) ||
user.getId().equals(comment.getUserId())){
// 执行content属性的修改
comment.setContent(commentVO.getContent());
// 执行修改,提交修改属性到数据库中
int num=commentMapper.updateById(comment);
if(num!=1){
throw new ServiceException("数据库忙");
}
// 修改成功返回修改后的对象
return comment;
}
throw new ServiceException("您不能修改别人的评论!");
}
完善控制层调用
CommentController类中调用业务逻辑层修改评论的方法
// 调用业务逻辑层
Comment comment=commentService
.updateComment(id, commentVO, user.getUsername());
// 返回comment
return comment;
完成js代码
到此为止,实际上已经可以实现页面上提交修改评论的内容影响数据库中的数据效果了,但是页面上的评论信息还是需要刷新页面才能展示修改后的信息
我们也要对js文件进行修改才能实现修改评论时,页面内容同时修改
updateComment方法的then中添加代码如下
.then(function (response){
// 输出返回值的类型
console.log(response.data);
console.log("返回值类型:"+typeof(response.data))
// 判断返回值类型是不是object
if(typeof(response.data)=="object"){
// 如果返回值类型是object就是修改完成了!
// 本次修改没有变化数组元素的数量(数据长度没变)
// 因为当前要修改的数组元素已经是answers数组的子元素
// Vue是不会对这样的修改进行页面上内容的更新的
// 既然它不会自动修改,就需要我们手动修改
// Vue提供了一个方法支持我们手动修改数组元素
// 这个方法会引起页面信息的同步变化
// Vue.set([要修改的数组],[要修改的下标],[修改成什么])
Vue.set(answer.comments,index,response.data);
// 修改成功之后,编辑框设计为自动收缩
$("#editComment"+commentId).collapse("hide");
}else{
alert(response.data);
}
})
随笔
从java中的集合中删除一个元素
我们需要知道从哪个集合中删除几号元素
js操作数组常用api
push() 向数组的末尾添加一个或更多元素,并返回新的长度
pop() 删除并返回数组的最后一个元素
unshift() 向数组的开头添加一个或更多元素,并返回新的长度
shift() 删除并返回数组的第一个元素
splice(index, howmany) 从index位置删除howmany个数组元素
splice(index, howmany, item) 从index位置删除howmany个数据元素并添加item元素
sort() 对数组的元素进行排序
reverse() 颠倒数组中元素的顺序
concat() 连接两个或更多的数组,并返回结果
join() 把数组的所有元素放入一个字符串,元素通过指定的分隔符进行分隔
控制方法参数注解总结
- @PathVariable 属于SpringMvc框架
能够从请求的路径中,定义的占位符变量中获得具体的值
- @Validated 属于SpringValidation框架
能够在控制器方法运行前按SpringValidation框架规定好的验证规则对标记的对象进行验证,将验证结果赋值给紧随其后的BindingResult对象
- @AuthenticationPrincipal 属于SpringSecurity框架
能够从SpringSecurity框架中获得登录成功的用户信息