如何创建一个社区的首页帖子分页

在创建分页之前,我们得先认识到,为什么要进行分页?一个大型网站的数据库将容纳大量的数据,而我们进行展示某部分数据时,为了保证浏览速度,不可能一次性地将所有数据进行传输,更何况,不是全部传输过去的数据第一时间就能有效利用,所以,只要每次将需要的数据传输过去就好,即使是后续需要的数据,那也是后面时间点需要完成的工作,为了做到这一点,必须对一个数据整体进行划分,合理地传输并展示给用户,其中,划分出来的每一部分可被称为一页数据,完成划分工作的就是分页操作。而分页操作在 spingboot 及 mybatis 的环境下,可被分为以下几种分页情况:

分类 优点 缺点
数组分页(逻辑分页):查询出所有数据,再利用 List 数组截取需要的数据 效率高 内存占用大
SQL 分页(物理分页):只从数据库中查询当前页的数据 占用内存小 每次都得书写 limit 语句,较为冗余,无法统一管理
拦截器分页:自定义拦截器实现了拦截所有以 ByPage 结尾的查询语句,并且利用获取到的分页相关参数统一在 SQL 语句后面加上limit分页的相关语句,底层仍是物理分页。 方便管理、内存占用小 数据量较小的情况可以考虑更高效的分页类型
RowBounds 分页:与数组分页类似,只是无需编写分页逻辑 效率高,适合数据量较小的情况,有效地防止内存溢出 内存占用大

本文资源

包括可运行代码和相关 SQL 文件如下:

此处内容需要评论回复后(审核通过)方可阅读。

问题阐述

了解为什么要进行分页以及分页的类型之后,现在如果分发给你一个任务:需要完成社区类型网站的首页以及个人帖子的展示,其中,帖子排序具有以下因素:

  • 帖子类型分为普通(0)和置顶(1)
  • 帖子状态分为正常(0)、精华(1)和拉黑(2)
  • 帖子创建时间:yyyy-MM-dd HH:mm:ss

各个因素顺序越靠前,优先级越高,请将帖子按合理顺序分页且排序,并且在明确当前第几页的情况下,保证当前页码前后两页都可展示并切换(首页、末页为特殊情况),实现首页、上一页、下一页、末页跳转功能即可。

注:为了保证过多无关代码,采用创建时间作为排序因素之一,而不是评价帖子优劣的帖子分数。

后端设计

帖子的建表语句如下:

