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

2023-11-01

目录

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 {
    @Resource
    private 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 string
    if(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 c
        left join userinfo u on c.uid=u.id
        where 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 也是需要在拦截器里边添加拦截的, 因为登陆之后才能删除评论.

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


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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

随机推荐

  • MATLAB实现doc文件的批量改名

    对于一个文件夹中的多个doc文件进行批量改名 下图中是笔者从学生那里收上来的记录表 说了要统一命名也没人听 我又懒得一个个改 只好费点时间编程了 两种实现的思路 一是从旧文件名中选取特定位置的字符 组成新的文件名 要求文件名有固定的位置 比
  • Swift之 ? 和 !

    04 June 2014 Swift语言使用var定义变量 但和别的语言不同 Swift里不会自动给变量赋初始值 也就是说变量不会有默认值 所以要求使用变量之前必须要对其初始化 如果在使用变量之前不进行初始化就会报错 var stringV
  • 产品设计七大定律

    Alan Cooper 交互设计之父 除非有更好的选择 否则就遵从标准 许多设计准则都基于人类心理学 人们如何感知 学习 推理 记忆 以及把意图转换为行动 菲茨定律 菲茨定律用来预测某点到目标位置所需要时间的数学模型 在页面中 大而近的目标
  • 共模电感

    一 背景 关电源会产生以下两类噪声 共模和差模 差模噪声 图a 的传播途径和输入电流相同 共模噪声 图b 表现为彼此相等且同相的噪声 其传播途径经绕组与地线相连 本文主要讲解抑制共模的共模电感的磁芯选择 二 共模电感的抑制原理 电感器对高频
  • Qt5 Qstring::asprintf(“%.3f“, a)精度问题,有时四舍五入,有时直接丢弃。

    问题描述 提示 这里描述具体问题 在Qt5 12开发软件时发现 用Qstring asprintf 3f a 这个函数做精度控制 有时直接四舍五入 有时直接将精度后面的数据拿掉 例如 Qstring asprintf 3f a 四舍五入 f
  • 机器学习算法 决策树

    文章目录 一 决策树的原理 二 决策树的构建 2 1 ID3算法构建决策树 2 2 C4 5 算法树的构建 2 3 CART 树的创建 三 决策树的优缺点 一 决策树的原理 决策树 Decision Tree 是一种非参数的有监督学习方法
  • 10.12黄金原油资讯直通车,黄金原油区间震荡后市操作建议

    黄金消息面与技术面解析 消息面 本周显然又是一个 超级周 数据方面 将迎来中国CPI PPI数据和进出口数据 美国将公布CPI PPI 零售销售等重磅经济数据 风险事件方面 OPEC EIA和IEA都将公布原油市场月度报告 美联储多位票委和
  • eslint+prettier+vue3格式化

    项目里面安装并配置eslint 参考官网执行如下命令 npm init eslint config 等价于 npm install eslint D 安装eslint npx eslint init 初始化配置eslint 执行后会有一些配
  • 【论文笔记】:UnitBox

    Title 2016 ACM MM UnitBox An Advanced Object Detection Network Abstract 传统的目标框含有四个独立的坐标变量 丢失了相互之间的信息 导致AP下降 Unit Box 提出了
  • java操作RabbitMQ

    1 创建虚拟主机 交换机 队列 RabbitMQ提供了自己的管理界面 可以通过管理界面来完成VirtualHost Exchange queue的创建 1 1创建VirtualHost 1 2创建交换机 创建交换机的时候需要指定虚拟主机以及
  • 切换默认python版本(解决ROS中python默认版本为python2的问题)

    1 前言 许多小伙伴在安装完ROS以后 需要基于python3写ROS程序 尤其是部署深度学习算法 但是ROS默认的python版本为python2 导致无法兼容一些基于python3写的算法 有的小伙伴会选择利用anaconda来创建py
  • 蓝桥杯单片机之AT24C02模块的使用

    蓝桥杯单片机之AT24C02时钟模块的使用 简介部分 EEPROM AT24C02 引脚示意 设备地址 Device Address 基本操作 字节写入 分析手册 字节读取 随机读取 根据需要读取的地址进行读取 分析手册 读与写函数代码 实
  • 计算并输出给定正整数n的所有因子(不包括1和自身)之和

    国二有题目 请编写函数fun 该函数的功能是 计算并输出给定正整数n的所有因子 不包括1和自身 之和 规定n的值不大于1000 例如 在主函数中从键盘给n输入的值为856 则输出为 sum 763 代码如何完成呢 分析 1 输入的数字要是整
  • 内网渗透—红日靶场三

    文章目录 0x01 环境配置 0x02 Centos getshell 0x03 Centos提权 0x04 内网穿透 设置路由 0x05 内网穿透 设置代理 0x06 获取内网目标shell 通过smb拿shell 或者本地挂代理使用k8
  • Windows环境下编译C++版的MXNet问题处理

    最近涉及要在c 上部署人脸检测的算法 要在Windows环境下编译运行MXNet 对于不熟悉c 的小白的我真是一件又让人抓狂又掉头发的事情 网上关于c 的部署的帖子少之又少 加上又是第一次摸这些东西 所以出现的bug真的数不胜数 写这个bl
  • 数据结构与算法之二叉树: Leetcode 145. 二叉树的后序遍历 (Typescript版)

    二叉树的后序遍历 https leetcode cn problems binary tree postorder traversal 描述 给你一棵二叉树的根节点 root 返回其节点值的 后序遍历 示例 1 输入 root 1 null
  • 适合普通大学生的前端开发学习路线

    大家好 我是帅地 假如你没有明确的目标 或许可以按照我说的学习路线来学习一波 我写的每一份学习路线 不会很全面 因为我认为 东西列的太多 反而不利于新手的学习 所以我列举的 都是比较必要的知识 当你把这些知识学了的时候 我相信你不需要别人的
  • 前端基础_使用moveTo与lineTo路径绘制火柴人

    使用moveTo与lineTo路径绘制火柴人 接下来看一下除了arc方法以外 其他使用路径绘制图形时会使用到的方法 moveTo x y 不绘制 只是将当前位置移动到新的目标坐标 x y lineTo x y 不仅将当前位置移动到新的目标坐
  • 工控CTF(wp)

    GUET工控CTF 所见非真 异常的流量分析 黑客的攻击 黑客的大意 丢失的数据 凯撒的秘密 工程的秘密 S7协议分析 轻松时刻 打不开的压缩包 失控的遥控 病毒文件分析 OPC协议分析 sign in 随意记录一下这次CTF的解题步骤 比
  • 文章详情页 - 评论功能的实现

    目录 1 准备工作 1 1 创建评论表 1 2 创建评论实体类 1 3 创建 mapper 层评论接口和对应的 xml 实现 1 4 准备评论的 service 层 1 5 准备评论的 controller 层 2 总的初始化详情页 2 1