文章详情页 - 评论功能的实现

目录

1. 准备工作

1.1 创建评论表

 1.2 创建评论实体类

 1.3 创建 mapper 层评论接口和对应的 xml  实现

1.4 准备评论的 service 层

1.5 准备评论的 controller 层

2. 总的初始化详情页

2.1 加载评论列表

2.1.1 实现前端代码

2.1.2 实现后端代码

2.2 查询当前登录用户的信息

2.2.1 实现前端代码

2.2.2 实现后端代码

3. 实现发表评论

3.1 实现前端代码

3.2 实现后端代码

4. 实现删除评论

4.1 实现前端代码

4.2 实现后端代码


1. 准备工作

本文是针对之前写的一篇博客系统项目实现了一个评论的扩展功能.

1.1 创建评论表

create table commentinfo(cid bigint auto_increment primary key comment '评论表的主键',aid bigint not null comment '文章表id',uid bigint not null comment '用户id',`content` varchar(500) not null comment '评论正文',createtime timestamp default CURRENT_TIMESTAMP() comment '评论的发表时间'
);

 1.2 创建评论实体类

@Data
@TableName("commentinfo")
public class CommentInfo implements Serializable {@TableId(type= IdType.AUTO)private long cid;private long aid;private long uid;private String content;private LocalDateTime createtime;
}

 1.3 创建 mapper 层评论接口和对应的 xml  实现

public interface CommentInfoMapper extends BaseMapper<CommentInfo> {
}

此处使用了 MyBatis-Plus 框架, 也可以使用之前的 MyBatis 框架, 根据个人喜好.

MyBatis-Plus 依赖:

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version>
</dependency>

 对应的 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.example.demo.mapper.CommentInfoMapper"></mapper>

1.4 准备评论的 service 层

service 接口的定义:

public interface ICommentInfoService extends IService<CommentInfo> {List<CommentInfoVo> getList(Integer aid);
}

service 接口的实现类:

@Service
public class CommentInfoServiceImpl extends ServiceImpl<CommentInfoMapper, CommentInfo> implements ICommentInfoService  {}

1.5 准备评论的 controller 层

@RestController
@RequestMapping("/comment")
public class CommentController {@Resourceprivate ICommentInfoService commentInfoService;
}

2. 总的初始化详情页

// 总的初始化详情页
function initPage() {// 初始化文章详情页initArtDetail();// 加载评论列表initComment();// 更新访问量updateCount();// 查询当前登录用户的信息 getSessionUser();
}
initPage();

详情页要做的事情有 :

  • 加载博客详情 (博客标题, 发布时间, 博客访问量, 博客正文)  - 参考之前的文章
  • 加载当前博客对应作者的身份信息(username, 文章数量) - 参考之前的文章
  • 加载博客下面的评论
  • 加载当前登录人的名字到评论框下面 (登录了才能评论)

对于第四条 "加载当前登录人的名字到评论框下面" >

如果用户没有登录, 那么展示效果如下 : 

 如果用户登录了, 那么展示效果如下 :

2.1 加载评论列表

我们在查看博客详情页的时候, 如果该文章下面有评论, 则需要查询数据库将评论列表查询出来, 并且倒序排序展示出来.

完善前端页面blog_content.html : 

<!-- 右侧内容详情 -->
<div class="container-right"><div class="blog-content"><!-- 博客标题 --><h3 id="title"></h3><!-- 博客时间 --><div class="date"><span id="createtime"></span> &nbsp;&nbsp;&nbsp;&nbsp;访问量: <span id="rcount"></span></div><!-- 博客正文 --><div id="editorDiv"></div><hr><div><h3 style="text-align: left;margin: 30px 0 0 0;">评 论</h3><div id="addcomment"><div><textarea id="comment_content" placeholder="此处输入评论内容"style="text-align: left;width: 25%;height: 80px;"></textarea></div><div><span id="comment_login_name" style="margin-left: 10px;">请先登录</span>:<input type="button" value="发表评论" class="btn" onclick="addComment()"style="margin-left: 20px;margin-top: 10px;"></div></div><h4 id="commentCount" style="margin-left: 300px;margin-top: 30px;"></h4><br><div id="commentlist" style="margin-left: 300px;"></div></div></div>
</div>

