Spring boot 搭建个人博客系统(四)——文章的发布和分页显示

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013967175/article/details/77450152

Spring boot 搭建个人博客系统(四)——文章的发布和分页显示

一直想用Spring boot 搭建一个属于自己的博客系统,刚好前段时间学习了叶神的牛客项目课受益匪浅,乘热打铁也主要是学习,好让自己熟悉这类项目开发的基本流程。系统采用Spring boot+MyBatis+MySQL的框架进行项目开发。

项目源码:Jblog
个人主页:tuzhenyu’s page
原文地址:Spring boot 搭建个人博客系统(四)——文章的发布和分页显示

0. 思路

  • 文章的发布是将文章内容以及文章信息插入到数据库相应的字段中。为了后续文章的显示,在发布文章时需要插入数据库文章标题(title),文章的描述(describe),文章的内容(content),文章的创建时间(create_date),文章的评论数目(commet_count)和文章的类别(category)等。为了文章编辑的便利引入Markdown编辑页面以及Markdown解析包将输入的Markdown语法的文本解析成相应的HTML文本插入数据库中。
  • 文章的显示是按照日期降序从数据库中取出文章,在主页按照发布时间早晚显示文章列表,在文章显示页面显示具体文章的内容。
  • 文章的分页显示是为减少页面显示的延迟,因为如果在页面上显示数据库中的所有文章,则所需的查询显示时间较长,降低了用户的体验。

1. 数据模型

文章的发布和显示功能需要操作数据库中的article表,使用MyBatis作为系统的ORM框架用来简化数据操作。

  • 添加数据库表实体类
public class Article {
    private int id;
    private String title;
    private String describes;
    private String content;
    private Date createdDate;
    private int commentCount;
    private String category;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescribes() {
        return describes;
    }

