RestTemplate请求,并返回Page<T>

本文介绍如何解决Spring RestTemplate与PageImpl不兼容的问题,通过创建自定义的MyPageImpl类并使用@JsonCreator及@JsonProperty注解来确保Jackson能够正确地将HTTP响应转换为所需的Page对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

填坑:Spring RestTemplate请求,并返回Page

RestTemplate指定返回泛型

RestTemplate.exchange()方法可以指定返回数据时,用泛型去包装

public <T> ResponseEntity<T> exchange(URI,HttpMethod, HttpEntity<?>,ParameterizedTypeReference<T>)

使用方法:

ParameterizedTypeReference<MyPageImpl<User>> typeRef = new ParameterizedTypeReference<MyPageImpl<User>>(){};
MyPageImpl<User> page = RestTemplate.exchange(uri,method,entity,typeRef);

这里面有个坑,因为org.springframework.data.domain.PageImpl,缺少默认的构造器及一些setter方法,所以Jackson不知道怎么去包装。
我的解决方法是新建一个继续自PageImpl的类

public class MyPageImpl<T> extends PageImpl {
    private boolean first;
    private boolean last;
    private long numberOfElements;
    private Object sort;
    private long totalPages;
    @JsonCreator
    public MyPageImpl(@JsonProperty("content") List<T> content,
                      @JsonProperty("number") int page,
                      @JsonProperty("size") int size,
                      @JsonProperty("totalElements") long total) {
        super(content, new PageRequest(page, size), total);
    }
}

主是使用@JsonCreator@JsonProperty告诉Jackson如何把数据注入自定义的构造器

参考
A Guide to Jackson Annotations

