Markdown 教程:单类通用Token接口请求工具(无继承、纯独立类、大白话易懂)
一、功能说明
市面上大部分第三方接口流程固定:
- 调用登录接口,拿到鉴权Token
- 存下Token和过期时间,重复查询不用反复登录
- 查数据时带上Token请求头
- Token失效自动重新获取再重试一次
- 解析返回的列表数据
本代码单独一个类,不继承任何父类,所有配置集中在最顶部,换接口只改常量,无硬编码文字,新手直接复制就能用。
二、所需Maven依赖
xml
<!-- http请求工具okhttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<!-- json解析工具fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.74</version>
</dependency>
三、完整独立工具类(无继承,全部逻辑写在一类)
java
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import okhttp3.*;
import java.util.ArrayList;
import java.util.List;
/**
* 通用Token鉴权接口请求工具
* 执行流程:登录拿token → 缓存token复用 → 带token查询数据 → 失效自动重试 → 解析列表
* 全部配置写在顶部常量,更换接口只修改常量即可
* 不继承任何类,完全独立,可直接复制使用
*/
public class CommonTokenApiUtil {
// ====================== 配置区(只修改这里,下方逻辑不用动) ======================
// 获取token的登录接口地址
private static final String LOGIN_URL = "https://xxxx.com/api/IPLogin";
// 分页查询数据的业务接口地址
private static final String SEARCH_URL = "https://xxxx.com/api/search";
// 每页查询条数
private static final int PAGE_SIZE = 50;
// 提前5分钟刷新token,避免查询中途过期
private static final long REFRESH_TOKEN_TIME = 5 * 60 * 1000;
// 详情页面拼接前缀
private static final String DETAIL_URL_PREFIX = "https://xxxx.com/asset/searchingDetail?id=";
// ==============================================================================
// 缓存token,全局复用
private static String cacheToken;
// token过期时间戳(毫秒)
private static long tokenExpireTime;
// 全局统一http请求对象,复用节省资源
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient();
// json请求固定头部类型
private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
/**
* 对外暴露的统一查询入口
* @param keyword 搜索关键词
* @param pageNum 当前页码
* @return 解析后的列表数据
*/
public List<ItemData> searchData(String keyword, int pageNum) {
System.out.println("开始执行接口查询,关键词:" + keyword + ",页码:" + pageNum);
List<ItemData> resultList = new ArrayList<>();
try {
// 1. 获取可用token,过期自动刷新
String token = getAvailableToken();
if (token == null) {
System.out.println("获取token失败,终止查询");
return resultList;
}
// 2. 构造查询接口请求参数
JSONObject searchParam = buildSearchParam(keyword, pageNum);
// 3. 第一次发起查询
String responseJson = sendSearchRequest(token, searchParam);
// 4. 返回为空说明token失效,刷新token重试一次
if (responseJson == null || responseJson.isBlank()) {
System.out.println("token失效,重新刷新后重试查询");
String newToken = getAvailableToken();
if (newToken != null) {
responseJson = sendSearchRequest(newToken, searchParam);
}
}
// 5. 解析返回的json数据,封装成实体
if (responseJson != null && !responseJson.isBlank()) {
resultList = parseResponseJson(responseJson);
System.out.println("查询完成,本次返回数据条数:" + resultList.size());
}
} catch (Exception e) {
System.out.println("查询接口出现异常:" + e.getMessage());
e.printStackTrace();
}
return resultList;
}
/**
* 获取有效的token,线程安全,未过期直接复用,过期重新登录
*/
private synchronized String getAvailableToken() {
long nowTime = System.currentTimeMillis();
// 判断token存在且未到刷新时间,直接返回缓存token
if (cacheToken != null && nowTime < tokenExpireTime - REFRESH_TOKEN_TIME) {
return cacheToken;
}
// 重新调用登录接口获取新token
return refreshToken();
}
/**
* 调用登录接口,刷新全新token
*/
private String refreshToken() {
try {
// 登录接口请求体,无参数传空json对象
JSONObject loginBody = new JSONObject();
Request request = new Request.Builder()
.url(LOGIN_URL)
.post(RequestBody.create(JSON_TYPE, loginBody.toJSONString()))
.addHeader("Content-Type", "application/json")
.build();
// 执行请求
try (Response response = HTTP_CLIENT.newCall(request).execute()) {
if (!response.isSuccessful() || response.body() == null) {
System.out.println("登录接口请求失败,状态码:" + response.code());
return null;
}
String respStr = response.body().string();
JSONObject respJson = JSONObject.parseObject(respStr);
// 判断登录是否成功
if (!respJson.getBooleanValue("succeeded")) {
System.out.println("登录获取token失败,提示:" + respJson.getString("message"));
return null;
}
JSONObject data = respJson.getJSONObject("data");
// 缓存token
cacheToken = data.getString("token");
// 计算过期时间戳
int expireSeconds = data.getIntValue("expiredOn");
tokenExpireTime = System.currentTimeMillis() + expireSeconds * 1000L;
System.out.println("token刷新成功,有效时长:" + expireSeconds + "秒");
return cacheToken;
}
} catch (Exception e) {
System.out.println("刷新token发生异常:" + e.getMessage());
e.printStackTrace();
return null;
}
}
/**
* 构造查询接口需要的json参数
*/
private JSONObject buildSearchParam(String keyword, int pageNum) {
JSONObject body = new JSONObject();
body.put("sort", "");
body.put("pageIndex", pageNum);
body.put("pageSize", PAGE_SIZE);
body.put("keywords", keyword);
return body;
}
/**
* 携带token发送查询POST请求
*/
private String sendSearchRequest(String token, JSONObject paramJson) throws Exception {
Request request = new Request.Builder()
.url(SEARCH_URL)
.post(RequestBody.create(JSON_TYPE, paramJson.toJSONString()))
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer " + token)
.build();
try (Response response = HTTP_CLIENT.newCall(request).execute()) {
if (response.isSuccessful() && response.body() != null) {
return response.body().string();
}
System.out.println("数据查询接口请求失败,状态码:" + response.code());
return null;
}
}
/**
* 解析接口返回的json,封装成自定义实体列表
*/
private List<ItemData> parseResponseJson(String jsonStr) {
List<ItemData> list = new ArrayList<>();
JSONObject rootJson = JSONObject.parseObject(jsonStr);
// 判断接口返回成功
if (!rootJson.getBooleanValue("succeeded")) {
System.out.println("查询接口业务失败,提示:" + rootJson.getString("message"));
return list;
}
JSONObject dataObj = rootJson.getJSONObject("data");
JSONObject hitsObj = dataObj.getJSONObject("hits");
JSONArray sourceArr = hitsObj.getJSONArray("source");
if (sourceArr == null || sourceArr.isEmpty()) {
System.out.println("当前页码无数据");
return list;
}
// 循环每一条数据封装
for (int i = 0; i < sourceArr.size(); i++) {
JSONObject item = sourceArr.getJSONObject(i);
ItemData data = new ItemData();
data.setTitle(item.getString("title"));
data.setAuthor(item.getString("author"));
data.setSource(item.getString("journal_name"));
data.setAbstractText(item.getString("abstract"));
data.setPublishDate(item.getString("pub_date_display"));
data.setKeyword(item.getString("keyword"));
// 拼接详情链接
String id = item.getString("id");
if (id != null) {
data.setDetailUrl(DETAIL_URL_PREFIX + id);
}
list.add(data);
}
return list;
}
// 内部实体:封装每条返回的数据
public static class ItemData {
private String title;
private String author;
private String source;
private String abstractText;
private String publishDate;
private String keyword;
private String detailUrl;
// getter setter
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public String getSource() { return source; }
public void setSource(String source) { this.source = source; }
public String getAbstractText() { return abstractText; }
public void setAbstractText(String abstractText) { this.abstractText = abstractText; }
public String getPublishDate() { return publishDate; }
public void setPublishDate(String publishDate) { this.publishDate = publishDate; }
public String getKeyword() { return keyword; }
public void setKeyword(String keyword) { this.keyword = keyword; }
public String getDetailUrl() { return detailUrl; }
public void setDetailUrl(String detailUrl) { this.detailUrl = detailUrl; }
}
// 测试main方法,直接运行就能测试
public static void main(String[] args) {
CommonTokenApiUtil util = new CommonTokenApiUtil();
// 查询关键词,第一页
List<ItemData> dataList = util.searchData("测试关键词", 1);
for (ItemData item : dataList) {
System.out.println("标题:" + item.getTitle() + " 作者:" + item.getAuthor());
}
}
}
四、代码大白话讲解
1. 顶部常量配置区
所有接口地址、分页大小、刷新时间全部写在最上面,以后换别的第三方接口,只改这几行,不用动下面业务代码。
2. 核心变量说明
cacheToken:存登录拿到的令牌,不用每次搜索都登录一次tokenExpireTime:记录token什么时候过期,提前5分钟自动换新HTTP_CLIENT:全局只用一个请求工具,频繁调用不会创建大量连接
3. 方法功能拆解
searchData:对外调用入口,传入关键词和页码直接拿结果getAvailableToken:判断token是否过期,过期自动刷新(加了同步锁,多线程不会同时刷新)refreshToken:调用登录接口获取新token,更新缓存和过期时间buildSearchParam:组装查询接口需要的一长串json参数,单独抽出来方便修改字段sendSearchRequest:带上token请求头发送查询parseResponseJson:把后端返回的复杂json,转换成简单实体对象,方便业务使用
4. 容错逻辑
- 拿不到token直接返回空列表,打印日志
- 查询返回空自动刷新token重试一次,解决token中途失效问题
- 所有网络、解析异常全部捕获打印,不会直接崩溃程序
五、使用方法
- 复制整个类到项目,无需引入父类、无需实现接口
- 修改顶部常量里的接口地址
- 在业务代码调用:
java
CommonTokenApiUtil api = new CommonTokenApiUtil();
List<CommonTokenApiUtil.ItemData> data = api.searchData("人工智能", 1);
- 自带main函数,右键运行可直接调试接口,快速验证是否能正常拿到数据