    public void setDescribes(String describes) {
        this.describes = describes;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getCreatedDate() {
        return createdDate;
    }

    public void setCreatedDate(Date createdDate) {
        this.createdDate = createdDate;
    }

    public int getCommentCount() {
        return commentCount;
    }

    public void setCommentCount(int commentCount) {
        this.commentCount = commentCount;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

}
  • 添加数据库操作DAO类
@Mapper
public interface ArticleDao {
    String TABLE_NAEM = " article ";
    String INSERT_FIELDS = " title, describes, content, created_Date, comment_Count, category ";
    String SELECT_FIELDS = " id, " + INSERT_FIELDS;

    @Insert({"insert into",TABLE_NAEM,"(",INSERT_FIELDS,") values (#{title},#{describes},#{content}" +
            ",#{createdDate},#{commentCount},#{category})"})
    int insertArticle(Article article);

    @Select({"select",SELECT_FIELDS,"from",TABLE_NAEM,"where id=#{id}"})
    Article selectById(int id);

    @Select({"select",SELECT_FIELDS,"from",TABLE_NAEM,"order by id desc limit #{offset},#{limit}"})
    List<Article> selectLatestArticles(@Param("offset") int offset, @Param("limit") int limit);

    @Select({"select",SELECT_FIELDS,"from",TABLE_NAEM,"where category=#{category} order by id desc limit #{offset},#{limit}"})
    List<Article> selecttArticlesByCategory(@Param("category") String category,@Param("offset") int offset, @Param("limit") int limit);

    @Select({"select count(id) from",TABLE_NAEM,"where category=#{category}"})
    int getArticleCountByCategory(@Param("category") String category);

    @Select({"select count(id) from",TABLE_NAEM})
    int getArticleCount();

    @Update({"update",TABLE_NAEM,"set comment_count = #{commentCount} where id = #{questionId}"})
    void updateCommentCount(@Param("questionId") int questionId,@Param("commentCount") int commentCount);

    @Delete({"delete from",TABLE_NAEM,"where id=#{id}"})
    void deleteById(int id);
}

2. 文章的发布

(1) 为了文章编辑的便利,添加Editor.md插件,在文章编辑页引入Markdown编辑器。

<form action="/articleAdd"  method = "post">
    <div class="title option"><label>Title</label><input type="text" class="form-control" id="title" name="title"></div>
    <div class="row categoryTag">
        <div class="col-md-6"><div><label>Category</label></div>
            <select class="" class="form-control" id="category"  name="category">
                <option value="Java">Java</option>
                <option value="Web">Web</option>
                <option value="Linux">Linux</option>
                <option value="分布式系统">分布式系统</option>
                <option value="数据库">数据库</option>
                <option value="算法">算法</option>
                <option value="其它">其它</option>
            </select>
        </div>
        <div class="col-md-6"><label>Tag</label><input type="text" class="form-control" id="tag" name="tag"></div>
    </div>
    <div class="describe option"><label>Describe</label><input type="text" class="form-control" id="describe" name="describe"></div>
    <div class="option"><label>Content</label></div>
    <div class="row">
        <div id="test-editormd">
            <textarea id="content" name="content" style="display:none;"></textarea>
        </div>
    </div>
    <div class="form-group articleSubmit option">
        <button  type="submit" class="btn btn-default">Submit</button>
    </div>
</form>
 editormd("test-editormd", {
    width   : "90%",
    height  : 640,
    syncScrolling : "single",
    path    : "<%=request.getContextPath()%>/resources/editormd/lib/",
    saveHTMLToTextarea : true
});

(2) 为了解析输入的Markdown语法的文本,添加flexmark-java插件,将Markdown语法的文本解析成能够直接显示的HTML文本,将文章内容和文章相关信息保存在数据库中。

<dependency>
    <groupId>com.vladsch.flexmark</groupId>
    <artifactId>flexmark-all</artifactId>
    <version>0.26.4</version>
</dependency>
@RequestMapping("/articleAdd")
    public String addArticle(@RequestParam("title")String title,@RequestParam("category")String category,
                             @RequestParam("tag")String tag,@RequestParam("describe")String describe,
                             @RequestParam("content")String content){
  Article article = new Article();
    article.setTitle(title);
    article.setDescribes(describe);
    article.setCreatedDate(new Date());
    article.setCommentCount(0);
    article.setContent(JblogUtil.tranfer(content));
    article.setCategory(category);
    int articleId = articleService.addArticle(article);

    return "redirect:/";
}
public static String tranfer(String content){
    MutableDataSet options = new MutableDataSet();
    Parser parser = Parser.builder(options).build();
    HtmlRenderer renderer = HtmlRenderer.builder(options).build();
    Node document = parser.parse(content);
    return renderer.render(document);
}

3. 文章的分页显示

(1) 为了加快页面显示,在文章列表显示的时候采用分页查询的方式,只查询显示一部分文章。考虑到数据库中文章的量级,系统的分页策略采用最简单的offset+limit的方式。

@RequestMapping(path = {"/","/index"})
    public String index(Model model){
       List<ViewObject> vos = new ArrayList<>();
       List<Article> articles = articleService.getLatestArticles(0,4);
       for (Article article:articles){
           ViewObject vo = new ViewObject();
           vo.set("article",article);
           vos.add(vo);
       }

       ViewObject pagination = new ViewObject();
       int count = articleService.getArticleCount();
       User user = hostHolder.getUser();
       if (user==null||"admin".equals(user.getRole())){
           model.addAttribute("create",1);
       }else {
           model.addAttribute("create",0);
       }
       pagination.set("current",1);
       pagination.set("nextPage",2);
       pagination.set("lastPage",count/4+1);
       model.addAttribute("pagination",pagination);

     return "index";
}

(2) 主页文章列表的显示

<ul class="articles">
  #foreach($vo in $vos)
    <li class="blogAticle">
      <div class="articleHeader">
        <p><a href="/article/$!{vo.article.id}">$!{vo.article.title}</a></p>
      </div>
      <div class="articleContent">
        <p>$!{vo.article.describes}</p>
      </div>
      <div class="articleFooter">
        <ul>
          <li><i class="fa fa-calendar" aria-hidden="true"></i><span>$date.format('yyyy-MM-dd', $!{vo.article.createdDate})</span></li>
          <li><i class="fa fa-eye" aria-hidden="true"></i><span>$!{vo.clickCount}</span></li>
          <li><i class="fa fa-list" aria-hidden="true"></i><span>$!{vo.article.category}</span></li>
          <li><i class="fa fa-tags" aria-hidden="true"></i>
            #foreach($tag in $vo.tags)
            <span>$!{tag.name}</span>
            #end
          </li>
          <li class="readMore"><a href="/article/$!{vo.article.id}">read more</a></li>
        </ul>
      </div>
    </li>
  #end
</ul>
<div class="paginationWapper">
  <ul class="pagination">
    #if($pagination.current > 1)
    <li>
      <a href="/page/$!{pagination.prePage}">&laquo;</a>
    </li>
    #else
    <li class="disabled">
      <a href="">&laquo;</a>
    </li>
    #end
    <li><a href="">$!{pagination.current}/$!{pagination.lastPage}</a></li>
    #if($pagination.current < $pagination.lastPage)
    <li>
      <a href="/page/$!{pagination.nextPage}">&raquo;</a>
    </li>
    #else
    <li class="disabled">
      <a href="">&raquo;</a>
    </li>
    #end
  </ul>
</div>

4. 分页查询

分页查询有很多实现的方案,在数据库数据量不大的情况下,各种方案的性能相差不大,但当数据库数据量到达一定量级之后,不同的查询方案的分页查询性能相差较大。
  • 最基本的分页方式:
SELECT * FROM articles order by id desc LIMIT 50, 10 

limit 子句的优点很明显,简单好用。缺点平时不显著,数据量一大就暴露了。数据库会完整扫描 offset 的行,然后继续扫描 200 行之后才把结果集返回。 offset 在 400W 的时候,这样的 SQL 做一次分页查询就已经至少耗时 5s 了。

  • 子查询的分页方式:
select * from table where id in (select id from table order by id limit #offset#, #size#)

利用子查询分页的语句的效率在 offset 达到百万级时相比直接 limit 有数倍的提升,但是这条语句不但没有避免遍历 offset,还做了大量的无用重复工作。

  • 主键范围分页方式:

    select * from table where id >= #minId# limit 200

    通过直接计算出主键的起始值,往后获取200条记录,这条语句执行效率较高,无需遍历offset条记录,但是要求通过主键查询。

5. 总结

利用Markdown插件发布存储文章,并通过分页查询的方式将文章列表分页显示在主页上。

展开阅读全文

没有更多推荐了,返回首页