package com.jkt.sjzy.data.batch; import com.jkt.sjzy.data.batch.runner.FetchFromApiRunner; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.scheduling.annotation.EnableScheduling; import java.util.Arrays; /** * Hello world! *git branch -M master */ @SpringBootApplication @EnableScheduling @MapperScan(basePackages = {"com.jkt.sjzy.data.batch.mapper"}) public class BatchDataApplication { public static void main( String[] args ) { ConfigurableApplicationContext context = SpringApplication.run(BatchDataApplication.class, args); FetchFromApiRunner runner = context.getBean(FetchFromApiRunner.class); boolean isFirstExecution = Arrays.asList(args).contains("--first-execution"); runner.fetchFromApi(isFirstExecution); System.exit(0); } } package com.jkt.sjzy.data.batch.handler.savemode; import cn.hutool.core.date.DateUtil; import cn.hutool.db.PageResult; import com.alibaba.fastjson2.JSONObject; import com.jkt.sjzy.data.batch.domain.entities.DataApiConfig; import com.jkt.sjzy.data.batch.domain.entities.DataApiTokenConfig; import com.jkt.sjzy.data.batch.enums.ApiType; import com.jkt.sjzy.data.batch.handler.fetch.FetchDataFactory; import com.jkt.sjzy.data.batch.handler.fetch.IFetchDataHandler; import com.jkt.sjzy.data.batch.handler.token.ITokenHandler; import com.jkt.sjzy.data.batch.handler.token.TokenHandlerFactory; import com.jkt.sjzy.data.batch.handler.write.IWriteDataHandler; import okhttp3.Headers; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** * @Author: wangruiming * @Date: 2025/6/26 17:26 */ public abstract class SaveDataTemplate implements ISaveDataHandler { @Override public void saveData(DataApiTokenConfig tokenConfig, DataApiConfig apiConfig, ApiType apiType, String beginDate, String endDate) { IWriteDataHandler writeDataHandler = preStep(apiConfig, apiType); IFetchDataHandler<?> init = FetchDataFactory.init(apiConfig.getFetchDataClazz()); init.fetchData(apiType, tokenConfig, apiConfig, writeDataHandler, beginDate, endDate); } /** * 前置操作步骤 * * @param apiConfig * @param apiType */ public abstract IWriteDataHandler preStep(DataApiConfig apiConfig, ApiType apiType); } package com.jkt.sjzy.data.batch.handler.fetch; import java.lang.reflect.InvocationTargetException; /** * @Author: wangruiming * @Date: 2025/6/26 17:52 */ public class FetchDataFactory { public static IFetchDataHandler<?> init(String clazz){ try { Class<?> aClass = Class.forName(clazz); return (IFetchDataHandler<?>) aClass.getConstructor().newInstance(); } catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) { throw new RuntimeException(e); } } } package com.jkt.sjzy.data.batch.handler.fetch; import com.jkt.sjzy.data.batch.domain.entities.DataApiConfig; import com.jkt.sjzy.data.batch.domain.entities.DataApiTokenConfig; import com.jkt.sjzy.data.batch.enums.ApiType; import com.jkt.sjzy.data.batch.handler.write.IWriteDataHandler; /** * @Author: wangruiming * @Date: 2025/6/26 17:50 */ public interface IFetchDataHandler<T> { void fetchData(ApiType apiType, DataApiTokenConfig tokenConfig, DataApiConfig config, IWriteDataHandler handler, String beginDate, String endDate); } package com.jkt.sjzy.data.batch.handler.fetch.cck; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.digest.DigestUtil; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.google.common.util.concurrent.RateLimiter; import com.jkt.sjzy.data.batch.domain.dao.cck.CckPageResult; import com.jkt.sjzy.data.batch.domain.entities.DataApiConfig; import com.jkt.sjzy.data.batch.domain.entities.DataApiTokenConfig; import com.jkt.sjzy.data.batch.domain.entities.JobLastRunningLog; import com.jkt.sjzy.data.batch.enums.ApiType; import com.jkt.sjzy.data.batch.exception.TokenExpiredException; import com.jkt.sjzy.data.batch.handler.fetch.IFetchDataHandler; import com.jkt.sjzy.data.batch.handler.token.ITokenHandler; import com.jkt.sjzy.data.batch.handler.token.TokenHandlerFactory; import com.jkt.sjzy.data.batch.handler.write.IWriteDataHandler; import com.jkt.sjzy.data.batch.service.JobLastRunningLogService; import com.jkt.sjzy.data.batch.service.ServiceRegistry; import com.jkt.sjzy.data.batch.util.JsonUtil; import com.jkt.sjzy.data.batch.util.RequestUtil; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import okhttp3.Headers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.core.env.Environment; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import static com.alibaba.fastjson2.JSONObject.parseObject; import static com.jkt.sjzy.data.batch.constant.Constant.*; /** * @Author: wangruiming * @Date: 2025/6/26 17:53 */ @Slf4j public class CckFetchDataHandler<T> implements IFetchDataHandler<T> { private final Environment environment; private final JobLastRunningLogService jobLastRunningLogService; public CckFetchDataHandler(Environment environment, JobLastRunningLogService jobLastRunningLogService) { this.environment = environment; this.jobLastRunningLogService = jobLastRunningLogService; } @Override public void fetchData(ApiType apiType, DataApiTokenConfig tokenConfig, DataApiConfig config, IWriteDataHandler handler, String beginDate, String endDate) { Set<String> idSet = new HashSet<>(); String timeRangeType = getTimeRangeType(); LocalDateTime now = LocalDateTime.now(); int pageNum = 1; //刷新token ITokenHandler tokenHandler = TokenHandlerFactory.getTokenHandler(tokenConfig); if (!tokenHandler.validateToken()) { tokenHandler.refreshToken(); } // 处理分页接口 if (config.getPaged()) { // String beginDate = "2019-01-01"; // String endDate = DateUtil.format(LocalDateTime.now(), "yyyy-MM-dd"); while (true) { List<Object> allData = new ArrayList<>(); CckPageResult pageResult = fetchPagedData(apiType, config, tokenHandler, pageNum, beginDate, endDate); if (pageResult == null || pageResult.getResources().isEmpty()) { break; } for (JSONObject data : pageResult.getResources()) { String id = extractId(data, apiType); if (id != null && idSet.add(id)) { T bean = processDataToBean(data, config, apiType); if (bean != null) { allData.add(bean); } } data = null; } handler.writeData(allData); if (pageNum >= pageResult.getTotalPageNo()) { break; } pageNum++; } } // 处理单日接口 else { LocalDate startDate = LocalDate.of(2025, 1, 1); LocalDate endDateObj = parseDate(endDate); LocalDate beginDateObj = parseDate(beginDate); if (beginDateObj.isBefore(startDate)) { beginDateObj = startDate; } // Iterator<LocalDate> dateIterator = startDate.datesUntil(endDate.plusDays(1)).iterator(); List<LocalDate> allDates = beginDateObj.datesUntil(endDateObj.plusDays(1)).collect(Collectors.toList()); int threadCount = Math.min(Runtime.getRuntime().availableProcessors() * 2, 10); ExecutorService executor = Executors.newFixedThreadPool(threadCount); Semaphore semaphore = new Semaphore(5); // 限制最大请求数 RateLimiter rateLimiter = RateLimiter.create(5.0); // 每秒最多5个请求 BlockingQueue<T> resultQueue = new LinkedBlockingQueue<>(); CountDownLatch latch = new CountDownLatch(allDates.size()); // while (dateIterator.hasNext()) { // 提交所有日期的请求任务 for (LocalDate date : allDates) { executor.submit(() -> { try { semaphore.acquire(); rateLimiter.acquire(); // LocalDate currentDate = dateIterator.next(); String dateStr = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 获取单日数据 JSONObject data = fetchSingleDayData(apiType, tokenHandler, config, dateStr); if (data != null) { String id = extractId(data, apiType); if (id != null && idSet.add(id)) { T bean = processDataToBean(data, config, apiType); if (bean != null) { resultQueue.put(bean); } data = null; } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { log.error("处理日期 {} 时出错", date, e); } finally { semaphore.release(); latch.countDown(); } }); } try { latch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.error("等待任务完成时被中断", e); } finally { executor.shutdown(); } List<Object> allData = new ArrayList<>(resultQueue); handler.writeData(allData); } jobLastRunningLogService.saveOrUpdate(new JobLastRunningLog(DigestUtil.md5Hex(config.getTargetTable()), apiType.getDesc(), now, (long) idSet.size())); } /** * 将JSON数据转换为对应的Bean对象 */ @SuppressWarnings("unchecked") private <T> T processDataToBean(JSONObject data, DataApiConfig apiConfig, ApiType apiType) { if (data == null) { return null; } // 转换为小写下划线命名 try { Class<?> clazz = Class.forName(DAO_CLAZZ_PATH.formatted(apiConfig.getProjectCode()) + apiType.getBeanClazz()); JsonUtil.jsonToUnderLineKey(data); return (T) JSONObject.parseObject(data.toJSONString(), clazz); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } /** * 获取单日数据 */ private JSONObject fetchSingleDayData(ApiType apiType, ITokenHandler tokenHandler, DataApiConfig config, String date) { AtomicInteger retryCount = new AtomicInteger(0); while (retryCount.getAndIncrement() < MAX_PAGE_RETRIES) { try { String url = config.getUrl() + "?date=" + date; Headers headers = Headers.of("x-token", tokenHandler.getToken()); String responseBody = RequestUtil.requestRaw("GET", url, headers, ""); return parseSingleResult(responseBody, apiType); } catch (TokenExpiredException e) { tokenHandler.refreshToken(); } catch (Exception e) { if (retryCount.get() < MAX_PAGE_RETRIES) { tokenHandler.sleepWithBackoff(retryCount.get()); } } } return null; } /** * 解析单日结果 */ private JSONObject parseSingleResult(String response, ApiType apiType) { if (StrUtil.isBlank(response)) { return null; } try { JSONObject responseObj = parseObject(response); if (responseObj == null) { return null; } // 检查响应状态码 int ret = responseObj.getIntValue("ret"); if (ret != 0) { log.warn("{}接口返回非成功状态: {}", apiType.getDesc(), ret); return null; } // 解析嵌套的data结构 if (responseObj.containsKey("data")) { JSONObject outerData = responseObj.getJSONObject("data"); if (outerData != null && outerData.containsKey("data")) { return outerData.getJSONObject("data"); } } } catch (Exception e) { log.error("解析{}单日数据失败: {}", apiType.getDesc(), e.getMessage()); } return null; } /** * 获取分页数据 */ private CckPageResult fetchPagedData(ApiType apiType, DataApiConfig config, ITokenHandler tokenHandler, int pageNum, String beginDate, String endDate) { AtomicInteger retryCount = new AtomicInteger(0); while (retryCount.getAndIncrement() < MAX_PAGE_RETRIES) { try { String url = config.getUrl() + "?beginDate=" + beginDate + "&pageNo=" + pageNum + "&pageSize=" + PAGE_SIZE + "&endDate=" + endDate; Headers headers = Headers.of("x-token", tokenHandler.getToken()); String responseBody = RequestUtil.requestRaw("GET", url, headers, ""); return parsePageResult(responseBody, apiType); } catch (TokenExpiredException e) { tokenHandler.refreshToken(); } catch (Exception e) { tokenHandler.sleepWithBackoff(retryCount.get()); } } return null; } /** * 从数据中提取唯一标识 */ private String extractId(JSONObject data, ApiType apiType) { if (data == null) { return null; } // 根据不同接口类型提取不同的ID字段 switch (apiType) { case ACTIVITY: return data.getString("ID"); case EVENT: return data.getString("ID"); case TRAINING_VIDEO: return data.getString("ID"); case TRAINING_TEXT: return data.getString("ID"); case EVENT_INFO: return data.getString("DATE"); case PASSENGER_INFO: return data.getString("DATETIME"); case ACTIVITY_INFO: return data.getString("DATE"); default: return data.getString("ID"); } } //日期格式参数化 private static LocalDate parseDate(String dateStr) { return dateStr.contains("T") ? LocalDateTime.parse(dateStr).toLocalDate() : LocalDate.parse(dateStr); } private String getTimeRangeType() { return environment.getProperty("data.fetch.timeRangeType", "NORMAL"); } /** * 解析分页结果 */ private CckPageResult parsePageResult(String response, ApiType apiType) { if (StrUtil.isBlank(response)) { return new CckPageResult(new ArrayList<>(), 0, 1); } List<JSONObject> dataList = new ArrayList<>(); int totalCount = 0; int totalPage = 1; try { JSONObject responseObj = parseObject(response); if (responseObj == null) { return new CckPageResult(dataList, totalCount, totalPage); } // 嵌套的data.data层 JSONObject dataObj = responseObj.getJSONObject("data").getJSONObject("data"); totalCount = dataObj.getIntValue("count"); int pageSize = dataObj.getIntValue("pageSize"); totalPage = (totalCount + pageSize - 1) / pageSize; JSONArray dataArray = dataObj.getJSONArray("list"); if (dataArray != null) { for (int i = 0; i < dataArray.size(); i++) { JSONObject data = dataArray.getJSONObject(i); if (data != null) { dataList.add(data); } } } } catch (Exception e) { log.error("解析{}分页数据失败: {}", apiType.getDesc(), e.getMessage()); } return new CckPageResult(dataList, totalCount, totalPage); } }
07-09
package com.example.demo; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.client.RestTemplate; // 引用海康安全认证库 import com.hikvision.artemis.sdk.ArtemisHttpUtil; import java.util.HashMap; import java.util.Map; @SpringBootApplication public class DemoApplication implements CommandLineRunner { private static final String API_URL = "http://192.25.10.58:443/api/acs/v2/door/events"; public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Override public void run(String... args) throws Exception { RestTemplate restTemplate = new RestTemplate(); // 构造请求参数 Map<String, Object> request = new HashMap<>(); request.put("pageNo", 1); request.put("pageSize", 10); request.put("doorIndexCodes", new String[]{"1f276203e5234bdca08f7d99e1097bba"}); request.put("doorName", "11"); request.put("readerDevIndexCodes", new String[]{"1f2762v4523547374"}); request.put("startTime", "2018-05-21T12:00:00+08:00"); request.put("endTime", "2018-05-21T12:00:00+08:00"); request.put("receiveStartTime", "2018-05-21T12:00:00+08:00"); request.put("receiveEndTime", "2018-05-21T12:00:00+08:00"); request.put("doorRegionIndexCode", "c654234f-61d4-4dcd-9d21-e7a45e0f1334"); request.put("eventTypes", new Integer[]{10}); request.put("personName", "xx"); request.put("sort", "personName"); request.put("order", "asc"); // 发送POST请求 String result = ArtemisHttpUtil.doPostStringArtemis(path, , request, "application/json"); // 打印返回结果 System.out.println("返回结果:"+response); } }重新完善代码
03-27
好的,以下是一个示例代码,演示如何在Spring Boot项目中使用`PageHelper`实现在SQL层面的分页: 首先,需要在`pom.xml`中添加`pagehelper`依赖: ```xml <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> ``` 然后,在`application.properties`中添加相应的配置: ```properties # 分页插件配置 pagehelper.helperDialect=mysql pagehelper.reasonable=true pagehelper.supportMethodsArguments=true pagehelper.params=count=countSql ``` 接下来,定义一个`Page`类,用于封装分页参数和查询结果: ```java public class Page<T> { private List<T> content; // 数据列表 private long total; // 总记录数 private int pageNum; // 当前页码 private int pageSize; // 每页记录数 private String sort; // 排序字段 private String order; // 排序方式 // getter和setter方法 } ``` 然后,定义一个`PageHelperUtil`工具类,用于调用`PageHelper`进行分页查询: ```java public class PageHelperUtil { public static <T> Page<T> startPage(PageSortInterface pageSort, Supplier<List<T>> supplier) { PageHelper.startPage(pageSort.getPageNum(), pageSort.getPageSize(), pageSort.getSort() + " " + pageSort.getOrder()); List<T> list = supplier.get(); PageInfo<T> pageInfo = new PageInfo<>(list); Page<T> page = new Page<>(); page.setContent(list); page.setTotal(pageInfo.getTotal()); page.setPageNum(pageInfo.getPageNum()); page.setPageSize(pageInfo.getPageSize()); page.setSort(pageSort.getSort()); page.setOrder(pageSort.getOrder()); return page; } } ``` 在`startPage`方法中,我们调用`PageHelper.startPage`方法,指定当前页码、每页记录数和排序方式,使用`Supplier`接口作为入参,接收一个查询结果的lambda表达式。然后,将查询结果封装为`Page`对象,包含数据列表、总记录数、当前页码、每页记录数、排序字段和排序方式。 最后,在`UserMapper`接口中定义一个`selectByRoleAndPage`方法,接收一个`PageSortInterface`类型的参数,返回一个`List<User>`类型的结果: ```java public interface UserMapper extends MyBaseMapper<User> { @SelectProvider(type = MySelectProvider.class, method = "dynamicSQL") List<User> selectByRoleAndPage(PageSortInterface pageSort); } ``` 在`selectByRoleAndPage`方法中,我们调用`MySelectProvider.dynamicSQL`方法生成查询语句,将`PageSortInterface`类型的参数作为入参传递给`PageHelperUtil.startPage`方法,实现分页查询。最终返回一个`List<User>`类型的结果。 使用示例如下: ```java @RequestMapping("/users") public Page<User> getUsers(@RequestParam int role, @RequestParam int pageNum, @RequestParam int pageSize, @RequestParam String sort, @RequestParam String order) { PageSortInterface pageSort = new PageSortImpl(pageNum, pageSize, sort, order); return PageHelperUtil.startPage(pageSort, () -> userMapper.selectByRoleAndPage(pageSort)); } ``` 在`getUsers`方法中,我们接收请求参数,创建一个`PageSortImpl`对象,实现`PageSortInterface`接口。然后,调用`PageHelperUtil.startPage`方法,传入`PageSortInterface`类型的参数和一个lambda表达式,获取分页查询结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值