DROP TABLE IF EXISTS `discuss_post`;
SET character_set_client = utf8mb4 ;
CREATE TABLE `discuss_post` (
  `id` int11 NOT NULL AUTO_INCREMENT,
  `user_id` varchar45 DEFAULT NULL,
  `title` varchar100 DEFAULT NULL,
  `content` text,
  `type` int11 DEFAULT NULL COMMENT '0-普通; 1-置顶;',
  `status` int(11) DEFAULT NULL COMMENT '0-正常; 1-精华; 2-拉黑;',
  `create_time` timestamp NULL DEFAULT NULL,
  `comment_count` int(11) DEFAULT NULL,
  `score` double DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

用户的建表语句如下:

DROP TABLE IF EXISTS `user`;
SET character_set_client = utf8mb4 ;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  `salt` varchar(50) DEFAULT NULL,
  `email` varchar(100) DEFAULT NULL,
  `type` int(11) DEFAULT NULL COMMENT '0-普通用户; 1-超级管理员; 2-版主;',
  `status` int(11) DEFAULT NULL COMMENT '0-未激活; 1-已激活;',
  `activation_code` varchar(100) DEFAULT NULL,
  `header_url` varchar(200) DEFAULT NULL,
  `create_time` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_username` (`username`(20)),
  KEY `index_email` (`email`(20))
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;

由于帖子数量较为庞大,所以,使用属于物理分页的 SQL 分页和拦截器分页,先从 SQL 分页说起解决方案。我们都知道,在 MVC 架构下,常见思路为使用 Mapper 进行数据库操作,将数据库数据映射到实体类,并由 Server 进行逻辑操作,所得到的结果交由 Controller 进行转发到前端。所以,我们先按照常规思路,创建对应实体类。

用户实体类:User.java

package top.yumuing.community.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;

/**
 * 
 * @TableName user
 */
@TableName(value ="user")
@Data
public class User implements Serializable {
    /**
     * 
     */
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String username;
    private String password;
    private String salt;//加盐
    private String email;
    /**
     * 0-普通用户; 1-超级管理员; 2-版主;
     */
    private Integer type;

    /**
     * 0-未激活; 1-已激活;
     */
    private Integer status;

    /**
     * 激活码
     */
    private String activationCode;

    /**
     * 头像url
     */
    private String headerUrl;
    private Date createTime;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getActivationCode() {
        return activationCode;
    }

    public void setActivationCode(String activationCode) {
        this.activationCode = activationCode;
    }

    public String getHeaderUrl() {
        return headerUrl;
    }

    public void setHeaderUrl(String headerUrl) {
        this.headerUrl = headerUrl;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        User other = (User) that;
        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
                && (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername()))
                && (this.getPassword() == null ? other.getPassword() == null : this.getPassword().equals(other.getPassword()))
                && (this.getSalt() == null ? other.getSalt() == null : this.getSalt().equals(other.getSalt()))
                && (this.getEmail() == null ? other.getEmail() == null : this.getEmail().equals(other.getEmail()))
                && (this.getType() == null ? other.getType() == null : this.getType().equals(other.getType()))
                && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
                && (this.getActivationCode() == null ? other.getActivationCode() == null : this.getActivationCode().equals(other.getActivationCode()))
                && (this.getHeaderUrl() == null ? other.getHeaderUrl() == null : this.getHeaderUrl().equals(other.getHeaderUrl()))
                && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()));    
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
        result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode());
        result = prime * result + ((getPassword() == null) ? 0 : getPassword().hashCode());
        result = prime * result + ((getSalt() == null) ? 0 : getSalt().hashCode());
        result = prime * result + ((getEmail() == null) ? 0 : getEmail().hashCode());
        result = prime * result + ((getType() == null) ? 0 : getType().hashCode());
        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
        result = prime * result + ((getActivationCode() == null) ? 0 : getActivationCode().hashCode());
        result = prime * result + ((getHeaderUrl() == null) ? 0 : getHeaderUrl().hashCode());
        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", username=").append(username);
        sb.append(", password=").append(password);
        sb.append(", salt=").append(salt);
        sb.append(", email=").append(email);
        sb.append(", type=").append(type);
        sb.append(", status=").append(status);
        sb.append(", activationCode=").append(activationCode);
        sb.append(", headerUrl=").append(headerUrl);
        sb.append(", createTime=").append(createTime);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}

帖子实体类:DiscussPost.java

package top.yumuing.community.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;

/**
 * 
 * @TableName discuss_post
 */
@TableName(value ="discuss_post")
@Data
public class DiscussPost implements Serializable {
    /**
     * 帖子 ID
     */
    @TableId(type = IdType.AUTO)
    private Integer id;

    /**
     * 用户 ID
     */
    private String userId;

    /**
     * 帖子标题
     */
    private String title;

    /**
     * 帖子内容
     */
    private String content;

    /**
     * 帖子类型 0-普通; 1-置顶;
     */
    private Integer type;

    /**
     * 帖子状态 0-正常; 1-精华; 2-拉黑;
     */
    private Integer status;

    /**
     * 帖子创建日期
     */
    private Date createTime;

    /**
     * 评论数量
     */
    private Integer commentCount;

    /**
     * 帖子分数
     */
    private Double score;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    public Integer getId() {
        return id;
    }

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

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getTitle() {
        return title;
    }

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

    public String getContent() {
        return content;
    }

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

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Integer getCommentCount() {
        return commentCount;
    }

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

    public Double getScore() {
        return score;
    }

    public void setScore(Double score) {
        this.score = score;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        DiscussPost other = (DiscussPost) that;
        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
            && (this.getTitle() == null ? other.getTitle() == null : this.getTitle().equals(other.getTitle()))
            && (this.getContent() == null ? other.getContent() == null : this.getContent().equals(other.getContent()))
            && (this.getType() == null ? other.getType() == null : this.getType().equals(other.getType()))
            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
            && (this.getCommentCount() == null ? other.getCommentCount() == null : this.getCommentCount().equals(other.getCommentCount()))
            && (this.getScore() == null ? other.getScore() == null : this.getScore().equals(other.getScore()));
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
        result = prime * result + ((getTitle() == null) ? 0 : getTitle().hashCode());
        result = prime * result + ((getContent() == null) ? 0 : getContent().hashCode());
        result = prime * result + ((getType() == null) ? 0 : getType().hashCode());
        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
        result = prime * result + ((getCommentCount() == null) ? 0 : getCommentCount().hashCode());
        result = prime * result + ((getScore() == null) ? 0 : getScore().hashCode());
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", userId=").append(userId);
        sb.append(", title=").append(title);
        sb.append(", content=").append(content);
        sb.append(", type=").append(type);
        sb.append(", status=").append(status);
        sb.append(", createTime=").append(createTime);
        sb.append(", commentCount=").append(commentCount);
        sb.append(", score=").append(score);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}

分页(信息)实体类:Page.java

该实体类只是用来承担 Server 查询出来的各种参数以及存有计算总结数字的方法的映射实体,方便 Controller 进行调用。

  • getOffset:获取当前页起始行 = 当前页码 每页显示上限-每页显示上限 = (current-1) limit
  • getTotal:获取总页数 = 总行数 / 每页显示上限 = rows / limit (若不能整除,则加一)
  • getFrom:获取起始页码,即当前页码前两页,若为首页及第二页,则为1
  • getTo:获取截止页码,即当前页码后两页,若为尾页及倒数第二页,则为总页数 total
package top.yumuing.community.entity;

// 封装分页信息
public class Page {
    // 当前页码
    private int current = 1;
    // 每页显示上限
    private int limit =10;
    // 数据总数
    private int rows;
    // 查询对应分页路径,复用分页链接
    private String path;

    public int getCurrent() {
        return current;
    }

    public void setCurrent(int current) {
        if (current >= 1){
            this.current = current;
        }
    }

    public int getLimit() {
        return limit;
    }

    public void setLimit(int limit) {
        if (limit >= 1 && limit <= 100){
            this.limit = limit;
        }
    }

    public int getRows() {
        return rows;
    }

    public void setRows(int rows) {
        if (rows >= 0){
            this.rows = rows;
        }
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    // 获取当前页起始行
    public int getOffset(){
        // current * limit - limit
        return (current-1) * limit;
    }

    // 获取总页数
    public int getTotal(){
        // rows / limit
        if (rows % limit == 0){
            return rows / limit;
        }else {
            return rows / limit + 1;
        }
    }
    // 获取起始页码
    public int getFrom(){
        int from = current - 2;
        return from < 1 ? 1 : from;
    }
    // 获取截止页码
    public int getTo(){
        int to = current +2;
        int total = getTotal();
        return to > total ? total : to;
    }
}

三个实体类创建结束,还有两个 mapper :DiscussPostMapper.java、UserMapper.java 以及对应的 xml 代码,如下:

DiscussPostMapper.java

在进行分页查询帖子时,为了与用户查询个人帖子功能复用,确定以下参数:

  • userId:首页默认为 0,而需要查询用户的帖子就填入对应 userId
  • offset:每一页起始行行号
  • limit:每页最多显示多少条数据

注:@Param("参数别名"):书写参数别名,需要动态拼接 SQL 语句时,传入的参数有且只有一个,参数之前必须增加 @Param("参数别名"),否则会报错。

package top.yumuing.community.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import top.yumuing.community.entity.DiscussPost;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import java.util.List;

/**
* @author yumuuing
* @description 针对表【discuss_post】的数据库操作Mapper
* @Entity top.yumuing.community.entity.DiscussPost
*/
@Mapper
public interface DiscussPostMapper extends BaseMapper<DiscussPost> {
    List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);

    int selectDiscussPostRows(@Param("userId") int userId);

}

UserMapper.java

package top.yumuing.community.mapper;
import org.apache.ibatis.annotations.Param;

import org.apache.ibatis.annotations.Mapper;
import top.yumuing.community.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
* @author yumuuing
* @description 针对表【user】的数据库操作Mapper
* @Entity top.yumuing.community.entity.User
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
    User selectOneById(@Param("id") int id);
}

UserMapper.xml

selectOneById:通过 ID 查询对应用户

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.yumuing.community.mapper.UserMapper">

    <resultMap id="BaseResultMap" type="top.yumuing.community.entity.User">
            <id property="id" column="id" jdbcType="INTEGER"/>
            <result property="username" column="username" jdbcType="VARCHAR"/>
            <result property="password" column="password" jdbcType="VARCHAR"/>
            <result property="salt" column="salt" jdbcType="VARCHAR"/>
            <result property="email" column="email" jdbcType="VARCHAR"/>
            <result property="type" column="type" jdbcType="INTEGER"/>
            <result property="status" column="status" jdbcType="INTEGER"/>
            <result property="activationCode" column="activation_code" jdbcType="VARCHAR"/>
            <result property="headerUrl" column="header_url" jdbcType="VARCHAR"/>
            <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    </resultMap>

    <sql id="Base_Column_List">
        id,username,password,
        salt,email,type,
        status,activation_code,header_url,
        create_time
    </sql>

    <select id="selectOneById" resultType="User">
        select
        <include refid="Base_Column_List"/>
        from user
        where id = #{id}
    </select>

</mapper>

DiscussPostMapper.xml

为了实现复用,使用以下代码,进行判断是否加上 and user_id = #{userId}

<if test="userId!=0">
    and user_id = #{userId}
 </if>

由于帖子状态为 2 时,为拉黑状态,不可展示给对应用户,所以添加上 order by type desc, create_time desc 语句进行判断,并且帖子类型为普通(0)和置顶(1),帖子展示为最新帖子,故两种属性采用倒序状态。selectDiscussPostRows 查询语句为获取该页帖子行数。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.yumuing.community.mapper.DiscussPostMapper">

    <resultMap id="BaseResultMap" type="top.yumuing.community.entity.DiscussPost">
            <id property="id" column="id" jdbcType="INTEGER"/>
            <result property="userId" column="user_id" jdbcType="VARCHAR"/>
            <result property="title" column="title" jdbcType="VARCHAR"/>
            <result property="content" column="content" jdbcType="VARCHAR"/>
            <result property="type" column="type" jdbcType="INTEGER"/>
            <result property="status" column="status" jdbcType="INTEGER"/>
            <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
            <result property="commentCount" column="comment_count" jdbcType="INTEGER"/>
            <result property="score" column="score" jdbcType="DOUBLE"/>
    </resultMap>

    <sql id="Base_Column_List">
        id,user_id,title,
        content,type,status,
        create_time,comment_count,score
    </sql>

    <select id="selectDiscussPosts" resultType="DiscussPost">
        select <include refid="Base_Column_List"></include>
        from discuss_post
        where status != 2
        <if test="userId!=0">
            and user_id = #{userId}
        </if>
        order by type  desc, create_time desc
        limit #{offset},#{limit}
    </select>

    <select id="selectDiscussPostRows" resultType="int">
        select count(id)
        from discuss_post
        where status != 2
        <if test="userId!=0">
            and user_id = #{userId}
        </if>
    </select>

</mapper>

在 Server 下采用 impl 实现对应接口的方式,这样,以后一旦需要修改数据库时,只需要实现另一个数据库的方法,在修改注入对象即可,无需更改 Controller 实际业务代码:对应接口代码如下:

DiscussPostService.java

package top.yumuing.community.service;

import org.springframework.stereotype.Service;
import top.yumuing.community.entity.DiscussPost;
import com.baomidou.mybatisplus.extension.service.IService;


import java.util.List;

/**
* @author yumuuing
* @description 针对表【discuss_post】的数据库操作Service
*/
@Service
public interface DiscussPostService extends IService<DiscussPost> {

    public List<DiscussPost> findDiscussPosts(int userId,int offset,int limit);

    public int findDiscussPostsRows(int userId);

}

UserService.java

package top.yumuing.community.service;

import org.springframework.stereotype.Service;
import top.yumuing.community.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;

/**
* @author yumuuing
* @description 针对表【user】的数据库操作Service
*/
@Service
public interface UserService extends IService<User> {

    public User findUserById(int id);

}

mysql 的 impl 实现方法:

DiscussPostServiceImpl.java

  • findDiscussPosts:该分页下帖子的所有信息
  • findDiscussPostsRows:该分页的行数
package top.yumuing.community.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import top.yumuing.community.entity.DiscussPost;
import top.yumuing.community.service.DiscussPostService;
import top.yumuing.community.mapper.DiscussPostMapper;
import org.springframework.stereotype.Service;

import java.util.List;

/**
* @author yumuuing
* @description 针对表【discuss_post】的数据库操作Service实现
*/
@Service
public class DiscussPostServiceImpl extends ServiceImpl<DiscussPostMapper, DiscussPost>
    implements DiscussPostService{
    @Resource
    public DiscussPostMapper discussPostMapper;

    @Override
    public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit) {
        return discussPostMapper.selectDiscussPosts(userId,offset,limit);
    }

    @Override
    public int findDiscussPostsRows(int userId) {
        return discussPostMapper.selectDiscussPostRows(userId);
    }
}

UserServiceImpl.java

  • findUserById:查询对应用户
package top.yumuing.community.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import top.yumuing.community.entity.User;
import top.yumuing.community.service.UserService;
import top.yumuing.community.mapper.UserMapper;
import org.springframework.stereotype.Service;

/**
* @author yumuuing
* @description 针对表【user】的数据库操作Service实现
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService{

    @Resource
    private UserMapper userMapper;

    @Override
    public User findUserById(int id) {
        return userMapper.selectOneById(id);
    }
}

现在我们已经实现了从 DAO 层到 Server 层的分页操作,其所需参数的查询,现在需要到 Controller 层进行转发到前端,进行调用和 UI 展示了。前端采用 thymeleaf 进行开发,不是严格的前后端开发形式,而是混合开发。

首先,我们得先利用 Page 实体类获取对应的分页信息,利用分页信息将数据库内的对应分页的帖子映射实体放入 discussPostList 中,利用 userServiceImpl.findUserById(Integer.parseInt(post.getUserId())) 获取与匹配在discussPostList 中匹配的 user,注意,用户拥有多个帖子,最好以帖子匹配用户,不然会存在重复信息,以 discussPost 为匹配要求,user 为匹配对象。放入同一个 map 对象中,再将所有的 map 对象放入 List 中,以便传输给前端。利用 model.addAttribute("discussPosts",discussPosts) 方法进行传输。

package top.yumuing.community.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import top.yumuing.community.entity.DiscussPost;
import top.yumuing.community.entity.Page;
import top.yumuing.community.entity.User;
import top.yumuing.community.service.DiscussPostService;
import top.yumuing.community.service.UserService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
public class HomeController {

    @Autowired
    @Qualifier("discussPostServiceImpl")
    private DiscussPostService discussPostServiceImpl;

    @Autowired
    @Qualifier("userServiceImpl")
    private UserService userServiceImpl;

    @RequestMapping(path = "/index",method = RequestMethod.GET)
    public String getIndecPage(Model model ,Page page){

        //获取分页
        page.setRows(discussPostServiceImpl.findDiscussPostsRows(0));
        page.setPath("/index");

        // 获取帖子
        List<DiscussPost> discussPostList = discussPostServiceImpl.findDiscussPosts(0,page.getOffset(),page.getLimit());
        List<Map<String,Object>> discussPosts = new ArrayList<>();
        if (discussPostList != null){
            for(DiscussPost post: discussPostList){
                Map<String, Object> map = new HashMap<>();
                map.put("post",post);
                User user = userServiceImpl.findUserById(Integer.parseInt(post.getUserId()));
                map.put("user",user);
                discussPosts.add(map);
            }
        }
        model.addAttribute("discussPosts",discussPosts);
        return "/index";
    }
}

前端调用

注意:xmlns:th="http://www.thymeleaf.org":导入 thymeleaf 模板标志

主要看帖子列表以及分页部分

帖子列表

thymeleaf 中,# 代表调用方法,$ 代表变量。

  • th:each="map:${discussPosts}:收到后端的传输数据,为 map 集合 List,利用此代码一一遍历,每次遍历出的元素命名为 map
  • ${map.user.headerUrl}、${map.post.title}、${map.user.username};都是获取 map 里对应的数据
  • th:utext=:如果不是使用 utext 可能会出现转义字符不显示为正确符号,而是普通字符
  • ${map.post.type==1}、${map.post.status==1}:前一个判断是否为置顶帖子,后一个判断是否为精华帖子,是则存在该元素,否则取消
  • ${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}:调用 dates.format() 方法格式化从 map 取出的帖子创建时间数据

分页

  • th:if="${page.rows>0}:如果帖子行数为 0 ,分页部分不存在
  • @{${page.path}(current=1)}:表示在域名后面拼接 page.path 变量数据,再加上 ?current=1,括号代表拼接 Get 参数。
  • class="|page-item ${page.current==1?'disabled':''}|":两竖杠代表静态数据,即不会改变数据,而 $ 后代表变量,如果 page.current==1 则无法点击,disabled 在 class 中代表无法点击。其他的类似
  • th:each="i:${#numbers.sequence(page.from,page.to)}":#numbers.sequence() 输出从某个数字到某个数字的顺序数字序列,获得的当前页数字赋给 i
  • th:href="@{${page.path}(current=${i})}" th:text="${i}:点击后进行正确的页数跳转,并显示对应数字

index.html 代码如下:

<!doctype html>  
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
    <link rel="stylesheet" th:href="@{/css/global.css}" />
    <title>yumuing 社区-首页</title>
</head>
<body>
<div class="nk-container">
    <!-- 头部 -->
    <header class="bg-dark sticky-top">
        <div class="container">
            <!-- 导航 -->
            <nav class="navbar navbar-expand-lg navbar-dark">
                <!-- 功能 -->
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav mr-auto">
                        <li class="nav-item ml-3 btn-group-vertical">
                            <a class="nav-link" href="index.html">首页</a>
                        </li>
                        <li class="nav-item ml-3 btn-group-vertical">
                            <a class="nav-link position-relative" href="#">消息<span class="badge badge-danger">12</span></a>
                        </li>
                        <li class="nav-item ml-3 btn-group-vertical">
                            <a class="nav-link" href="#">注册</a>
                        </li>
                        <li class="nav-item ml-3 btn-group-vertical">
                            <a class="nav-link" href="#">登录</a>
                        </li>
                        <li class="nav-item ml-3 btn-group-vertical dropdown">
                            <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                <img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
                            </a>
                            <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                                <a class="dropdown-item text-center" href="#">个人主页</a>
                                <a class="dropdown-item text-center" href="#">账号设置</a>
                                <a class="dropdown-item text-center" href="#">退出登录</a>
                                <div class="dropdown-divider"></div>
                                <span class="dropdown-item text-center text-secondary">nowcoder</span>
                            </div>
                        </li>
                    </ul>
                    <!-- 搜索 -->
                    <form class="form-inline my-2 my-lg-0" action="#">
                        <input class="form-control mr-sm-2" type="search" aria-label="Search" />
                        <button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
                    </form>
                </div>
            </nav>
        </div>
    </header>

    <!-- 内容 -->
    <div class="main">
        <div class="container">
            <div class="position-relative">
                <!-- 筛选条件 -->
                <ul class="nav nav-tabs mb-3">
                    <li class="nav-item">
                        <a class="nav-link active" href="#">最新</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#">最热</a>
                    </li>
                </ul>
                <button type="button" class="btn btn-primary btn-sm position-absolute rt-0" data-toggle="modal" data-target="#publishModal">我要发布</button>
            </div>
            <!-- 弹出框 -->
            <div class="modal fade" id="publishModal" tabindex="-1" role="dialog" aria-labelledby="publishModalLabel" aria-hidden="true">
                <div class="modal-dialog modal-lg" role="document">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title" id="publishModalLabel">新帖发布</h5>
                            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                <span aria-hidden="true">&times;</span>
                            </button>
                        </div>
                        <div class="modal-body">
                            <form>
                                <div class="form-group">
                                    <label for="recipient-name" class="col-form-label">标题:</label>
                                    <input type="text" class="form-control" id="recipient-name">
                                </div>
                                <div class="form-group">
                                    <label for="message-text" class="col-form-label">正文:</label>
                                    <textarea class="form-control" id="message-text" rows="15"></textarea>
                                </div>
                            </form>
                        </div>
                        <div class="modal-footer">
                            <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
                            <button type="button" class="btn btn-primary" id="publishBtn">发布</button>
                        </div>
                    </div>
                </div>
            </div>
            <!-- 提示框 -->
            <div class="modal fade" id="hintModal" tabindex="-1" role="dialog" aria-labelledby="hintModalLabel" aria-hidden="true">
                <div class="modal-dialog modal-lg" role="document">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title" id="hintModalLabel">提示</h5>
                        </div>
                        <div class="modal-body" id="hintBody">
                            发布完毕!
                        </div>
                    </div>
                </div>
            </div>

            <!-- 帖子列表 -->
            <ul class="list-unstyled">
                <li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
                    <a href="#">
                        <img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
                    </a>
                    <div class="media-body">
                        <h6 class="mt-0 mb-3">
                            <a href="#" th:utext="${map.post.title}"></a>
                            <span class="badge badge-secondary bg-primary" th:if="${map.post.type==1}">置顶</span>
                            <span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华</span>
                        </h6>
                        <div class="text-muted font-size-12">
                            <u class="mr-3" th:utext="${map.user.username}">yumuing</u> 发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}"></b>
                            <ul class="d-inline float-right">
                                <li class="d-inline ml-2">赞 11</li>
                                <li class="d-inline ml-2">|</li>
                                <li class="d-inline ml-2">回帖 7</li>
                            </ul>
                        </div>
                    </div>
                </li>
            </ul>
            <!-- 分页 -->
            <nav class="mt-5" th:if="${page.rows>0}">
                <ul class="pagination justify-content-center">
                    <li class="page-item">
                        <a class="page-link" th:href="@{${page.path}(current=1)}">首页</a>
                    </li>
                    <li th:class="|page-item ${page.current==1?'disabled':''}|">
                        <a class="page-link" th:href="@{${page.path}(current=${page.current-1})}">上一页</a></li>
                    <li th:class="|page-item ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
                        <a class="page-link" th:href="@{${page.path}(current=${i})}" th:text="${i}">1</a>
                    </li>
                    <li th:class="|page-item ${page.current==page.total?'disabled':''}|">
                        <a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页</a>
                    </li>
                    <li class="page-item">
                        <a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页</a>
                    </li>
                </ul>
            </nav>
        </div>
    </div>

    <!-- 尾部 -->
    <footer class="bg-dark">
        <div class="container">
            <div class="row">
                <!-- 二维码 -->
                <div class="col-4 qrcode">
                    <img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
                </div>
                <!-- 公司信息 -->
                <div class="col-8 detail-info">
                    <div class="row">
                        <div class="col">
                            <ul class="nav">
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">关于我们</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">加入我们</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">意见反馈</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">企业服务</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">联系我们</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">免责声明</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">友情链接</a>
                                </li>
                            </ul>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col">
                            <ul class="nav btn-group-vertical company-info">
                                <li class="nav-item text-white-50">
                                    公司地址:#
                                </li>
                                <li class="nav-item text-white-50">
                                    联系方式:#
                                </li>
                                <li class="nav-item text-white-50">
                                    #
                                </li>
                                <li class="nav-item text-white-50">
                                    ######## &nbsp;&nbsp;&nbsp;&nbsp;
                                    <img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
                                    ######## 号
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </footer>
</div>

<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script th:src="@{/js/global.js}"></script>
<script th:src="@{js/index.js}"></script>
</body>
</html>
最后修改:2023 年 03 月 10 日
声明 😋 -博客名称: Yumuing 博客:做技术的朝拜者
🤓 -本文链接: https://yumuing.top/archives/23.html
🤔 -内容来源: 部分内容可能来源于公共网络,如有侵权,请联系博主进行核实删除。
😶 -转载说明: 请勿用于商业用途,转载请注明出处!



如果文章对你有用,评论一下、点个赞吧!或者请博主喝一杯咖啡吧!