java 抓取对接第三方API平台数据 完整实现 | 先获取登录Token + Token鉴权查询 + 缓存重试机 📅 2026/6/26 14:16:37 Markdown 教程单类通用Token接口请求工具无继承、纯独立类、大白话易懂一、功能说明市面上大部分第三方接口流程固定调用登录接口拿到鉴权Token存下Token和过期时间重复查询不用反复登录查数据时带上Token请求头Token失效自动重新获取再重试一次解析返回的列表数据本代码单独一个类不继承任何父类所有配置集中在最顶部换接口只改常量无硬编码文字新手直接复制就能用。二、所需Maven依赖!-- http请求工具okhttp --dependencygroupIdcom.squareup.okhttp3/groupIdartifactIdokhttp/artifactIdversion4.9.0/version/dependency!-- json解析工具fastjson --dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.74/version/dependency三、完整独立工具类无继承全部逻辑写在一类importcom.alibaba.fastjson.JSONArray;importcom.alibaba.fastjson.JSONObject;importokhttp3.*;importjava.util.ArrayList;importjava.util.List;/** * 通用Token鉴权接口请求工具 * 执行流程登录拿token → 缓存token复用 → 带token查询数据 → 失效自动重试 → 解析列表 * 全部配置写在顶部常量更换接口只修改常量即可 * 不继承任何类完全独立可直接复制使用 */publicclassCommonTokenApiUtil{// 配置区只修改这里下方逻辑不用动 // 获取token的登录接口地址privatestaticfinalStringLOGIN_URLhttps://xxxx.com/api/IPLogin;// 分页查询数据的业务接口地址privatestaticfinalStringSEARCH_URLhttps://xxxx.com/api/search;// 每页查询条数privatestaticfinalintPAGE_SIZE50;// 提前5分钟刷新token避免查询中途过期privatestaticfinallongREFRESH_TOKEN_TIME5*60*1000;// 详情页面拼接前缀privatestaticfinalStringDETAIL_URL_PREFIXhttps://xxxx.com/asset/searchingDetail?id;// // 缓存token全局复用privatestaticStringcacheToken;// token过期时间戳毫秒privatestaticlongtokenExpireTime;// 全局统一http请求对象复用节省资源privatestaticfinalOkHttpClientHTTP_CLIENTnewOkHttpClient();// json请求固定头部类型privatestaticfinalMediaTypeJSON_TYPEMediaType.parse(application/json; charsetutf-8);/** * 对外暴露的统一查询入口 * param keyword 搜索关键词 * param pageNum 当前页码 * return 解析后的列表数据 */publicListItemDatasearchData(Stringkeyword,intpageNum){System.out.println(开始执行接口查询关键词keyword页码pageNum);ListItemDataresultListnewArrayList();try{// 1. 获取可用token过期自动刷新StringtokengetAvailableToken();if(tokennull){System.out.println(获取token失败终止查询);returnresultList;}// 2. 构造查询接口请求参数JSONObjectsearchParambuildSearchParam(keyword,pageNum);// 3. 第一次发起查询StringresponseJsonsendSearchRequest(token,searchParam);// 4. 返回为空说明token失效刷新token重试一次if(responseJsonnull||responseJson.isBlank()){System.out.println(token失效重新刷新后重试查询);StringnewTokengetAvailableToken();if(newToken!null){responseJsonsendSearchRequest(newToken,searchParam);}}// 5. 解析返回的json数据封装成实体if(responseJson!null!responseJson.isBlank()){resultListparseResponseJson(responseJson);System.out.println(查询完成本次返回数据条数resultList.size());}}catch(Exceptione){System.out.println(查询接口出现异常e.getMessage());e.printStackTrace();}returnresultList;}/** * 获取有效的token线程安全未过期直接复用过期重新登录 */privatesynchronizedStringgetAvailableToken(){longnowTimeSystem.currentTimeMillis();// 判断token存在且未到刷新时间直接返回缓存tokenif(cacheToken!nullnowTimetokenExpireTime-REFRESH_TOKEN_TIME){returncacheToken;}// 重新调用登录接口获取新tokenreturnrefreshToken();}/** * 调用登录接口刷新全新token */privateStringrefreshToken(){try{// 登录接口请求体无参数传空json对象JSONObjectloginBodynewJSONObject();RequestrequestnewRequest.Builder().url(LOGIN_URL).post(RequestBody.create(JSON_TYPE,loginBody.toJSONString())).addHeader(Content-Type,application/json).build();// 执行请求try(ResponseresponseHTTP_CLIENT.newCall(request).execute()){if(!response.isSuccessful()||response.body()null){System.out.println(登录接口请求失败状态码response.code());returnnull;}StringrespStrresponse.body().string();JSONObjectrespJsonJSONObject.parseObject(respStr);// 判断登录是否成功if(!respJson.getBooleanValue(succeeded)){System.out.println(登录获取token失败提示respJson.getString(message));returnnull;}JSONObjectdatarespJson.getJSONObject(data);// 缓存tokencacheTokendata.getString(token);// 计算过期时间戳intexpireSecondsdata.getIntValue(expiredOn);tokenExpireTimeSystem.currentTimeMillis()expireSeconds*1000L;System.out.println(token刷新成功有效时长expireSeconds秒);returncacheToken;}}catch(Exceptione){System.out.println(刷新token发生异常e.getMessage());e.printStackTrace();returnnull;}}/** * 构造查询接口需要的json参数 */privateJSONObjectbuildSearchParam(Stringkeyword,intpageNum){JSONObjectbodynewJSONObject();body.put(sort,);body.put(pageIndex,pageNum);body.put(pageSize,PAGE_SIZE);body.put(keywords,keyword);returnbody;}/** * 携带token发送查询POST请求 */privateStringsendSearchRequest(Stringtoken,JSONObjectparamJson)throwsException{RequestrequestnewRequest.Builder().url(SEARCH_URL).post(RequestBody.create(JSON_TYPE,paramJson.toJSONString())).addHeader(Content-Type,application/json).addHeader(Authorization,Bearer token).build();try(ResponseresponseHTTP_CLIENT.newCall(request).execute()){if(response.isSuccessful()response.body()!null){returnresponse.body().string();}System.out.println(数据查询接口请求失败状态码response.code());returnnull;}}/** * 解析接口返回的json封装成自定义实体列表 */privateListItemDataparseResponseJson(StringjsonStr){ListItemDatalistnewArrayList();JSONObjectrootJsonJSONObject.parseObject(jsonStr);// 判断接口返回成功if(!rootJson.getBooleanValue(succeeded)){System.out.println(查询接口业务失败提示rootJson.getString(message));returnlist;}JSONObjectdataObjrootJson.getJSONObject(data);JSONObjecthitsObjdataObj.getJSONObject(hits);JSONArraysourceArrhitsObj.getJSONArray(source);if(sourceArrnull||sourceArr.isEmpty()){System.out.println(当前页码无数据);returnlist;}// 循环每一条数据封装for(inti0;isourceArr.size();i){JSONObjectitemsourceArr.getJSONObject(i);ItemDatadatanewItemData();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));// 拼接详情链接Stringiditem.getString(id);if(id!null){data.setDetailUrl(DETAIL_URL_PREFIXid);}list.add(data);}returnlist;}// 内部实体封装每条返回的数据publicstaticclassItemData{privateStringtitle;privateStringauthor;privateStringsource;privateStringabstractText;privateStringpublishDate;privateStringkeyword;privateStringdetailUrl;// getter setterpublicStringgetTitle(){returntitle;}publicvoidsetTitle(Stringtitle){this.titletitle;}publicStringgetAuthor(){returnauthor;}publicvoidsetAuthor(Stringauthor){this.authorauthor;}publicStringgetSource(){returnsource;}publicvoidsetSource(Stringsource){this.sourcesource;}publicStringgetAbstractText(){returnabstractText;}publicvoidsetAbstractText(StringabstractText){this.abstractTextabstractText;}publicStringgetPublishDate(){returnpublishDate;}publicvoidsetPublishDate(StringpublishDate){this.publishDatepublishDate;}publicStringgetKeyword(){returnkeyword;}publicvoidsetKeyword(Stringkeyword){this.keywordkeyword;}publicStringgetDetailUrl(){returndetailUrl;}publicvoidsetDetailUrl(StringdetailUrl){this.detailUrldetailUrl;}}// 测试main方法直接运行就能测试publicstaticvoidmain(String[]args){CommonTokenApiUtilutilnewCommonTokenApiUtil();// 查询关键词第一页ListItemDatadataListutil.searchData(测试关键词,1);for(ItemDataitem: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中途失效问题所有网络、解析异常全部捕获打印不会直接崩溃程序五、使用方法复制整个类到项目无需引入父类、无需实现接口修改顶部常量里的接口地址在业务代码调用CommonTokenApiUtilapinewCommonTokenApiUtil();ListCommonTokenApiUtil.ItemDatadataapi.searchData(人工智能,1);自带main函数右键运行可直接调试接口快速验证是否能正常拿到数据