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

news2024/9/23 23:35:38

目录

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, "请先登录! ");
    }
    // 3.将评论对象插入数据库
    CommentInfo commentInfo = new CommentInfo();
    commentInfo.setAid(aid);
    commentInfo.setContent(content);
    commentInfo.setUid(userInfo.getId());
    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.coloradmin.cn/o/806752.html

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

相关文章

SFP6012A-ASEMI代理海矽美快恢复二极管参数、尺寸、规格

编辑&#xff1a;ll SFP6012A-ASEMI代理海矽美快恢复二极管参数、尺寸、规格 型号&#xff1a;SFP6012A 品牌&#xff1a;ASEMI 封装&#xff1a;TO-247AC 恢复时间&#xff1a;100ns 正向电流&#xff1a;60A 反向耐压&#xff1a;1200V 芯片大小&#xff1a;102MIL*2…

苍穹外卖day-04

苍穹外卖day-04 本项目学自黑马程序员的《苍穹外卖》项目&#xff0c;是瑞吉外卖的Plus版本 功能更多&#xff0c;更加丰富。 结合资料&#xff0c;和自己对学习过程中的一些看法和问题解决情况上传课件笔记 视频&#xff1a;https://www.bilibili.com/video/BV1TP411v7v6/?sp…

一、window安装vagrant

篇章一、window安装vagrant 前言 在日常的学习中&#xff0c;需要在Window中学习Linux相关的操作命令&#xff0c;在本地熟悉Linux服务器环境&#xff0c;因此需要在电脑中安装Vagrant虚拟机来管理所需安装的Linux系统&#xff08;也就是后续的Centos-7&#xff09;。 1、下…

无涯教程-jQuery - Bounce方法函数

弹跳效果可以与effect()方法一起使用。这会在垂直或水平方向多次反弹元素。 Bounce - 语法 selector.effect( "bounce", {arguments}, speed ); 这是所有参数的描述- direction - 效果的方向。可以是"上(up)"&#xff0c;"下(down)"&#xf…

Debian9离线安装docker

1. 前言 在服务器禁止外网访问的情况下&#xff0c;无法通过apt-get install安装docker&#xff0c;使得docker安装变得异常曲折 本地下载安装包&#xff0c;scp到服务器通过dpkg -i 手动安装&#xff0c;启动docker服务失败… … 各种坑&#xff0c;猛男也要落泪 &#x1f92…

基于IAP的嵌入式系统在线编程设计(学习)

摘要&#xff1a;为了实现嵌入式系统程序的在线升级&#xff0c;提出一种基于IAP在线编程的程序更新方法。 以STM32L431控制器为例&#xff0c;该方法对控制器的片内FLASH进行区域划分&#xff0c;分别存放引导程序、执行程序及待更新程序。 系统通过运行引导程序将待更新程序…

飞行动力学-第15节-part2-松杆中性点 之 基础点摘要

飞行动力学-第15节-part2-松杆中性点 之 基础点摘要 1. 松杆中性点2. 松浮角2. 杆力梯度3. 参考资料 1. 松杆中性点 stick fixed&#xff1a; N 0 N_0 N0​&#xff0c;握杆&#xff0c;升降舵固定stick free&#xff1a; N 0 ′ N_0 N0′​&#xff0c;松杆&#xff0c;升降舵…

linux -网络编程一网络基本概念和Socket编程

目录 1 网络基础概念 1.1 协议 1.2分层模型 1.3 数据通信过程 1.4 网络应用程序的设计模式 1.5 以太网帧格式 1.6网络名词术语解析(自行阅读扫盲) 2 SOCKET编程 2.1 socket编程预备知识 2.2 socket编程主要的API函数介绍 目标&#xff1a; 了解OSI七层、TCP/IP四层模…

论文浅尝 | 预训练Transformer用于跨领域知识图谱补全

