分类首页
标签首页
博客分类首页和标签首页是几乎相同的,因此,前后端的逻辑基本上没啥差别,这里只以博客分类首页为例说明,标签首页可自行查看源码。
源码获取:Spting Boot Blog
1. 需求分析
博客分类首页包含两个部分:
- 分类栏:显示分类的个数,以及具体每个分类的名称和对应的博客的数量
- 博客列表:根据点击选择的不同分类,子列表部分显示当前类别下所有的博客
因此,我们需要查询数据库获取分类的总个数,对于每个分类来说,还要获取它拥有的博客总数,以及所有博客组成的列表,列表的查询要实现分页查询。
2. 前端处理
博客分类首页对应的前端涉及如下:
<div class="m-container-small m-padded-tb-big animated fadeIn">
<div class="ui container">
<div class="ui top attached segment">
<div class="ui middle aligned two column grid">
<div class="column">
<h3 class="ui teal header">分类</h3>
</div>
<div class="right aligned column">
共 <h2 class="ui orange header m-inline-block m-text-thin" th:text="${#arrays.length(types)}"> 1 </h2> 个
</div>
</div>
</div>
<div class="ui attached segment m-padded-tb-large">
<div class="ui labeled button m-margin-tb-tiny" th:each="type : ${types}">
<a href="#" th:href="@{/types/{id}(id=${type.id})}" class="ui basic button" th:classappend="${type.id==activeTypeId} ? 'teal'" th:text="${type.name}">思考与感悟</a>
<div class="ui basic left pointing label" th:classappend="${type.id==activeTypeId} ? 'teal'" th:text="${#arrays.length(type.blogs)}">24</div>
</div>
</div>
<div class="ui top attached teal segment">
<div class="ui padded vertical segment m-padded-tb-large" th:each="blog : ${page.content}">
<div class="ui middle 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" th:text="${blog.title}">标题</a></h3>
<p class="m-text" th:text="|${blog.description}......|">内容</p>
<div class="ui grid">
<div class="eleven wide column">
<div class="ui mini horizontal link list">
<div class="item">
<img src="https://unsplash.it/100/100?image=1005" th:src="@{${blog.user.avatar}}" alt="" class="ui avatar image">
<div class="content"><a href="#" class="header" th:text="${blog.user.nickname}" >Forlogen</a></div>
</div>
<div class="item">
<i class="calendar icon"></i><span th:text="${#dates.format(blog.updateTime,'yyyy-MM-dd')}">2020-06-26</span>
</div>
<div class="item">
<i class="eye icon"></i> <span th:text="${blog.views}">2</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.type.name}">认知升级</a>
</div>
</div>
</div>
<div class="five wide column">
<a href="#" th:href="@{/blog/{id}(id=${blog.id})}" target="_blank">
<img src="https://unsplash.it/800/450?image=1005" th:src="@{${blog.firstPicture}}" alt="" class="ui rounded image">
</a>
</div>
</div>
</div>
</div>
</div>
</div>
前端首先获取后端传来的types列表,并使用模板引擎内置的array.length()
获取列表的长度,即类别的总个数。对于每一个类别来说,使用th:each
遍历获取具体的类别,然后获取type的id、name和blogs,同样使用array.length()
获取每个类别所拥有的博客总数。博客列表详情部分和首页的是相同的,这里不再赘述。
3. 后端处理
表现层的处理逻辑如下所示,整个部分只使用一个方法即可:
@Controller
public class TypeShowController {
@Autowired
private TypeService typeService;
@Autowired
private BlogService blogService;
@GetMapping("/types/{id}")
public String types(@PageableDefault(size = 8, sort = {"updateTime"}, direction = Sort.Direction.DESC) Pageable pageable, @PathVariable Long id, Model model) {
List<Type> types = typeService.listTypeTop(10000);
if (id == -1) {
id = types.get(0).getId();
}
BlogQuery blogQuery = new BlogQuery();
blogQuery.setTypeId(id);
model.addAttribute("types", types);
model.addAttribute("page", blogService.listBlog(pageable, blogQuery));
model.addAttribute("activeTypeId", id);
return "types";
}
}
通过调用业务层的listTypeTop
来获取类别列表,方法的定义如下:
public interface TypeService {
List<Type> listTypeTop(Integer size);
}
@Service
public class TypeServiceImpl implements TypeService {
@Autowired
TypeRepository typeRepository;
@Override
public List<Type> listTypeTop(Integer size) {
// 根据博客的size属性降序排列
Sort sort = Sort.by(Sort.Direction.DESC,"blogs.size");
Pageable pageable = PageRequest.of(0,size,sort);
// 分页查询
return typeRepository.findTop(pageable);
}
}
对应的持久层实现为:
public interface TypeRepository extends JpaRepository<Type, Long> {
@Query("select t from Type t")
List<Type> findTop(Pageable pageable);
}
然后需要根据选择的列表查询出对应的博客列表,使用的是listBlog()
,它接收的参数是查询条件,这里只使用type的id字段值,还有就是分页查询的Pageable对象。方法的实现如下:
public interface BlogService {
// 根据复合查询条件获取博客列表,实现分页查询
Page<Blog> listBlog(Pageable pageable, BlogQuery blog);
}
@Service
public class BlogServiceImpl implements BlogService {
@Autowired
private BlogRepository blogRepository;
@Override
public Page<Blog> listBlog(Pageable pageable, BlogQuery blog) {
// 这里调用了JpaSpecificationExecutor中的findAll方法,方法的参数为Specification对象
return blogRepository.findAll(new Specification<Blog>() {
// 重写toPredicate方法,添加查询条件
@Override
public Predicate toPredicate(Root<Blog> root,
CriteriaQuery<?> cq,
CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<>();
// 如果输入了标题信息,则根据标题构建查询语句
// 这里使用模糊查询
if (!"".equals(blog.getTitle()) && blog.getTitle() != null){
predicates.add(cb.like(root.<String>get("title"), "%" + blog.getTitle() + "%"));
}
// 如果输入了类型,则获取输入类型对应的类型id,将其作为查询条件
if (blog.getTypeId() != null) {
predicates.add(cb.equal(root.<Type>get("type").get("id"), blog.getTypeId()));
}
// 如果点了推荐,则同样将其作为查询条件
if (blog.isRecommend()) {
predicates.add(cb.equal(root.<Boolean>get("recommend"), blog.isRecommend()));
}
// 构建复合查询条件
cq.where(predicates.toArray(new Predicate[predicates.size()]));
return null;
}
}, pageable);
}
由于这里使用到了id,因此实际的查询条件只有id。