从前边的页面可以看出, 加载评论列表既包含了评论内容, 又包含了当前登录人, 所以需要使用到多表联查, 一旦涉及到了多表联查, 那么使用 MyBatis-Plus 就没有 MyBatis 那么方便了. 其次, 我们可以看到评论后面带有删除按钮, 因为这篇文章属于张三的, 所以他可以管理他文章下面的评论, 而如果这篇文章不属于张三, 那么他查看详情页就不能显示删除按钮.

2.1.1 实现前端代码

因为是要获取当前文章下的评论, 所以需要传递一个文章 ID 给后端, 文章 ID 从哪来呢, 博客详情页的 URL 中带有对应文章的 aid, 所以可以从 URL 中取.

function getURLParam(key) {var params = location.search;  // query stringif(params.indexOf("?") >= 0) {params = params.substring(1);// 键值对之间使用 & 分割var paramArr = params.split('&');for(var i = 0; i < paramArr.length; i++) {// 键和值使用 = 分割var namevalues = paramArr[i].split("=");if(namevalues[0] == key) {return namevalues[1];}}} else {return "";}
}

从 URL 中获取文章 ID 在多个方法中都需要使用到, 我们可以将其封装成一个工具方法, 再通过 src 属性引入.

前端实现代码 : 

var aid = getURLParam("id"); // 文章ID// 加载评论列表
function initComment() {jQuery.ajax({url:"/comment/list",type:"GET",data:{"aid":aid},success:function(body) {if(body.code==200 && body.data!=null) {var commentListHtml = "";for(let comment of body.data) {commentListHtml += '<div style="margin-bottom: 26px;">';commentListHtml += comment.username + ':' + comment.content;commentListHtml += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a class="comment_del_class" style="display: none;text-decoration:none; color:grey;" href="javascript:del(' +comment.cid + ')">删除</a>';commentListHtml += '</div>';}jQuery("#commentlist").html(commentListHtml);var commentCount = body.data.length;jQuery("#commentCount").html('共 ' + commentCount + ' 条评论');}}});
}initComment();

【注意】此处要给删除按钮添加 display: none 样式, 后续在 getSessionUser() 方法中会做判断, 判断当前登录人和文章作者是否为同一个人, 如果是则显示删除按钮, 如果不是, 则不显示删除按钮.

2.1.2 实现后端代码

因为本次查询需要包含一个 username, 而基础的评论 model 类是没有的, 所以可以新增一个commentinfo 的扩展类 - model.vo.CommentInfoVo.java

@Data
public class CommentInfoVo extends CommentInfo implements Serializable {private String username;
}

写 mapper 层 :

List<CommentInfoVo> getList(@Param("aid")Integer aid);

对应的 xml 实现 :

<select id="getList" resultType="com.example.demo.model.vo.CommentInfoVo">SELECT c.*,u.username from commentinfo cleft join userinfo u on c.uid=u.idwhere c.aid=#{aid}order by c.cid desc
</select>

写 service 层 :

ICommentInfoService

List<CommentInfoVo> getList(Integer aid);

CommentInfoServiceImpl

@Resource
private CommentInfoMapper commentInfoMapper;@Override
public List<CommentInfoVo> getList(Integer aid) {return commentInfoMapper.getList(aid);
}

写 controller 层:

/*** 获取评论列表* @param aid* @return*/
@RequestMapping("/list")
public Object getCommentList(Integer aid) {// 1. 参数效验if(aid==null || aid<=0) {return AjaxResult.fail(-1, "参数有误!");}// 2. 查询数据库List<CommentInfoVo> commentResult = commentInfoService.getList(aid);// 3. 就结果返回给前端return AjaxResult.success(commentResult);
}

后端接口 /comment/list 记得要在拦截器里边放行, 如果拦截了, 那么用户不登录, 就看不到评论了, 我们想要的效果是: 

就算用户不登录, 所有人的的博客列表页查看文章详情的时候, 也能看到评论, 只是不管文章归属人是谁都不会显示删除按钮. 

2.2 查询当前登录用户的信息

这个接口是为了实现 : 用户登录了才能发表评论,用户未登录,就显示请先登录.

2.2.1 实现前端代码

var islogin = false; // 是否登录 [是否能发表评论]// [是否要显示发表评论的人的名字] 获取当前登录的用户
function getSessionUser() {jQuery.ajax({url: "/user/myinfo",type: "GET",data: {},success: function (body) {if (body.code == 200 && body.data != null && body.data.id >= 0) {// 当前用户已经登录islogin = true;jQuery("#comment_login_name").html(body.data.username);  // 评论人// 判断当前文章是否是当前登录用户发表的, 如果是就显示删除按钮isArtByMe(aid); } else {// 当前用户未登录}}});
}// [是否要显示评论删除按钮] -  判断当前文章是否属于当前登录用户
function isArtByMe(aid) {jQuery.ajax({url: "/user/isartbyme",type: "GET",data: {"aid": aid},success: function (res) {if (res.code == 200 && res.data == 1) {// 当前文章归属于当前登录用户jQuery(".comment_del_class").each(function (i) {jQuery(this).show();});}}});
}

getSessionUser() 方法的作用是控制登录了才能发表评论,未登录不能发表评论.

isArtByMe()  方法的作用是控制是否显示删除按钮.

【注意】此处最好将 isArtByMe() 方法写在 getSessionUser() 中 ajax 最后, 如果写在外面, 会出现这样一个问题 : 

" 因为同一个页面下面的 ajax  请求的执行顺序是不一定的, 那么就有可能先执行了控制是否显示删除按钮的 ajax, 再执行 getSessionUser(), 如果是这样, 那么这篇文章属不属于当前登录人, 都不会显示删除按钮, 没有登录怎么判断文章归属人是吧."

所以 isArtByMe() 方法要在 getSessionUser() 方法执行后再调用.

2.2.2 实现后端代码

这部分代码比较简单, 实现 controller 层后, 后面的 mapper,xml,service 照猫画虎都能实现好.

// 获取登录人的身份信息
@RequestMapping("/myinfo")
public Object myInfo(HttpServletRequest request) {// 从 session 工具类中拿用户登录信息UserInfo userInfo = SessionUtil.getLoginUser(request);if (userInfo == null || userInfo.getId() <= 0) {return AjaxResult.fail(-2, "当前用户未登录!");}return AjaxResult.success(userInfo);
}@RequestMapping("/isartbyme")
public Object isArtByMe(Integer aid, HttpServletRequest request) {if(aid == null || aid <= 0) {return AjaxResult.fail(-1, "参数有误! ");}UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo == null || userInfo.getId() <= 0) {return AjaxResult.fail(-2, "当前用户未登录! ");}ArticleInfo articleInfo = articleService.getById(aid);if(articleInfo != null && articleInfo.getId() >= 0&& articleInfo.getUid() == userInfo.getId()) {// 文章归属于当前登录人return AjaxResult.success(1);}return AjaxResult.success(0);
}

此处的 /user/myinfo 和 /user/isArtByMe 接口都需要在拦截器里边放行. 

3. 实现发表评论

3.1 实现前端代码

给 "发表评论按钮" 加上点击事件:

// 添加评论
function addComment() {// 拿到评论正文var comment_content = jQuery("#comment_content");// 1.非空效验if(comment_content.val().trim() == "") {alert("请输入你的评论! ");comment_content.focus();return false;}// 2.登录判断if(!islogin) {alert("您还未登录, 请先登录! ");return false;}// 3.将前端数据发送给后端//   3.1 文章 id//   3.2 评论内容jQuery.ajax({url:"comment/add",type:"POST",data:{"aid":aid,"content":comment_content.val()},// 4.将后端返回的数据显示给用户success:function(body) {if(body.code==200 && body.data==1) {alert("评论已发表");// 刷新当前页面location.href = location.href;} else {alert("评论发表失败: " + body.msg);}}});
}

3.2 实现后端代码

/*** 发表评论* @return*/
@RequestMapping("/add")
public Object add(Long aid, String content, HttpServletRequest request) {// 1.参数效验if(aid == null || aid == 0 || !StringUtils.hasLength(content)) {// 非法参数return AjaxResult.fail(-1, "非法参数");}// 2.组装数据UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo == null || userInfo.getId() <= 0) {return AjaxResult.fail(-2, "请先登录! ");}CommentInfo commentInfo = new CommentInfo();commentInfo.setAid(aid);commentInfo.setContent(content);commentInfo.setUid(userInfo.getId());// 3.将评论对象插入数据库boolean result = commentInfoService.save(commentInfo);// 4.将数据库执行结果返回给前端return AjaxResult.success(result ? 1 : 0);
}

发表评论的路由 /comment/add 需要在拦截器里边配置拦截, 因为只有登录了才能发表评论.

4. 实现删除评论

4.1 实现前端代码

// 删除评论
function del(cid) {if(!confirm("确定删除")) {return false;}// 1.参数效验if(cid == "" || cid <= 0) {alert("抱歉: 操作失败, 请刷新页面后重试! ");return false;}if(aid == "" || aid <= 0) {alert("抱歉: 删除评论失败, 请刷新页面后重试! ");return false;}// 2.发送数据给后端(aid,cid)jQuery.ajax({url:"/comment/del",type:"POST",data:{"cid":cid,"aid":aid},success:function(body) {if(body.code == 200 && body.data == 1) {alert("评论删除成功! ");// 刷新当前页面location.href = location.href;} else {alert("抱歉, 评论删除失败! " + body.msg);}}});
}

想要删除评论, 那么至少得传递两个参数给后端, 一个是 cid (评论 ID), 一个是 aid (文章 ID), cid 在前边加载评论列表的时候, 已经在返回数据 body 中的 comment 对象中拿到了, aid 呢 ,前面已经调用了工具方法 getURLParam 获取并保存 aid 全局变量中, 因此也可以直接拿到.

【注意】虽然是否能够删除评论需要拿着 aid 查询出具体的 articleinfo 对象, 再拿着这个对象的 uid 和登录人的 uid 进行比较, 相同才可以删除评论. 但是此处不能将 uid (用户人的 ID) 通过 ajax 发给后端, 一旦 uid 通过参数来接受登录人的 ID 了, 那么就有被篡改的风险, 别人可以写一个接口绕过你的 ajax 直接访问后端接口 (例如: postman), 这样就非常不安全, 所以 uid 可以从后端的 session 中获取.

4.2 实现后端代码

/*** 删除评论* @param cid* @param aid* @return*/
@RequestMapping("/del")
public Object del(Long cid, Long aid, HttpServletRequest request) {// 1.参数效验if(cid == null || cid <= 0 || aid == null || aid <= 0) {// 非法参数return AjaxResult.fail(-1, "非法参数! ");}// 2.效验权限UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo == null || userInfo.getId() <= 0) {// 无效登录return AjaxResult.fail(-2, "请先登录! ");}// 拿到当前文章对应的作者身份信息 (uid)ArticleInfo articleInfo = articleService.getById(aid);if(articleInfo == null || articleInfo.getId() <= 0) {return AjaxResult.fail(-3, "非法的文章ID! ");}// 如果文章对应的 uid 和 session 中的 uid 不一致, 则不能删除if(articleInfo.getUid() != userInfo.getId()) {return AjaxResult.fail(-4, "非法操作! ");}boolean result = commentInfoService.removeById(cid);return AjaxResult.success(result ? 1 : 0);
}

删除评论的路由 /comment/del 也是需要在拦截器里边添加拦截的, 因为登陆之后才能删除评论.

另外评论表中还有一个发表评论时间的字段, 由于我个人是前端小白, 不知道如何将发表评论时间更好的展示在评论列表那里, 于是我就干脆不展示了, 根据个人喜好来实现即可.


至此, 评论功能就全部实现了~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/17933.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自然语言处理从入门到应用——LangChain:提示(Prompts)-[提示模板:部分填充的提示模板和提示合成]

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 部分填充的提示模板 提示模板是一个具有.format方法的类&#xff0c;它接受一个键值映射并返回一个字符串&#xff08;一个提示&#xff09;&#xff0c;以传递给语言模型。与其他方法一样&#xff0c;将提示模板进行…

Android Notification的使用

在AndroidManifest.xml中添加权限 <uses-permission android:name"android.permission.POST_NOTIFICATIONS" />常用通知的写法&#xff0c;兼容android 8.0之后的版本 private NotificationManager getNotificationManager() {return (NotificationManager) g…

自然语言处理从入门到应用——LangChain:提示(Prompts)-[提示模板:创建自定义提示模板和含有Few-Shot示例的提示模板]

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 创建自定义提示模板 假设我们希望LLM根据函数名称生成该函数的英文语言解释。为了实现这个任务&#xff0c;我们将创建一个自定义的提示模板&#xff0c;以函数名称作为输入&#xff0c;并格式化提示模板以提供函数的…

elasticsearch 安装

下载 https://www.elastic.co/downloads/past-releases/elasticsearch-7-8-0 1 解压 tar xf elasticsearch-7.8.0.tgz -C /usr/local/ 2 普通用户运行 elasticsearch的启动方式有点特殊&#xff0c;只能以普通用户的方式运维 useradd elasticsearch chown -R elasticsearc…

list与流迭代器stream_iterator

运行代码&#xff1a; //list与流迭代器 #include"std_lib_facilities.h" //声明Item类 struct Item {string name;int iid;double value;Item():name(" "),iid(0),value(0.0){}Item(string ss,int ii,double vv):name(ss),iid(ii),value(vv){}friend ist…

Redis的安装部署以及基本的使用

目录 一、Linux下直接安装Redis &#xff08;1&#xff09;下载Redis安装包 &#xff08;2&#xff09;安装GCC编译环境 &#xff08;3&#xff09;安装Redis &#xff08;4&#xff09;服务启动 &#xff08;5&#xff09;后台启动 二、使用Docker安装部署Redis &…

mysql主从复制原理及应用

一、主从复制简介 MySQL主从复制是一种异步、基于日志的、单向的数据库复制技术&#xff0c;它通过在主服务器上启用二进制日志并将其发送给一个或多个从服务器&#xff0c;实现了从服务器与主服务器之间的数据同步。主服务器将所有的数据库操作记录到二进制日志中&#xff0c…

2023年第四届“华数杯”数学建模思路 - 案例:异常检测

文章目录 赛题思路一、简介 -- 关于异常检测异常检测监督学习 二、异常检测算法2. 箱线图分析3. 基于距离/密度4. 基于划分思想 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 一、简介 – 关于异常检测 异常…

云原生之深入解析如何在Kubernetes下快速构建企业级云原生日志系统

一、概述 ELK 是三个开源软件的缩写&#xff0c;分别表示 Elasticsearch , Logstash, Kibana , 它们都是开源软件。新增了一个 FileBeat&#xff0c;它是一个轻量级的日志收集处理工具 (Agent)&#xff0c;Filebeat 占用资源少&#xff0c;适合于在各个服务器上搜集日志后传输…

Django使用用户列表的展示和添加

接着上一篇&#xff1a;https://blog.csdn.net/javascript_good/article/details/132027702 来实现用户表的查询和添加 1、创建数据库表 在models.py 中&#xff0c;增加UserInfo类&#xff0c;包括字段姓名、密码、年龄、账号余额、入职时间、所属部门、性别 verbose_name 就…

2023年7月文章一览

受台风“杜苏芮”的残余环流影响&#xff0c;雨从7月下到了8月。2023年7月总共更新了4篇文章&#xff0c;如下&#xff1a; 1.Visual Studio Code错误&#xff1a;Cannot build and debug because the … 2.C语言实现解一元二次方程 3.《Programming Abstractions In C》阅读笔…

解决AttributeError: ‘DataParallel‘ object has no attribute ‘xxxx‘

问题描述 训练模型时&#xff0c;分阶段训练&#xff0c;第二阶段加载第一阶段训练好的模型的参数&#xff0c;接着训练 第一阶段训练&#xff0c;含有代码 if (train_on_gpu):if torch.cuda.device_count() > 1:net nn.DataParallel(net)net net.to(device)第二阶段训练…

STM32 LoRa(学习二)

LoRa关键参数说明 LoRa数据包由三个部分组成&#xff1a;前导码、可选报头、数据有效负载。 前导码&#xff1a;用于保持接收机与输入的数据流同步。默认情况下&#xff0c;数据包含有12个符号长度的前导码。前导码是一个可以通过编程来设置的变量&#xff0c;所以前导码的长度…

台式机/工控机通过网线共享笔记本电脑无线网络(待续)

1、 将台式机通过网线和笔记本连接。 2、 将笔记本的“本地连接”和“无线网络连接”的ipv4均设置为自动获取。 4.修改台式机的IP地址为如下&#xff08;对应笔记本信息&#xff09; IP地址为192.168.XXX.12 子网掩码为255.255.255.0 默认网关为192.168.XXX.1 首选DNS为192.16…

git | git使用心得记录

公司里项目最近使用Git进行协作开发&#xff0c;总结一下使用心得 一、第一次用git&#xff0c;完全同步最新代码checkout 按照以下步骤操作 1、git init 2、git remote add origin 远程仓库的地址https://gitlab.xxxx.com.cn/xx/xx/xxx/Android/baseline/x.x.x.git(远程仓库…

VB客运中心汽车售票管理系统设计与实现

摘 要:该系统是信息管理系统在售票管理方面的一个分支和具体运用,是为长治客运中心而设计的管理售票、车次、票价及客票收入统计等日常事物的系统。此系统选择Visual Basic 6.0作为开发工具来实现客运中心汽车售票所要求的各种功能。本文主要介绍了开发此管理系统的背景、必要…

【linux 搜索】linux中常见搜索命令用法

文章目录 1 find命令2 grep命令3 locate命令4 which命令5 whereis命令6 find和grep组合 在Linux中&#xff0c;您可以使用多种方法来搜索文件和内容。以下是一些常用的搜索方法介绍&#xff1a; 1 find命令 find命令用于在文件系统中搜索文件和目录。它的基本语法如下&#xf…

Git详解和命令大全

目录 一、Git 的基本概念二、Git 的安装和使用三、Git 的版本分支管理四、Git 的命令大全1. 常用命令2. 命令大全 五、版本分支管理的最佳实践六、Git 实践七、高级特性八、Git 的未来发展 Git 是一款开源的分布式版本控制系统&#xff0c;可以有效地处理从小到非常大的项目版本…

解密Redis:应对面试中的缓存相关问题

文章目录 1. 缓存穿透问题及解决方案2. 缓存击穿问题及解决方案3. 缓存雪崩问题及解决方案4. Redis的数据持久化5. Redis的过期删除策略和数据淘汰策略6. Redis分布式锁和主从同步7. Redis集群方案8. Redis的数据一致性保障和高可用性方案 导语&#xff1a; 在面试过程中&#…

HCIP中期考试实验

考试需求 1、该拓扑为公司网络&#xff0c;其中包括公司总部、公司分部以及公司骨干网&#xff0c;不包含运营商公网部分。 2、设备名称均使用拓扑上名称改名&#xff0c;并且区分大小写。 3、整张拓扑均使用私网地址进行配置。 4、整张网络中&#xff0c;运行OSPF协议或者BGP…