ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SpringBoot 把PageHelper分页信息返回给前端

2026/8/10 17:27:39 拓冰建站 浏览量
SpringBoot 把PageHelper分页信息返回给前端

第1步:定义收纳HttpHeaders和HttpStatus的 线程容器

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;public class ResponseContext {private static ThreadLocal<HttpHeaders> ThreadLocalHeaders = new InheritableThreadLocal<>();private static ThreadLocal<HttpStatus> ThreadLocalStatus = new InheritableThreadLocal<>();public static void addHeaders(String key, String value) {if (ThreadLocalHeaders.get() == null) {ThreadLocalHeaders.set(new HttpHeaders());}ThreadLocalHeaders.get().add(key, value);}public static void setResponseCode(HttpStatus httpStatus) {ThreadLocalStatus.set(httpStatus);}public static HttpHeaders getHeaders() {return ThreadLocalHeaders.get();}public static HttpStatus getResponseCode() {return ThreadLocalStatus.get();}public static void clear() {if (ThreadLocalHeaders.get() != null) {ThreadLocalHeaders.remove();}if (ThreadLocalStatus.get() != null) {ThreadLocalStatus.remove();}}
}

第2步:封装 把分页信息转储到线程容器 的方法

import com.github.pagehelper.Page;
import com.github.pagehelper.PageInfo;
import com.zhangziwa.practisesvr.utils.response.ResponseContext;public class PageHeaderUtils {private static final String PAGE_NUM = "page_num"; // 当前第几页private static final String PAGE_SIZE = "page_size"; // 每页显示的条数private static final String PREV_PAGE = "prev_page"; // 上一页页码private static final String NEXT_PAGE = "next_page"; // 下一页页码private static final String TOTAL_COUNT = "total_count"; // 总条数private static final String TOTAL_PAGE = "total_page"; // 总页数public static <E> void setPageHeader(Page<E> page) {if (page == null) {return;}ResponseContext.addHeaders(PAGE_NUM, String.valueOf(page.getPageNum()));ResponseContext.addHeaders(PAGE_SIZE, String.valueOf(page.getPageSize()));ResponseContext.addHeaders(TOTAL_COUNT, String.valueOf(page.getTotal()));ResponseContext.addHeaders(TOTAL_PAGE, String.valueOf(page.getPages() == 0 ? 1 : page.getPages()));if (page.getPages() == 0 || page.getPages() == 1) {ResponseContext.addHeaders(PREV_PAGE, "");ResponseContext.addHeaders(NEXT_PAGE, "");} else if (page.getPageNum() == 1) {ResponseContext.addHeaders(PREV_PAGE, "");ResponseContext.addHeaders(NEXT_PAGE, String.valueOf(page.getPages() + 1));} else if (page.getPageNum() == page.getPages()) {ResponseContext.addHeaders(PREV_PAGE, String.valueOf(page.getPages() - 1));ResponseContext.addHeaders(NEXT_PAGE, "");} else {ResponseContext.addHeaders(PREV_PAGE, String.valueOf(page.getPages() - 1));ResponseContext.addHeaders(NEXT_PAGE, String.valueOf(page.getPages() + 1));}}/*** 设置分页头信息* @param page 分页对象* @param <E> 分页对象的数据类型*/public static <E> void setPageHeader(PageInfo<E> page) {if (page == null) {return;}ResponseContext.addHeaders(PAGE_NUM, String.valueOf(page.getPageNum()));  // 设置当前页码ResponseContext.addHeaders(PAGE_SIZE, String.valueOf(page.getPageSize()));  // 设置每页显示数量ResponseContext.addHeaders(TOTAL_COUNT, String.valueOf(page.getTotal()));  // 设置总记录数ResponseContext.addHeaders(TOTAL_PAGE, String.valueOf(page.getPages() == 0 ? 1 : page.getPages()));  // 设置总页数// page.getPages()=1表示就1页,前后页都不存在,故也算特殊场景.也为了page.getPages()+1和page.getPages()-1不会对[1,page.getPages()]越界if (page.getPages() == 0 || page.getPages() == 1) {ResponseContext.addHeaders(PREV_PAGE, "");  // 上一页ResponseContext.addHeaders(NEXT_PAGE, "");  // 下一页} else if (page.getPageNum() == 1) {ResponseContext.addHeaders(PREV_PAGE, "");  // 上一页ResponseContext.addHeaders(NEXT_PAGE, String.valueOf(page.getPages() + 1));  // 下一页} else if (page.getPageNum() == page.getPages()) {ResponseContext.addHeaders(PREV_PAGE, String.valueOf(page.getPages() - 1));  // 上一页ResponseContext.addHeaders(NEXT_PAGE, "");  // 下一页} else {ResponseContext.addHeaders(PREV_PAGE, String.valueOf(page.getPages() - 1));  // 上一页ResponseContext.addHeaders(NEXT_PAGE, String.valueOf(page.getPages() + 1));  // 下一页}}
}

第3步:分页查询后 把HttpHeaders和HttpStatus收集到线程容器

