SpringBoot开源项目个人博客——(7)博客管理

有了分类后,就可以做博客管理,博客新增、查询,删除,编辑修改,搜索博客功能,重点是mybatis的多表查询

这里引用oneStar的内容,不详细描述,具体可看大佬的内容。

并不是全部按顺序步骤写的记录,只是以这种顺序写,内容结构比较好排版。

一、搭建好对应的基础结构:

  • dao包下的BlogDao接口,BlogDao.xml
  • service包下的BlogService 接口和impl包下的BlogServiceImpl接口实现类
  • admin包下的BlogController控制器

如果对下面的Controller觉得不理解,可以再编写之前,先细看一遍前端页面,对了解建立哪些接口,哪些功能有帮助。


二、博客列表查询

在查询文章列表的时候,前端页面需要显示分类名称,但博客数据表没有分类字段,这时候需要构建DTO/VO实体类。

  1. entity 里的每一个字段,与数据库相对应,

  2. vo 里的每一个字段,是和你前台 html 页面相对应,

  3. dto 这是用来转换从 entity 到 vo,或者从 vo 到 entity 的中间的东西 。(DTO中拥有的字段应该是entity中或者是vo中的一个子集)

https://blog.csdn.net/qq_40871196/article/details/107575188

com.xx包下创建queryvo包,创建BlogQuery查询列表实体类,根据前端需要查询的内容来定义变量,有:主键(id)、标题(title)、更新时间(updateTime)、是否推荐(recommend)、是否发布(published)、分类id(typeId)、分类(type)

代码如下:(省去get、set、toString、构造)

public class BlogQuery {
    //根据前端 需要查询的内容来定义变量
    /*分类->id,名字; 博客id,标题,推荐,草稿or发布,修改时间*/
    private Long id;
    private String title;
    private Date updateTime;
    private boolean recommend;
    private boolean published;
    private Long typeId; //选分类要用的
    private Type type;
}
  1. 在BlogDao下添加接口

    //查询文章管理列表
    List<BlogQuery> getAllBlogQuery();
    
  2. 重点)mapper文件的多表查询

    <!--查询文章管理 列表,查出指定内容。分类名称需要联表查询-->
    <select id="getAllBlogQuery" resultMap="blog">
        select b.id,
               b.title,
               b.update_time,
               b.recommend,
               b.published,
               b.type_id,
               tt.id,
               tt.name
        from t_blog b
                 left join t_type tt on tt.id = b.type_id
        order by b.update_time desc;
    </select>

    <!--type="com.hxj.queryvo.BlogQuery"-->
    <resultMap id="blog" type="BlogQuery">
        <!--    id 属性是 resultMap 的唯一标识      -->
        <!--column数据库中的字段, property实体类的属性-->
        <id property="id" column="id"/>
        <result property="title" column="title"/>
        <result property="updateTime" column="update_time"/>
        <result property="recommend" column="recommend"/>
        <result property="published" column="published"/>
        <result property="typeId" column="type_id"/>
        <association property="type" javaType="Type">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
        </association>
    </resultMap>

在mybatis配置了驼峰转换(而且BlogQuery中的字段除了驼峰和同名外,没有与数据库又差别),实际上resultMap 只需要association多对一映射,一个复杂类型的关联。(mybatis多表查询知识,不在此讲述。)

复杂的属性,需要单独处理
    对象 : association  (多个学生 关联 一个老师)--》多/一个博客对应着一个分类
    集合 : collection (老师有多个学生)--》一个分类又多篇博客

- List<T> 集合用collection,集合中的泛型信息, 我们使用ofType获取。
- javaType=" " 指定实体类中属性的类型。
  1. 在BlogService接口下添加

    //查询文章管理列表
    List<BlogQuery> getAllBlog();
    
  2. 在BlogServiceImpl接口实现类添加

    @Autowired
    private BlogDao blogDao;
    
    //查询文章管理列表
    @Override
    public List<BlogQuery> getAllBlogQuery() {
        return blogDao.getAllBlogQuery();
    }
    
  3. 在admin包下的BlogController类中添加
    (都是使用pageHelper分页查询,此后不再强调)

    @Autowired
    private BlogService blogService;
    @Autowired
    private TypeService typeService;
    
    //博客列表
    @RequestMapping("/blogs")
    public String blogs(Model model, @RequestParam(defaultValue = "1",value = "pageNum")Integer pageNum){
        //按照排序字段, 倒序
        String orderBy = " update_time desc";
        PageHelper.startPage(pageNum, 5,orderBy);
        List<BlogQuery> list = blogService.getAllBlogQuery();
        PageInfo<BlogQuery> pageInfo = new PageInfo<>(list);//封装 查询信息
    
        model.addAttribute("types",typeService.getAllType());
        model.addAttribute("pageInfo",pageInfo);
    
        return "admin/blogs";
    }
    

