建站笔记(十)
博客首页显示
本文将从前端页面的显示来进行讲述,分别有:首页显示、博客详情页面显示、分类页面显示、时间轴页面显示、音乐盒页面显示、留言板页面显示、友人帐页面显示、照片墙页面显示、关于我页面显示、搜索页面显示,这里就直接在一篇博客中全部给出,留言功能和评论功能实现基本一样,只会讲述其中一个。
一、首页显示
分析
问:首页显示需要考虑到哪些问题?
答:根据前端页面功能来看,两个最基本的查询,一个是查询最新文章列表,一个是查询最新推荐文章;还有就是搜搜索功能,根据关键字搜索博客;另外,就是统计博客信息,有博客总数、访问总数、评论总数、留言总数。
问:那各个功能需要怎样设计呢?
答: 这里根据每个功能来进行分析
- 查询最新文章列表:定义一个FirstPageBlog首页实体类来查询首页文章列表信息,并定义getAllFirstPageBlog接口来关联SQL实现查询功能
- 查询最新推荐文章:定义一个RecommendBlog实体类来查询推荐文章列表信息,并定义getRecommendedBlog接口来关联SQL实现查询
- 搜索博客:由于搜索博客显示的还是博客列表信息,所以还是通过FirstPageBlog实体类来显示查询信息,并定义getSearchBlog接口来关联SQL实现查询功能
- 统计博客信息:统计博客信息分别定义了getBlogTotal、getBlogViewTotal、getBlogCommentTotal、getBlogMessageTotal接口来关联SQL实现博客总数、访问总数、评论总数、留言总数的统计
1. 定义实体类
根据上面的分析,这里需要定义两个查询实体类,分别是:最新博客列表实体类(FirstPageBlog)、最新推荐实体类(RecommendBlog)
最新博客列表实体类
由于首页最新博客列表除了需要显示博客信息外,还需要显示分类、作者等信息,所以还需要定义分类名称和用户名、用户头像属性。在queryvo包下创建FirstPageBlog实体类,代码如下:
package com.cbx.queryvo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* @author cbx
* @date 2022/3/3
* @apiNote 博客首页数据实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class FirstPageBlog {
//Blog
private Long id;
private String title;
private String firstPicture;
private Integer views;
private Integer commentCount;
private Date updateTime;
private String description;
//Type
private String typeName;
//User
private String nickname;
private String avatar;
}
最新推荐实体类
最新推荐只要显示博客标题、首图信息,但要注意这里要体现出是否推荐到推荐栏来,所以还要有个boolean类型的变量recommend,如下:
package com.cbx.queryvo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author cbx
* @date 2022/3/3
* @apiNote 推荐博客数据实体类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RecommendBlog {
private Long id;
private String title;
private String firstPicture;
private boolean recommend;
}
2. 持久层接口
由于都是和博客相关的接口,这里就都写在BlogDao类中,mapper和业务层也是一样。根据上面的分析,这里需要定义以下接口,在BlogDao接口中添加如下:
//查询首页最新博客列表信息
List<FirstPageBlog> getFirstPageBlog();
//查询首页最新推荐信息
List<RecommendBlog> getAllRecommendBlog();
//搜索博客列表
List<FirstPageBlog> getSearchBlog(String query);
//统计博客总数
Integer getBlogTotal();
//统计访问总数
Integer getBlogViewTotal();
//统计评论总数
Integer getBlogCommentTotal();
//统计留言总数
Integer getBlogMessageTotal();
3. mapper
mapper是和持久层接口相对应的,在BlogDao.xml中添加如下SQL:
<resultMap id="firstPageBlog" type="com.cbx.queryvo.FirstPageBlog">
<id property="id" column="id"/>
<result property="title" column="title"/>
<result property="firstPicture" column="first_picture"/>
<result property="views" column="views"/>
<result property="commentCount" column="comment_count"/>
<result property="updateTime" column="update_time"/>
<result property="description" column="description"/>
<result property="typeName" column="name"/>
<result property="nickname" column="nickname"/>
<result property="avatar" column="avatar"/>
</resultMap>
<!--查询首页最新博客列表信息-->
<select id="getFirstPageBlog" resultMap="firstPageBlog">
select b.id,b.title,b.first_picture,b.views,b.comment_count,b.update_time,b.description,
t.name,u.nickname,u.avatar
from myblog.t_blog b,myblog.t_type t,myblog.t_user u
where b.type_id = t.id and u.id = b.user_id order by b.update_time desc
</select>
<!--查询首页最新推荐信息-->
<select id="getAllRecommendBlog" resultType="com.cbx.queryvo.RecommendBlog">
select * from myblog.t_blog where t_blog.recommend = true order by t_blog.create_time desc limit 4;
</select>
<!--搜索博客列表-->
<select id="getSearchBlog" resultMap="firstPageBlog">
<bind name="pattern" value="'%' + query + '%'"/>
select b.id,b.title,b.first_picture,b.views,b.comment_count,b.update_time,b.description,
t.name,
u.nickname,u.avatar
from myblog.t_blog b,myblog.t_type t,myblog.t_user u
where b.type_id = t.id and u.id = b.user_id and (b.title like #{pattern} or b.content like # {pattern})
order by b.update_time desc
</select>
<!--统计博客总数-->
<select id="getBlogTotal" resultType="java.lang.Integer">
select count(*) from myblog.t_blog
</select>
<!--统计访问总数统计访问总数-->
<select id="getBlogViewTotal" resultType="java.lang.Integer">
select coalesce (sum(views),0) from myblog.t_blog
</select>
<!--统计评论总数-->
<select id="getBlogCommentTotal" resultType="java.lang.Integer">
select count(*) from myblog.t_comment
</select>
<!--统计留言总数-->
<select id="getBlogMessageTotal" resultType="java.lang.Integer">
select count(*) from myblog.t_message
</select>
讲解:
查询首页最新博客列表信息和查询推荐文章都是前面提到过的知识点,搜索文章也在博客管理里面有讲解过,用的是模糊查询,这里说一统计访问总数的SQL,在上一版的代码中,用的是:select sum(views) from myblog.t_blog,这里用的是:select coalesce (sum(views),0) from myblog.t_blog,在上一版中,当sum求和返回为null时,是会报空指针异常的,这里用coalesce (sum(views),0),当sum求和为null时赋值为0,就能解决这个问题。查询最新推荐只显示最新的4个,即limit 4
单独使用SUM函数,如果要查是字段没有数据则返回null,但我们希望如果为null则返回一个固定值就可以使用COALESCE 函数。
4. 业务层
业务层接口
在BlogService接口中定义以下接口
//查询首页最新博客列表信息
List<FirstPageBlog> getAllFirstPageBlog();
//查询首页最新推荐信息
List<RecommendBlog> getRecommendedBlog();
//搜索博客列表
List<FirstPageBlog> getSearchBlog(String query);
//统计博客总数
Integer getBlogTotal();
//统计访问总数
Integer getBlogViewTotal();
//统计评论总数
Integer getBlogCommentTotal();
//统计留言总数
Integer getBlogMessageTotal();
接口实现类
在BlogServiceImpl接口实现类中添加:
//查询首页最新博客列表信息
@Override
public List<FirstPageBlog> getAllFirstPageBlog() {
return blogDao.getFirstPageBlog();
}
//查询首页最新推荐信息
@Override
public List<RecommendBlog> getRecommendedBlog() {
List<RecommendBlog> allRecommendBlog = blogDao.getAllRecommendBlog();
return allRecommendBlog;
}
//搜索博客列表
@Override
public List<FirstPageBlog> getSearchBlog(String query) {
return blogDao.getSearchBlog(query);
}
//统计博客总数
@Override
public Integer getBlogTotal() {
return blogDao.getBlogTotal();
}
//统计访问总数
@Override
public Integer getBlogViewTotal() {
return blogDao.getBlogViewTotal();
}
//统计评论总数
@Override
public Integer getBlogCommentTotal() {
return blogDao.getBlogCommentTotal();
}
//统计留言总数
@Override
public Integer getBlogMessageTotal() {
return blogDao.getBlogMessageTotal();
}
5. 控制器
在controller包下创建IndexController类,根据前面的功能分析,编写如下代码:
package com.cbx.controller;
import com.cbx.queryvo.BlogQuery;
import com.cbx.queryvo.DetailedBlog;
import com.cbx.queryvo.FirstPageBlog;
import com.cbx.queryvo.RecommendBlog;
import com.cbx.service.BlogService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* @author cbx
* @date 2022/3/3
* @apiNote 首页控制器
*/
@Controller
public class IndexController {
@Autowired
private BlogService blogService;
//分页查询博客列表
@GetMapping("/")
public String index(Model model, @RequestParam(defaultValue = "1",value = "pageNum")Integer pageNum){
PageHelper.startPage(pageNum,10);
List<FirstPageBlog> allFirstPageBlog = blogService.getAllFirstPageBlog();
List<RecommendBlog> recommendedBlog = blogService.getRecommendedBlog();
PageInfo<FirstPageBlog> pageInfo = new PageInfo<>(allFirstPageBlog);
// System.out.println(pageInfo);
model.addAttribute("pageInfo",pageInfo);
model.addAttribute("recommendedBlogs",recommendedBlog);
return "index";
}
// 搜索博客
@PostMapping("/search")
public String search(Model model,@RequestParam(defaultValue = "1",value = "pageNum")Integer pageNum,
@RequestParam String query){
PageHelper.startPage(pageNum,1000); // 最多显示1000篇搜索结果文章
List<FirstPageBlog> searchBlog = blogService.getSearchBlog(query);
PageInfo<FirstPageBlog> pageInfo = new PageInfo<>(searchBlog);
model.addAttribute("pageInfo",pageInfo);
model.addAttribute("query",query);
return "search";
}
// 博客信息统计
@GetMapping("/footer/blogmessage")
public String blogMessage(Model model){
Integer blogTotal = blogService.getBlogTotal();
Integer blogViewTotal = blogService.getBlogViewTotal();
Integer blogMessageTotal = blogService.getBlogMessageTotal();
Integer blogCommentTotal = blogService.getBlogCommentTotal();
model.addAttribute("blogTotal",blogTotal);
model.addAttribute("blogViewTotal",blogViewTotal);
model.addAttribute("blogMessageTotal",blogMessageTotal);
model.addAttribute("blogCommentTotal",blogCommentTotal);
return "index :: blogMessage"; //返回index页面下的blogMessage模板内容
}
}
6. 前后端交互
- 最新推荐:
<div class="ui stackable m-container-mini m-opacity grid">
<div class="m-margin-tb-tiny four wide column" th:each="blog : ${recommendedBlogs}">
<a href="#" class="class_outer" th:href="@{/blog/{id}(id=${blog.id})}" target="_blank">
<img src="../static/images/backimg1.jpg" th:src="@{${blog.firstPicture}}" alt="" class="ui rounded image">
<span class="class_cover" >
<h4 class="m-font-size-blog-text m-margin-tb-tiny" th:text="${blog.title}">XXX</h4>
</span>
</a>
</div>
</div>
- 文章列表
<div class="ui padded segment m-padded-tb-large m-opacity" th:each="blog : ${pageInfo.list}">
<div class="ui large aligned mobile reversed stackable grid">
<!--博文信息-->
<div class="eleven wide column ">
<h3 class="ui header" ><a href="#" th:href="@{/blog/{id}(id=${blog.id})}" target="_blank" class="m-black m-title-font" th:text="${blog.title}">xxx</a></h3>
<p class="m-text m-margin-top-max" th:text="|${blog.description}......|">xxx</p>
<div class="ui m-margin-top-max grid">
<div class="eleven wide column">
<div class="ui mini horizontal link list">
<div class="item">
<img src="../static/images/me.jpg" th:src="@{${blog.avatar}}" alt="" class="ui avatar image">
<div class="content"><a href="#" th:href="@{/about}" target="_blank" class="header" th:text="${blog.nickname}" >くりやまさん</a></div>
</div>
<div class="item">
<i class="calendar icon"></i><span th:text="${#dates.format(blog.createTime,'yyyy-MM-dd')}">2022-03-03</span>
</div>
<div class="item">
<i class="eye icon"></i> <span th:text="${blog.views}">2222</span>
</div>
<div class="item">
<i class="comment outline icon"></i> <span th:text="${blog.commentCount}">2222</span>
</div>
</div>
</div>
<div class="right aligned five wide column">
<a href="#" target="_blank" class="ui teal basic label m-padded-tiny m-text-thin" th:text="${blog.typeName}">好文</a>
</div>
</div>
</div>
<!--博文图片-->
<div class="five wide column">
<a href="#" th:href="@{/blog/{id}(id=${blog.id})}" target="_blank">
<img src="../static/images/backimg1.jpg" th:src="@{${blog.firstPicture}}" alt="" class="ui rounded image">
</a>
</div>
</div>
</div>
- 分页显示文章列
<div class="ui bottom attached segment m-opacity stackable grid">
<div class="three wide column" align="center">
<a class="item" th:href="@{/(pageNum=${pageInfo.hasPreviousPage}?${pageInfo.prePage}:1)}" th:unless="${pageInfo.isFirstPage}">上一页</a>
</div>
<div class="ten wide column" align="center">
<p> <span th:text="${pageInfo.pageNum}"></span> / <span th:text="${pageInfo.pages}"></span> </p>
</div>
<div class="three wide column" align="center">
<a class="item" th:href="@{/(pageNum=${pageInfo.hasNextPage}?${pageInfo.nextPage}:${pageInfo.pages})}" th:unless="${pageInfo.isLastPage}">下一页</a>
</div>
</div>
- 搜索博客
<form name="search" action="#" th:action="@{/search}" method="post" target="_blank">
<div class="ui icon transparent input m-margin-tb-tiny" style="color: white">
<input style="color: white" type="text" name="query" placeholder="Search...." th:value="${query}">
<i onclick="document.forms['search'].submit()" class="search link icon"></i>
</div>
</form>
- 统计博客信息
HTML:
<div id="blog-message">
<div class="ui inverted link list" style="align-content: center;margin-top: 10px" th:fragment="blogMessage">
<div class="m-text-thin" style="text-align: left;margin-left: 75px;">
文章总数: <h2 class="ui orange header m-inline-block m-margin-top-null" style="font-size:medium;" th:text="${blogTotal}"> 14 </h2> 篇
</div>
<div class="m-text-thin" style="text-align: left;margin-left: 75px">
访问总数: <h2 class="ui orange header m-inline-block m-margin-top-null" style="font-size:medium;" th:text="${blogViewTotal}"> 14 </h2> 次
</div>
<div class="m-text-thin" style="text-align: left;margin-left: 75px">
评论总数: <h2 class="ui orange header m-inline-block m-margin-top-null" style="font-size:medium;" th:text="${blogCommentTotal}"> 14 </h2> 条
</div>
<div class="m-text-thin" style="text-align: left;margin-left: 75px">
留言总数: <h2 class="ui orange header m-inline-block m-margin-top-null" style="font-size:medium;" th:text="${blogMessageTotal}"> 14 </h2> 条
</div>
</div>
</div>
注意th:fragment="blogMessage",这里将这一部分作为一个模板,其他页面的下方都可以用这里的模板替换,也就是说,每个页面都可以用index页面的这一个模板来显示文章总数,访问总数,评论总数以及留言总数。只要在页面加载的时候主动调用IndexController里的 @GetMapping("/footer/blogmessage")接口即可。即通过下方的js加载。
JS:
$('#blog-message').load(/*[[@{/footer/blogmessage}]]*/"/footer/blogmessage");
7. 运行访问
运行项目,访问 http://localhost:8080/, 在后台添加测试文章后能在前端页面查看,并且在底部栏可以查看本站的信息
至此,SpringBoot搭建个人博客的博客首页显示开发完成。
评论