游标分页请求代码示例
<h2>简要描述</h2>
<p>获取【用户列表】和【订单列表】接口采用了游标分页的方式获取分页数据,不同于常规分页方式,其接口响应返回的分页信息不同:</p>
<h3>游标分页返回数据列表响应结构</h3>
<table>
<thead>
<tr>
<th style="text-align: left;">参数名 </th>
<th style="text-align: left;">类型 </th>
<th>说明 </th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">cursor </td>
<td style="text-align: left;">String </td>
<td>游标(下次翻页带上该参数)</td>
</tr>
<tr>
<td style="text-align: left;">isLast </td>
<td style="text-align: left;">Boolean </td>
<td>是否最后一页 </td>
</tr>
<tr>
<td style="text-align: left;">list </td>
<td style="text-align: left;">List </td>
<td>数据列表 </td>
</tr>
</tbody>
</table>
<ul>
<li>
<p><code>cursor</code> 游标相当于分页书签:标识当前页所在位置和下次请求分页数据起始位置;</p>
</li>
<li><code>isLast</code> 是否最后一页,可用于循环获取数据中的判定条件。</li>
</ul>
<h3>游标分页返回数据列表响应示例</h3>
<p>获取【用户列表】响应:</p>
<pre><code class="language-json">{
&quot;code&quot;: 0,
&quot;data&quot;: {
&quot;cursor&quot;: &quot;69136464311496704_min:614&quot;,
&quot;isLast&quot;: false,
&quot;list&quot;: [
{
&quot;adChannelType&quot;: 5,
&quot;adId&quot;: null,
&quot;advertiserId&quot;: null,
&quot;appId&quot;: 19743565588729856,
&quot;code&quot;: 873626316,
&quot;createDateTime&quot;: &quot;2024-01-15 00:05:19&quot;,
&quot;headImgUrl&quot;: null,
&quot;id&quot;: 77552339973046272,
&quot;ip&quot;: null,
&quot;lastLoginTime&quot;: null,
&quot;loginCount&quot;: 0,
&quot;memberName&quot;: &quot;游客&quot;,
&quot;mobile&quot;: null,
&quot;partnerId&quot;: 19743564976361472,
&quot;promotionId&quot;: 0,
&quot;promotionKeywords&quot;: null,
&quot;state&quot;: null,
&quot;ua&quot;: null,
&quot;updateDateTime&quot;: &quot;2024-01-15 00:05:19&quot;
},
{
&quot;adChannelType&quot;: 5,
&quot;adId&quot;: &quot;&quot;,
&quot;advertiserId&quot;: &quot;&quot;,
&quot;appId&quot;: 19743565588729856,
&quot;code&quot;: 873625742,
&quot;createDateTime&quot;: &quot;2023-12-22 18:43:38&quot;,
&quot;headImgUrl&quot;: null,
&quot;id&quot;: 69136464311496704,
&quot;ip&quot;: &quot;101.40.83.18&quot;,
&quot;lastLoginTime&quot;: &quot;2023-12-22 18:44:26&quot;,
&quot;loginCount&quot;: 3,
&quot;memberName&quot;: &quot;游客&quot;,
&quot;mobile&quot;: null,
&quot;partnerId&quot;: 19743564976361472,
&quot;promotionId&quot;: 0,
&quot;promotionKeywords&quot;: null,
&quot;state&quot;: null,
&quot;ua&quot;: &quot;Mozilla/5.0 ... Language/zh_CN ABI/arm64&quot;,
&quot;updateDateTime&quot;: &quot;2023-12-22 18:43:38&quot;
}
]
},
&quot;msg&quot;: &quot;OK&quot;,
&quot;traceId&quot;: &quot;761aba3b-394a-49aa-b791-c1b3273027ce&quot;
}</code></pre>
<h3>示例接口</h3>
<ul>
<li>【用户列表】接口:2024年4月20日之前版本,后续该接口如有变更,请按最新接口说明修改您的程序。</li>
</ul>
<h3>编程语言</h3>
<ul>
<li>JAVA</li>
</ul>
<h3>外部依赖</h3>
<ul>
<li>OkHttp:HTTP 客户端框架;</li>
<li>Gson:JSON 序列化反序列化库。</li>
</ul>
<p>引入依赖:</p>
<pre><code>&lt;dependencies&gt;
&lt;dependency&gt;
&lt;groupId&gt;com.squareup.okhttp3&lt;/groupId&gt;
&lt;artifactId&gt;okhttp&lt;/artifactId&gt;
&lt;version&gt;4.12.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;com.google.code.gson&lt;/groupId&gt;
&lt;artifactId&gt;gson&lt;/artifactId&gt;
&lt;version&gt;2.10.1&lt;/version&gt;
&lt;/dependency&gt;
&lt;/dependencies&gt;</code></pre>
<h2>代码</h2>
<h3>Main.java</h3>
<pre><code class="language-java">package com.col.okhttp.cursor;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import okhttp3.*;
import com.google.gson.Gson;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
public class Main {
/**
* OkHttpClient 实例
*/
private static final OkHttpClient client = new OkHttpClient();
/**
* Gson 实例
*/
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter()).create();
/**
* 协议
*/
private static final String SCHEME = &quot;https&quot;;
/**
* 主机
*/
private static final String HOST = &quot;gateway-partner.nm-serv.cn&quot;;
/**
* 获取用户列表路径
*/
private static final String MEMBER_LIST_PATH = &quot;member/list&quot;;
/**
* 授权 ID,用于请求头中的 Auth-Id,实际使用时应替换为真实的授权 ID,
* 如何获取授权 ID 请参考文档:https://www.showdoc.com.cn/gatewaypartnerv1/10572543503726557
* 注意:实际使用时请勿将【授权 ID】硬编码在代码中,应使用安全的方式存储和管理授权 ID
*/
private static final String AUTH_ID = &quot;{AUTH_ID}&quot;;
/**
* 授权 Token,用于请求头中的 Authorization,实际使用时应替换为真实的授权码,
* 如何获取授权码 请参考文档:https://www.showdoc.com.cn/gatewaypartnerv1/10572543503726557
* 注意:实际使用时请勿将【授权码】硬编码在代码中,应使用安全的方式存储和管理授权码
*/
private static final String AUTHORIZATION = &quot;{AUTHORIZATION}&quot;;
/**
* 默认分页大小
*/
private static final String DEFAULT_SIZE = &quot;100&quot;;
/**
* 日期时间格式
*/
private static final String DATE_TIME_FORMAT = &quot;yyyy-MM-dd HH:mm:ss&quot;;
/**
* 日期时间格式化器
*/
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME_FORMAT);
public static void main(String[] args) {
// 应用 ID,实际使用时应替换为真实的应用 ID
Long appId = 1L;
LocalDateTime now = LocalDateTime.now();
LocalDateTime yesterday = now.minusDays(1);
String beginDate = yesterday.format(formatter);
String endDate = now.format(formatter);
List&lt;MemberRes&gt; memberList = getMemberList(appId,
null,
beginDate,
endDate,
null,
null);
if (memberList != null) {
handleMemberList(memberList);
}
}
/**
* 处理用户列表
*
* @param memberList 用户列表
*/
private static void handleMemberList(List&lt;MemberRes&gt; memberList) {
// 处理数据
// memberList.forEach(System.out::println);
}
/**
* 获取用户列表
*
* @param appId 应用 ID
* @param memberId 用户 ID
* @param beginDate 用户注册开始时间
* @param endDate 用户注册结束时间
* @param lastLoginBeginDate 用户最后登录开始时间
* @param lastLoginEndDate 用户最后登录结束时间
* @return 用户列表
*/
private static List&lt;MemberRes&gt; getMemberList(Long appId,
Long memberId,
String beginDate,
String endDate,
String lastLoginBeginDate,
String lastLoginEndDate) {
if (appId == null || appId &lt;= 0) {
throw new IllegalArgumentException(&quot;应用 ID 不能为空&quot;);
}
// region 构造请求参数 Map
Map&lt;String, String&gt; params = new HashMap&lt;&gt;();
params.put(&quot;appId&quot;, String.valueOf(appId));
if (memberId != null &amp;&amp; memberId &gt; 0) {
params.put(&quot;memberId&quot;, String.valueOf(memberId));
}
if (beginDate != null &amp;&amp; !beginDate.isEmpty()) {
params.put(&quot;beginDate&quot;, beginDate);
}
if (endDate != null &amp;&amp; !endDate.isEmpty()) {
params.put(&quot;endDate&quot;, endDate);
}
if (lastLoginBeginDate != null &amp;&amp; !lastLoginBeginDate.isEmpty()) {
params.put(&quot;lastLoginBeginDate&quot;, lastLoginBeginDate);
}
if (lastLoginEndDate != null &amp;&amp; !lastLoginEndDate.isEmpty()) {
params.put(&quot;lastLoginEndDate&quot;, lastLoginEndDate);
}
params.put(&quot;size&quot;, DEFAULT_SIZE);
// endregion
// 分离路径,用于构造请求 URL,不分离的话,路径中包含的 / 会被转义
String[] paths = MEMBER_LIST_PATH.split(&quot;/&quot;);
// 构造请求 URL
HttpUrl httpUrl = getHttpUrl(paths, params);
// 用户列表
List&lt;MemberRes&gt; memberResList = new ArrayList&lt;&gt;();
try {
// 游标分页查询用户列表
ApiResponse&lt;CursorPageRes&lt;MemberRes&gt;&gt; apiResponse;
CursorPageRes&lt;MemberRes&gt; cursorPageRes;
do {
apiResponse = getMemberList(httpUrl);
// region 判断响应
if (apiResponse == null) {
System.out.println(&quot;apiResponse is null&quot;);
return memberResList;
}
if (apiResponse.getCode() != 0) {
System.out.println(&quot;apiResponse.getMsg() = &quot; + apiResponse.getMsg());
System.out.println(&quot;apiResponse.getTraceId() = &quot; + apiResponse.getTraceId());
return memberResList;
}
if (apiResponse.getData() == null) {
System.out.println(&quot;apiResponse.getData() is null&quot;);
return memberResList;
}
// endregion
// 获取游标分页响应
cursorPageRes = apiResponse.getData();
if (cursorPageRes.getList() == null || cursorPageRes.getList().isEmpty()) {
System.out.println(&quot;cursorPageRes.getList() is null&quot;);
return memberResList;
}
// 添加列表
memberResList.addAll(cursorPageRes.getList());
// 判断是否还有下一页
if (!cursorPageRes.getIsLast()) {
// 构造下一页请求 URL
// 下一页请求 URL 中包含了游标参数 cursor,用于获取下一页数据
// 其它参数不变,只需修改游标参数 cursor
httpUrl = httpUrl.newBuilder().setQueryParameter(&quot;cursor&quot;, cursorPageRes.getCursor()).build();
}
} while (!cursorPageRes.getIsLast());// 循环条件:还有下一页
return memberResList;
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
/**
* 获取用户列表
*
* @param url 请求 URL
* @return 用户列表
* @throws Exception 异常
*/
private static ApiResponse&lt;CursorPageRes&lt;MemberRes&gt;&gt; getMemberList(HttpUrl url) throws Exception {
Request request = new Request.Builder()
.url(url)
.headers(getHeaders())
.build();
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful() &amp;&amp; response.body() != null) {
// 获取响应体
String serializedObject = response.body().string();
// 反序列化为 ApiResponse&lt;CursorPageRes&lt;MemberRes&gt;&gt;,可以选用其它序列化反序列化框架
Type type = new TypeToken&lt;ApiResponse&lt;CursorPageRes&lt;MemberRes&gt;&gt;&gt;() {
}.getType();
return gson.fromJson(serializedObject, type);
} else {
throw new RuntimeException(&quot;请求失败&quot;);
}
}
}
/**
* 构造请求 URL
*
* @param paths 路径数组
* @param params 请求参数 Map
* @return 请求 URL
*/
private static HttpUrl getHttpUrl(String[] paths, Map&lt;String, String&gt; params) {
HttpUrl.Builder builder = new HttpUrl.Builder()
.scheme(SCHEME)
.host(HOST);
for (String path : paths) {
builder.addPathSegment(path); // 添加路径
}
for (Map.Entry&lt;String, String&gt; entry : params.entrySet()) {
builder.addQueryParameter(entry.getKey(), entry.getValue()); // 添加查询参数
}
return builder.build();
}
/**
* 获取请求头
*
* @return 请求头
*/
private static Headers getHeaders() {
return new Headers.Builder()
.add(&quot;Auth-Id&quot;, AUTH_ID) // 授权 ID
.add(&quot;Authorization&quot;, AUTHORIZATION) // 授权 Token
.build();
}
}
</code></pre>
<h3>ApiResponse.java</h3>
<pre><code class="language-java">package com.col.okhttp.cursor;
import java.io.Serializable;
/**
* API 响应
*
* @param &lt;T&gt; 数据类型
*/
public class ApiResponse&lt;T&gt; implements Serializable {
/**
* 返回码
*/
private Integer code;
/**
* 返回消息
*/
private String msg;
/**
* 链路追踪 ID
*/
private String traceId;
/**
* 返回数据
*/
private T data;
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
public String getTraceId() {
return traceId;
}
public T getData() {
return data;
}
}
</code></pre>
<h3>CursorPageRes.java</h3>
<pre><code>package com.col.okhttp.cursor;
import java.io.Serializable;
import java.util.List;
/**
* 游标分页响应
*
* @param &lt;T&gt; 数据类型
*/
public class CursorPageRes&lt;T&gt; implements Serializable {
/**
* 游标(下次翻页带上该参数)
*/
private String cursor;
/**
* 是否最后一页
*/
private Boolean isLast;
/**
* 数据列表
*/
private List&lt;T&gt; list;
public String getCursor() {
return cursor;
}
public Boolean getIsLast() {
return isLast;
}
public List&lt;T&gt; getList() {
return list;
}
}
</code></pre>
<h3>LocalDateTimeTypeAdapter.java</h3>
<pre><code class="language-java">package com.col.okhttp.cursor;
import com.google.gson.*;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* LocalDateTime 类型适配器
*/
public class LocalDateTimeTypeAdapter implements JsonSerializer&lt;LocalDateTime&gt;, JsonDeserializer&lt;LocalDateTime&gt; {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd HH:mm:ss&quot;);
@Override
public JsonElement serialize(LocalDateTime localDateTime, Type srcType,
JsonSerializationContext context) {
return new JsonPrimitive(formatter.format(localDateTime));
}
@Override
public LocalDateTime deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
return LocalDateTime.parse(json.getAsString(), formatter);
}
}
</code></pre>
<h3>MemberRes.java</h3>
<pre><code class="language-java">package com.col.okhttp.cursor;
import java.io.Serializable;
import java.time.LocalDateTime;
public class MemberRes implements Serializable {
/**
* 用户 ID
*/
private Long id;
/**
* 用户名称
*/
private String memberName;
/**
* 创建时间
*/
private LocalDateTime createDateTime;
/**
* 更新时间
*/
private LocalDateTime updateDateTime;
/**
* 头像
*/
private String headImgUrl;
/**
* 绑定手机号
*/
private String mobile;
/**
* 所属子分销商 ID
*/
private Long partnerId;
/**
* 归属应用 ID
*/
private Long appId;
/**
* 推广 ID
*/
private Long promotionId;
/**
* 推广链接口令
*/
private String promotionKeywords;
/**
* 登录次数
*/
private Integer loginCount;
/**
* 最近登录时间
*/
private LocalDateTime lastLoginTime;
/**
* 用户 code
*/
private Long code;
/**
* 状态:1-正常;2-隐藏
*/
private Integer state;
/**
* IP
*/
private String ip;
/**
* 注册 UA
*/
private String ua;
/**
* 广告账户 ID
*/
private String advertiserId;
/**
* 广告计划 ID
*/
private String adId;
/**
* 广告媒体渠道类型
*/
private Integer adChannelType;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getMemberName() {
return memberName;
}
public void setMemberName(String memberName) {
this.memberName = memberName;
}
public LocalDateTime getCreateDateTime() {
return createDateTime;
}
public void setCreateDateTime(LocalDateTime createDateTime) {
this.createDateTime = createDateTime;
}
public LocalDateTime getUpdateDateTime() {
return updateDateTime;
}
public void setUpdateDateTime(LocalDateTime updateDateTime) {
this.updateDateTime = updateDateTime;
}
public String getHeadImgUrl() {
return headImgUrl;
}
public void setHeadImgUrl(String headImgUrl) {
this.headImgUrl = headImgUrl;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public Long getPartnerId() {
return partnerId;
}
public void setPartnerId(Long partnerId) {
this.partnerId = partnerId;
}
public Long getAppId() {
return appId;
}
public void setAppId(Long appId) {
this.appId = appId;
}
public Long getPromotionId() {
return promotionId;
}
public void setPromotionId(Long promotionId) {
this.promotionId = promotionId;
}
public String getPromotionKeywords() {
return promotionKeywords;
}
public void setPromotionKeywords(String promotionKeywords) {
this.promotionKeywords = promotionKeywords;
}
public Integer getLoginCount() {
return loginCount;
}
public void setLoginCount(Integer loginCount) {
this.loginCount = loginCount;
}
public LocalDateTime getLastLoginTime() {
return lastLoginTime;
}
public void setLastLoginTime(LocalDateTime lastLoginTime) {
this.lastLoginTime = lastLoginTime;
}
public Long getCode() {
return code;
}
public void setCode(Long code) {
this.code = code;
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getUa() {
return ua;
}
public void setUa(String ua) {
this.ua = ua;
}
public String getAdvertiserId() {
return advertiserId;
}
public void setAdvertiserId(String advertiserId) {
this.advertiserId = advertiserId;
}
public String getAdId() {
return adId;
}
public void setAdId(String adId) {
this.adId = adId;
}
public Integer getAdChannelType() {
return adChannelType;
}
public void setAdChannelType(Integer adChannelType) {
this.adChannelType = adChannelType;
}
}
</code></pre>