本文介绍了22.博客系统项目开发之文章编辑模块功能完善求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。
对技术面试,学习经验等有一些体会,在此分享。
Spring Boot 博客系统项目开发之文章编辑模块功能完善
本项目的开源仓库地址为:https://github.com/ZHENFENG13/My-Blog
除 My-Blog 项目外,我也在维护另外一个开源项目,仓库地址为:https://github.com/newbee-ltd/newbee-mall
感兴趣的朋友可以去关注一下。
文章总览
前一个实验中主要介绍了文章编辑器 Editor.md 的整合和使用,一个易用、合适的编辑器对于博客系统来说是非常重要的,尤其是对于一个开源在 GitHub 上的博客系统,因为会有世界各地的 Java 开发者可能会用到它,如果连最基础的编辑器都没有整合完善的话,这个博客系统的生命周期肯定很低,因此花费了一个章节来介绍和实际的整合。文章模块肯定不止编辑器的整合,包括文章表也不止一个内容字段,接下去的实验我们会对文章模块进行分析并作出表结构设计,之后进行编辑页面的制作和功能开发。
知识点
- 博客文章模块表结构设计
- 文章编辑页面制作
- 文章添加功能实现
环境
- JDK 1.8 或者更高版本
- Spring Boot 2.1.0-RELEASE
- Maven 3+
博客文章模块简介
博客 (blog) 一词源于 Web Log (网络日志)的缩写,是一种十分简单的个人信息发布方式,任何人都可以快速完成个人网页的创建、发布和更新。博客就是开放的私人空间,可以充分利用超文本链接、网络互动、动态更新等特点在网络中,精选并链接全球互联网中最有价值的信息、知识与资源,也可以将个人工作过程、生活故事、思想历程、闪现的灵感等及时记录和发布,发挥个人无限的表达力。
写博客的目的是为了记录以及分享,相较于当前流行的传输媒介来说,博客这种媒介形式可以说是历史悠久了,目前已经很少有人使用这种形式来记录和分享自己的日常,现在新的媒介层出不穷,微博、朋友圈、抖音等更符合大部分人的需求,博客这种形式已经日渐式微,写博客这个事情貌似只在互联网从业人员中还有一定的地位,开发人员也比较喜欢使用这种形式去分享和记录自己的知识和经历,对于大部分开发者来说,有一个自己的博客可以发出自己的声音也是一件十分令人兴奋的事情,这种博客暂且把它叫做技术博客吧,我们这次实践开发的项目也是技术博客系统的搭建。
这里简单的列举几个开发者使用频繁的几个博客平台:
- CSDN
- 博客园
- 简书
当然还有其他一些博客网站,这里我就不一一列举了,大家可以去看一下各个博客平台的文章详情页,不论博客这种形式已经出现了多少年,亦或者是不同的博客平台,博客这种表现形式的底层依然是文章,每一篇博客其实都是文章,文章中包括文章标题、文章内容,这两块儿是必不可少的,其他的内容可能因为平台之间的差异而有细微的不同。
表结构设计及 Mapper 文件自动生成
表结构设计
这里以 CSDN 平台的文章编辑模块为例,我们来确定一下文章表的字段设计,编辑模块如下图所示:
通过上图我们可以得出以下字段:
- 文章标题
- 文章内容
- 文章标签
- 文章分类
- 发布状态
以上是字段是博客文章实体应该具有的基础字段,不管是哪个博客平台都会存在这些字段,我们的博客系统上在此基础上增加了几个字段:
- 文章封面图(为了页面美观)
- 阅读量(博客文章的基本字段)
- 是否允许评论(有评论模块,可以控制评论模块的开放和关闭)
文章表的 SQL 设计如下,直接执行如下 SQL 语句即可:
USE `my_blog_db`; DROP TABLE IF EXISTS `tb_blog`; CREATE TABLE `tb_blog` ( `blog_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '博客表主键id', `blog_title` varchar(200) NOT NULL COMMENT '博客标题', `blog_sub_url` varchar(200) NOT NULL COMMENT '博客自定义路径url', `blog_cover_image` varchar(200) NOT NULL COMMENT '博客封面图', `blog_content` mediumtext NOT NULL COMMENT '博客内容', `blog_category_id` int(11) NOT NULL COMMENT '博客分类id', `blog_category_name` varchar(50) NOT NULL COMMENT '博客分类(冗余字段)', `blog_tags` varchar(200) NOT NULL COMMENT '博客标签', `blog_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0-草稿 1-发布', `blog_views` bigint(20) NOT NULL DEFAULT '0' COMMENT '阅读量', `enable_comment` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0-允许评论 1-不允许评论', `is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除 0=否 1=是', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`blog_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
MyBatis-Generator 插件自动生成 Mapper 文件
首先,我们使用 MyBatis-Generator 插件将该表对应的 Mapper 文件及对应的实体类和 Dao 层接口生成出来,这部分代码就不贴在文章中了,大家可以通过下载源码来查看。
需要注意的是,在代码生成后需要在 dao 层下的 BlogMapper 接口类上添加 @Mapper
注解以将其注册到 IOC 容器中以供后续调用(如果已经在主类上添加 @MapperScan
注解这一步可以省略)。其次,在 tb_blog 表中,我们设计了一个 is_deleted 字段,用于逻辑删除的标志位,由于 is_deleted 的字段设计,我们对表中数据的删除都是软删除,因为是个人博客,这么做的目的主要也是为了防止误删,因此我们需要修改 Mapper 文件中的 查询语句和删除语句,将 is_deleted 条件带上,修改后的语句如下:(注:完整代码位于 resources/mapper/BlogMapper.xml)
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="ResultMapWithBLOBs"> select <include refid="Base_Column_List"/> , <include refid="Blob_Column_List"/> from tb_blog where blog_id = #{blogId,jdbcType=BIGINT} and is_deleted = 0 </select> <update id="deleteByPrimaryKey" parameterType="java.lang.Long"> UPDATE tb_blog SET is_deleted = 1 where blog_id = #{blogId,jdbcType=BIGINT} and is_deleted = 0 </update>
通过以上代码我们可以看出,在删除操作时我们并不是执行 delete 语句,而是将需要删除的文章记录的 is_deleted 字段修改为 1,这样就表示该文章已经被执行了删除操作,那么其他的 select 查询语句就需要在查询条件中添加 is_deleted = 0 将“被删除”的记录给过滤出去。
编辑页面完善
接下来,把编辑页面按照字段来完善一下,以前文中 CSDN 平台的文章编辑页面为例,将其他需要输入内容的字段填充到页面 DOM 中,目前编辑页面只有一个编辑框来输入文章字段。某些字段只需要一个 input 框即可,比如文章标题字段,而其他一些字段的输入则需要一些前端插件来完成,比如标签、博客封面图,仅仅是 input 框肯定是无法满足需求的,比如标签字段和分类字段,我们想要的最终效果是下面这样的:
- 标签字段输入
- 分类选择
接下来我们把文章编辑页面完善一下,同时也把这些小插件整合进来。
引入相关依赖
编辑页面中有如下字段需要使用插件来完善交互:
- 标签字段
- 分类字段
- 文章内容字段(已实现)
- 封面图字段
以上字段所需要的插件也是使用的比较常用的开源插件,插件如下:
- tagsinput(标签)
- select2(分类)
- Editor.md(文章内容)
- ajaxupload(图片上传)
引入插件首先需要把这些依赖文件放到 resources/static/admin/plugins 目录下,目录结构如下:
之后在 edit.html 文件中引到页面中,代码如下:(注:完整代码位于 resources/templates/admin/edit.html)
- CSS 文件
<link th:href="@{/admin/plugins/editormd/css/editormd.css}" rel="stylesheet" /> <link th:href="@{/admin/plugins/tagsinput/jquery.tagsinput.css}" rel="stylesheet" /> <link th:href="@{/admin/plugins/select2/select2.css}" rel="stylesheet" />
- JS 文件
<!-- editor.md --> <script th:src="@{/admin/plugins/editormd/editormd.min.js}"></script> <!-- tagsinput --> <script th:src="@{/admin/plugins/tagsinput/jquery.tagsinput.min.js}"></script> <!-- Select2 --> <script th:src="@{/admin/plugins/select2/select2.full.min.js}"></script> <!-- ajaxupload --> <script th:src="@{/admin/plugins/ajaxupload/ajaxupload.js}"></script>
编辑页面代码
根据字段新增对应的输入框以及 DOM 组件,代码如下:
<div class="card-body"> <!-- 几个基础的输入框,名称、分类等输入框 --> <form id="blogForm" onsubmit="return false;"> <div class="form-group" style="display:flex;"> <input type="text" class="form-control col-sm-6" id="blogName" name="blogName" placeholder="*请输入文章标题(必填)" /> <input type="text" class="form-control" id="blogTags" name="blogTags" placeholder="请输入文章标签" style="width: 100%;" /> </div> <div class="form-group" style="display:flex;"> <input type="text" class="form-control col-sm-6" id="blogSubUrl" name="blogSubUrl" placeholder="请输入自定义路径,如:springboot-mybatis,默认为id" /> <select class="form-control select2" style="width: 100%;" id="blogCategoryId" data-placeholder="请选择分类..." > <th:block th:if="${null == categories}"> <option value="0" selected="selected">默认分类</option> </th:block> <th:block th:unless="${null == categories}"> <th:block th:each="c : ${categories}"> <option th:value="${c.categoryId}" th:text="${c.categoryName}"> </option> </th:block> </th:block> </select> </div> <div class="form-group" id="blog-editormd"> <textarea style="display:none;"></textarea> </div> <div class="form-group"> <div class="col-sm-4"> <img id="blogCoverImage" src="/admin/dist/img/img-upload.png" style="height: 64px;width: 64px;" /> </div> </div> <br /> <div class="form-group"> <div class="col-sm-4"> <button class="btn btn-info" style="margin-bottom: 5px;" id="uploadCoverImage" > <i class="fa fa-picture-o"></i> 上传封面 </button> <button class="btn btn-secondary" style="margin-bottom: 5px;" id="randomCoverImage" > <i class="fa fa-random"></i> 随机封面 </button> </div> </div> <div class="form-group"> <label class="control-label">文章状态: </label> <input name="blogStatus" type="radio" id="publish" checked="true" value="1" /> 发布 <input name="blogStatus" type="radio" id="draft" value="0" /> 草稿 <label class="control-label">是否允许评论: </label> <input name="enableComment" type="radio" id="enableCommentTrue" checked="true" value="0" /> 是 <input name="enableComment" type="radio" id="enableCommentFalse" value="1" /> 否 </div> <div class="form-group"> <!-- 按钮 --> <button class="btn btn-info float-right" style="margin-left: 5px;" id="confirmButton" > 保存文章</button > <button class="btn btn-secondary float-right" style="margin-left: 5px;" id="cancelButton" > 返回文章列表</button > </div> </form> </div>
其中文章标题字段和自定义路径字段是直接使用的 input 框,标签字段会使用 tagsinput 插件,分类字段会使用 select2 下来选择框插件,博客封面图则是使用图片上传插件,文章内容的输入使用的是 Editor.md 编辑器,文章状态字段和评论开关字段使用的是 radio 选择框,最下面是两个功能按钮,文章保存和返回按钮。
初始化插件
在 resources/static/admin/dist/js 目录下新增 edit.js 文件,把原来在 edit.html 文件中写的 Editor.md 初始化 js 代码也移到 edit.js 文件中,并添加如下代码:
“`js
var blogEditor;
// Tags Input
$(‘#blogTags’).tagsInput({
width: ‘100%’,
height: ’38px’,
defaultText: ‘文章标签’,
});
//Initialize Select2 Elements
$(‘.select2’).select2();
$(function () {
blogEditor = editormd(‘blog-editormd’, {
width: ‘100%’,
height: 640,
syncScrolling: ‘single’,
path: ‘/admin/plugins/editormd/lib/’,
toolbarModes: ‘full’,
/*图片上传配置/
imageUpload: true,
imageFormats: [‘jpg’, ‘jpeg’, ‘gif’, ‘png’, ‘bmp’, ‘webp’], //图片上传格式
imageUploadURL: ‘/admin/blogs/md/uploadfile’,
onload: function (obj) {
//上传成功之后的回调
},
});
new AjaxUpload(‘#uploadCoverImage’, {
action: ‘/admin/upload/file’,
name: ‘file’,
autoSubmit: true,
responseType: ‘json’,
onSubmit: function (file, extension) {
if (
!(extension && /^(jpg|jpeg|png|gif)$/.test(extension.toLowerCase()))
) {
alert(‘只支持jpg、png、gif格式的文件!’);
return false;
}
},
onComplete: function (file, r) {
if (r != null && r.resultCode == 200) {
$(‘#blogCoverImage’).attr(‘src’, r.data);
$(‘#blogCoverImage’).attr(
‘style’,
‘width: 128px;height: 128px;display:block;’
);
return false;
} else {
Spring Boot 博客系统项目开发之文章编辑模块功能完善
本项目的开源仓库地址为:https://github.com/ZHENFENG13/My-Blog
除 My-Blog 项目外,我也在维护另外一个开源项目,仓库地址为:https://github.com/newbee-ltd/newbee-mall
感兴趣的朋友可以去关注一下。
文章总览
前一个实验中主要介绍了文章编辑器 Editor.md 的整合和使用,一个易用、合适的编辑器对于博客系统来说是非常重要的,尤其是对于一个开源在 GitHub 上的博客系统,因为会有世界各地的 Java 开发者可能会用到它,如果连最基础的编辑器都没有整合完善的话,这个博客系统的生命周期肯定很低,因此花费了一个章节来介绍和实际的整合。文章模块肯定不止编辑器的整合,包括文章表也不止一个内容字段,接下去的实验我们会对文章模块进行分析并作出表结构设计,之后进行编辑页面的制作和功能开发。
知识点
- 博客文章模块表结构设计
- 文章编辑页面制作
- 文章添加功能实现
环境
- JDK 1.8 或者更高版本
- Spring Boot 2.1.0-RELEASE
- Maven 3+
博客文章模块简介
博客 (blog) 一词源于 Web Log (网络日志)的缩写,是一种十分简单的个人信息发布方式,任何人都可以快速完成个人网页的创建、发布和更新。博客就是开放的私人空间,可以充分利用超文本链接、网络互动、动态更新等特点在网络中,精选并链接全球互联网中最有价值的信息、知识与资源,也可以将个人工作过程、生活故事、思想历程、闪现的灵感等及时记录和发布,发挥个人无限的表达力。
写博客的目的是为了记录以及分享,相较于当前流行的传输媒介来说,博客这种媒介形式可以说是历史悠久了,目前已经很少有人使用这种形式来记录和分享自己的日常,现在新的媒介层出不穷,微博、朋友圈、抖音等更符合大部分人的需求,博客这种形式已经日渐式微,写博客这个事情貌似只在互联网从业人员中还有一定的地位,开发人员也比较喜欢使用这种形式去分享和记录自己的知识和经历,对于大部分开发者来说,有一个自己的博客可以发出自己的声音也是一件十分令人兴奋的事情,这种博客暂且把它叫做技术博客吧,我们这次实践开发的项目也是技术博客系统的搭建。
这里简单的列举几个开发者使用频繁的几个博客平台:
- CSDN
- 博客园
- 简书
当然还有其他一些博客网站,这里我就不一一列举了,大家可以去看一下各个博客平台的文章详情页,不论博客这种形式已经出现了多少年,亦或者是不同的博客平台,博客这种表现形式的底层依然是文章,每一篇博客其实都是文章,文章中包括文章标题、文章内容,这两块儿是必不可少的,其他的内容可能因为平台之间的差异而有细微的不同。
表结构设计及 Mapper 文件自动生成
表结构设计
这里以 CSDN 平台的文章编辑模块为例,我们来确定一下文章表的字段设计,编辑模块如下图所示:
通过上图我们可以得出以下字段:
- 文章标题
- 文章内容
- 文章标签
- 文章分类
- 发布状态
以上是字段是博客文章实体应该具有的基础字段,不管是哪个博客平台都会存在这些字段,我们的博客系统上在此基础上增加了几个字段:
- 文章封面图(为了页面美观)
- 阅读量(博客文章的基本字段)
- 是否允许评论(有评论模块,可以控制评论模块的开放和关闭)
文章表的 SQL 设计如下,直接执行如下 SQL 语句即可:
USE `my_blog_db`; DROP TABLE IF EXISTS `tb_blog`; CREATE TABLE `tb_blog` ( `blog_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '博客表主键id', `blog_title` varchar(200) NOT NULL COMMENT '博客标题', `blog_sub_url` varchar(200) NOT NULL COMMENT '博客自定义路径url', `blog_cover_image` varchar(200) NOT NULL COMMENT '博客封面图', `blog_content` mediumtext NOT NULL COMMENT '博客内容', `blog_category_id` int(11) NOT NULL COMMENT '博客分类id', `blog_category_name` varchar(50) NOT NULL COMMENT '博客分类(冗余字段)', `blog_tags` varchar(200) NOT NULL COMMENT '博客标签', `blog_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0-草稿 1-发布', `blog_views` bigint(20) NOT NULL DEFAULT '0' COMMENT '阅读量', `enable_comment` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0-允许评论 1-不允许评论', `is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除 0=否 1=是', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`blog_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
MyBatis-Generator 插件自动生成 Mapper 文件
首先,我们使用 MyBatis-Generator 插件将该表对应的 Mapper 文件及对应的实体类和 Dao 层接口生成出来,这部分代码就不贴在文章中了,大家可以通过下载源码来查看。
需要注意的是,在代码生成后需要在 dao 层下的 BlogMapper 接口类上添加 @Mapper
注解以将其注册到 IOC 容器中以供后续调用(如果已经在主类上添加 @MapperScan
注解这一步可以省略)。其次,在 tb_blog 表中,我们设计了一个 is_deleted 字段,用于逻辑删除的标志位,由于 is_deleted 的字段设计,我们对表中数据的删除都是软删除,因为是个人博客,这么做的目的主要也是为了防止误删,因此我们需要修改 Mapper 文件中的 查询语句和删除语句,将 is_deleted 条件带上,修改后的语句如下:(注:完整代码位于 resources/mapper/BlogMapper.xml)
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="ResultMapWithBLOBs"> select <include refid="Base_Column_List"/> , <include refid="Blob_Column_List"/> from tb_blog where blog_id = #{blogId,jdbcType=BIGINT} and is_deleted = 0 </select> <update id="deleteByPrimaryKey" parameterType="java.lang.Long"> UPDATE tb_blog SET is_deleted = 1 where blog_id = #{blogId,jdbcType=BIGINT} and is_deleted = 0 </update>
通过以上代码我们可以看出,在删除操作时我们并不是执行 delete 语句,而是将需要删除的文章记录的 is_deleted 字段修改为 1,这样就表示该文章已经被执行了删除操作,那么其他的 select 查询语句就需要在查询条件中添加 is_deleted = 0 将“被删除”的记录给过滤出去。
编辑页面完善
接下来,把编辑页面按照字段来完善一下,以前文中 CSDN 平台的文章编辑页面为例,将其他需要输入内容的字段填充到页面 DOM 中,目前编辑页面只有一个编辑框来输入文章字段。某些字段只需要一个 input 框即可,比如文章标题字段,而其他一些字段的输入则需要一些前端插件来完成,比如标签、博客封面图,仅仅是 input 框肯定是无法满足需求的,比如标签字段和分类字段,我们想要的最终效果是下面这样的:
- 标签字段输入
- 分类选择
接下来我们把文章编辑页面完善一下,同时也把这些小插件整合进来。
引入相关依赖
编辑页面中有如下字段需要使用插件来完善交互:
- 标签字段
- 分类字段
- 文章内容字段(已实现)
- 封面图字段
以上字段所需要的插件也是使用的比较常用的开源插件,插件如下:
- tagsinput(标签)
- select2(分类)
- Editor.md(文章内容)
- ajaxupload(图片上传)
引入插件首先需要把这些依赖文件放到 resources/static/admin/plugins 目录下,目录结构如下:
之后在 edit.html 文件中引到页面中,代码如下:(注:完整代码位于 resources/templates/admin/edit.html)
- CSS 文件
<link th:href="@{/admin/plugins/editormd/css/editormd.css}" rel="stylesheet" /> <link th:href="@{/admin/plugins/tagsinput/jquery.tagsinput.css}" rel="stylesheet" /> <link th:href="@{/admin/plugins/select2/select2.css}" rel="stylesheet" />
- JS 文件
<!-- editor.md --> <script th:src="@{/admin/plugins/editormd/editormd.min.js}"></script> <!-- tagsinput --> <script th:src="@{/admin/plugins/tagsinput/jquery.tagsinput.min.js}"></script> <!-- Select2 --> <script th:src="@{/admin/plugins/select2/select2.full.min.js}"></script> <!-- ajaxupload --> <script th:src="@{/admin/plugins/ajaxupload/ajaxupload.js}"></script>
编辑页面代码
根据字段新增对应的输入框以及 DOM 组件,代码如下:
<div class="card-body"> <!-- 几个基础的输入框,名称、分类等输入框 --> <form id="blogForm" onsubmit="return false;"> <div class="form-group" style="display:flex;"> <input type="text" class="form-control col-sm-6" id="blogName" name="blogName" placeholder="*请输入文章标题(必填)" /> <input type="text" class="form-control" id="blogTags" name="blogTags" placeholder="请输入文章标签" style="width: 100%;" /> </div> <div class="form-group" style="display:flex;"> <input type="text" class="form-control col-sm-6" id="blogSubUrl" name="blogSubUrl" placeholder="请输入自定义路径,如:springboot-mybatis,默认为id" /> <select class="form-control select2" style="width: 100%;" id="blogCategoryId" data-placeholder="请选择分类..." > <th:block th:if="${null == categories}"> <option value="0" selected="selected">默认分类</option> </th:block> <th:block th:unless="${null == categories}"> <th:block th:each="c : ${categories}"> <option th:value="${c.categoryId}" th:text="${c.categoryName}"> </option> </th:block> </th:block> </select> </div> <div class="form-group" id="blog-editormd"> <textarea style="display:none;"></textarea> </div> <div class="form-group"> <div class="col-sm-4"> <img id="blogCoverImage" src="/admin/dist/img/img-upload.png" style="height: 64px;width: 64px;" /> </div> </div> <br /> <div class="form-group"> <div class="col-sm-4"> <button class="btn btn-info" style="margin-bottom: 5px;" id="uploadCoverImage" > <i class="fa fa-picture-o"></i> 上传封面 </button> <button class="btn btn-secondary" style="margin-bottom: 5px;" id="randomCoverImage" > <i class="fa fa-random"></i> 随机封面 </button> </div> </div> <div class="form-group"> <label class="control-label">文章状态: </label> <input name="blogStatus" type="radio" id="publish" checked="true" value="1" /> 发布 <input name="blogStatus" type="radio" id="draft" value="0" /> 草稿 <label class="control-label">是否允许评论: </label> <input name="enableComment" type="radio" id="enableCommentTrue" checked="true" value="0" /> 是 <input name="enableComment" type="radio" id="enableCommentFalse" value="1" /> 否 </div> <div class="form-group"> <!-- 按钮 --> <button class="btn btn-info float-right" style="margin-left: 5px;" id="confirmButton" > 保存文章</button > <button class="btn btn-secondary float-right" style="margin-left: 5px;" id="cancelButton" > 返回文章列表</button > </div> </form> </div>
其中文章标题字段和自定义路径字段是直接使用的 input 框,标签字段会使用 tagsinput 插件,分类字段会使用 select2 下来选择框插件,博客封面图则是使用图片上传插件,文章内容的输入使用的是 Editor.md 编辑器,文章状态字段和评论开关字段使用的是 radio 选择框,最下面是两个功能按钮,文章保存和返回按钮。
初始化插件
在 resources/static/admin/dist/js 目录下新增 edit.js 文件,把原来在 edit.html 文件中写的 Editor.md 初始化 js 代码也移到 edit.js 文件中,并添加如下代码:
“`js
var blogEditor;
// Tags Input
$(‘#blogTags’).tagsInput({
width: ‘100%’,
height: ’38px’,
defaultText: ‘文章标签’,
});
//Initialize Select2 Elements
$(‘.select2’).select2();
$(function () {
blogEditor = editormd(‘blog-editormd’, {
width: ‘100%’,
height: 640,
syncScrolling: ‘single’,
path: ‘/admin/plugins/editormd/lib/’,
toolbarModes: ‘full’,
/*图片上传配置/
imageUpload: true,
imageFormats: [‘jpg’, ‘jpeg’, ‘gif’, ‘png’, ‘bmp’, ‘webp’], //图片上传格式
imageUploadURL: ‘/admin/blogs/md/uploadfile’,
onload: function (obj) {
//上传成功之后的回调
},
});
new AjaxUpload(‘#uploadCoverImage’, {
action: ‘/admin/upload/file’,
name: ‘file’,
autoSubmit: true,
responseType: ‘json’,
onSubmit: function (file, extension) {
if (
!(extension && /^(jpg|jpeg|png|gif)$/.test(extension.toLowerCase()))
) {
alert(‘只支持jpg、png、gif格式的文件!’);
return false;
}
},
onComplete: function (file, r) {
if (r != null && r.resultCode == 200) {
$(‘#blogCoverImage’).attr(‘src’, r.data);
$(‘#blogCoverImage’).attr(
‘style’,
‘width: 128px;height: 128px;display:block;’
);
return false;
} else {
Spring Boot 博客系统项目开发之文章编辑模块功能完善
本项目的开源仓库地址为:https://github.com/ZHENFENG13/My-Blog
除 My-Blog 项目外,我也在维护另外一个开源项目,仓库地址为:https://github.com/newbee-ltd/newbee-mall
感兴趣的朋友可以去关注一下。
文章总览
前一个实验中主要介绍了文章编辑器 Editor.md 的整合和使用,一个易用、合适的编辑器对于博客系统来说是非常重要的,尤其是对于一个开源在 GitHub 上的博客系统,因为会有世界各地的 Java 开发者可能会用到它,如果连最基础的编辑器都没有整合完善的话,这个博客系统的生命周期肯定很低,因此花费了一个章节来介绍和实际的整合。文章模块肯定不止编辑器的整合,包括文章表也不止一个内容字段,接下去的实验我们会对文章模块进行分析并作出表结构设计,之后进行编辑页面的制作和功能开发。
知识点
- 博客文章模块表结构设计
- 文章编辑页面制作
- 文章添加功能实现
环境
- JDK 1.8 或者更高版本
- Spring Boot 2.1.0-RELEASE
- Maven 3+
博客文章模块简介
博客 (blog) 一词源于 Web Log (网络日志)的缩写,是一种十分简单的个人信息发布方式,任何人都可以快速完成个人网页的创建、发布和更新。博客就是开放的私人空间,可以充分利用超文本链接、网络互动、动态更新等特点在网络中,精选并链接全球互联网中最有价值的信息、知识与资源,也可以将个人工作过程、生活故事、思想历程、闪现的灵感等及时记录和发布,发挥个人无限的表达力。
写博客的目的是为了记录以及分享,相较于当前流行的传输媒介来说,博客这种媒介形式可以说是历史悠久了,目前已经很少有人使用这种形式来记录和分享自己的日常,现在新的媒介层出不穷,微博、朋友圈、抖音等更符合大部分人的需求,博客这种形式已经日渐式微,写博客这个事情貌似只在互联网从业人员中还有一定的地位,开发人员也比较喜欢使用这种形式去分享和记录自己的知识和经历,对于大部分开发者来说,有一个自己的博客可以发出自己的声音也是一件十分令人兴奋的事情,这种博客暂且把它叫做技术博客吧,我们这次实践开发的项目也是技术博客系统的搭建。
这里简单的列举几个开发者使用频繁的几个博客平台:
- CSDN
- 博客园
- 简书
当然还有其他一些博客网站,这里我就不一一列举了,大家可以去看一下各个博客平台的文章详情页,不论博客这种形式已经出现了多少年,亦或者是不同的博客平台,博客这种表现形式的底层依然是文章,每一篇博客其实都是文章,文章中包括文章标题、文章内容,这两块儿是必不可少的,其他的内容可能因为平台之间的差异而有细微的不同。
表结构设计及 Mapper 文件自动生成
表结构设计
这里以 CSDN 平台的文章编辑模块为例,我们来确定一下文章表的字段设计,编辑模块如下图所示:
通过上图我们可以得出以下字段:
- 文章标题
- 文章内容
- 文章标签
- 文章分类
- 发布状态
以上是字段是博客文章实体应该具有的基础字段,不管是哪个博客平台都会存在这些字段,我们的博客系统上在此基础上增加了几个字段:
- 文章封面图(为了页面美观)
- 阅读量(博客文章的基本字段)
- 是否允许评论(有评论模块,可以控制评论模块的开放和关闭)
文章表的 SQL 设计如下,直接执行如下 SQL 语句即可:
USE `my_blog_db`; DROP TABLE IF EXISTS `tb_blog`; CREATE TABLE `tb_blog` ( `blog_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '博客表主键id', `blog_title` varchar(200) NOT NULL COMMENT '博客标题', `blog_sub_url` varchar(200) NOT NULL COMMENT '博客自定义路径url', `blog_cover_image` varchar(200) NOT NULL COMMENT '博客封面图', `blog_content` mediumtext NOT NULL COMMENT '博客内容', `blog_category_id` int(11) NOT NULL COMMENT '博客分类id', `blog_category_name` varchar(50) NOT NULL COMMENT '博客分类(冗余字段)', `blog_tags` varchar(200) NOT NULL COMMENT '博客标签', `blog_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0-草稿 1-发布', `blog_views` bigint(20) NOT NULL DEFAULT '0' COMMENT '阅读量', `enable_comment` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0-允许评论 1-不允许评论', `is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除 0=否 1=是', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`blog_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
MyBatis-Generator 插件自动生成 Mapper 文件
首先,我们使用 MyBatis-Generator 插件将该表对应的 Mapper 文件及对应的实体类和 Dao 层接口生成出来,这部分代码就不贴在文章中了,大家可以通过下载源码来查看。
需要注意的是,在代码生成后需要在 dao 层下的 BlogMapper 接口类上添加 @Mapper
注解以将其注册到 IOC 容器中以供后续调用(如果已经在主类上添加 @MapperScan
注解这一步可以省略)。其次,在 tb_blog 表中,我们设计了一个 is_deleted 字段,用于逻辑删除的标志位,由于 is_deleted 的字段设计,我们对表中数据的删除都是软删除,因为是个人博客,这么做的目的主要也是为了防止误删,因此我们需要修改 Mapper 文件中的 查询语句和删除语句,将 is_deleted 条件带上,修改后的语句如下:(注:完整代码位于 resources/mapper/BlogMapper.xml)
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="ResultMapWithBLOBs"> select <include refid="Base_Column_List"/> , <include refid="Blob_Column_List"/> from tb_blog where blog_id = #{blogId,jdbcType=BIGINT} and is_deleted = 0 </select> <update id="deleteByPrimaryKey" parameterType="java.lang.Long"> UPDATE tb_blog SET is_deleted = 1 where blog_id = #{blogId,jdbcType=BIGINT} and is_deleted = 0 </update>
通过以上代码我们可以看出,在删除操作时我们并不是执行 delete 语句,而是将需要删除的文章记录的 is_deleted 字段修改为 1,这样就表示该文章已经被执行了删除操作,那么其他的 select 查询语句就需要在查询条件中添加 is_deleted = 0 将“被删除”的记录给过滤出去。
编辑页面完善
接下来,把编辑页面按照字段来完善一下,以前文中 CSDN 平台的文章编辑页面为例,将其他需要输入内容的字段填充到页面 DOM 中,目前编辑页面只有一个编辑框来输入文章字段。某些字段只需要一个 input 框即可,比如文章标题字段,而其他一些字段的输入则需要一些前端插件来完成,比如标签、博客封面图,仅仅是 input 框肯定是无法满足需求的,比如标签字段和分类字段,我们想要的最终效果是下面这样的:
- 标签字段输入
- 分类选择
接下来我们把文章编辑页面完善一下,同时也把这些小插件整合进来。
引入相关依赖
编辑页面中有如下字段需要使用插件来完善交互:
- 标签字段
- 分类字段
- 文章内容字段(已实现)
- 封面图字段
以上字段所需要的插件也是使用的比较常用的开源插件,插件如下:
- tagsinput(标签)
- select2(分类)
- Editor.md(文章内容)
- ajaxupload(图片上传)
引入插件首先需要把这些依赖文件放到 resources/static/admin/plugins 目录下,目录结构如下:
之后在 edit.html 文件中引到页面中,代码如下:(注:完整代码位于 resources/templates/admin/edit.html)
- CSS 文件
<link th:href="@{/admin/plugins/editormd/css/editormd.css}" rel="stylesheet" /> <link th:href="@{/admin/plugins/tagsinput/jquery.tagsinput.css}" rel="stylesheet" /> <link th:href="@{/admin/plugins/select2/select2.css}" rel="stylesheet" />
- JS 文件
<!-- editor.md --> <script th:src="@{/admin/plugins/editormd/editormd.min.js}"></script> <!-- tagsinput --> <script th:src="@{/admin/plugins/tagsinput/jquery.tagsinput.min.js}"></script> <!-- Select2 --> <script th:src="@{/admin/plugins/select2/select2.full.min.js}"></script> <!-- ajaxupload --> <script th:src="@{/admin/plugins/ajaxupload/ajaxupload.js}"></script>
编辑页面代码
根据字段新增对应的输入框以及 DOM 组件,代码如下:
<div class="card-body"> <!-- 几个基础的输入框,名称、分类等输入框 --> <form id="blogForm" onsubmit="return false;"> <div class="form-group" style="display:flex;"> <input type="text" class="form-control col-sm-6" id="blogName" name="blogName" placeholder="*请输入文章标题(必填)" /> <input type="text" class="form-control" id="blogTags" name="blogTags" placeholder="请输入文章标签" style="width: 100%;" /> </div> <div class="form-group" style="display:flex;"> <input type="text" class="form-control col-sm-6" id="blogSubUrl" name="blogSubUrl" placeholder="请输入自定义路径,如:springboot-mybatis,默认为id" /> <select class="form-control select2" style="width: 100%;" id="blogCategoryId" data-placeholder="请选择分类..." > <th:block th:if="${null == categories}"> <option value="0" selected="selected">默认分类</option> </th:block> <th:block th:unless="${null == categories}"> <th:block th:each="c : ${categories}"> <option th:value="${c.categoryId}" th:text="${c.categoryName}"> </option> </th:block> </th:block> </select> </div> <div class="form-group" id="blog-editormd"> <textarea style="display:none;"></textarea> </div> <div class="form-group"> <div class="col-sm-4"> <img id="blogCoverImage" src="/admin/dist/img/img-upload.png" style="height: 64px;width: 64px;" /> </div> </div> <br /> <div class="form-group"> <div class="col-sm-4"> <button class="btn btn-info" style="margin-bottom: 5px;" id="uploadCoverImage" > <i class="fa fa-picture-o"></i> 上传封面 </button> <button class="btn btn-secondary" style="margin-bottom: 5px;" id="randomCoverImage" > <i class="fa fa-random"></i> 随机封面 </button> </div> </div> <div class="form-group"> <label class="control-label">文章状态: </label> <input name="blogStatus" type="radio" id="publish" checked="true" value="1" /> 发布 <input name="blogStatus" type="radio" id="draft" value="0" /> 草稿 <label class="control-label">是否允许评论: </label> <input name="enableComment" type="radio" id="enableCommentTrue" checked="true" value="0" /> 是 <input name="enableComment" type="radio" id="enableCommentFalse" value="1" /> 否 </div> <div class="form-group"> <!-- 按钮 --> <button class="btn btn-info float-right" style="margin-left: 5px;" id="confirmButton" > 保存文章</button > <button class="btn btn-secondary float-right" style="margin-left: 5px;" id="cancelButton" > 返回文章列表</button > </div> </form> </div>
其中文章标题字段和自定义路径字段是直接使用的 input 框,标签字段会使用 tagsinput 插件,分类字段会使用 select2 下来选择框插件,博客封面图则是使用图片上传插件,文章内容的输入使用的是 Editor.md 编辑器,文章状态字段和评论开关字段使用的是 radio 选择框,最下面是两个功能按钮,文章保存和返回按钮。
初始化插件
在 resources/static/admin/dist/js 目录下新增 edit.js 文件,把原来在 edit.html 文件中写的 Editor.md 初始化 js 代码也移到 edit.js 文件中,并添加如下代码:
“`js
var blogEditor;
// Tags Input
$(‘#blogTags’).tagsInput({
width: ‘100%’,
height: ’38px’,
defaultText: ‘文章标签’,
});
//Initialize Select2 Elements
$(‘.select2’).select2();
$(function () {
blogEditor = editormd(‘blog-editormd’, {
width: ‘100%’,
height: 640,
syncScrolling: ‘single’,
path: ‘/admin/plugins/editormd/lib/’,
toolbarModes: ‘full’,
/*图片上传配置/
imageUpload: true,
imageFormats: [‘jpg’, ‘jpeg’, ‘gif’, ‘png’, ‘bmp’, ‘webp’], //图片上传格式
imageUploadURL: ‘/admin/blogs/md/uploadfile’,
onload: function (obj) {
//上传成功之后的回调
},
});
new AjaxUpload(‘#uploadCoverImage’, {
action: ‘/admin/upload/file’,
name: ‘file’,
autoSubmit: true,
responseType: ‘json’,
onSubmit: function (file, extension) {
if (
!(extension && /^(jpg|jpeg|png|gif)$/.test(extension.toLowerCase()))
) {
alert(‘只支持jpg、png、gif格式的文件!’);
return false;
}
},
onComplete: function (file, r) {
if (r != null && r.resultCode == 200) {
$(‘#blogCoverImage’).attr(‘src’, r.data);
$(‘#blogCoverImage’).attr(
‘style’,
‘width: 128px;height: 128px;display:block;’
);
return false;
} else {
部分转自互联网,侵权删除联系
最新评论