本文介绍了23.博客系统项目开发之文章模块功能实现求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。
对技术面试,学习经验等有一些体会,在此分享。
Spring Boot 博客系统项目开发之文章模块功能实现
本项目的开源仓库地址为:https://github.com/ZHENFENG13/My-Blog
除 My-Blog 项目外,我也在维护另外一个开源项目,仓库地址为:https://github.com/newbee-ltd/newbee-mall
感兴趣的朋友可以去关注一下。
文章总览
文章的编辑页面以及添加功能已经完成,因为功能比较重要所以用了两个实验来讲解,因为文章实体及相关联的实体是整个系统中最重要的内容,在博客展示系统中也会继续讲解。这个实验我会继续来完善文章模块的相关页面、交互逻辑以及后端功能,主要包括文章修改功能实现、文章管理页面制作和功能实现,这样,整个后台管理系统中的文章模块就开发完成了。
知识点
- 文章修改功能(页面跳转、数据回显、修改逻辑代码、Ajax 调用接口逻辑)
- 文章管理接口实现
- 文章管理页面设计及编码
- 文章管理模块测试
环境
- JDK 1.8 或者更高版本
- Spring Boot 2.1.0-RELEASE
- Maven 3+
文章修改功能
前一个实验中讲解了文章添加的功能实现,包括编辑页面制作和后端接口实现,本实验继续来讲解文章修改功能的整个流程和逻辑实现。
想要修改一篇文章,首先需要获取这篇文章的所有属性,之后再回显到编辑页面中,用户根据需要来修改页面上的内容,点击保存按钮后会想后端发送文章修改请求,后端接口接收到请求后会进行参数验证以及相应的逻辑操作,之后进行数据的入库操作,整个文章修改流程完成。
文章详情
根据流程,首先我们需要获取文章详情,但是文章编辑页面已经有了,所以就没有做成接口形式,而是采用与添加文章时相同的方式,将请求转发到编辑页即可,因为要获取文章详情所以需要根据一个字段来查询,这里就选择 id 作为传参了,在 BlogController 中新增如下代码:(注:完整代码位于 com.site.blog.my.core.controller.admin.BlogController.java)
@GetMapping("/blogs/edit/{blogId}") public String edit(HttpServletRequest request, @PathVariable("blogId") Long blogId) { request.setAttribute("path", "edit"); Blog blog = blogService.getBlogById(blogId); if (blog == null) { return "error/error_400"; } request.setAttribute("blog", blog); request.setAttribute("categories", categoryService.getAllCategories()); return "admin/edit"; }
在访问 /blogs/edit/{blogId} 时,会把文章编辑页所需的文章详情内容查询出来并转发到 edit 页面。
页面回显
改造文章编辑页面 edit.html 的代码,通过 Thymeleaf 语法将前一个请求携带的 blog 对象进行读取并显示在编辑页面对应的 DOM 中,修改代码如下:(注:完整代码位于 resources/templates/admin/edit.html)
<form id="blogForm" onsubmit="return false;"> <div class="form-group" style="display:flex;"> <input type="hidden" id="blogId" name="blogId" th:value="${blog!=null and blog.blogId!=null }?${blog.blogId}: 0"> <input type="text" class="form-control col-sm-6" id="blogName" name="blogName" placeholder="*请输入文章标题(必填)" th:value="${blog!=null and blog.blogTitle!=null }?${blog.blogTitle}: ''" required="true"> <input type="text" class="form-control" id="blogTags" name="blogTags" placeholder="请输入文章标签" th:value="${blog!=null and blog.blogTags!=null }?${blog.blogTags}: ''" style="width: 100%;"> </div> <div class="form-group" style="display:flex;"> <input type="text" class="form-control col-sm-6" id="blogSubUrl" name="blogSubUrl" th:value="${blog!=null and blog.blogSubUrl!=null }?${blog.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}" th:selected="${null !=blog and null !=blog.blogCategoryId and blog.blogCategoryId==c.categoryId} ?true:false"> > </option> </th:block> </th:block> </select> </div> <div class="form-group" id="blog-editormd"> <textarea style="display:none;" th:utext="${blog!=null and blog.blogContent !=null}?${blog.blogContent}: ''"></textarea> </div> <div class="form-group"> <div class="col-sm-4"> <th:block th:if="${null == blog}"> <img id="blogCoverImage" src="/admin/dist/img/img-upload.png" style="height: 64px;width: 64px;"> </th:block> <th:block th:unless="${null == blog}"> <img id="blogCoverImage" th:src="${blog.blogCoverImage}" style="width:160px ;height: 120px;display:block;"> </th:block> </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 th:checked="${null==blog||(null !=blog and null !=blog.blogStatus and blog.blogStatus==1)} ?true:false" value="1"/> 发布 <input name="blogStatus" type="radio" id="draft" value="0" th:checked="${null !=blog and null !=blog.blogStatus and blog.blogStatus==0} ?true:false"/> 草稿 <label class="control-label">是否允许评论: </label> <input name="enableComment" type="radio" id="enableCommentTrue" checked=true th:checked="${null==blog||(null !=blog and null !=blog.enableComment and blog.enableComment==0)} ?true:false" value="0"/> 是 <input name="enableComment" type="radio" id="enableCommentFalse" value="1" th:checked="${null !=blog and null !=blog.enableComment and blog.enableComment==1} ?true:false"/> 否 </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>
只是在原来编辑 DOM 的基础上加上 Thymeleaf 读取的语法,并不是特别难的知识点,这样改造之后就完成了编辑功能的前两步,获取详情并回显到编辑页面中。
文章修改接口实现
前一个实验中我们讲解了文章添加接口的实现,而大部分功能模块的修改接口,都与添加接口类似,唯一的不同点就是修改接口需要知道修改的是哪一条,因此可以模仿添加接口来实现文章修改接口,修改接口负责接收前端的 POST 请求并处理其中的参数,接收的参数为用户在博客编辑页面输入的所有字段内容以及文章的主键 id,字段名称与对应的含义如下:
- “blogId“: 文章主键
- “blogTitle“: 文章标题
- “blogSubUrl“: 自定义路径
- “blogCategoryId“: 分类 id (下拉框中选择)
- “blogTags“: 标签字段(以逗号分隔)
- “blogContent“: 文章内容(编辑器中的 md 文档)
- “blogCoverImage“: 封面图(上传图片或者随机图片的路径)
- “blogStatus“: 文章状态
- “enableComment“: 评论开关
- 控制层代码(注:完整代码位于 com.site.blog.my.core.controller.admin.BlogController.java)
在 BlogController 中新增 update() 方法,接口的映射地址为 /blogs/update,请求方法为 POST,代码如下:
@PostMapping("/blogs/update") @ResponseBody public Result update(@RequestParam("blogId") Long blogId, @RequestParam("blogTitle") String blogTitle, @RequestParam(name = "blogSubUrl", required = false) String blogSubUrl, @RequestParam("blogCategoryId") Integer blogCategoryId, @RequestParam("blogTags") String blogTags, @RequestParam("blogContent") String blogContent, @RequestParam("blogCoverImage") String blogCoverImage, @RequestParam("blogStatus") Byte blogStatus, @RequestParam("enableComment") Byte enableComment) { if (StringUtils.isEmpty(blogTitle)) { return ResultGenerator.genFailResult("请输入文章标题"); } if (blogTitle.trim().length() > 150) { return ResultGenerator.genFailResult("标题过长"); } if (StringUtils.isEmpty(blogTags)) { return ResultGenerator.genFailResult("请输入文章标签"); } if (blogTags.trim().length() > 150) { return ResultGenerator.genFailResult("标签过长"); } if (blogSubUrl.trim().length() > 150) { return ResultGenerator.genFailResult("路径过长"); } if (StringUtils.isEmpty(blogContent)) { return ResultGenerator.genFailResult("请输入文章内容"); } if (blogTags.trim().length() > 100000) { return ResultGenerator.genFailResult("文章内容过长"); } if (StringUtils.isEmpty(blogCoverImage)) { return ResultGenerator.genFailResult("封面图不能为空"); } Blog blog = new Blog(); blog.setBlogId(blogId); blog.setBlogTitle(blogTitle); blog.setBlogSubUrl(blogSubUrl); blog.setBlogCategoryId(blogCategoryId); blog.setBlogTags(blogTags); blog.setBlogContent(blogContent); blog.setBlogCoverImage(blogCoverImage); blog.setBlogStatus(blogStatus); blog.setEnableComment(enableComment); String updateBlogResult = blogService.updateBlog(blog); if ("success".equals(updateBlogResult)) { return ResultGenerator.genSuccessResult("修改成功"); } else { return ResultGenerator.genFailResult(updateBlogResult); } }
首先会对参数进行校验,之后交给业务层代码进行操作,与添加接口不同的是传参,多了主键 id,我们需要知道要修改的哪一条数据。
- 业务层代码(注:完整代码位于 com.site.blog.my.core.service.impl.BlogServiceImpl.java)
在 service 包中新建 BlogService 并定义接口方法 updateBlog()
,下面为具体的实现方法代码:
@Override @Transactional public String updateBlog(Blog blog) { Blog blogForUpdate = blogMapper.selectByPrimaryKey(blog.getBlogId()); if (blogForUpdate == null) { return "数据不存在"; } blogForUpdate.setBlogTitle(blog.getBlogTitle()); blogForUpdate.setBlogSubUrl(blog.getBlogSubUrl()); blogForUpdate.setBlogContent(blog.getBlogContent()); blogForUpdate.setBlogCoverImage(blog.getBlogCoverImage()); blogForUpdate.setBlogStatus(blog.getBlogStatus()); blogForUpdate.setEnableComment(blog.getEnableComment()); BlogCategory blogCategory = categoryMapper.selectByPrimaryKey(blog.getBlogCategoryId()); if (blogCategory == null) { blogForUpdate.setBlogCategoryId(0); blogForUpdate.setBlogCategoryName("默认分类"); } else { //设置博客分类名称 blogForUpdate.setBlogCategoryName(blogCategory.getCategoryName()); blogForUpdate.setBlogCategoryId(blogCategory.getCategoryId()); //分类的排序值加1 blogCategory.setCategoryRank(blogCategory.getCategoryRank() + 1); } //处理标签数据 String[] tags = blog.getBlogTags().split(","); if (tags.length > 6) { return "标签数量限制为6"; } blogForUpdate.setBlogTags(blog.getBlogTags()); //新增的tag对象 List<BlogTag> tagListForInsert = new ArrayList<>(); //所有的tag对象,用于建立关系数据 List<BlogTag> allTagsList = new ArrayList<>(); for (int i = 0; i < tags.length; i++) { BlogTag tag = tagMapper.selectByTagName(tags[i]); if (tag == null) { //不存在就新增 BlogTag tempTag = new BlogTag(); tempTag.setTagName(tags[i]); tagListForInsert.add(tempTag); } else { allTagsList.add(tag); } } //新增标签数据不为空->新增标签数据 if (!CollectionUtils.isEmpty(tagListForInsert)) { tagMapper.batchInsertBlogTag(tagListForInsert); } List<BlogTagRelation> blogTagRelations = new ArrayList<>(); //新增关系数据 allTagsList.addAll(tagListForInsert); for (BlogTag tag : allTagsList) { BlogTagRelation blogTagRelation = new BlogTagRelation(); blogTagRelation.setBlogId(blog.getBlogId()); blogTagRelation.setTagId(tag.getTagId()); blogTagRelations.add(blogTagRelation); } //修改blog信息->修改分类排序值->删除原关系数据->保存新的关系数据 categoryMapper.updateByPrimaryKeySelective(blogCategory); //删除原关系数据 blogTagRelationMapper.deleteByBlogId(blog.getBlogId()); blogTagRelationMapper.batchInsert(blogTagRelations); if (blogMapper.updateByPrimaryKeySelective(blogForUpdate) > 0) { return "success"; } return "修改失败"; }
这个方法可以结合文章添加的业务层方法来学习,这里仅仅讲一下不同点,首先,updateBlog()
方法会判断是否存在当前想要修改的记录,之后的标签及标签关系处理逻辑与 saveBlog()
方法一样,不同点是关系数据的保存,saveBlog()
方法是直接保存关系数据,因为是全新的关系数据,而 updateBlog()
方法的处理则需要修改,因为在修改前就可能已经存在关系数据了,所以需要先把关系数据删掉再保存新的关系数据。
- 关键 SQL
根据文章 id 删除原来的关系数据 (注:完整代码位于 resources/mapper/BlogTagRelationMapper.xml)
<delete id="deleteByBlogId" parameterType="java.lang.Long"> delete from tb_blog_tag_relation where blog_id = #{blogId,jdbcType=BIGINT} </delete>
Ajax 调用修改接口
对文章数据进行修改之后可以点击信息编辑框下方的保存文章按钮,此时会调用后端接口并进行数据的交互,js 实现代码如下:(注:完整代码位于 resources/static/admin/dist/js/edit.js)
$('#confirmButton').click(function () { var blogId = $('#blogId').val(); var blogTitle = $('#blogName').val(); var blogSubUrl = $('#blogSubUrl').val(); var blogCategoryId = $('#blogCategoryId').val(); var blogTags = $('#blogTags').val(); var blogContent = blogEditor.getMarkdown(); var blogCoverImage = $('#blogCoverImage')[0].src; var blogStatus = $("input[name='blogStatus']:checked").val(); var enableComment = $("input[name='enableComment']:checked").val(); if (isNull(blogTitle)) { swal('请输入文章标题', { icon: 'error', }); return; } if (!validLength(blogTitle, 150)) { swal('标题过长', { icon: 'error', }); return; } if (!validLength(blogSubUrl, 150)) { swal('路径过长', { icon: 'error', }); return; } if (isNull(blogCategoryId)) { swal('请选择文章分类', { icon: 'error', }); return; } if (isNull(blogTags)) { swal('请输入文章标签', { icon: 'error', }); return; } if (!validLength(blogTags, 150)) { swal('标签过长', { icon: 'error', }); return; } if (isNull(blogContent)) { swal('请输入文章内容', { icon: 'error', }); return; } if (!validLength(blogTags, 100000)) { swal('文章内容过长', { icon: 'error', }); return; } if (isNull(blogCoverImage) || blogCoverImage.indexOf('img-upload') != -1) { swal('封面图片不能为空', { icon: 'error', }); return; } var url = '/admin/blogs/save'; var swlMessage = '保存成功'; var data = { blogTitle: blogTitle, blogSubUrl: blogSubUrl, blogCategoryId: blogCategoryId, blogTags: blogTags, blogContent: blogContent, blogCoverImage: blogCoverImage, blogStatus: blogStatus, enableComment: enableComment, }; //blogId大于0则为修改操作 if (blogId > 0) { url = '/admin/blogs/update'; swlMessage = '修改成功'; data = { blogId: blogId, blogTitle: blogTitle, blogSubUrl: blogSubUrl, blogCategoryId: blogCategoryId, blogTags: blogTags, blogContent: blogContent, blogCoverImage: blogCoverImage, blogStatus: blogStatus, enableComment: enableComment, }; } console.log(data); $.ajax({ type: 'POST', //方法类型 url: url, data: data, success: function (result) { if (result.resultCode == 200) { swal(swlMessage, { icon: 'success', }); } else { swal(result.message, { icon: 'error', }); } }, error: function () { swal('操作失败', { icon: 'error', }); }, }); });
这个方法就是直接改造前一个实验中的方法,在保存按钮的点击事件处理函数中,首先判断 blogId 是否大于 0,如果大于 0 则证明这是一个修改请求,之后封装数据并向后端发送 Ajax 请求修改文章。
文章管理页面制作
导航栏
Spring Boot 博客系统项目开发之文章模块功能实现
本项目的开源仓库地址为:https://github.com/ZHENFENG13/My-Blog
除 My-Blog 项目外,我也在维护另外一个开源项目,仓库地址为:https://github.com/newbee-ltd/newbee-mall
感兴趣的朋友可以去关注一下。
文章总览
文章的编辑页面以及添加功能已经完成,因为功能比较重要所以用了两个实验来讲解,因为文章实体及相关联的实体是整个系统中最重要的内容,在博客展示系统中也会继续讲解。这个实验我会继续来完善文章模块的相关页面、交互逻辑以及后端功能,主要包括文章修改功能实现、文章管理页面制作和功能实现,这样,整个后台管理系统中的文章模块就开发完成了。
知识点
- 文章修改功能(页面跳转、数据回显、修改逻辑代码、Ajax 调用接口逻辑)
- 文章管理接口实现
- 文章管理页面设计及编码
- 文章管理模块测试
环境
- JDK 1.8 或者更高版本
- Spring Boot 2.1.0-RELEASE
- Maven 3+
文章修改功能
前一个实验中讲解了文章添加的功能实现,包括编辑页面制作和后端接口实现,本实验继续来讲解文章修改功能的整个流程和逻辑实现。
想要修改一篇文章,首先需要获取这篇文章的所有属性,之后再回显到编辑页面中,用户根据需要来修改页面上的内容,点击保存按钮后会想后端发送文章修改请求,后端接口接收到请求后会进行参数验证以及相应的逻辑操作,之后进行数据的入库操作,整个文章修改流程完成。
文章详情
根据流程,首先我们需要获取文章详情,但是文章编辑页面已经有了,所以就没有做成接口形式,而是采用与添加文章时相同的方式,将请求转发到编辑页即可,因为要获取文章详情所以需要根据一个字段来查询,这里就选择 id 作为传参了,在 BlogController 中新增如下代码:(注:完整代码位于 com.site.blog.my.core.controller.admin.BlogController.java)
@GetMapping("/blogs/edit/{blogId}") public String edit(HttpServletRequest request, @PathVariable("blogId") Long blogId) { request.setAttribute("path", "edit"); Blog blog = blogService.getBlogById(blogId); if (blog == null) { return "error/error_400"; } request.setAttribute("blog", blog); request.setAttribute("categories", categoryService.getAllCategories()); return "admin/edit"; }
在访问 /blogs/edit/{blogId} 时,会把文章编辑页所需的文章详情内容查询出来并转发到 edit 页面。
页面回显
改造文章编辑页面 edit.html 的代码,通过 Thymeleaf 语法将前一个请求携带的 blog 对象进行读取并显示在编辑页面对应的 DOM 中,修改代码如下:(注:完整代码位于 resources/templates/admin/edit.html)
<form id="blogForm" onsubmit="return false;"> <div class="form-group" style="display:flex;"> <input type="hidden" id="blogId" name="blogId" th:value="${blog!=null and blog.blogId!=null }?${blog.blogId}: 0"> <input type="text" class="form-control col-sm-6" id="blogName" name="blogName" placeholder="*请输入文章标题(必填)" th:value="${blog!=null and blog.blogTitle!=null }?${blog.blogTitle}: ''" required="true"> <input type="text" class="form-control" id="blogTags" name="blogTags" placeholder="请输入文章标签" th:value="${blog!=null and blog.blogTags!=null }?${blog.blogTags}: ''" style="width: 100%;"> </div> <div class="form-group" style="display:flex;"> <input type="text" class="form-control col-sm-6" id="blogSubUrl" name="blogSubUrl" th:value="${blog!=null and blog.blogSubUrl!=null }?${blog.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}" th:selected="${null !=blog and null !=blog.blogCategoryId and blog.blogCategoryId==c.categoryId} ?true:false"> > </option> </th:block> </th:block> </select> </div> <div class="form-group" id="blog-editormd"> <textarea style="display:none;" th:utext="${blog!=null and blog.blogContent !=null}?${blog.blogContent}: ''"></textarea> </div> <div class="form-group"> <div class="col-sm-4"> <th:block th:if="${null == blog}"> <img id="blogCoverImage" src="/admin/dist/img/img-upload.png" style="height: 64px;width: 64px;"> </th:block> <th:block th:unless="${null == blog}"> <img id="blogCoverImage" th:src="${blog.blogCoverImage}" style="width:160px ;height: 120px;display:block;"> </th:block> </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 th:checked="${null==blog||(null !=blog and null !=blog.blogStatus and blog.blogStatus==1)} ?true:false" value="1"/> 发布 <input name="blogStatus" type="radio" id="draft" value="0" th:checked="${null !=blog and null !=blog.blogStatus and blog.blogStatus==0} ?true:false"/> 草稿 <label class="control-label">是否允许评论: </label> <input name="enableComment" type="radio" id="enableCommentTrue" checked=true th:checked="${null==blog||(null !=blog and null !=blog.enableComment and blog.enableComment==0)} ?true:false" value="0"/> 是 <input name="enableComment" type="radio" id="enableCommentFalse" value="1" th:checked="${null !=blog and null !=blog.enableComment and blog.enableComment==1} ?true:false"/> 否 </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>
只是在原来编辑 DOM 的基础上加上 Thymeleaf 读取的语法,并不是特别难的知识点,这样改造之后就完成了编辑功能的前两步,获取详情并回显到编辑页面中。
文章修改接口实现
前一个实验中我们讲解了文章添加接口的实现,而大部分功能模块的修改接口,都与添加接口类似,唯一的不同点就是修改接口需要知道修改的是哪一条,因此可以模仿添加接口来实现文章修改接口,修改接口负责接收前端的 POST 请求并处理其中的参数,接收的参数为用户在博客编辑页面输入的所有字段内容以及文章的主键 id,字段名称与对应的含义如下:
- “blogId“: 文章主键
- “blogTitle“: 文章标题
- “blogSubUrl“: 自定义路径
- “blogCategoryId“: 分类 id (下拉框中选择)
- “blogTags“: 标签字段(以逗号分隔)
- “blogContent“: 文章内容(编辑器中的 md 文档)
- “blogCoverImage“: 封面图(上传图片或者随机图片的路径)
- “blogStatus“: 文章状态
- “enableComment“: 评论开关
- 控制层代码(注:完整代码位于 com.site.blog.my.core.controller.admin.BlogController.java)
在 BlogController 中新增 update() 方法,接口的映射地址为 /blogs/update,请求方法为 POST,代码如下:
@PostMapping("/blogs/update") @ResponseBody public Result update(@RequestParam("blogId") Long blogId, @RequestParam("blogTitle") String blogTitle, @RequestParam(name = "blogSubUrl", required = false) String blogSubUrl, @RequestParam("blogCategoryId") Integer blogCategoryId, @RequestParam("blogTags") String blogTags, @RequestParam("blogContent") String blogContent, @RequestParam("blogCoverImage") String blogCoverImage, @RequestParam("blogStatus") Byte blogStatus, @RequestParam("enableComment") Byte enableComment) { if (StringUtils.isEmpty(blogTitle)) { return ResultGenerator.genFailResult("请输入文章标题"); } if (blogTitle.trim().length() > 150) { return ResultGenerator.genFailResult("标题过长"); } if (StringUtils.isEmpty(blogTags)) { return ResultGenerator.genFailResult("请输入文章标签"); } if (blogTags.trim().length() > 150) { return ResultGenerator.genFailResult("标签过长"); } if (blogSubUrl.trim().length() > 150) { return ResultGenerator.genFailResult("路径过长"); } if (StringUtils.isEmpty(blogContent)) { return ResultGenerator.genFailResult("请输入文章内容"); } if (blogTags.trim().length() > 100000) { return ResultGenerator.genFailResult("文章内容过长"); } if (StringUtils.isEmpty(blogCoverImage)) { return ResultGenerator.genFailResult("封面图不能为空"); } Blog blog = new Blog(); blog.setBlogId(blogId); blog.setBlogTitle(blogTitle); blog.setBlogSubUrl(blogSubUrl); blog.setBlogCategoryId(blogCategoryId); blog.setBlogTags(blogTags); blog.setBlogContent(blogContent); blog.setBlogCoverImage(blogCoverImage); blog.setBlogStatus(blogStatus); blog.setEnableComment(enableComment); String updateBlogResult = blogService.updateBlog(blog); if ("success".equals(updateBlogResult)) { return ResultGenerator.genSuccessResult("修改成功"); } else { return ResultGenerator.genFailResult(updateBlogResult); } }
首先会对参数进行校验,之后交给业务层代码进行操作,与添加接口不同的是传参,多了主键 id,我们需要知道要修改的哪一条数据。
- 业务层代码(注:完整代码位于 com.site.blog.my.core.service.impl.BlogServiceImpl.java)
在 service 包中新建 BlogService 并定义接口方法 updateBlog()
,下面为具体的实现方法代码:
@Override @Transactional public String updateBlog(Blog blog) { Blog blogForUpdate = blogMapper.selectByPrimaryKey(blog.getBlogId()); if (blogForUpdate == null) { return "数据不存在"; } blogForUpdate.setBlogTitle(blog.getBlogTitle()); blogForUpdate.setBlogSubUrl(blog.getBlogSubUrl()); blogForUpdate.setBlogContent(blog.getBlogContent()); blogForUpdate.setBlogCoverImage(blog.getBlogCoverImage()); blogForUpdate.setBlogStatus(blog.getBlogStatus()); blogForUpdate.setEnableComment(blog.getEnableComment()); BlogCategory blogCategory = categoryMapper.selectByPrimaryKey(blog.getBlogCategoryId()); if (blogCategory == null) { blogForUpdate.setBlogCategoryId(0); blogForUpdate.setBlogCategoryName("默认分类"); } else { //设置博客分类名称 blogForUpdate.setBlogCategoryName(blogCategory.getCategoryName()); blogForUpdate.setBlogCategoryId(blogCategory.getCategoryId()); //分类的排序值加1 blogCategory.setCategoryRank(blogCategory.getCategoryRank() + 1); } //处理标签数据 String[] tags = blog.getBlogTags().split(","); if (tags.length > 6) { return "标签数量限制为6"; } blogForUpdate.setBlogTags(blog.getBlogTags()); //新增的tag对象 List<BlogTag> tagListForInsert = new ArrayList<>(); //所有的tag对象,用于建立关系数据 List<BlogTag> allTagsList = new ArrayList<>(); for (int i = 0; i < tags.length; i++) { BlogTag tag = tagMapper.selectByTagName(tags[i]); if (tag == null) { //不存在就新增 BlogTag tempTag = new BlogTag(); tempTag.setTagName(tags[i]); tagListForInsert.add(tempTag); } else { allTagsList.add(tag); } } //新增标签数据不为空->新增标签数据 if (!CollectionUtils.isEmpty(tagListForInsert)) { tagMapper.batchInsertBlogTag(tagListForInsert); } List<BlogTagRelation> blogTagRelations = new ArrayList<>(); //新增关系数据 allTagsList.addAll(tagListForInsert); for (BlogTag tag : allTagsList) { BlogTagRelation blogTagRelation = new BlogTagRelation(); blogTagRelation.setBlogId(blog.getBlogId()); blogTagRelation.setTagId(tag.getTagId()); blogTagRelations.add(blogTagRelation); } //修改blog信息->修改分类排序值->删除原关系数据->保存新的关系数据 categoryMapper.updateByPrimaryKeySelective(blogCategory); //删除原关系数据 blogTagRelationMapper.deleteByBlogId(blog.getBlogId()); blogTagRelationMapper.batchInsert(blogTagRelations); if (blogMapper.updateByPrimaryKeySelective(blogForUpdate) > 0) { return "success"; } return "修改失败"; }
这个方法可以结合文章添加的业务层方法来学习,这里仅仅讲一下不同点,首先,updateBlog()
方法会判断是否存在当前想要修改的记录,之后的标签及标签关系处理逻辑与 saveBlog()
方法一样,不同点是关系数据的保存,saveBlog()
方法是直接保存关系数据,因为是全新的关系数据,而 updateBlog()
方法的处理则需要修改,因为在修改前就可能已经存在关系数据了,所以需要先把关系数据删掉再保存新的关系数据。
- 关键 SQL
根据文章 id 删除原来的关系数据 (注:完整代码位于 resources/mapper/BlogTagRelationMapper.xml)
<delete id="deleteByBlogId" parameterType="java.lang.Long"> delete from tb_blog_tag_relation where blog_id = #{blogId,jdbcType=BIGINT} </delete>
Ajax 调用修改接口
对文章数据进行修改之后可以点击信息编辑框下方的保存文章按钮,此时会调用后端接口并进行数据的交互,js 实现代码如下:(注:完整代码位于 resources/static/admin/dist/js/edit.js)
$('#confirmButton').click(function () { var blogId = $('#blogId').val(); var blogTitle = $('#blogName').val(); var blogSubUrl = $('#blogSubUrl').val(); var blogCategoryId = $('#blogCategoryId').val(); var blogTags = $('#blogTags').val(); var blogContent = blogEditor.getMarkdown(); var blogCoverImage = $('#blogCoverImage')[0].src; var blogStatus = $("input[name='blogStatus']:checked").val(); var enableComment = $("input[name='enableComment']:checked").val(); if (isNull(blogTitle)) { swal('请输入文章标题', { icon: 'error', }); return; } if (!validLength(blogTitle, 150)) { swal('标题过长', { icon: 'error', }); return; } if (!validLength(blogSubUrl, 150)) { swal('路径过长', { icon: 'error', }); return; } if (isNull(blogCategoryId)) { swal('请选择文章分类', { icon: 'error', }); return; } if (isNull(blogTags)) { swal('请输入文章标签', { icon: 'error', }); return; } if (!validLength(blogTags, 150)) { swal('标签过长', { icon: 'error', }); return; } if (isNull(blogContent)) { swal('请输入文章内容', { icon: 'error', }); return; } if (!validLength(blogTags, 100000)) { swal('文章内容过长', { icon: 'error', }); return; } if (isNull(blogCoverImage) || blogCoverImage.indexOf('img-upload') != -1) { swal('封面图片不能为空', { icon: 'error', }); return; } var url = '/admin/blogs/save'; var swlMessage = '保存成功'; var data = { blogTitle: blogTitle, blogSubUrl: blogSubUrl, blogCategoryId: blogCategoryId, blogTags: blogTags, blogContent: blogContent, blogCoverImage: blogCoverImage, blogStatus: blogStatus, enableComment: enableComment, }; //blogId大于0则为修改操作 if (blogId > 0) { url = '/admin/blogs/update'; swlMessage = '修改成功'; data = { blogId: blogId, blogTitle: blogTitle, blogSubUrl: blogSubUrl, blogCategoryId: blogCategoryId, blogTags: blogTags, blogContent: blogContent, blogCoverImage: blogCoverImage, blogStatus: blogStatus, enableComment: enableComment, }; } console.log(data); $.ajax({ type: 'POST', //方法类型 url: url, data: data, success: function (result) { if (result.resultCode == 200) { swal(swlMessage, { icon: 'success', }); } else { swal(result.message, { icon: 'error', }); } }, error: function () { swal('操作失败', { icon: 'error', }); }, }); });
这个方法就是直接改造前一个实验中的方法,在保存按钮的点击事件处理函数中,首先判断 blogId 是否大于 0,如果大于 0 则证明这是一个修改请求,之后封装数据并向后端发送 Ajax 请求修改文章。
文章管理页面制作
导航栏
Spring Boot 博客系统项目开发之文章模块功能实现
本项目的开源仓库地址为:https://github.com/ZHENFENG13/My-Blog
除 My-Blog 项目外,我也在维护另外一个开源项目,仓库地址为:https://github.com/newbee-ltd/newbee-mall
感兴趣的朋友可以去关注一下。
文章总览
文章的编辑页面以及添加功能已经完成,因为功能比较重要所以用了两个实验来讲解,因为文章实体及相关联的实体是整个系统中最重要的内容,在博客展示系统中也会继续讲解。这个实验我会继续来完善文章模块的相关页面、交互逻辑以及后端功能,主要包括文章修改功能实现、文章管理页面制作和功能实现,这样,整个后台管理系统中的文章模块就开发完成了。
知识点
- 文章修改功能(页面跳转、数据回显、修改逻辑代码、Ajax 调用接口逻辑)
- 文章管理接口实现
- 文章管理页面设计及编码
- 文章管理模块测试
环境
- JDK 1.8 或者更高版本
- Spring Boot 2.1.0-RELEASE
- Maven 3+
文章修改功能
前一个实验中讲解了文章添加的功能实现,包括编辑页面制作和后端接口实现,本实验继续来讲解文章修改功能的整个流程和逻辑实现。
想要修改一篇文章,首先需要获取这篇文章的所有属性,之后再回显到编辑页面中,用户根据需要来修改页面上的内容,点击保存按钮后会想后端发送文章修改请求,后端接口接收到请求后会进行参数验证以及相应的逻辑操作,之后进行数据的入库操作,整个文章修改流程完成。
文章详情
根据流程,首先我们需要获取文章详情,但是文章编辑页面已经有了,所以就没有做成接口形式,而是采用与添加文章时相同的方式,将请求转发到编辑页即可,因为要获取文章详情所以需要根据一个字段来查询,这里就选择 id 作为传参了,在 BlogController 中新增如下代码:(注:完整代码位于 com.site.blog.my.core.controller.admin.BlogController.java)
@GetMapping("/blogs/edit/{blogId}") public String edit(HttpServletRequest request, @PathVariable("blogId") Long blogId) { request.setAttribute("path", "edit"); Blog blog = blogService.getBlogById(blogId); if (blog == null) { return "error/error_400"; } request.setAttribute("blog", blog); request.setAttribute("categories", categoryService.getAllCategories()); return "admin/edit"; }
在访问 /blogs/edit/{blogId} 时,会把文章编辑页所需的文章详情内容查询出来并转发到 edit 页面。
页面回显
改造文章编辑页面 edit.html 的代码,通过 Thymeleaf 语法将前一个请求携带的 blog 对象进行读取并显示在编辑页面对应的 DOM 中,修改代码如下:(注:完整代码位于 resources/templates/admin/edit.html)
<form id="blogForm" onsubmit="return false;"> <div class="form-group" style="display:flex;"> <input type="hidden" id="blogId" name="blogId" th:value="${blog!=null and blog.blogId!=null }?${blog.blogId}: 0"> <input type="text" class="form-control col-sm-6" id="blogName" name="blogName" placeholder="*请输入文章标题(必填)" th:value="${blog!=null and blog.blogTitle!=null }?${blog.blogTitle}: ''" required="true"> <input type="text" class="form-control" id="blogTags" name="blogTags" placeholder="请输入文章标签" th:value="${blog!=null and blog.blogTags!=null }?${blog.blogTags}: ''" style="width: 100%;"> </div> <div class="form-group" style="display:flex;"> <input type="text" class="form-control col-sm-6" id="blogSubUrl" name="blogSubUrl" th:value="${blog!=null and blog.blogSubUrl!=null }?${blog.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}" th:selected="${null !=blog and null !=blog.blogCategoryId and blog.blogCategoryId==c.categoryId} ?true:false"> > </option> </th:block> </th:block> </select> </div> <div class="form-group" id="blog-editormd"> <textarea style="display:none;" th:utext="${blog!=null and blog.blogContent !=null}?${blog.blogContent}: ''"></textarea> </div> <div class="form-group"> <div class="col-sm-4"> <th:block th:if="${null == blog}"> <img id="blogCoverImage" src="/admin/dist/img/img-upload.png" style="height: 64px;width: 64px;"> </th:block> <th:block th:unless="${null == blog}"> <img id="blogCoverImage" th:src="${blog.blogCoverImage}" style="width:160px ;height: 120px;display:block;"> </th:block> </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 th:checked="${null==blog||(null !=blog and null !=blog.blogStatus and blog.blogStatus==1)} ?true:false" value="1"/> 发布 <input name="blogStatus" type="radio" id="draft" value="0" th:checked="${null !=blog and null !=blog.blogStatus and blog.blogStatus==0} ?true:false"/> 草稿 <label class="control-label">是否允许评论: </label> <input name="enableComment" type="radio" id="enableCommentTrue" checked=true th:checked="${null==blog||(null !=blog and null !=blog.enableComment and blog.enableComment==0)} ?true:false" value="0"/> 是 <input name="enableComment" type="radio" id="enableCommentFalse" value="1" th:checked="${null !=blog and null !=blog.enableComment and blog.enableComment==1} ?true:false"/> 否 </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>
只是在原来编辑 DOM 的基础上加上 Thymeleaf 读取的语法,并不是特别难的知识点,这样改造之后就完成了编辑功能的前两步,获取详情并回显到编辑页面中。
文章修改接口实现
前一个实验中我们讲解了文章添加接口的实现,而大部分功能模块的修改接口,都与添加接口类似,唯一的不同点就是修改接口需要知道修改的是哪一条,因此可以模仿添加接口来实现文章修改接口,修改接口负责接收前端的 POST 请求并处理其中的参数,接收的参数为用户在博客编辑页面输入的所有字段内容以及文章的主键 id,字段名称与对应的含义如下:
- “blogId“: 文章主键
- “blogTitle“: 文章标题
- “blogSubUrl“: 自定义路径
- “blogCategoryId“: 分类 id (下拉框中选择)
- “blogTags“: 标签字段(以逗号分隔)
- “blogContent“: 文章内容(编辑器中的 md 文档)
- “blogCoverImage“: 封面图(上传图片或者随机图片的路径)
- “blogStatus“: 文章状态
- “enableComment“: 评论开关
- 控制层代码(注:完整代码位于 com.site.blog.my.core.controller.admin.BlogController.java)
在 BlogController 中新增 update() 方法,接口的映射地址为 /blogs/update,请求方法为 POST,代码如下:
@PostMapping("/blogs/update") @ResponseBody public Result update(@RequestParam("blogId") Long blogId, @RequestParam("blogTitle") String blogTitle, @RequestParam(name = "blogSubUrl", required = false) String blogSubUrl, @RequestParam("blogCategoryId") Integer blogCategoryId, @RequestParam("blogTags") String blogTags, @RequestParam("blogContent") String blogContent, @RequestParam("blogCoverImage") String blogCoverImage, @RequestParam("blogStatus") Byte blogStatus, @RequestParam("enableComment") Byte enableComment) { if (StringUtils.isEmpty(blogTitle)) { return ResultGenerator.genFailResult("请输入文章标题"); } if (blogTitle.trim().length() > 150) { return ResultGenerator.genFailResult("标题过长"); } if (StringUtils.isEmpty(blogTags)) { return ResultGenerator.genFailResult("请输入文章标签"); } if (blogTags.trim().length() > 150) { return ResultGenerator.genFailResult("标签过长"); } if (blogSubUrl.trim().length() > 150) { return ResultGenerator.genFailResult("路径过长"); } if (StringUtils.isEmpty(blogContent)) { return ResultGenerator.genFailResult("请输入文章内容"); } if (blogTags.trim().length() > 100000) { return ResultGenerator.genFailResult("文章内容过长"); } if (StringUtils.isEmpty(blogCoverImage)) { return ResultGenerator.genFailResult("封面图不能为空"); } Blog blog = new Blog(); blog.setBlogId(blogId); blog.setBlogTitle(blogTitle); blog.setBlogSubUrl(blogSubUrl); blog.setBlogCategoryId(blogCategoryId); blog.setBlogTags(blogTags); blog.setBlogContent(blogContent); blog.setBlogCoverImage(blogCoverImage); blog.setBlogStatus(blogStatus); blog.setEnableComment(enableComment); String updateBlogResult = blogService.updateBlog(blog); if ("success".equals(updateBlogResult)) { return ResultGenerator.genSuccessResult("修改成功"); } else { return ResultGenerator.genFailResult(updateBlogResult); } }
首先会对参数进行校验,之后交给业务层代码进行操作,与添加接口不同的是传参,多了主键 id,我们需要知道要修改的哪一条数据。
- 业务层代码(注:完整代码位于 com.site.blog.my.core.service.impl.BlogServiceImpl.java)
在 service 包中新建 BlogService 并定义接口方法 updateBlog()
,下面为具体的实现方法代码:
@Override @Transactional public String updateBlog(Blog blog) { Blog blogForUpdate = blogMapper.selectByPrimaryKey(blog.getBlogId()); if (blogForUpdate == null) { return "数据不存在"; } blogForUpdate.setBlogTitle(blog.getBlogTitle()); blogForUpdate.setBlogSubUrl(blog.getBlogSubUrl()); blogForUpdate.setBlogContent(blog.getBlogContent()); blogForUpdate.setBlogCoverImage(blog.getBlogCoverImage()); blogForUpdate.setBlogStatus(blog.getBlogStatus()); blogForUpdate.setEnableComment(blog.getEnableComment()); BlogCategory blogCategory = categoryMapper.selectByPrimaryKey(blog.getBlogCategoryId()); if (blogCategory == null) { blogForUpdate.setBlogCategoryId(0); blogForUpdate.setBlogCategoryName("默认分类"); } else { //设置博客分类名称 blogForUpdate.setBlogCategoryName(blogCategory.getCategoryName()); blogForUpdate.setBlogCategoryId(blogCategory.getCategoryId()); //分类的排序值加1 blogCategory.setCategoryRank(blogCategory.getCategoryRank() + 1); } //处理标签数据 String[] tags = blog.getBlogTags().split(","); if (tags.length > 6) { return "标签数量限制为6"; } blogForUpdate.setBlogTags(blog.getBlogTags()); //新增的tag对象 List<BlogTag> tagListForInsert = new ArrayList<>(); //所有的tag对象,用于建立关系数据 List<BlogTag> allTagsList = new ArrayList<>(); for (int i = 0; i < tags.length; i++) { BlogTag tag = tagMapper.selectByTagName(tags[i]); if (tag == null) { //不存在就新增 BlogTag tempTag = new BlogTag(); tempTag.setTagName(tags[i]); tagListForInsert.add(tempTag); } else { allTagsList.add(tag); } } //新增标签数据不为空->新增标签数据 if (!CollectionUtils.isEmpty(tagListForInsert)) { tagMapper.batchInsertBlogTag(tagListForInsert); } List<BlogTagRelation> blogTagRelations = new ArrayList<>(); //新增关系数据 allTagsList.addAll(tagListForInsert); for (BlogTag tag : allTagsList) { BlogTagRelation blogTagRelation = new BlogTagRelation(); blogTagRelation.setBlogId(blog.getBlogId()); blogTagRelation.setTagId(tag.getTagId()); blogTagRelations.add(blogTagRelation); } //修改blog信息->修改分类排序值->删除原关系数据->保存新的关系数据 categoryMapper.updateByPrimaryKeySelective(blogCategory); //删除原关系数据 blogTagRelationMapper.deleteByBlogId(blog.getBlogId()); blogTagRelationMapper.batchInsert(blogTagRelations); if (blogMapper.updateByPrimaryKeySelective(blogForUpdate) > 0) { return "success"; } return "修改失败"; }
这个方法可以结合文章添加的业务层方法来学习,这里仅仅讲一下不同点,首先,updateBlog()
方法会判断是否存在当前想要修改的记录,之后的标签及标签关系处理逻辑与 saveBlog()
方法一样,不同点是关系数据的保存,saveBlog()
方法是直接保存关系数据,因为是全新的关系数据,而 updateBlog()
方法的处理则需要修改,因为在修改前就可能已经存在关系数据了,所以需要先把关系数据删掉再保存新的关系数据。
- 关键 SQL
根据文章 id 删除原来的关系数据 (注:完整代码位于 resources/mapper/BlogTagRelationMapper.xml)
<delete id="deleteByBlogId" parameterType="java.lang.Long"> delete from tb_blog_tag_relation where blog_id = #{blogId,jdbcType=BIGINT} </delete>
Ajax 调用修改接口
对文章数据进行修改之后可以点击信息编辑框下方的保存文章按钮,此时会调用后端接口并进行数据的交互,js 实现代码如下:(注:完整代码位于 resources/static/admin/dist/js/edit.js)
$('#confirmButton').click(function () { var blogId = $('#blogId').val(); var blogTitle = $('#blogName').val(); var blogSubUrl = $('#blogSubUrl').val(); var blogCategoryId = $('#blogCategoryId').val(); var blogTags = $('#blogTags').val(); var blogContent = blogEditor.getMarkdown(); var blogCoverImage = $('#blogCoverImage')[0].src; var blogStatus = $("input[name='blogStatus']:checked").val(); var enableComment = $("input[name='enableComment']:checked").val(); if (isNull(blogTitle)) { swal('请输入文章标题', { icon: 'error', }); return; } if (!validLength(blogTitle, 150)) { swal('标题过长', { icon: 'error', }); return; } if (!validLength(blogSubUrl, 150)) { swal('路径过长', { icon: 'error', }); return; } if (isNull(blogCategoryId)) { swal('请选择文章分类', { icon: 'error', }); return; } if (isNull(blogTags)) { swal('请输入文章标签', { icon: 'error', }); return; } if (!validLength(blogTags, 150)) { swal('标签过长', { icon: 'error', }); return; } if (isNull(blogContent)) { swal('请输入文章内容', { icon: 'error', }); return; } if (!validLength(blogTags, 100000)) { swal('文章内容过长', { icon: 'error', }); return; } if (isNull(blogCoverImage) || blogCoverImage.indexOf('img-upload') != -1) { swal('封面图片不能为空', { icon: 'error', }); return; } var url = '/admin/blogs/save'; var swlMessage = '保存成功'; var data = { blogTitle: blogTitle, blogSubUrl: blogSubUrl, blogCategoryId: blogCategoryId, blogTags: blogTags, blogContent: blogContent, blogCoverImage: blogCoverImage, blogStatus: blogStatus, enableComment: enableComment, }; //blogId大于0则为修改操作 if (blogId > 0) { url = '/admin/blogs/update'; swlMessage = '修改成功'; data = { blogId: blogId, blogTitle: blogTitle, blogSubUrl: blogSubUrl, blogCategoryId: blogCategoryId, blogTags: blogTags, blogContent: blogContent, blogCoverImage: blogCoverImage, blogStatus: blogStatus, enableComment: enableComment, }; } console.log(data); $.ajax({ type: 'POST', //方法类型 url: url, data: data, success: function (result) { if (result.resultCode == 200) { swal(swlMessage, { icon: 'success', }); } else { swal(result.message, { icon: 'error', }); } }, error: function () { swal('操作失败', { icon: 'error', }); }, }); });
这个方法就是直接改造前一个实验中的方法,在保存按钮的点击事件处理函数中,首先判断 blogId 是否大于 0,如果大于 0 则证明这是一个修改请求,之后封装数据并向后端发送 Ajax 请求修改文章。
文章管理页面制作
导航栏
部分转自互联网,侵权删除联系
最新评论