Spring Boot - 个人博客 - 分类/标签首页


分类首页

博客分类首页

标签首页

博客标签首页

博客分类首页和标签首页是几乎相同的,因此,前后端的逻辑基本上没啥差别,这里只以博客分类首页为例说明,标签首页可自行查看源码。

源码获取: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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值