SpringBoot聚合项目:达内知道(十一)-删除用户评论、修改用户评论、问题采纳
下面我们来实现删除评论的功能,注意:删除用户的评论并不是随意删除的:
-
讲师可以删除任何人的评论(包括自己的、其他讲师的、学生的)
-
学生只能删除自己发布的评论,不能删除讲师或者其他同学的评论
删除思路:按id删除评论是Mybatis Plus提供的功能,直接编写业务逻辑层即可。
业务逻辑层中:
-
先判断是不是讲师,如果是讲师,则直接删除;
-
如果不是讲师,即学生,判断评论的发布者id和当前登录用户id是否一致,如果一致可以删除,否则不能删除(即保证不同同学间评论不能彼此删除)
1.1 编写业务逻辑层
在接口ICommentService添加方法:
//用户删除评论的方法,使用boolean类型的返回值,判断是否删除成功
boolean removeComment(Integer commentId,String username);
在CommentServiceImpl类中实现该方法:
1.2 编写控制层代码
在CommentController中添加方法:
//按id删除评论 v1/comments/85/delete
启动服务,进行同步测试,测试路径为:http://localhost:8888/v1/comments/27/delete,进行删除评论,删除效果如下所示:
(1)删除成功
(2)由于该评论已不存在,删除失败
1.3 编写html绑定
任何用户方面不可逆操作都要有二次确认,尽量选择相对友好的二次确认,避免使用系统弹窗。下面这个效果是点击删除时,出现右侧的删除红色图章,点击该图章时进行删除操作,不点击删除时,不出现该删除图章。
下面就来编写这个弹出效果,在detail_teacher.html 336行附近修改代码:
<!--老师角色或者属于本用户的评论可以删除该评论-->
<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)"><!--修改:toggle添加动画效果-->
删除
</a>
<a class="badge badge-pill badge-danger text-white"
style="display: none;cursor: pointer"
@click="removeComment(comment.id)"><!--悬浮变小手,指定删除id-->
<i class="fa fa-close"></i>
</a>
1.4 编写js代码
删除评论的js代码也编写在question_detail.js的answersApp对象中,代码如下:
//删除评论
removeComment:function(commentId){
axios({
url:"/v1/comments/"+commentId+"/delete",
method:"get"
}).then(function(response){
console.log(response.data);
})
}
启动服务,进行删除评论操作,点击删除后,评论会在数据库中进行删除,但是评论列表显示还在。
1.5 同步删除结果
我们希望在删除评论时,将评论显示的内容也同时删除,这样就要去删除Vue中answers对象中指定answer对象中comments数组中的某一个对象,我们可以通过直接传入必要参数到删除方法中来简化删除过程,在detail_teacher.html的314行附近修改代码:
<li class="media my-2"
v-for="(comment,index) in answer.comments"><!--遍历评论,传入当前评论和对应当前索引下标-->
其中,index能够代表当前循环的索引值(从0开始,恰巧匹配数组下标)。
在342行附近进行修改:
<a class="badge badge-pill badge-danger text-white"
style="display: none;cursor: pointer"
@click="removeComment(comment.id,index,answer)"><!--指定删除评论id、索引下标、当前回答对象-->
<!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
<i class="fa fa-close"></i>
</a>
上面代码在执行删除时,传入了index和answer对象,我们可以在删除的js代码中,直接删除answer对象中comments数组的第index位置的元素,从而定位到要删除的评论。
question_detail.js中删除评论的js代码:
// 174行附近 ↓↓↓↓↓↓↓↓↓↓↓↓↓
removeComment:function(commentId,index,answer){//此处要传入删除需要的参数
axios({
url:"/v1/comments/"+commentId+"/delete",
method:"get"
}).then(function(response){
console.log(response.data);
// js代码删除数组中元素的方法splice([删除的索引值],[删除几个])
//↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
answer.comments.splice(index,1);//必须要写1,否则全部删除
})
}
重新服务,进行测试,查看删除评论时,对应的评论是不是立即删除!
注意:同时也可以测试学生删除评论功能逻辑:先用学生身份进行登录,点击问题详情页后响应404页面,修改浏览器地址栏为http://localhost:8888/question/detail_teacher.html?157(注意一定要指定删除id),先添加评论,再进行删除,删除后进行刷新(shift+F5),发现:学生身份只能删除自己增加的评论,其他人的评论都不能删除,与设计逻辑一致。
2 修改用户评论
因为评论是需要表单提交的,提交方式是post,所以我们先做控制层和页面,方便测试。
页面方面和添加评论有一样的问题,即当我们点击编辑时,所有编辑全部展开,我们先处理这个问题。
2.1 修改html页面代码
在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>
351行附近:
<div class="collapse" id="editCommemt1"
:id="'editComment'+comment.id"><!--修改id-->
<!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
<div class="card card-body border-light">
<form action="" method="post" class="needs-validation" novalidate
@submit.prevent="updateComment(comment.id,answer.id,index,answer)"><!--组织原有表单提交效果-->
<!-- ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ -->
<div class="form-group">
<textarea class="form-control"
id="textareaComment1" name="content" rows="4"
required></textarea>
<div class="invalid-feedback">
内容不能为空!
</div>
</div>
<button type="submit" class="btn btn-primary my-1 float-right">提交修改</button>
</form>
</div>
</div>
2.2 编写js代码
在question_detail.js文件中answersApp对象中添加方法:
//修改评论
updateComment:function(commentId,answerId,index,answer){//传入评论id、回答id、索引下标、回答
//获得修改对象
let textarea=$("#editComment"+commentId+" textarea");
//获得修改内容
let content=textarea.val();
//新建表单,赋值
let form=new FormData();
form.append("answerId",answerId);
form.append("content",content);
//发请求
axios({
url:"/v1/comments/"+commentId+"/update",
method:"post",
data:form
}).then(function(response){
//将要修改的评论重新赋值对象
//answer.comments[index]=response.data;
//上面的修改并不会引发vue对页面的刷新
//也就是说,数组中的数据已经发生了变化,但是页面中数据不变
// 是因为Vue不会对当前绑定数组中元素的子元素进行修改时自动刷新
// 所以,想修改数据还想让页面刷新,需要使用下面的命令:
Vue.set(answer.comments,index,response.data);
//Vue.set([修改哪个数组],[修改哪个索引],[修改成什么])
//清空文本域中的内容(修改评论提交后,文本框内内容清空)
textarea.val("");
//修改后收缩文本框(修改评论提交后,文本框收缩)
$("#editComment"+commentId).collapse("hide");//前端BootStrap内容
})
// ["red","black","blue","white"]
}
2.3 编写控制层代码接收信息
先编写一个能够接收js代码请求表单的控制代码:
// 修改评论的功能
启动服务后,登录讲师首页,进入问题详情页,进行修改评论,提交效果如下:
2.4 编写业务逻辑层
修改方法MybatisPlus也有提供,我们可以选择直接使用这个方法,也可以自己编写一个针对当前修改业务的方法。在当前业务中,我们选择使用MybatisPlus给我们提供的方法,所以不需要编写数据访问层。
在ICommentService接口添加方法:
//修改用户评论的方法
Comment updateComment(Integer commentId,CommentVo commentVo,String username);//需要评论id、表单对象、用户名
在CommentServiceImpl类中实现该方法:
//用户修改评论的业务逻辑层方法实现
2.5 控制层调用
在CommentController修改评论的方法添加业务逻辑层调用:
// 修改评论的功能
重启服务,修改评论进行测试,如果修改成功,并且页面立即变化,表示一切正常!
(1)浏览器
(2)IDEA
(3)数据库
(4)添加下面代码后:
//清空文本域中的内容(修改评论提交后,文本框内内容清空)
textarea.val("");
//修改后收缩文本框(修改评论提交后,文本框收缩)
$("#editComment"+commentId).collapse("hide");//前端BootStrap内容
提交评论后,文本域内容清空,同时收起文本域。
2.6 实现学生评论
现在我们的项目学生登录跳转到学生首页,学生首页点击问题标签还不能跳转到详情页,我们需要给学生也开发一个问题详情页。学生和讲师问题详情页的区别在于:
-
学生没有删除和回答问题的权限
-
学生没有富文本编辑器
先删除原有的detail_student.html,直接复制detail_teacher.html并重命名为detail_student.html,然后将上图需要删除的位置从detail_student.html页面中删除即可。
删除内容如下:
找到学生首页index_student.html,在学生首页的标题连接处修改为:index_student.html的203行附近
<a class="text-dark" href="question/detail.html"
v-text="question.title"
:href="'/question/detail_student.html?'+question.id"><!--页面绑定、修改href-->
eclipse 如何导入项目?
</a>
重启服务,登录学生首页,通过学生首页就可以访问问题详情页了。按F12会出现右侧警告,可以忽略。
3 问题的采纳
最后我们要实现问题的采纳效果,我们项目的设计采纳形式有3种:
-
学生采纳 : 学生登录后,将自己提问的问题中的某个回答标记为采纳,问题状态变为已解决(项目中采用这种方式进行讲解);
-
讲师采纳 : 学生提问后,长时间不采纳,也不讨论,视为该学员不活跃,在讲师已经回答问题的前提下,可以将回答标记为采纳,问题状态变为已解决;
-
超时自动采纳 : 学生提问,讲师回复后,没有任何用户再有新的操作,到一定时间后,我们可以设置系统自动采纳。
我们的项目允许一个提问多个回答被采纳,已经解决的问题仍然可以继续讨论!
3.1 采纳按钮二次确认
因为采纳也是用户不可逆的操作,需要和删除一样的二次确认,所以我们设计在点击采纳答案后,在右侧弹出一个绿色的√表示实际的采纳操作。
在detail_student.html的373行附近修改代码:
<!--白色字体、指针变小手、添加toggle方法进行显示状态切换-->
<a class="btn btn-primary mx-2 text-white"
style="cursor: pointer;"
onclick="$(this).next().toggle(300)"
>采纳答案</a>
<!--弹出√:绿色图章背景、白色字体、中间带√、设置样式:鼠标变小手、添加点击事件-->
<a class="badge badge-pill badge-success text-white"
style="display: none;cursor: pointer"
@click="answerSolved(answer.id)">
<i class="fa fa-check"></i><!--check表示图章√,close表示图章×-->
</a>
3.2 编写数据访问层
编写采纳回答的数据访问层,需要编写两个修改方法:采纳回答涉及回答和问题表中的状态修改
-
修改answer的accept_status的方法
-
修改question的status的方法
在answerMapper编写方法修改accept_status:
//采纳回答:修改指定id的回答的accept_status,注意后面涉及到两个参数的特殊情况,返回的是受影响的行数
question问题对象有3个状态:
-
0:未回复
-
1:已回复
-
2:已解决
当涉及3个及以上状态时,需要定义声明常量,我们应该在Question类中定义这3个数组对应的常量:
public class Question implements Serializable {
private static final long serialVersionUID = 1L;
// 声明问题状态的3个常量
public static final Integer POSTED=0; // 已提交\未回复
public static final Integer SOLVING=1; // 正在采纳\已回复
public static final Integer SOLVED=2; // 已经采纳\已解决
// 其它代码略
}
在QuestionMapper添加修改status状态的方法:
//采纳回答:将当前问题的状态修改为已解决
推荐对上面mapper中编写的两个方法测试一下,测试代码如下:
输出结果:
(1)IDEA
(2)accept_status由0变为1
(3)status由1变为2
3.3 编写业务逻辑层
我们要编写的是采纳回答的业务,先在IAnswerService接口中添加方法:
//采纳回答的方法
boolean accept(Integer answerId,String username);//目前只考虑了学生自己提出的问题
在AnswerServiceImpl实现代码如下:
3.4 编写控制层方法
在AnswerController中编写采纳回答的方法:
// 采纳回答的业务逻辑层
重启服务,进行同步测试:
(1)先登小明同学,访问路径:http://localhost:8888/v1/answers/42/solved,提示“权限不足”
(2)再登李四同学,访问路径:http://localhost:8888/v1/answers/42/solved,提示“采纳完成”
(3)再次刷新或者访问该路径,提示“该问题已经被采纳”
3.5 编写js代码
我们在本章节开始时已经编写了html的绑定,点击采纳回答出现二次确认,再点击√进行回答的采纳。
我们继续在question_detail.js文件中编写,answersApp下添加answerSolved方法,代码如下:
//回答修改为已解决状态
answerSolved:function(answerId){
axios({
url:"/v1/answers/"+answerId+"/solved",
method:"get"
}).then(function(response){
alert(response.data);
})
}
重启服务,进行测试:
(1)先登录小明同学,访问路径:http://localhost:8888/question/detail_student.html?164,进行采纳答案,提示“权限不足”
(2)再登录李四同学,访问路径:http://localhost:8888/question/detail_student.html?164,进行采纳答案,提示“采纳成功”
(3)再次刷新或者访问该路径,提示“该问题已经被采纳”
(4)回到问题首页,问题状态变为已解决