笔记整理&#xff1a;汪俊杰&#xff0c;浙江大学硕士&#xff0c;研究方向为知识图谱 链接&#xff1a;https://arxiv.org/pdf/2303.15682.pdf 动机 传统的直推式(tranductive)或者归纳式(inductive)的知识图谱补全(KGC)模型都关注于域内(in-domain)数据&#xff0c;而比较少关…

Centos7 安装man中文版手册

查找man中文安装包&#xff1a; yum search man-pages 安装man-pages-zh-CN.noarch: yum install -y man-pages-zh-CN.noarch

飞行动力学 - 第15节-part 1-操纵力与铰链力矩 之 基础点摘要

飞行动力学 - 第15节-part 1-操纵力与铰链力矩 之 基础点摘要 1. HOTAS全拼2. 操纵杆力&铰链力矩3. 铰链力矩4. 气动补偿&#xff08;Aerodynamic Balancing&#xff09;5. 参考资料 1. HOTAS全拼 Hands On Throttle And Stick 2. 操纵杆力&铰链力矩 操纵杆力&#…

JGJ59-2011建筑施工安全检查标准

为科学评价建筑施工现场安全生产&#xff0c;预防生产安全事故的发生&#xff0c;保障施工人员的安全和健康&#xff0c;提高施工管理水平&#xff0c;实现安全检查工作的标准化&#xff0c;制定本标准。 本标准适用于房屋建筑工程施工现场安全生产的检查评定。 建筑施工安全…

jenkins 配置git

在linux 中输入 保证git 安装成功 git --version使用查看git 安装目录&#xff08;非源码安装直接用yum 安装的&#xff09; which gitjenkins 中到 系统管理–>全局工具配置–> Git installations 新建一个项目 选择自由风格 源码管理选择 git 如果使用的是码云&a…

Python不是一门伟大的语言

作为一门简洁易用、生态蓬勃且具有高泛用性的编程语言&#xff0c;Python一直以来都被不少人称作“编程语言中的瑞士军刀”。 尤其随着近来AI热潮席卷全球&#xff0c;Python在编程语言圈中的地位也随之水涨船高&#xff0c;甚至一度被视作AI专用语言或大数据专用语言。 然而…

螺旋矩阵 II

给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]] 示例 2&#xff1a; 输入&#xff1a;n 1 输出&a…

九耶:冯·诺伊曼体系

冯诺伊曼体系&#xff08;Von Neumann architecture&#xff09;是一种计算机体系结构&#xff0c;它由匈牙利数学家冯诺伊曼于1945年提出。冯诺伊曼体系是现代计算机体系结构的基础&#xff0c;几乎所有的通用计算机都采用了这种体系结构。 冯诺伊曼体系的核心思想是将计算机硬…

【雕爷学编程】Arduino动手做(175)---机智云ESP8266开发板模块2

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

设计模式-备忘录模式在Java中使用示例-象棋悔棋

场景 备忘录模式 备忘录模式提供了一种状态恢复的实现机制&#xff0c;使得用户可以方便地回到一个特定的历史步骤&#xff0c;当新的状态无效 或者存在问题时&#xff0c;可以使用暂时存储起来的备忘录将状态复原&#xff0c;当前很多软件都提供了撤销(Undo)操作&#xff0…

CSDN周赛65期简要题解

最近几期周赛里&#xff0c;貌似 Python 又变成 C 站的亲儿子了。输入形式是列表还不过瘾&#xff0c;现在输出形式也要求是列表&#xff0c;而且是连一个逗号、空格、中括号都不能少的 Python 标准列表形式。虽然对 Python 来说是信手拈来&#xff0c;但总要考虑一下其他编程语…

建木-进阶使用-自动化部署-流程自动化部署-节点化部署-将jar包运行在指定的服务器上-ssh文件书写-docker镜像创建

阿丹&#xff1a; 建木的搭建以及在jar包中的dockerfile文件的书写。已经完毕。现在开始使用建木的流程化部署来完成自动化部署。 进入我们自己配置的建木页面 新建一个分组&#xff0c;方便我们不同项目的管理 起名要求见名知意 使用图形项目 开始新建一个流程&#xff0c;…