This commit is contained in:
Leo
2026-01-14 22:55:36 +08:00
parent ab062b3b5a
commit b43daf2965
10 changed files with 1766 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
package com.ruoyi.jarvis.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* WPS365开放平台配置
*
* @author system
*/
@Configuration
@Component
@ConfigurationProperties(prefix = "wps365")
public class WPS365Config {
private static final Logger log = LoggerFactory.getLogger(WPS365Config.class);
/** 应用IDAppId */
private String appId;
/** 应用密钥AppKey */
private String appKey;
/** 授权回调地址 */
private String redirectUri;
/** API基础地址 */
private String apiBaseUrl = "https://open.wps.cn/api/v1";
/** OAuth授权地址 */
private String oauthUrl = "https://open.wps.cn/oauth2/v1/authorize";
/** 获取Token地址 */
private String tokenUrl = "https://open.wps.cn/oauth2/v1/token";
/** 刷新Token地址 */
private String refreshTokenUrl = "https://open.wps.cn/oauth2/v1/token";
/**
* 配置初始化后验证
*/
@PostConstruct
public void init() {
log.info("WPS365配置加载 - appId: {}, redirectUri: {}, apiBaseUrl: {}",
appId != null && appId.length() > 10 ? appId.substring(0, 10) + "..." : (appId != null ? appId : "null"),
redirectUri != null ? redirectUri : "null",
apiBaseUrl);
if (appId == null || appId.trim().isEmpty()) {
log.warn("WPS365应用ID未配置请检查application.yml中的wps365.app-id");
}
if (appKey == null || appKey.trim().isEmpty()) {
log.warn("WPS365应用密钥未配置请检查application.yml中的wps365.app-key");
}
if (redirectUri == null || redirectUri.trim().isEmpty()) {
log.warn("WPS365回调地址未配置请检查application.yml中的wps365.redirect-uri");
}
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getAppKey() {
return appKey;
}
public void setAppKey(String appKey) {
this.appKey = appKey;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
public String getApiBaseUrl() {
return apiBaseUrl;
}
public void setApiBaseUrl(String apiBaseUrl) {
this.apiBaseUrl = apiBaseUrl;
}
public String getOauthUrl() {
return oauthUrl;
}
public void setOauthUrl(String oauthUrl) {
this.oauthUrl = oauthUrl;
}
public String getTokenUrl() {
return tokenUrl;
}
public void setTokenUrl(String tokenUrl) {
this.tokenUrl = tokenUrl;
}
public String getRefreshTokenUrl() {
return refreshTokenUrl;
}
public void setRefreshTokenUrl(String refreshTokenUrl) {
this.refreshTokenUrl = refreshTokenUrl;
}
}

View File

@@ -0,0 +1,108 @@
package com.ruoyi.jarvis.domain.dto;
import java.io.Serializable;
/**
* WPS365 Token信息
*
* @author system
*/
public class WPS365TokenInfo implements Serializable {
private static final long serialVersionUID = 1L;
/** 访问令牌 */
private String accessToken;
/** 刷新令牌 */
private String refreshToken;
/** 令牌类型 */
private String tokenType;
/** 过期时间(秒) */
private Integer expiresIn;
/** 作用域 */
private String scope;
/** 用户ID */
private String userId;
/** 创建时间戳(毫秒) */
private Long createTime;
public WPS365TokenInfo() {
this.createTime = System.currentTimeMillis();
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public Integer getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(Integer expiresIn) {
this.expiresIn = expiresIn;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Long getCreateTime() {
return createTime;
}
public void setCreateTime(Long createTime) {
this.createTime = createTime;
}
/**
* 检查token是否过期
*/
public boolean isExpired() {
if (expiresIn == null || createTime == null) {
return true;
}
long currentTime = System.currentTimeMillis();
long expireTime = createTime + (expiresIn * 1000L);
// 提前5分钟认为过期留出刷新时间
return currentTime >= (expireTime - 5 * 60 * 1000);
}
}

View File

@@ -0,0 +1,94 @@
package com.ruoyi.jarvis.service;
import com.alibaba.fastjson2.JSONObject;
import java.util.List;
import java.util.Map;
/**
* WPS365 API服务接口
*
* @author system
*/
public interface IWPS365ApiService {
/**
* 获取用户信息
*
* @param accessToken 访问令牌
* @return 用户信息
*/
JSONObject getUserInfo(String accessToken);
/**
* 获取文件列表
*
* @param accessToken 访问令牌
* @param params 查询参数page, page_size等
* @return 文件列表
*/
JSONObject getFileList(String accessToken, Map<String, Object> params);
/**
* 获取文件信息
*
* @param accessToken 访问令牌
* @param fileToken 文件token
* @return 文件信息
*/
JSONObject getFileInfo(String accessToken, String fileToken);
/**
* 更新单元格数据KSheet - 在线表格)
*
* @param accessToken 访问令牌
* @param fileToken 文件token
* @param sheetIdx 工作表索引从0开始
* @param range 单元格范围A1:B2
* @param values 单元格值(二维数组,第一维是行,第二维是列)
* @return 更新结果
*/
JSONObject updateCells(String accessToken, String fileToken, int sheetIdx, String range, List<List<Object>> values);
/**
* 读取单元格数据
*
* @param accessToken 访问令牌
* @param fileToken 文件token
* @param sheetIdx 工作表索引
* @param range 单元格范围
* @return 单元格数据
*/
JSONObject readCells(String accessToken, String fileToken, int sheetIdx, String range);
/**
* 获取工作表列表
*
* @param accessToken 访问令牌
* @param fileToken 文件token
* @return 工作表列表
*/
JSONObject getSheetList(String accessToken, String fileToken);
/**
* 创建数据表
*
* @param accessToken 访问令牌
* @param fileToken 文件token
* @param sheetName 工作表名称
* @return 创建结果
*/
JSONObject createSheet(String accessToken, String fileToken, String sheetName);
/**
* 批量更新单元格数据
*
* @param accessToken 访问令牌
* @param fileToken 文件token
* @param sheetIdx 工作表索引
* @param updates 更新列表每个元素包含range和values
* @return 更新结果
*/
JSONObject batchUpdateCells(String accessToken, String fileToken, int sheetIdx, List<Map<String, Object>> updates);
}

View File

@@ -0,0 +1,66 @@
package com.ruoyi.jarvis.service;
import com.ruoyi.jarvis.domain.dto.WPS365TokenInfo;
/**
* WPS365 OAuth授权服务接口
*
* @author system
*/
public interface IWPS365OAuthService {
/**
* 获取授权URL
*
* @param state 状态参数可选用于防止CSRF攻击
* @return 授权URL
*/
String getAuthUrl(String state);
/**
* 通过授权码获取访问令牌
*
* @param code 授权码
* @return Token信息
*/
WPS365TokenInfo getAccessTokenByCode(String code);
/**
* 刷新访问令牌
*
* @param refreshToken 刷新令牌
* @return 新的Token信息
*/
WPS365TokenInfo refreshAccessToken(String refreshToken);
/**
* 获取当前用户的访问令牌
*
* @return Token信息如果未授权则返回null
*/
WPS365TokenInfo getCurrentToken();
/**
* 保存用户令牌信息
*
* @param userId 用户ID
* @param tokenInfo Token信息
*/
void saveToken(String userId, WPS365TokenInfo tokenInfo);
/**
* 清除用户令牌信息
*
* @param userId 用户ID
*/
void clearToken(String userId);
/**
* 检查令牌是否有效
*
* @param tokenInfo Token信息
* @return true表示有效false表示需要刷新或重新授权
*/
boolean isTokenValid(WPS365TokenInfo tokenInfo);
}

View File

@@ -0,0 +1,200 @@
package com.ruoyi.jarvis.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.jarvis.config.WPS365Config;
import com.ruoyi.jarvis.service.IWPS365ApiService;
import com.ruoyi.jarvis.util.WPS365ApiUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
* WPS365 API服务实现类
*
* @author system
*/
@Service
public class WPS365ApiServiceImpl implements IWPS365ApiService {
private static final Logger log = LoggerFactory.getLogger(WPS365ApiServiceImpl.class);
@Autowired
private WPS365Config wps365Config;
@Override
public JSONObject getUserInfo(String accessToken) {
try {
String url = wps365Config.getApiBaseUrl() + "/user/info";
return WPS365ApiUtil.httpRequest("GET", url, accessToken, null);
} catch (Exception e) {
log.error("获取用户信息失败", e);
throw new RuntimeException("获取用户信息失败: " + e.getMessage(), e);
}
}
@Override
public JSONObject getFileList(String accessToken, Map<String, Object> params) {
try {
StringBuilder url = new StringBuilder(wps365Config.getApiBaseUrl() + "/files");
// 添加查询参数
if (params != null && !params.isEmpty()) {
url.append("?");
boolean first = true;
for (Map.Entry<String, Object> entry : params.entrySet()) {
if (!first) {
url.append("&");
}
url.append(entry.getKey()).append("=").append(entry.getValue());
first = false;
}
}
return WPS365ApiUtil.httpRequest("GET", url.toString(), accessToken, null);
} catch (Exception e) {
log.error("获取文件列表失败", e);
throw new RuntimeException("获取文件列表失败: " + e.getMessage(), e);
}
}
@Override
public JSONObject getFileInfo(String accessToken, String fileToken) {
try {
String url = wps365Config.getApiBaseUrl() + "/files/" + fileToken;
return WPS365ApiUtil.httpRequest("GET", url, accessToken, null);
} catch (Exception e) {
log.error("获取文件信息失败 - fileToken: {}", fileToken, e);
throw new RuntimeException("获取文件信息失败: " + e.getMessage(), e);
}
}
@Override
public JSONObject updateCells(String accessToken, String fileToken, int sheetIdx, String range, List<List<Object>> values) {
try {
// WPS365 KSheet API: /api/v1/openapi/ksheet/:file_token/sheets/:sheet_idx/cells
String url = wps365Config.getApiBaseUrl() + "/openapi/ksheet/" + fileToken + "/sheets/" + sheetIdx + "/cells";
// 构建请求体
JSONObject requestBody = new JSONObject();
requestBody.put("range", range);
// 将values转换为JSONArray
JSONArray valuesArray = new JSONArray();
if (values != null) {
for (List<Object> row : values) {
JSONArray rowArray = new JSONArray();
if (row != null) {
for (Object cell : row) {
rowArray.add(cell != null ? cell : "");
}
}
valuesArray.add(rowArray);
}
}
requestBody.put("values", valuesArray);
String bodyStr = requestBody.toJSONString();
log.debug("更新单元格数据 - url: {}, range: {}, values: {}", url, range, bodyStr);
return WPS365ApiUtil.httpRequest("POST", url, accessToken, bodyStr);
} catch (Exception e) {
log.error("更新单元格数据失败 - fileToken: {}, sheetIdx: {}, range: {}", fileToken, sheetIdx, range, e);
throw new RuntimeException("更新单元格数据失败: " + e.getMessage(), e);
}
}
@Override
public JSONObject readCells(String accessToken, String fileToken, int sheetIdx, String range) {
try {
// WPS365 KSheet API: GET /api/v1/openapi/ksheet/:file_token/sheets/:sheet_idx/cells
String url = wps365Config.getApiBaseUrl() + "/openapi/ksheet/" + fileToken + "/sheets/" + sheetIdx + "/cells";
if (range != null && !range.trim().isEmpty()) {
url += "?range=" + java.net.URLEncoder.encode(range, "UTF-8");
}
return WPS365ApiUtil.httpRequest("GET", url, accessToken, null);
} catch (Exception e) {
log.error("读取单元格数据失败 - fileToken: {}, sheetIdx: {}, range: {}", fileToken, sheetIdx, range, e);
throw new RuntimeException("读取单元格数据失败: " + e.getMessage(), e);
}
}
@Override
public JSONObject getSheetList(String accessToken, String fileToken) {
try {
// WPS365 KSheet API: GET /api/v1/openapi/ksheet/:file_token/sheets
String url = wps365Config.getApiBaseUrl() + "/openapi/ksheet/" + fileToken + "/sheets";
return WPS365ApiUtil.httpRequest("GET", url, accessToken, null);
} catch (Exception e) {
log.error("获取工作表列表失败 - fileToken: {}", fileToken, e);
throw new RuntimeException("获取工作表列表失败: " + e.getMessage(), e);
}
}
@Override
public JSONObject createSheet(String accessToken, String fileToken, String sheetName) {
try {
// WPS365 KSheet API: POST /api/v1/openapi/ksheet/:file_token/sheets
String url = wps365Config.getApiBaseUrl() + "/openapi/ksheet/" + fileToken + "/sheets";
JSONObject requestBody = new JSONObject();
requestBody.put("name", sheetName);
String bodyStr = requestBody.toJSONString();
return WPS365ApiUtil.httpRequest("POST", url, accessToken, bodyStr);
} catch (Exception e) {
log.error("创建数据表失败 - fileToken: {}, sheetName: {}", fileToken, sheetName, e);
throw new RuntimeException("创建数据表失败: " + e.getMessage(), e);
}
}
@Override
public JSONObject batchUpdateCells(String accessToken, String fileToken, int sheetIdx, List<Map<String, Object>> updates) {
try {
// WPS365 KSheet API: POST /api/v1/openapi/ksheet/:file_token/sheets/:sheet_idx/cells/batch
String url = wps365Config.getApiBaseUrl() + "/openapi/ksheet/" + fileToken + "/sheets/" + sheetIdx + "/cells/batch";
JSONObject requestBody = new JSONObject();
JSONArray updatesArray = new JSONArray();
if (updates != null) {
for (Map<String, Object> update : updates) {
JSONObject updateObj = new JSONObject();
updateObj.put("range", update.get("range"));
// 将values转换为JSONArray
@SuppressWarnings("unchecked")
List<List<Object>> values = (List<List<Object>>) update.get("values");
JSONArray valuesArray = new JSONArray();
if (values != null) {
for (List<Object> row : values) {
JSONArray rowArray = new JSONArray();
if (row != null) {
for (Object cell : row) {
rowArray.add(cell != null ? cell : "");
}
}
valuesArray.add(rowArray);
}
}
updateObj.put("values", valuesArray);
updatesArray.add(updateObj);
}
}
requestBody.put("updates", updatesArray);
String bodyStr = requestBody.toJSONString();
return WPS365ApiUtil.httpRequest("POST", url, accessToken, bodyStr);
} catch (Exception e) {
log.error("批量更新单元格数据失败 - fileToken: {}, sheetIdx: {}", fileToken, sheetIdx, e);
throw new RuntimeException("批量更新单元格数据失败: " + e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,207 @@
package com.ruoyi.jarvis.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.jarvis.config.WPS365Config;
import com.ruoyi.jarvis.domain.dto.WPS365TokenInfo;
import com.ruoyi.jarvis.service.IWPS365OAuthService;
import com.ruoyi.jarvis.util.WPS365ApiUtil;
import com.ruoyi.common.core.redis.RedisCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* WPS365 OAuth授权服务实现类
*
* @author system
*/
@Service
public class WPS365OAuthServiceImpl implements IWPS365OAuthService {
private static final Logger log = LoggerFactory.getLogger(WPS365OAuthServiceImpl.class);
/** Redis key前缀用于存储用户token */
private static final String TOKEN_KEY_PREFIX = "wps365:token:";
/** Token过期时间默认30天 */
private static final long TOKEN_EXPIRE_TIME = 30 * 24 * 60 * 60;
@Autowired
private WPS365Config wps365Config;
@Autowired
private RedisCache redisCache;
@Override
public String getAuthUrl(String state) {
if (wps365Config == null) {
throw new RuntimeException("WPS365配置未加载请检查WPS365Config是否正确注入");
}
String appId = wps365Config.getAppId();
String redirectUri = wps365Config.getRedirectUri();
String oauthUrl = wps365Config.getOauthUrl();
log.debug("获取授权URL - appId: {}, redirectUri: {}, oauthUrl: {}", appId, redirectUri, oauthUrl);
// 验证配置参数
if (appId == null || appId.trim().isEmpty()) {
throw new RuntimeException("WPS365应用ID未配置请检查application.yml中的wps365.app-id配置");
}
if (redirectUri == null || redirectUri.trim().isEmpty()) {
throw new RuntimeException("WPS365回调地址未配置请检查application.yml中的wps365.redirect-uri配置");
}
// 构建授权URL
StringBuilder authUrl = new StringBuilder();
authUrl.append(oauthUrl);
authUrl.append("?client_id=").append(appId);
try {
String encodedRedirectUri = java.net.URLEncoder.encode(redirectUri, "UTF-8");
authUrl.append("&redirect_uri=").append(encodedRedirectUri);
} catch (java.io.UnsupportedEncodingException e) {
log.error("URL编码失败", e);
authUrl.append("&redirect_uri=").append(redirectUri);
}
authUrl.append("&response_type=code");
// WPS365的scope根据官方文档设置
authUrl.append("&scope=file.readwrite,user.info");
// 添加state参数
if (state == null || state.trim().isEmpty()) {
state = UUID.randomUUID().toString();
}
authUrl.append("&state=").append(state);
String result = authUrl.toString();
log.info("生成授权URL: {}", result);
return result;
}
@Override
public WPS365TokenInfo getAccessTokenByCode(String code) {
try {
JSONObject result = WPS365ApiUtil.getAccessToken(
wps365Config.getAppId(),
wps365Config.getAppKey(),
code,
wps365Config.getRedirectUri(),
wps365Config.getTokenUrl()
);
// 解析响应并创建TokenInfo对象
WPS365TokenInfo tokenInfo = new WPS365TokenInfo();
tokenInfo.setAccessToken(result.getString("access_token"));
tokenInfo.setRefreshToken(result.getString("refresh_token"));
tokenInfo.setTokenType(result.getString("token_type"));
tokenInfo.setExpiresIn(result.getInteger("expires_in"));
tokenInfo.setScope(result.getString("scope"));
tokenInfo.setUserId(result.getString("user_id"));
log.info("成功获取访问令牌 - userId: {}", tokenInfo.getUserId());
return tokenInfo;
} catch (Exception e) {
log.error("通过授权码获取访问令牌失败", e);
throw new RuntimeException("获取访问令牌失败: " + e.getMessage(), e);
}
}
@Override
public WPS365TokenInfo refreshAccessToken(String refreshToken) {
try {
JSONObject result = WPS365ApiUtil.refreshAccessToken(
wps365Config.getAppId(),
wps365Config.getAppKey(),
refreshToken,
wps365Config.getRefreshTokenUrl()
);
// 解析响应并创建TokenInfo对象
WPS365TokenInfo tokenInfo = new WPS365TokenInfo();
tokenInfo.setAccessToken(result.getString("access_token"));
tokenInfo.setRefreshToken(result.getString("refresh_token"));
tokenInfo.setTokenType(result.getString("token_type"));
tokenInfo.setExpiresIn(result.getInteger("expires_in"));
tokenInfo.setScope(result.getString("scope"));
tokenInfo.setUserId(result.getString("user_id"));
log.info("成功刷新访问令牌 - userId: {}", tokenInfo.getUserId());
return tokenInfo;
} catch (Exception e) {
log.error("刷新访问令牌失败", e);
throw new RuntimeException("刷新访问令牌失败: " + e.getMessage(), e);
}
}
@Override
public WPS365TokenInfo getCurrentToken() {
// 这里需要根据实际业务获取当前用户ID
// 暂时返回null需要在Controller中处理用户ID获取逻辑
return null;
}
@Override
public void saveToken(String userId, WPS365TokenInfo tokenInfo) {
if (userId == null || userId.trim().isEmpty()) {
throw new IllegalArgumentException("用户ID不能为空");
}
if (tokenInfo == null || tokenInfo.getAccessToken() == null) {
throw new IllegalArgumentException("Token信息不能为空");
}
String key = TOKEN_KEY_PREFIX + userId;
redisCache.setCacheObject(key, tokenInfo, (int) TOKEN_EXPIRE_TIME, TimeUnit.SECONDS);
log.info("保存用户Token - userId: {}", userId);
}
@Override
public void clearToken(String userId) {
if (userId == null || userId.trim().isEmpty()) {
return;
}
String key = TOKEN_KEY_PREFIX + userId;
redisCache.deleteObject(key);
log.info("清除用户Token - userId: {}", userId);
}
@Override
public boolean isTokenValid(WPS365TokenInfo tokenInfo) {
if (tokenInfo == null) {
return false;
}
// 检查是否过期
if (tokenInfo.isExpired()) {
log.debug("Token已过期");
return false;
}
// 检查必要字段
if (tokenInfo.getAccessToken() == null || tokenInfo.getAccessToken().trim().isEmpty()) {
log.debug("Token缺少accessToken");
return false;
}
return true;
}
/**
* 根据用户ID获取Token从Redis
*/
public WPS365TokenInfo getTokenByUserId(String userId) {
if (userId == null || userId.trim().isEmpty()) {
return null;
}
String key = TOKEN_KEY_PREFIX + userId;
return redisCache.getCacheObject(key);
}
}

View File

@@ -0,0 +1,240 @@
package com.ruoyi.jarvis.util;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
/**
* WPS365 API工具类
*
* @author system
*/
public class WPS365ApiUtil {
private static final Logger log = LoggerFactory.getLogger(WPS365ApiUtil.class);
// 静态初始化块:禁用系统代理
static {
System.setProperty("java.net.useSystemProxies", "false");
System.clearProperty("http.proxyHost");
System.clearProperty("http.proxyPort");
System.clearProperty("https.proxyHost");
System.clearProperty("https.proxyPort");
log.info("已禁用系统代理设置WPS365 API将直接连接");
}
/**
* 获取访问令牌
*
* @param appId 应用ID
* @param appKey 应用密钥
* @param code 授权码
* @param redirectUri 回调地址
* @param tokenUrl Token地址
* @return 包含access_token和refresh_token的JSON对象
*/
public static JSONObject getAccessToken(String appId, String appKey, String code, String redirectUri, String tokenUrl) {
try {
// 构建请求参数
StringBuilder params = new StringBuilder();
params.append("grant_type=authorization_code");
params.append("&client_id=").append(appId);
params.append("&client_secret=").append(appKey);
params.append("&code=").append(code);
params.append("&redirect_uri=").append(java.net.URLEncoder.encode(redirectUri, "UTF-8"));
// 使用HttpURLConnection不使用代理
URL url = new URL(tokenUrl);
java.net.Proxy proxy = java.net.Proxy.NO_PROXY;
HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Accept", "application/json");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(10000);
conn.setReadTimeout(30000);
// 写入请求参数
try (OutputStream os = conn.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
osw.write(params.toString());
osw.flush();
}
// 读取响应
int statusCode = conn.getResponseCode();
BufferedReader reader;
if (statusCode >= 200 && statusCode < 300) {
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
} else {
reader = new BufferedReader(new InputStreamReader(conn.getErrorStream(), StandardCharsets.UTF_8));
}
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
String responseStr = response.toString();
log.info("获取访问令牌响应: statusCode={}, response={}", statusCode, responseStr);
if (statusCode < 200 || statusCode >= 300) {
throw new RuntimeException("获取访问令牌失败: HTTP " + statusCode + ", response=" + responseStr);
}
return JSON.parseObject(responseStr);
} catch (Exception e) {
log.error("获取访问令牌失败", e);
throw new RuntimeException("获取访问令牌失败: " + e.getMessage(), e);
}
}
/**
* 刷新访问令牌
*
* @param appId 应用ID
* @param appKey 应用密钥
* @param refreshToken 刷新令牌
* @param refreshTokenUrl 刷新令牌地址
* @return 包含新的access_token和refresh_token的JSON对象
*/
public static JSONObject refreshAccessToken(String appId, String appKey, String refreshToken, String refreshTokenUrl) {
try {
// 构建请求参数
StringBuilder params = new StringBuilder();
params.append("grant_type=refresh_token");
params.append("&client_id=").append(appId);
params.append("&client_secret=").append(appKey);
params.append("&refresh_token=").append(refreshToken);
// 使用HttpURLConnection不使用代理
URL url = new URL(refreshTokenUrl);
java.net.Proxy proxy = java.net.Proxy.NO_PROXY;
HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Accept", "application/json");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(10000);
conn.setReadTimeout(30000);
// 写入请求参数
try (OutputStream os = conn.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
osw.write(params.toString());
osw.flush();
}
// 读取响应
int statusCode = conn.getResponseCode();
BufferedReader reader;
if (statusCode >= 200 && statusCode < 300) {
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
} else {
reader = new BufferedReader(new InputStreamReader(conn.getErrorStream(), StandardCharsets.UTF_8));
}
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
String responseStr = response.toString();
log.info("刷新访问令牌响应: statusCode={}, response={}", statusCode, responseStr);
if (statusCode < 200 || statusCode >= 300) {
throw new RuntimeException("刷新访问令牌失败: HTTP " + statusCode + ", response=" + responseStr);
}
return JSON.parseObject(responseStr);
} catch (Exception e) {
log.error("刷新访问令牌失败", e);
throw new RuntimeException("刷新访问令牌失败: " + e.getMessage(), e);
}
}
/**
* 发送HTTP请求
*
* @param method 请求方法GET/POST/PUT/DELETE
* @param url 请求URL
* @param accessToken 访问令牌
* @param body 请求体JSON字符串GET请求可为null
* @return 响应JSON对象
*/
public static JSONObject httpRequest(String method, String url, String accessToken, String body) {
try {
log.debug("发送HTTP请求: method={}, url={}", method, url);
URL requestUrl = new URL(url);
java.net.Proxy proxy = java.net.Proxy.NO_PROXY;
HttpURLConnection conn = (HttpURLConnection) requestUrl.openConnection(proxy);
conn.setRequestMethod(method);
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Authorization", "Bearer " + accessToken);
conn.setConnectTimeout(10000);
conn.setReadTimeout(30000);
if ("POST".equals(method) || "PUT".equals(method) || "PATCH".equals(method)) {
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
if (body != null && !body.isEmpty()) {
try (OutputStream os = conn.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
osw.write(body);
osw.flush();
}
}
}
// 读取响应
int statusCode = conn.getResponseCode();
BufferedReader reader;
if (statusCode >= 200 && statusCode < 300) {
reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
} else {
reader = new BufferedReader(new InputStreamReader(conn.getErrorStream(), StandardCharsets.UTF_8));
}
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
String responseStr = response.toString();
log.debug("HTTP响应: statusCode={}, response={}", statusCode, responseStr);
if (statusCode < 200 || statusCode >= 300) {
throw new RuntimeException("HTTP请求失败: " + statusCode + ", response=" + responseStr);
}
if (responseStr == null || responseStr.trim().isEmpty()) {
return new JSONObject();
}
return JSON.parseObject(responseStr);
} catch (Exception e) {
log.error("HTTP请求失败: method={}, url={}", method, url, e);
throw new RuntimeException("HTTP请求失败: " + e.getMessage(), e);
}
}
}