三、搜索博客列表

搜索是使用的MySQL的模糊查询,根据博客标题和博客分类查询出想要搜索的文章,需要创建有标题分类属性的实体类做vo查询

queryvo包下创建SearchBlog实体类(省去get、set、toString、构造)

public class SearchBlog {
    private String title;
    private  Long typeId;
}
  1. 在BlogDao下添加接口:

        //查询文章(传searchBlog , 返回的是 BlogQuery 列表)
        List<BlogQuery> searchByTitleAndType(SearchBlog searchBlog);
    
  2. mapper文件

    <!--搜索博客列表: 复用 blog的 resultMap(解决命名冲突),因为结果 类型是 QueryBlog
        有 传title 就拼; 有传typeId也拼上-->
        <select id="searchByTitleAndType" parameterType="SearchBlog" resultMap="blog">
            select  b.id,
                    b.title,
                    b.update_time,
                    b.recommend,
                    b.published,
                    b.type_id,
                    t.id,
                    t.name
            from t_blog b,t_type t
            <where>
                <if test="1 == 1">
                   and b.type_id = t.id
                </if>
                <if test="typeId != null">
                    and b.type_id = #{typeId}
                </if>
                <if test="title != null">
                    and b.title like concat('%',#{title},'%')
                </if>
            </where>
        </select>
    
  3. 在BlogService接口下添加:

    //查询文章(传searchBlog , 返回的是 BlogQuery 列表)
        List<BlogQuery> searchByTitleAndType(SearchBlog searchBlog);
    
  4. 在BlogService接口实现类添加:

     @Override
        public List<BlogQuery> searchByTitleAndType(SearchBlog searchBlog) {
            return blogDao.searchByTitleAndType(searchBlog);
        }
    
  5. 在admin包下的BlogController类中添加:

//搜索结果 也需要分页
    @PostMapping("/blogs/search")
    public  String search(SearchBlog searchBlog,Model model,
                          @RequestParam(defaultValue = "1",value = "pageNum" ) Integer pageNum){

        //前端送来的 title 和 typeId 组成searchBlog, 查出来 QueryBlog 列表,和展示 全部文章操作类似
        String orderBy = " update_time desc "; //按照排序字段, 倒序

        PageHelper.startPage(pageNum, 5,orderBy);
        List<BlogQuery> blogBySearch = blogService.searchByTitleAndType(searchBlog);  //顺序不能乱

        PageInfo<BlogQuery> pageInfo = new PageInfo<>(blogBySearch);//封装查询结果
        model.addAttribute("pageInfo",pageInfo);
        return "admin/blogs :: blogList";  //刷新 查询结果页面的 thymeleaf片段

    }

分页处理,兼容了搜索

  1. 修改了一下前端

主要使用到这3个隐藏域

在这里插入图片描述
定义要更新区域成一个fragment:

在这里插入图片描述

分页部分:

<div class="ui container">
...
.....
.....
<div class="two wide column" align="center">
    <a class="item" style="cursor: pointer" onclick="page(this)" th:attr="data-page=1" th:unless="${pageInfo.isFirstPage}">首页</a>
</div>

<div class="two wide column" align="center">
    <!--<a class="item" th:href="@{/admin/blogs(pageNum=${pageInfo.hasPreviousPage}?${pageInfo.prePage}:1)}" th:unless="${pageInfo.isFirstPage}">上一页</a>-->
    <a class="item" style="cursor: pointer" onclick="page(this)" th:attr="data-page=${pageInfo.hasPreviousPage}?${pageInfo.prePage}:1" th:unless="${pageInfo.isFirstPage}">上一页</a>
</div>

<div class="eight wide column" align="center">
    <p><span th:text="${pageInfo.pageNum}"></span> 页,共 <span th:text="${pageInfo.pages}"></span> 页,有 <span th:text="${pageInfo.total}"></span> 篇文章</p>
</div>

<div class="two wide column " align="center">
    <!--<a class="item" th:href="@{/admin/blogs(pageNum=${pageInfo.hasNextPage}?${pageInfo.nextPage}:${pageInfo.pages})}" th:unless="${pageInfo.isLastPage}">下一页</a>-->
    <a class="item" style="cursor: pointer" onclick="page(this)" th:attr="data-page=${pageInfo.hasNextPage}?${pageInfo.nextPage}:${pageInfo.pages}" th:unless="${pageInfo.isLastPage}">下一页</a>
</div>

<div class="two wide column " align="center">
    <a class="item" style="cursor: pointer" onclick="page(this)" th:attr="data-page=${pageInfo.pages}" th:unless="${pageInfo.isLastPage}">尾页</a>
</div>
    
</div>

JS部分

$('#clear-btn')
    .on('click', function() {
    $('.ui.type.dropdown')
        .dropdown('clear')
    ;
})
;

function page(obj) {
    $("[name='page']").val($(obj).data("page"));
    loaddata();
}

$("#search-btn").click(function () {
    $("[name='page']").val(0);
    loaddata();
});
function loaddata() {
    $("#table-container").load(/*[[@{/admin/blogs/search}]]*/
        "/admin/blogs/search",{
            title : $("[name='title']").val(),
            typeId : $("[name='typeId']").val(),
            pageNum : $("[name='page']").val()
        });
}

这样其实点跳转页的时候,就相当于 点击了搜索。(输入搜索条件后,直接点击下一页也能够实现条件查询)

即使没有输入条件,也传的是空字符串,等于全查询。(实现兼容效果)

  1. 后端控制器接口

就是搜索博客的Controller,实现了分页和搜索功能。

四、博客新增

新增不需要构建VO, 使用entity即可

  1. 在BlogDao下添加接口

    //保存新增博客
        int saveBlog(Blog blog);
    
  2. mapper文件

    <!--保存新增博客,id自增,不写也行,-->
        <insert id="saveBlog" parameterType="Blog">
            insert into t_blog (appreciation, commentabled, content, create_time, description, first_picture, flag,
                                published, recommend, share_statement, title, update_time, views, type_id, user_id,
                                comment_count)
            values (#{appreciation}, #{commentabled}, #{content}, #{createTime}, #{description}, #{firstPicture}, #{flag},
                    #{published}, #{recommend}, #{shareStatement}, #{title}, #{updateTime}, #{views}, #{typeId}, #{userId},
                    #{commentCount})
        </insert>
    
  3. 在BlogService接口下添加

    //保存新增博客
        int saveBlog(Blog blog);
    
  4. 在BlogServiceImpl接口实现类添加

     @Override
        public int saveBlog(Blog blog) {
            blog.setCreateTime(new Date());
            blog.setUpdateTime(new Date());
            blog.setViews(0);
            blog.setCommentCount(0);
            //其余前端可以传
            return blogDao.saveBlog(blog);
        }
    

    前端传来的字段比实体类的少了一点,需要在这里补充。

  5. 在admin包下的BlogController类中添加

    这里的后端校验不再解释,和分类的后端校验同理。(此后的一样)

    到entity类中,对要校验的字段加注解即可。(可翻我的分类管理博客),记得到前端增加要显示的校验字段的错误显示。

    例如我的:
    在这里插入图片描述
    在这里插入图片描述

    //跳转到新增页面
        @RequestMapping("/blogs/input")
        public  String input(Model model){
    
            model.addAttribute("blog",new Blog()); //传博客对象
            model.addAttribute("types",typeService.getAllType());  //传分类信息
    
            return "admin/blogs-input";
        }
    
        //提交 新增(没有传blog id)
        @PostMapping("/blogs")
        public String post(@Valid Blog blog, BindingResult result, RedirectAttributes attributes, HttpSession session,Model model){
    
            if(result.hasErrors()){
                //校验出来,发现问题,把message传前端
                return "admin/blogs-input";
            }
    
            //新增需要传递blog对象, 里面需要 有 user,分类
            blog.setUser((User) session.getAttribute("user"));
            blog.setType(typeService.getType( blog.getType().getId() ));
            //设置blog中的typeId属性,设置用户id
            blog.setTypeId(blog.getType().getId());
            blog.setUserId(blog.getUser().getId());
    
    
    
            int k = blogService.saveBlog(blog);
            if(k>0){
                attributes.addFlashAttribute("message","新增成功");
            }else {
                attributes.addFlashAttribute("message","新增失败");
            }
    
            return "redirect:/admin/blogs";
        }
    
    

    前端传来的字段比实体类的少了一点,需要在此补充。

五、博客编辑

为了简化查询,单独创建博客显示类BlogShow类,查询出需要编辑的博客信息。(在新增时使用Blog类,但是编辑时(一些东西不需要更新),只需要将初始化过的Blog部分属性抽出来修改即可,创建一个VO是比较合理的。)

queryvo包下创建ShowBlog实体类(省去get、set、toString、构造)

public class ShowBlog {
    private Long id;
    private String title;
    private String content;
    private String description;
    private String firstPicture;

    private String flag; //是否为原创
    private boolean recommend;
    private boolean published;
    private boolean shareStatement;
    private boolean commentabled;
    private boolean appreciation;

    private Date updateTime;//更新时间
    private Long typeId;
}
  1. 在BlogDao下添加接口
	//编辑博客
    int updateBlog(ShowBlog showBlog);

    //查询编辑修改的文章
    ShowBlog getBlogById(Long id);
  1. mapper文件
<!--只查出 ShowBlog的数据(除了updateTime)-->
    <select id="getBlogById" resultMap="ShowBlog">
        select id,
               appreciation,
               commentabled,
               content,
               description,
               first_picture,
               flag,
               published,
               recommend,
               share_statement,
               title,
               type_id
        from t_blog
        where id = #{id};
    </select>

    <resultMap id="ShowBlog" type="ShowBlog">
        <!--column数据库中的字段, property实体类的属性-->
        <!--    哪里不一致就写哪里就行 -->
        <result column="first_picture" property="firstPicture"/>
        <result column="share_statement" property="shareStatement"/>
        <result column="type_id" property="typeId"/>

    </resultMap>

当时还没配置驼峰转换,所以写了resultMap,实际配置后可以不需要写

  1. 在BlogService接口下添加

     	//编辑博客
        int updateBlog(ShowBlog showBlog);
    
        //查询编辑修改的文章
        ShowBlog getBlogById(Long id);
    
  2. 在BlogServiceImpl接口实现类添加

    	@Override
        public ShowBlog getBlogById(Long id) {
            return blogDao.getBlogById(id);
        }
    
        @Override
        public int updateBlog(ShowBlog showBlog) {
            showBlog.setUpdateTime( new Date());
            return blogDao.updateBlog(showBlog);
        }
    
  3. 在admin包下的BlogController类中添加

    	//跳转到 修改博客
        @GetMapping("/blogs/{id}/input")
        public String editInput(@PathVariable Long id,Model model){
            ShowBlog blogById = blogService.getBlogById(id);
            System.out.println(blogById.toString());
    
            List<Type> allType = typeService.getAllType();
    
    
            model.addAttribute("blog",blogById);
            model.addAttribute("types",allType); //分类列表选择
    
    
            return "admin/blogs-input";
        }
    
        //提交编辑修改的为文章
        @PostMapping("/blogs/{id}")
        public String editPost(ShowBlog showBlog,RedirectAttributes attributes){
    
            int k = blogService.updateBlog(showBlog);
            if(k>0){
                attributes.addFlashAttribute("message","修改成功");
            }else{
                attributes.addFlashAttribute("message","修改失败");
            }
    
            return "redirect:/admin/blogs";
        }
    

六、博客删除

删除只需要根据博客Id来访问对应的控制器即可

  1. 在BlogDao下添加接口

        //删除博客
        void deleteBlog(Long id );
    
  2. mapper文件

        <!--删除博客-->
        <delete id="deleteBlog">
            delete
            from t_blog
            where id = #{id};
        </delete>
    
  3. 在BlogService接口下添加

        //删除博客
        void deleteBlog(Long id);
    
  4. 在BlogServiceImpl接口实现类添加

        @Override
        public void deleteBlog(Long id) {
            blogDao.deleteBlog(id);
        }
    
  5. 在admin包下的BlogController类中添加

        //删除博客
        @GetMapping("/blogs/{id}/delete")
        public String delete(@PathVariable Long id,RedirectAttributes attributes){
            blogService.deleteBlog(id);
            attributes.addFlashAttribute("message","删除成功");
            return "redirect:/admin/blogs";
        }
    

至此博客管理功能完成,这部分创建了3个VO实体类, BlogQuery(博客列表),SearchBlog(博客搜索),ShowBlog(编辑博客)。

接下来的友链、相册就比较简单了,业务不算复杂和分类很像。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当前课程中博客项目的实战源码是我在 GitHub上开源项目 My-Blog,目前已有 3000 多个 star:本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 大部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 个人博客项目功能的讲解,通过本课程的学习,不仅仅让你掌握基本的 Spring Boot 开发能力以及 Spring Boot 项目的大部分开发使用场景,同时帮你提前甄别和处理掉将要遇到的技术难点,认真学完这个课程后,你将会对 Spring Boot 有更加深入而全面的了解,同时你也会得到一个大家都在使用的博客系统源码,你可以根据自己的需求和想法进行改造,也可以直接使用它来作为自己的个人网站,这个课程一定会给你带来巨大的收获。作者寄语本课程录制于 2020 年,代码基于 Spring Boot 2.x 版本。到目前为止,Spring Boot 技术栈也有一些版本升级,比如 Spring Boot 2.7 发版、Spring Boot 3.x 版本发布正式版本。对于这些情况,笔者会在本课程实战项目的开源仓库中创建不同的代码分支,保持实战项目的源码更新,保证读者朋友们不会学习过气的知识点。课程特色 课程内容紧贴 Spring Boot 技术栈,涵盖大部分 Spring Boot 使用场景。开发教程详细完整、文档资源齐全、实验过程循序渐进简单明了。实践项目页面美观且实用,交互效果完美。包含从零搭建项目、以及完整的后台管理系统和博客展示系统两个系统的功能开发流程。技术栈新颖且知识点丰富,学习后可以提升大家对于知识的理解和掌握,对于提升你的市场竞争力有一定的帮助。实战项目预览    

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值