@Service
@Slf4j
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;# PageInfo方式@Overridepublic List<Student> listStudents(Integer pageNum, Integer PageSize) {PageHelper.startPage(PageUtils.getPageNum(pageNum), PageUtils.getPageSize(PageSize), PageUtils.isQueryTotalCount());PageHelper.orderBy("age asc");List<Student> students = userMapper.listStudents();PageInfo<Student> studentPageInfo = PageInfo.of(students);// 收集分页信息到 ThreadLocalPageHeaderUtils.setPageHeader(studentPageInfo);// 收集HttpStatus到 ThreadLocal// ResponseContext.setResponseCode(num2HttpStatus("200")); // 为了使用一下num2HttpStatus方法ResponseContext.setResponseCode(HttpStatus.OK);return students;}# Page 方式@Overridepublic List<Student> listStudents2(Integer pageNum, Integer PageSize) {PageHelper.startPage(PageUtils.getPageNum(pageNum), PageUtils.getPageSize(PageSize), PageUtils.isQueryTotalCount());PageHelper.orderBy("age asc");Page<Student> students = (Page<Student>) userMapper.listStudents();PageHeaderUtils.setPageHeader(students);ResponseContext.setResponseCode(HttpStatus.OK);return students;}# 多数据源聚合时的手工分页@Overridepublic List<Student> listStudents3(Integer pageNum, Integer pageSize) {// 模拟数据聚合List<Student> students3 = new ArrayList<>();List<Student> students1 = userMapper.listStudents();List<Student> students2 = userMapper.listStudents();students3.addAll(students1);students3.addAll(students2);Page<Student> studentsPage = PageHeaderUtils.manualPage(students3, pageNum, pageSize);PageHeaderUtils.setPageHeader(studentsPage);ResponseContext.setResponseCode(HttpStatus.OK);return studentsPage;}
}
public class HttpStatusUtils {public static HttpStatus num2HttpStatus(String num) {HttpStatus httpStatus = HttpStatus.NOT_FOUND;for (HttpStatus status : HttpStatus.values()) {if (Integer.parseInt(num) == status.value()) {return status;}}return httpStatus;}
}

单次分页查询可以使用PageHelper,但是如果多数据源数据聚合时PageHelper不再适用,可以使用手工分页的方式进行分页。

# 使用PageHelper进行分页时
PageHelper.startPage(PageUtils.getPageNum(pageNum), PageUtils.getPageSize(PageSize), PageUtils.isQueryTotalCount());# 数据聚合场景下的手工分页
public class PageHeaderUtils {// 也适合EmptyList场景 Page{count=true, pageNum=1, pageSize=10, startRow=0, endRow=0, total=0, pages=0, reasonable=null, pageSizeZero=null}public static <E> Page<E> manualPage(List<E> res, Integer pageNum, Integer pageSize) {if (res == null) {res = new ArrayList<>();}int pageNumber = PageUtils.getPageNum(pageNum);int sizePerPage = PageUtils.getPageSize(pageSize);int totalSize = res.size();List<E> pageElements = res.stream().skip((long) (pageNumber - 1) * sizePerPage).limit(sizePerPage).toList(); // 分页Page<E> page = new Page<>();page.setPageNum(pageNumber);page.setPageSize(sizePerPage);page.setTotal(totalSize);page.setPages((totalSize / pageSize + ((totalSize % pageSize == 0) ? 0 : 1)));page.addAll(pageElements);return page;}
}

第4步:检查controller层, @Controller → @RestController

@RestController
@Slf4j
public class SearchController {@AutowiredUserService userService;@RequestMapping(value = "/getAllStudents", method = RequestMethod.GET)public List<Student> getAllStudents() {List<Student> students = userService.listStudents(1, 10);students.forEach(System.out::println);return students;}@RequestMapping(value = "/getAllStudents2", method = RequestMethod.GET)public List<Student> getAllStudents2() {List<Student> students = userService.listStudents2(1, 10);students.forEach(System.out::println);return students;}@RequestMapping(value = "/getAllStudents3", method = RequestMethod.GET)public List<Student> getAllStudents3() {List<Student> students = userService.listStudents3(1, 5);students.forEach(System.out::println);return students;}
}

第5步:线程容器收纳的 HttpHeaders和HttpStatus 添加到ServerHttpResponse

import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;import static java.util.Objects.nonNull;@ControllerAdvice
public class ResponsePostAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class clazz,ServerHttpRequest request, ServerHttpResponse response) {HttpHeaders headers = response.getHeaders();// 分页信息添加到ServerHttpResponseHttpHeaders headersContext = ResponseContext.getHeaders();if (nonNull(headersContext) && !headersContext.isEmpty()) {headers.addAll(headersContext);}// 状态码添加到ServerHttpResponseif (nonNull(ResponseContext.getResponseCode())) {response.setStatusCode(ResponseContext.getResponseCode());}return body;}
}

第6步:清理 线程容器

定义拦截器执行ResponseContext.clear();,用于资源清理

import com.zhangziwa.practisesvr.utils.response.ResponseContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;@Component
public class ResponsePostInterceptor implements HandlerInterceptor {//在Controller执行之前调用,如果返回false,controller不执行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.err.println("***ResponsePostInterceptor.preHandle***");return true;}//controller执行之后,且页面渲染之前调用@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.err.println("***ResponsePostInterceptor.postHandle***");}//页面渲染之后调用,一般用于资源清理操作@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.err.println("***ResponsePostInterceptor.afterCompletion***");ResponseContext.clear();}
}

注册拦截器

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {@Autowiredprivate ResponsePostInterceptor responsePostInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(responsePostInterceptor);}
}

第7步:测试结果

在这里插入图片描述
在这里插入图片描述