优化需要刷新令牌的存储源,去除定时任务刷新,改为每次请求时检查是否过期并刷新。
This commit is contained in:
@@ -11,6 +11,8 @@ public class StorageConfigConstant {
|
||||
|
||||
public static final String REFRESH_TOKEN_KEY = "refreshToken";
|
||||
|
||||
public static final String REFRESH_TOKEN_EXPIRED_AT_KEY = "refreshTokenExpiredAt";
|
||||
|
||||
public static final String PROXY_DOWNLOAD_KEY = "enableProxyDownload";
|
||||
|
||||
public static final String PROXY_UPLOAD_KEY = "enableProxyUpload";
|
||||
|
||||
@@ -2,6 +2,7 @@ package im.zhaojun.zfile.module.storage.model.bo;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import im.zhaojun.zfile.module.storage.model.dto.RefreshTokenInfoDTO;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
@@ -15,10 +16,11 @@ import java.util.Date;
|
||||
@ToString
|
||||
public class RefreshTokenCacheBO {
|
||||
|
||||
private static final Cache<Integer, RefreshTokenInfo> REFRESH_TOKEN_INFO_CACHE = CacheUtil.newFIFOCache(100);
|
||||
private static final Cache<Integer, RefreshTokenInfo> REFRESH_TOKEN_INFO_CACHE = CacheUtil.newFIFOCache(1024);
|
||||
|
||||
public static void putRefreshTokenInfo(Integer storageId, RefreshTokenInfo lastRefreshTime) {
|
||||
REFRESH_TOKEN_INFO_CACHE.put(storageId, lastRefreshTime);
|
||||
public static void putRefreshTokenInfo(Integer storageId, RefreshTokenInfo refreshTokenInfo) {
|
||||
refreshTokenInfo.setStorageId(storageId);
|
||||
REFRESH_TOKEN_INFO_CACHE.put(storageId, refreshTokenInfo);
|
||||
}
|
||||
|
||||
public static RefreshTokenInfo getRefreshTokenInfo(Integer storageId) {
|
||||
@@ -28,17 +30,21 @@ public class RefreshTokenCacheBO {
|
||||
@Data
|
||||
public static class RefreshTokenInfo {
|
||||
|
||||
private Integer storageId;
|
||||
|
||||
private boolean success;
|
||||
|
||||
private Date lastRefreshTime;
|
||||
|
||||
private String msg;
|
||||
|
||||
private RefreshTokenInfoDTO data;
|
||||
|
||||
public static RefreshTokenInfo success() {
|
||||
public static RefreshTokenInfo success(RefreshTokenInfoDTO data) {
|
||||
RefreshTokenInfo info = new RefreshTokenInfo();
|
||||
info.setSuccess(true);
|
||||
info.setLastRefreshTime(new Date());
|
||||
info.setData(data);
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -50,6 +56,25 @@ public class RefreshTokenCacheBO {
|
||||
return info;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
if (!success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lastRefreshTime == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data == null || data.getExpiredAt() == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
long expireTime = data.getExpiredAt() * 1000L;
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long timeDiff = expireTime - currentTime;
|
||||
return timeDiff < 5 * 60 * 1000L;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,8 +25,13 @@ public class OAuth2TokenDTO {
|
||||
private boolean success;
|
||||
|
||||
private String body;
|
||||
|
||||
/**
|
||||
* 令牌到期时间,时间戳,单位毫秒
|
||||
*/
|
||||
private Integer expiredAt;
|
||||
|
||||
public static OAuth2TokenDTO success(String clientId, String clientSecret, String redirectUri, String accessToken, String refreshToken, String body) {
|
||||
public static OAuth2TokenDTO success(String clientId, String clientSecret, String redirectUri, String accessToken, String refreshToken, String body, Integer expiredAt) {
|
||||
OAuth2TokenDTO token = new OAuth2TokenDTO();
|
||||
token.setClientId(clientId);
|
||||
token.setClientSecret(clientSecret);
|
||||
@@ -35,6 +40,7 @@ public class OAuth2TokenDTO {
|
||||
token.setBody(body);
|
||||
token.setAccessToken(accessToken);
|
||||
token.setRefreshToken(refreshToken);
|
||||
token.setExpiredAt(expiredAt);
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package im.zhaojun.zfile.module.storage.model.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class RefreshTokenInfoDTO {
|
||||
|
||||
/**
|
||||
* 访问令牌,用于访问受保护的资源
|
||||
*/
|
||||
private String accessToken;
|
||||
|
||||
/**
|
||||
* 刷新令牌,用于获取新的访问令牌
|
||||
*/
|
||||
private String refreshToken;
|
||||
|
||||
/**
|
||||
* 会话令牌,通常用于 AWS 等云存储服务的临时凭证
|
||||
*/
|
||||
private String sessionToken;
|
||||
|
||||
/**
|
||||
* 过期时间戳(单位: 秒)
|
||||
*/
|
||||
private Integer expiredAt;
|
||||
|
||||
public Date getExpiredAtDate() {
|
||||
if (expiredAt == null) {
|
||||
return null;
|
||||
}
|
||||
// 如果 expiredAt 是 10 位时间戳(秒)
|
||||
if (expiredAt > 1_000_000_000) {
|
||||
return new Date(expiredAt * 1000L);
|
||||
} else {
|
||||
// 否则认为 expiredAt 是过期时间(单位: 秒)
|
||||
return new Date((expiredAt + System.currentTimeMillis() / 1000) * 1000L);
|
||||
}
|
||||
}
|
||||
|
||||
public static RefreshTokenInfoDTO success(String accessToken, String refreshToken, String sessionToken, Integer expiredAt) {
|
||||
RefreshTokenInfoDTO token = new RefreshTokenInfoDTO();
|
||||
token.setAccessToken(accessToken);
|
||||
token.setRefreshToken(refreshToken);
|
||||
token.setSessionToken(sessionToken);
|
||||
|
||||
if (expiredAt != null) {
|
||||
// 如果 expiredAt 是 10 位时间戳(秒)
|
||||
if (expiredAt > 1_000_000_000) {
|
||||
token.setExpiredAt(expiredAt);
|
||||
} else {
|
||||
// 否则认为 expiredAt 是过期时间(单位: 秒)
|
||||
token.setExpiredAt(expiredAt + (int) (System.currentTimeMillis() / 1000));
|
||||
}
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public static RefreshTokenInfoDTO success(String accessToken, String refreshToken, Integer expiredAt) {
|
||||
return success(accessToken, refreshToken, null, expiredAt);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,6 +59,9 @@ public class StorageSourceAllParamDTO implements Serializable {
|
||||
@Schema(name = "刷新令牌", example = "15063f61.86400.1292922000-2346678-1243281asd-1asa")
|
||||
private String refreshToken;
|
||||
|
||||
@Schema(name = "刷新令牌到期时间(秒)", example = "1752994685")
|
||||
private Integer refreshTokenExpiredAt;
|
||||
|
||||
@Schema(name = "secretId", example = "LTAI4FjfXqXxQZQZ")
|
||||
private String secretId;
|
||||
|
||||
|
||||
@@ -21,15 +21,6 @@ public class DogeCloudParam extends S3BaseParam {
|
||||
@StorageParamItem(ignoreInput = true, onlyOverwrite = { StorageParamItemAnnoEnum.IGNORE_INPUT })
|
||||
private String bucketName;
|
||||
|
||||
@StorageParamItem(ignoreInput = true, onlyOverwrite = { StorageParamItemAnnoEnum.IGNORE_INPUT })
|
||||
private String s3AccessKey;
|
||||
|
||||
@StorageParamItem(ignoreInput = true, onlyOverwrite = { StorageParamItemAnnoEnum.IGNORE_INPUT })
|
||||
private String s3SecretKey;
|
||||
|
||||
@StorageParamItem(ignoreInput = true, onlyOverwrite = { StorageParamItemAnnoEnum.IGNORE_INPUT })
|
||||
private String s3SessionToken;
|
||||
|
||||
@StorageParamItem(name = "存储空间名称", order = 40)
|
||||
private String originBucketName;
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ public class GoogleDriveParam extends ProxyTransferParam {
|
||||
@StorageParamItem(name = "刷新令牌", order = 5)
|
||||
private String refreshToken;
|
||||
|
||||
@StorageParamItem(name = "刷新令牌到期时间戳(秒)", hidden = true, required = false)
|
||||
private Integer refreshTokenExpiredAt;
|
||||
|
||||
@StorageParamItem(name = "网盘", order = 6, required = false)
|
||||
private String driveId;
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ public class MicrosoftDriveParam extends OptionalProxyTransferParam {
|
||||
@StorageParamItem(name = "刷新令牌", order = 5)
|
||||
private String refreshToken;
|
||||
|
||||
@StorageParamItem(name = "刷新令牌到期时间戳(秒)", hidden = true, required = false)
|
||||
private Integer refreshTokenExpiredAt;
|
||||
|
||||
@StorageParamItem(name = "基路径", defaultValue = "/", order = 6, description = "基路径表示该存储源哪个目录在 ZFile 中作为根目录,如: '/','/文件夹1'")
|
||||
private String basePath;
|
||||
|
||||
|
||||
+2
-1
@@ -66,7 +66,8 @@ public abstract class AbstractMicrosoftOAuth2Service implements IOAuth2Service {
|
||||
JSONObject jsonBody = JSONObject.parseObject(responseBody);
|
||||
String accessToken = jsonBody.getString(ACCESS_TOKEN_FIELD_NAME);
|
||||
String refreshToken = jsonBody.getString(REFRESH_TOKEN_FIELD_NAME);
|
||||
return OAuth2TokenDTO.success(clientId, clientSecret, redirectUri, accessToken, refreshToken, responseBody);
|
||||
Integer expiresIn = jsonBody.getInteger(EXPIRES_IN_FIELD_NAME);
|
||||
return OAuth2TokenDTO.success(clientId, clientSecret, redirectUri, accessToken, refreshToken, responseBody, expiresIn);
|
||||
}
|
||||
|
||||
public abstract String getEndPoint();
|
||||
|
||||
+2
-1
@@ -77,7 +77,8 @@ public class GoogleDriveOAuth2ServiceImpl implements IOAuth2Service {
|
||||
|
||||
String accessToken = jsonBody.getString(ACCESS_TOKEN_FIELD_NAME);
|
||||
String refreshToken = jsonBody.getString(REFRESH_TOKEN_FIELD_NAME);
|
||||
return OAuth2TokenDTO.success(clientId, clientSecret, redirectUri, accessToken, refreshToken, responseBody);
|
||||
Integer expiresIn = jsonBody.getInteger(EXPIRES_IN_FIELD_NAME);
|
||||
return OAuth2TokenDTO.success(clientId, clientSecret, redirectUri, accessToken, refreshToken, responseBody, expiresIn);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@ public interface IOAuth2Service {
|
||||
*/
|
||||
String REFRESH_TOKEN_FIELD_NAME = "refresh_token";
|
||||
|
||||
/**
|
||||
* 过期时间字段名称
|
||||
*/
|
||||
String EXPIRES_IN_FIELD_NAME = "expires_in";
|
||||
|
||||
|
||||
String generateAuthorizationUrl(String clientId, String clientSecret, String redirectUri);
|
||||
|
||||
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package im.zhaojun.zfile.module.storage.schedule;
|
||||
|
||||
import im.zhaojun.zfile.module.storage.context.StorageSourceContext;
|
||||
import im.zhaojun.zfile.module.storage.service.base.RefreshTokenService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 刷新使用所有 AccessToken 机制的定时任务, 存储源实现 #{@link RefreshTokenService} 接口, 表示是需要刷新 AccessToken 的存储源
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
@Slf4j
|
||||
public class AccessTokenRefreshSchedule {
|
||||
|
||||
/**
|
||||
* 项目启动 30 秒后, 每 10 分钟执行一次刷新 OneDrive Token 的定时任务.
|
||||
*/
|
||||
@Scheduled(fixedRate = 1000 * 60 * 10, initialDelay = 1000 * 10)
|
||||
public void autoRefreshAccessToken() {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("开始执行需要定期刷新 AccessToken 存储源的定时任务");
|
||||
}
|
||||
|
||||
Map<Integer, RefreshTokenService> refreshTokenServiceMap = StorageSourceContext.getAllRefreshTokenStorageSource();
|
||||
|
||||
for (Map.Entry<Integer, RefreshTokenService> refreshTokenServiceEntry : refreshTokenServiceMap.entrySet()) {
|
||||
Integer storageId = refreshTokenServiceEntry.getKey();
|
||||
RefreshTokenService refreshTokenService = refreshTokenServiceEntry.getValue();
|
||||
try {
|
||||
refreshTokenService.refreshAccessToken();
|
||||
log.info("成功刷新存储源 AccessToken, 存储源 id: {}, 存储源类型: {}",
|
||||
storageId, refreshTokenService.getStorageTypeEnum().getDescription());
|
||||
} catch (Exception e) {
|
||||
log.error("刷新存储源 AccessToken 失败, 存储源 id: {}, 存储源类型: {}",
|
||||
storageId, refreshTokenService.getStorageTypeEnum().getDescription(), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("执行需要定期刷新 AccessToken 存储源的定时任务完成");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -78,7 +78,7 @@ public abstract class AbstractBaseFileService<P extends IStorageParam> implement
|
||||
}
|
||||
}
|
||||
|
||||
String getStorageSimpleInfo() {
|
||||
protected String getStorageSimpleInfo() {
|
||||
return String.format("存储源 [id=%s, name=%s, type: %s]", storageId, name, getStorageTypeEnum().getDescription());
|
||||
}
|
||||
|
||||
|
||||
+88
-90
@@ -1,9 +1,13 @@
|
||||
package im.zhaojun.zfile.module.storage.service.base;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.jwt.JWT;
|
||||
import cn.hutool.jwt.JWTPayload;
|
||||
import cn.hutool.jwt.JWTUtil;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import im.zhaojun.zfile.core.exception.ErrorCode;
|
||||
@@ -17,12 +21,14 @@ import im.zhaojun.zfile.core.util.StringUtils;
|
||||
import im.zhaojun.zfile.module.storage.constant.StorageConfigConstant;
|
||||
import im.zhaojun.zfile.module.storage.model.bo.RefreshTokenCacheBO;
|
||||
import im.zhaojun.zfile.module.storage.model.bo.StorageSourceMetadata;
|
||||
import im.zhaojun.zfile.module.storage.model.dto.OAuth2TokenDTO;
|
||||
import im.zhaojun.zfile.module.storage.model.dto.RefreshTokenInfoDTO;
|
||||
import im.zhaojun.zfile.module.storage.model.entity.StorageSourceConfig;
|
||||
import im.zhaojun.zfile.module.storage.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.zfile.module.storage.model.param.MicrosoftDriveParam;
|
||||
import im.zhaojun.zfile.module.storage.model.result.FileItemResult;
|
||||
import im.zhaojun.zfile.module.storage.oauth2.service.IOAuth2Service;
|
||||
import im.zhaojun.zfile.module.storage.service.StorageSourceConfigService;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.hc.client5.http.classic.HttpClient;
|
||||
@@ -30,13 +36,9 @@ import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.http.client.*;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.ResourceAccessException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@@ -92,22 +94,36 @@ public abstract class AbstractMicrosoftDriveService<P extends MicrosoftDrivePara
|
||||
* OneDrive 文件类型
|
||||
*/
|
||||
private static final String ONE_DRIVE_FILE_FLAG = "file";
|
||||
|
||||
/**
|
||||
* 访问令牌字段名称
|
||||
*/
|
||||
public static final String ACCESS_TOKEN_FIELD_NAME = "access_token";
|
||||
|
||||
/**
|
||||
* 刷新令牌字段名称
|
||||
*/
|
||||
public static final String REFRESH_TOKEN_FIELD_NAME = "refresh_token";
|
||||
|
||||
/*
|
||||
* 设置 RestTemplate 使用 Netty 底层实现,默认的实现不支持 PATCH 请求
|
||||
*/
|
||||
private volatile RestTemplate restTemplate;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
Integer refreshTokenExpiredAt = param.getRefreshTokenExpiredAt();
|
||||
if (refreshTokenExpiredAt == null) {
|
||||
try {
|
||||
JWT jwt = JWTUtil.parseToken(param.getAccessToken());
|
||||
JWTPayload payload = jwt.getPayload();
|
||||
refreshTokenExpiredAt = Convert.toInt(payload.getClaim("exp"));
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("初始化时尝试根据 AccessToken 自动解析到期时间: {}", refreshTokenExpiredAt);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("初始化时尝试根据 AccessToken 自动解析到期时间异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshTokenExpiredAt == null) {
|
||||
refreshAccessToken();
|
||||
} else {
|
||||
RefreshTokenInfoDTO tokenInfoDTO = RefreshTokenInfoDTO.success(param.getAccessToken(), param.getRefreshToken(), refreshTokenExpiredAt);
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.success(tokenInfoDTO));
|
||||
}
|
||||
}
|
||||
|
||||
public RestTemplate getRestTemplate() {
|
||||
// 双重检查锁,避免重复创建 RestTemplate 实例的同时减少锁的开销
|
||||
if (restTemplate == null) {
|
||||
@@ -147,19 +163,8 @@ public abstract class AbstractMicrosoftDriveService<P extends MicrosoftDrivePara
|
||||
requestUrl = DRIVER_ITEMS_URL;
|
||||
}
|
||||
|
||||
JSONObject root = executeRetryableRequest(context -> {
|
||||
int retryCount = context.getRetryCount();
|
||||
if (retryCount > 0) {
|
||||
HttpClientErrorException ex = (HttpClientErrorException) context.getLastThrowable();
|
||||
log.warn("{} 调用 OneDrive 列表时出现了网络异常, 响应信息: [{}], 将尝试重新刷新 token 后再试. 文件路径为: [{}]",
|
||||
getStorageSimpleInfo(), ex.getResponseBodyAsString(), fullPath, ex);
|
||||
refreshAccessToken();
|
||||
}
|
||||
|
||||
HttpEntity<Object> entity = getAuthorizationHttpEntity();
|
||||
return getRestTemplate().exchange(requestUrl, HttpMethod.GET, entity, JSONObject.class, getGraphEndPoint(), getType(), fullPath).getBody();
|
||||
});
|
||||
|
||||
HttpEntity<Object> entity = getAuthorizationHttpEntity();
|
||||
JSONObject root = getRestTemplate().exchange(requestUrl, HttpMethod.GET, entity, JSONObject.class, getGraphEndPoint(), getType(), fullPath).getBody();
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -201,25 +206,8 @@ public abstract class AbstractMicrosoftDriveService<P extends MicrosoftDrivePara
|
||||
@Nullable
|
||||
private JSONObject getFileOriginInfo(String pathAndName) {
|
||||
String fullPath = StringUtils.concat(param.getBasePath(), pathAndName);
|
||||
|
||||
// 404 代表文件不存在, 直接返回 null
|
||||
return executeRetryableRequest(context -> {
|
||||
int retryCount = context.getRetryCount();
|
||||
if (retryCount > 0) {
|
||||
refreshAccessToken();
|
||||
HttpClientErrorException ex = (HttpClientErrorException) context.getLastThrowable();
|
||||
// 404 代表文件不存在, 直接返回 null
|
||||
if (ex instanceof HttpClientErrorException.NotFound) {
|
||||
return null;
|
||||
}
|
||||
log.warn("{} 调用 OneDrive 获取文件信息时出现了网络异常, 响应信息: [{}], 将尝试重新刷新 token 后再试. 获取文件路径为: {}",
|
||||
getStorageSimpleInfo(), ex.getResponseBodyAsString(), fullPath, ex);
|
||||
|
||||
}
|
||||
|
||||
HttpEntity<Object> entity = getAuthorizationHttpEntity();
|
||||
return getRestTemplate().exchange(DRIVER_ITEM_URL, HttpMethod.GET, entity, JSONObject.class, getGraphEndPoint(), getType(), fullPath).getBody();
|
||||
});
|
||||
HttpEntity<Object> entity = getAuthorizationHttpEntity();
|
||||
return getRestTemplate().exchange(DRIVER_ITEM_URL, HttpMethod.GET, entity, JSONObject.class, getGraphEndPoint(), getType(), fullPath).getBody();
|
||||
}
|
||||
|
||||
|
||||
@@ -470,23 +458,23 @@ public abstract class AbstractMicrosoftDriveService<P extends MicrosoftDrivePara
|
||||
@Override
|
||||
public void refreshAccessToken() {
|
||||
try {
|
||||
OAuth2TokenDTO refreshToken = getAndRefreshToken();
|
||||
RefreshTokenInfoDTO tokenInfoDTO = getAndRefreshToken();
|
||||
|
||||
if (refreshToken.getAccessToken() == null || refreshToken.getRefreshToken() == null) {
|
||||
if (tokenInfoDTO.getAccessToken() == null || tokenInfoDTO.getRefreshToken() == null) {
|
||||
throw new SystemException("存储源 " + storageId + " 刷新令牌失败, 获取到令牌为空.");
|
||||
}
|
||||
|
||||
StorageSourceConfig accessTokenConfig =
|
||||
storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.ACCESS_TOKEN_KEY);
|
||||
StorageSourceConfig refreshTokenConfig =
|
||||
storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.REFRESH_TOKEN_KEY);
|
||||
accessTokenConfig.setValue(refreshToken.getAccessToken());
|
||||
refreshTokenConfig.setValue(refreshToken.getRefreshToken());
|
||||
StorageSourceConfig accessTokenConfig = storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.ACCESS_TOKEN_KEY);
|
||||
StorageSourceConfig refreshTokenConfig = storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.REFRESH_TOKEN_KEY);
|
||||
StorageSourceConfig refreshTokenExpiredAtConfig = storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.REFRESH_TOKEN_EXPIRED_AT_KEY);
|
||||
accessTokenConfig.setValue(tokenInfoDTO.getAccessToken());
|
||||
refreshTokenConfig.setValue(tokenInfoDTO.getRefreshToken());
|
||||
refreshTokenExpiredAtConfig.setValue(String.valueOf(tokenInfoDTO.getExpiredAt()));
|
||||
|
||||
storageSourceConfigService.updateBatch(storageId, Arrays.asList(accessTokenConfig, refreshTokenConfig));
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.success());
|
||||
storageSourceConfigService.updateBatch(storageId, Arrays.asList(accessTokenConfig, refreshTokenConfig, refreshTokenExpiredAtConfig));
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.success(tokenInfoDTO));
|
||||
} catch (Exception e) {
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.fail(getStorageTypeEnum().getDescription() + " AccessToken 刷新失败: " + e.getMessage()));
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.fail("AccessToken 刷新失败: " + e.getMessage()));
|
||||
throw new SystemException("存储源 " + storageId + " 刷新令牌失败, 获取时发生异常.", e);
|
||||
|
||||
}
|
||||
@@ -546,32 +534,10 @@ public abstract class AbstractMicrosoftDriveService<P extends MicrosoftDrivePara
|
||||
*/
|
||||
private <T> HttpEntity<T> getAuthorizationHttpEntity(T body) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
StorageSourceConfig accessTokenConfig =
|
||||
storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.ACCESS_TOKEN_KEY);
|
||||
headers.setBearerAuth(accessTokenConfig.getValue());
|
||||
String accessToken = checkExpiredAndGetAccessToken();
|
||||
headers.setBearerAuth(accessToken);
|
||||
return new HttpEntity<>(body, headers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行可重试 1 次的任务, 对抛出的异常转为 ZFileRetryException(Unchecked Exception)
|
||||
* @param retryCallback 可重试的任务
|
||||
* @return 任务执行结果
|
||||
*
|
||||
* @param <T> 任务执行结果类型
|
||||
*/
|
||||
private <T> T executeRetryableRequest(RetryCallback<T, Throwable> retryCallback) {
|
||||
RetryTemplate retryTemplate = RetryTemplate.builder().maxAttempts(2).retryOn(HttpClientErrorException.class).build();
|
||||
|
||||
T result;
|
||||
try {
|
||||
result = retryTemplate.execute(retryCallback);
|
||||
} catch (Throwable e) {
|
||||
throw new SystemException("请求失败", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageSourceMetadata getStorageSourceMetadata() {
|
||||
@@ -590,7 +556,7 @@ public abstract class AbstractMicrosoftDriveService<P extends MicrosoftDrivePara
|
||||
*
|
||||
* @return 刷新后的 Token
|
||||
*/
|
||||
private OAuth2TokenDTO getAndRefreshToken() {
|
||||
private RefreshTokenInfoDTO getAndRefreshToken() {
|
||||
StorageSourceConfig refreshStorageSourceConfig =
|
||||
storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.REFRESH_TOKEN_KEY);
|
||||
|
||||
@@ -601,7 +567,7 @@ public abstract class AbstractMicrosoftDriveService<P extends MicrosoftDrivePara
|
||||
"&grant_type=refresh_token";
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("{} 尝试刷新令牌, 请求参数: [{}]", getStorageSimpleInfo(), param);
|
||||
log.debug("{} 尝试刷新令牌, 请求参数: {}", getStorageSimpleInfo(), param);
|
||||
}
|
||||
|
||||
String authenticateUrl = AUTHENTICATE_URL.replace("{authenticateEndPoint}", getAuthenticateEndPoint());
|
||||
@@ -612,16 +578,19 @@ public abstract class AbstractMicrosoftDriveService<P extends MicrosoftDrivePara
|
||||
String responseBody = response.body();
|
||||
int responseStatus = response.getStatus();
|
||||
|
||||
log.info("{} 刷新令牌完成. [httpStatus: {}]", getStorageSimpleInfo(), responseStatus);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("{} 刷新令牌完成. 响应状态码: {}, 响应体: {}", getStorageSimpleInfo(), responseStatus, responseBody);
|
||||
}
|
||||
|
||||
if (responseStatus != HttpStatus.OK.value()) {
|
||||
return OAuth2TokenDTO.fail(getClientId(), getClientSecret(), getRedirectUri(), responseBody);
|
||||
throw new SystemException(responseBody);
|
||||
}
|
||||
|
||||
JSONObject jsonBody = JSONObject.parseObject(responseBody);
|
||||
String accessToken = jsonBody.getString(ACCESS_TOKEN_FIELD_NAME);
|
||||
String refreshToken = jsonBody.getString(REFRESH_TOKEN_FIELD_NAME);
|
||||
return OAuth2TokenDTO.success(getClientId(), getClientSecret(), getRedirectUri(), accessToken, refreshToken, responseBody);
|
||||
String accessToken = jsonBody.getString(IOAuth2Service.ACCESS_TOKEN_FIELD_NAME);
|
||||
String refreshToken = jsonBody.getString(IOAuth2Service.REFRESH_TOKEN_FIELD_NAME);
|
||||
Integer expiresIn = jsonBody.getInteger(IOAuth2Service.EXPIRES_IN_FIELD_NAME);
|
||||
return RefreshTokenInfoDTO.success(accessToken, refreshToken, expiresIn);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -634,4 +603,33 @@ public abstract class AbstractMicrosoftDriveService<P extends MicrosoftDrivePara
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 AccessToken 是否过期,如果过期则刷新 AccessToken 并返回新的 AccessToken。
|
||||
*/
|
||||
private String checkExpiredAndGetAccessToken() {
|
||||
RefreshTokenCacheBO.RefreshTokenInfo refreshTokenInfo = RefreshTokenCacheBO.getRefreshTokenInfo(storageId);
|
||||
|
||||
if (refreshTokenInfo == null || refreshTokenInfo.isExpired()) {
|
||||
// 使用双重检查锁定机制,确保同一个 storageId 只会有一个线程在刷新 AccessToken
|
||||
synchronized (("storage-refresh-" + storageId).intern()) {
|
||||
// 双重检查,再次从缓存中获取,确认是否其他线程已经刷新过
|
||||
refreshTokenInfo = RefreshTokenCacheBO.getRefreshTokenInfo(storageId);
|
||||
if (refreshTokenInfo == null || refreshTokenInfo.isExpired()) {
|
||||
if (refreshTokenInfo == null || refreshTokenInfo.isExpired()) {
|
||||
log.info("{} AccessToken 未获取或已过期, 尝试刷新: {}", getStorageSimpleInfo(), refreshTokenInfo);
|
||||
refreshAccessToken();
|
||||
refreshTokenInfo = RefreshTokenCacheBO.getRefreshTokenInfo(storageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshTokenInfo == null) {
|
||||
throw new SystemException("存储源 " + storageId + " AccessToken 刷新失败: 未找到刷新令牌信息.");
|
||||
}
|
||||
|
||||
return refreshTokenInfo.getData().getAccessToken();
|
||||
}
|
||||
|
||||
}
|
||||
-5
@@ -12,11 +12,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Slf4j
|
||||
public abstract class AbstractOneDriveServiceBase<P extends OneDriveParam> extends AbstractMicrosoftDriveService<P> {
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
refreshAccessToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return "me";
|
||||
|
||||
-5
@@ -7,11 +7,6 @@ import im.zhaojun.zfile.module.storage.model.param.SharePointParam;
|
||||
*/
|
||||
public abstract class AbstractSharePointServiceBase<P extends SharePointParam> extends AbstractMicrosoftDriveService<SharePointParam> {
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
refreshAccessToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return "sites/" + param.getSiteId();
|
||||
|
||||
+83
-36
@@ -8,6 +8,8 @@ import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import im.zhaojun.zfile.core.exception.core.SystemException;
|
||||
import im.zhaojun.zfile.module.storage.model.bo.RefreshTokenCacheBO;
|
||||
import im.zhaojun.zfile.module.storage.model.dto.RefreshTokenInfoDTO;
|
||||
import im.zhaojun.zfile.module.storage.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.module.storage.model.param.DogeCloudParam;
|
||||
import im.zhaojun.zfile.module.storage.service.base.AbstractS3BaseFileService;
|
||||
@@ -16,6 +18,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Service;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
@@ -31,10 +34,11 @@ import java.net.URI;
|
||||
@Slf4j
|
||||
public class DogeCloudServiceImpl extends AbstractS3BaseFileService<DogeCloudParam> implements RefreshTokenService {
|
||||
|
||||
private AwsCredentials awsCredentials;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
refreshAccessToken();
|
||||
|
||||
Region oss = Region.of("automatic");
|
||||
URI endpointOverride = URI.create(param.getEndPoint());
|
||||
|
||||
@@ -42,16 +46,14 @@ public class DogeCloudServiceImpl extends AbstractS3BaseFileService<DogeCloudPar
|
||||
.overrideConfiguration(getClientConfiguration())
|
||||
.region(oss)
|
||||
.endpointOverride(endpointOverride)
|
||||
.credentialsProvider(() -> AwsSessionCredentials.create(param.getS3AccessKey(), param.getS3SecretKey(), param.getS3SessionToken()))
|
||||
.credentialsProvider(this::checkExpiredAndGetAwsCredentials)
|
||||
.build();
|
||||
|
||||
super.s3Presigner = S3Presigner.builder()
|
||||
.region(oss)
|
||||
.endpointOverride(endpointOverride)
|
||||
.credentialsProvider(() -> AwsSessionCredentials.create(param.getS3AccessKey(), param.getS3SecretKey(), param.getS3SessionToken()))
|
||||
.credentialsProvider(this::checkExpiredAndGetAwsCredentials)
|
||||
.build();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,48 +63,93 @@ public class DogeCloudServiceImpl extends AbstractS3BaseFileService<DogeCloudPar
|
||||
|
||||
@Override
|
||||
public void refreshAccessToken() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("channel", "OSS_FULL");
|
||||
jsonObject.put("scopes", param.getOriginBucketName());
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("channel", "OSS_FULL");
|
||||
jsonObject.put("scopes", param.getOriginBucketName());
|
||||
|
||||
String apiDomain = "https://api.dogecloud.com";
|
||||
String apiPath = "/auth/tmp_token.json";
|
||||
String apiDomain = "https://api.dogecloud.com";
|
||||
String apiPath = "/auth/tmp_token.json";
|
||||
|
||||
String token = getToken(apiPath, jsonObject.toJSONString());
|
||||
String jsonString = jsonObject.toJSONString();
|
||||
String token = getToken(apiPath, jsonString);
|
||||
|
||||
HttpResponse httpResponse = HttpUtil.createPost(apiDomain + apiPath)
|
||||
.body(jsonObject.toJSONString())
|
||||
.header(Header.AUTHORIZATION, "TOKEN " + param.getAccessKey() + ":" + token).execute();
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("{} 尝试获取 S3 临时密钥, 请求参数: {}", getStorageSimpleInfo(), param);
|
||||
}
|
||||
|
||||
String body = httpResponse.body();
|
||||
JSONObject resultJsonObject = JSONObject.parseObject(body);
|
||||
if (resultJsonObject.getInteger("code") != 200){
|
||||
//TODO 根据返回ERR_CODE定义错误类型 https://docs.dogecloud.com/oss/api-introduction?id=%e9%94%99%e8%af%af%e4%bb%a3%e7%a0%81%e5%88%97%e8%a1%a8
|
||||
log.error("获取 Token 失败, response: {}", body);
|
||||
throw new BizException(resultJsonObject.getString("msg"));
|
||||
HttpResponse httpResponse = HttpUtil.createPost(apiDomain + apiPath)
|
||||
.body(jsonString)
|
||||
.header(Header.AUTHORIZATION, "TOKEN " + param.getAccessKey() + ":" + token)
|
||||
.execute();
|
||||
|
||||
String body = httpResponse.body();
|
||||
int responseStatus = httpResponse.getStatus();
|
||||
|
||||
JSONObject resultJsonObject = JSONObject.parseObject(body);
|
||||
if (resultJsonObject.getInteger("code") != 200){
|
||||
log.error("{} 获取 S3 临时密钥失败, 响应头: {}", getStorageSimpleInfo(), body);
|
||||
throw new BizException(resultJsonObject.getString("msg"));
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("{} 获取 S3 临时密钥完成. 响应状态码: {}, 响应体: {}", getStorageSimpleInfo(), responseStatus, body);
|
||||
}
|
||||
|
||||
JSONObject data = resultJsonObject.getJSONObject("data");
|
||||
JSONObject credentials = data.getJSONObject("Credentials");
|
||||
String s3AccessKey = credentials.getString("accessKeyId");
|
||||
String s3SecretKey = credentials.getString("secretAccessKey");
|
||||
String s3SessionToken = credentials.getString("sessionToken");
|
||||
Integer expiredAt = data.getInteger("ExpiredAt");
|
||||
|
||||
JSONArray bucketsArray = data.getJSONArray("Buckets");
|
||||
if (bucketsArray == null || bucketsArray.isEmpty()) {
|
||||
throw new SystemException("存储空间名称不存在");
|
||||
}
|
||||
JSONObject buckets = bucketsArray.getJSONObject(0);
|
||||
param.setBucketName(buckets.getString("s3Bucket"));
|
||||
param.setEndPoint(buckets.getString("s3Endpoint"));
|
||||
|
||||
RefreshTokenInfoDTO tokenInfoDTO = RefreshTokenInfoDTO.success(s3AccessKey, s3SecretKey, s3SessionToken, expiredAt);
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.success(tokenInfoDTO));
|
||||
|
||||
awsCredentials = AwsSessionCredentials.create(s3AccessKey, s3SecretKey, s3SessionToken);
|
||||
} catch (Exception e) {
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.fail("AccessToken 刷新失败: " + e.getMessage()));
|
||||
throw new SystemException("存储源 " + storageId + " 刷新令牌失败, 获取时发生异常.", e);
|
||||
}
|
||||
JSONObject credentials = resultJsonObject.getJSONObject("data").getJSONObject("Credentials");
|
||||
|
||||
param.setS3AccessKey(credentials.getString("accessKeyId"));
|
||||
param.setS3SecretKey(credentials.getString("secretAccessKey"));
|
||||
param.setS3SessionToken(credentials.getString("sessionToken"));
|
||||
|
||||
JSONArray bucketsArray = resultJsonObject.getJSONObject("data").getJSONArray("Buckets");
|
||||
if (bucketsArray == null || bucketsArray.isEmpty()) {
|
||||
throw new SystemException("存储空间名称不存在");
|
||||
}
|
||||
|
||||
JSONObject buckets = bucketsArray.getJSONObject(0);
|
||||
|
||||
param.setBucketName(buckets.getString("s3Bucket"));
|
||||
param.setEndPoint(buckets.getString("s3Endpoint"));
|
||||
}
|
||||
|
||||
|
||||
private String getToken(String apiPath, String paramsText) {
|
||||
String signStr = apiPath + "\n" + paramsText;
|
||||
return SecureUtil.hmacSha1(param.getSecretKey()).digestHex(signStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 AccessToken 是否过期,并获取最新的 AwsCredentials。
|
||||
*/
|
||||
private AwsCredentials checkExpiredAndGetAwsCredentials() {
|
||||
RefreshTokenCacheBO.RefreshTokenInfo refreshTokenInfo = RefreshTokenCacheBO.getRefreshTokenInfo(storageId);
|
||||
|
||||
if (refreshTokenInfo == null || refreshTokenInfo.isExpired()) {
|
||||
// 使用双重检查锁定机制,确保同一个 storageId 只会有一个线程在刷新 AccessToken
|
||||
synchronized (("storage-refresh-" + storageId).intern()) {
|
||||
// 双重检查,再次从缓存中获取,确认是否其他线程已经刷新过
|
||||
refreshTokenInfo = RefreshTokenCacheBO.getRefreshTokenInfo(storageId);
|
||||
if (refreshTokenInfo == null || refreshTokenInfo.isExpired()) {
|
||||
log.info("{} S3 临时密钥未获取或已过期, 尝试刷新.", getStorageSimpleInfo());
|
||||
refreshAccessToken();
|
||||
refreshTokenInfo = RefreshTokenCacheBO.getRefreshTokenInfo(storageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshTokenInfo == null) {
|
||||
throw new SystemException("存储源 " + storageId + " AccessToken 刷新失败: 未找到刷新令牌信息.");
|
||||
}
|
||||
|
||||
return awsCredentials;
|
||||
}
|
||||
|
||||
}
|
||||
+67
-33
@@ -24,12 +24,13 @@ import im.zhaojun.zfile.module.storage.constant.StorageConfigConstant;
|
||||
import im.zhaojun.zfile.module.storage.constant.StorageSourceConnectionProperties;
|
||||
import im.zhaojun.zfile.module.storage.model.bo.RefreshTokenCacheBO;
|
||||
import im.zhaojun.zfile.module.storage.model.bo.StorageSourceMetadata;
|
||||
import im.zhaojun.zfile.module.storage.model.dto.OAuth2TokenDTO;
|
||||
import im.zhaojun.zfile.module.storage.model.dto.RefreshTokenInfoDTO;
|
||||
import im.zhaojun.zfile.module.storage.model.entity.StorageSourceConfig;
|
||||
import im.zhaojun.zfile.module.storage.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.zfile.module.storage.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.module.storage.model.param.GoogleDriveParam;
|
||||
import im.zhaojun.zfile.module.storage.model.result.FileItemResult;
|
||||
import im.zhaojun.zfile.module.storage.oauth2.service.IOAuth2Service;
|
||||
import im.zhaojun.zfile.module.storage.service.StorageSourceConfigService;
|
||||
import im.zhaojun.zfile.module.storage.service.base.AbstractProxyTransferService;
|
||||
import im.zhaojun.zfile.module.storage.service.base.RefreshTokenService;
|
||||
@@ -61,10 +62,7 @@ import java.io.OutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
@@ -105,7 +103,17 @@ public class GoogleDriveServiceImpl extends AbstractProxyTransferService<GoogleD
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
refreshAccessToken();
|
||||
Integer refreshTokenExpiredAt = param.getRefreshTokenExpiredAt();
|
||||
if (refreshTokenExpiredAt == null) {
|
||||
refreshAccessToken();
|
||||
} else {
|
||||
RefreshTokenInfoDTO tokenInfoDTO = RefreshTokenInfoDTO.success(param.getAccessToken(), param.getRefreshToken(), refreshTokenExpiredAt);
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.success(tokenInfoDTO));
|
||||
}
|
||||
}
|
||||
|
||||
private String getIdByPath(String path) {
|
||||
return getIdByPath(path, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,7 +286,7 @@ public class GoogleDriveServiceImpl extends AbstractProxyTransferService<GoogleD
|
||||
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
HttpUriRequest httpUriRequest = RequestBuilder.post(DRIVE_FILE_UPLOAD_URL)
|
||||
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + param.getAccessToken())
|
||||
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + checkExpiredAndGetAccessToken())
|
||||
.setEntity(entity)
|
||||
.build();
|
||||
|
||||
@@ -385,7 +393,7 @@ public class GoogleDriveServiceImpl extends AbstractProxyTransferService<GoogleD
|
||||
*
|
||||
* @return 刷新后的 Token
|
||||
*/
|
||||
public OAuth2TokenDTO getRefreshToken() {
|
||||
public RefreshTokenInfoDTO getRefreshToken() {
|
||||
StorageSourceConfig refreshStorageSourceConfig =
|
||||
storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.REFRESH_TOKEN_KEY);
|
||||
|
||||
@@ -395,31 +403,30 @@ public class GoogleDriveServiceImpl extends AbstractProxyTransferService<GoogleD
|
||||
"&grant_type=refresh_token" +
|
||||
"&access_type=offline";
|
||||
|
||||
log.info("存储源 {}({}) 尝试刷新令牌", storageId, this.getStorageTypeEnum().getDescription());
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("存储源 {}({}) 尝试刷新令牌, 参数信息为: {}", storageId, this.getStorageTypeEnum().getDescription(), param);
|
||||
log.debug("{} 尝试刷新令牌, 请求参数: {}", getStorageSimpleInfo(), param);
|
||||
}
|
||||
|
||||
HttpRequest post = commonHttpRequest(HttpUtil.createPost(REFRESH_TOKEN_URL + "?" + paramStr));
|
||||
HttpRequest post = HttpUtil.createPost(REFRESH_TOKEN_URL + "?" + paramStr);
|
||||
post.timeout(5 * 1000);
|
||||
HttpResponse response = post.execute();
|
||||
String responseBody = response.body();
|
||||
|
||||
log.info("存储源 {}({}) 刷新令牌完成, 响应信息为: httpStatus: {}", storageId, this.getStorageTypeEnum().getDescription(), response.getStatus());
|
||||
String responseBody = response.body();
|
||||
int responseStatus = response.getStatus();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("存储源 {}({}) 刷新令牌完成, 响应信息为: {}", storageId, this.getStorageTypeEnum().getDescription(), responseBody);
|
||||
log.debug("{} 刷新令牌完成. 响应状态码: {}, 响应体: {}", getStorageSimpleInfo(), responseStatus, responseBody);
|
||||
}
|
||||
|
||||
|
||||
JSONObject jsonBody = JSONObject.parseObject(responseBody);
|
||||
|
||||
if (response.getStatus() != HttpStatus.OK.value()) {
|
||||
return OAuth2TokenDTO.fail(param.getClientId(), param.getClientSecret(), param.getRedirectUri(), responseBody);
|
||||
throw new SystemException(responseBody);
|
||||
}
|
||||
|
||||
String accessToken = jsonBody.getString("access_token");
|
||||
return OAuth2TokenDTO.success(param.getClientId(), param.getClientSecret(), param.getRedirectUri(), accessToken, null, responseBody);
|
||||
JSONObject jsonBody = JSONObject.parseObject(responseBody);
|
||||
String accessToken = jsonBody.getString(IOAuth2Service.ACCESS_TOKEN_FIELD_NAME);
|
||||
String refreshToken = jsonBody.getString(IOAuth2Service.REFRESH_TOKEN_FIELD_NAME);
|
||||
Integer expiresIn = jsonBody.getInteger(IOAuth2Service.EXPIRES_IN_FIELD_NAME);
|
||||
return RefreshTokenInfoDTO.success(accessToken, refreshToken, expiresIn);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -428,23 +435,23 @@ public class GoogleDriveServiceImpl extends AbstractProxyTransferService<GoogleD
|
||||
@Override
|
||||
public void refreshAccessToken() {
|
||||
try {
|
||||
OAuth2TokenDTO refreshToken = getRefreshToken();
|
||||
RefreshTokenInfoDTO tokenInfoDTO = getRefreshToken();
|
||||
|
||||
if (refreshToken.getAccessToken() == null) {
|
||||
if (tokenInfoDTO.getAccessToken() == null) {
|
||||
throw new SystemException("存储源 " + storageId + " 刷新令牌失败, 获取到令牌为空.");
|
||||
}
|
||||
|
||||
StorageSourceConfig accessTokenConfig =
|
||||
storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.ACCESS_TOKEN_KEY);
|
||||
accessTokenConfig.setValue(refreshToken.getAccessToken());
|
||||
StorageSourceConfig accessTokenConfig = storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.ACCESS_TOKEN_KEY);
|
||||
StorageSourceConfig refreshTokenConfig = storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.REFRESH_TOKEN_KEY);
|
||||
StorageSourceConfig refreshTokenExpiredAtConfig = storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.REFRESH_TOKEN_EXPIRED_AT_KEY);
|
||||
accessTokenConfig.setValue(tokenInfoDTO.getAccessToken());
|
||||
refreshTokenConfig.setValue(tokenInfoDTO.getRefreshToken());
|
||||
refreshTokenExpiredAtConfig.setValue(String.valueOf(tokenInfoDTO.getExpiredAt()));
|
||||
|
||||
storageSourceConfigService.updateBatch(storageId, Collections.singletonList(accessTokenConfig));
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.success());
|
||||
param.setAccessToken(refreshToken.getAccessToken());
|
||||
param.setRefreshToken(refreshToken.getRefreshToken());
|
||||
log.info("存储源 {} 刷新 AccessToken 成功", storageId);
|
||||
storageSourceConfigService.updateBatch(storageId, Arrays.asList(accessTokenConfig, refreshTokenConfig, refreshTokenExpiredAtConfig));
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.success(tokenInfoDTO));
|
||||
} catch (Exception e) {
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.fail(getStorageTypeEnum().getDescription() + " AccessToken 刷新失败: " + e.getMessage()));
|
||||
RefreshTokenCacheBO.putRefreshTokenInfo(storageId, RefreshTokenCacheBO.RefreshTokenInfo.fail("AccessToken 刷新失败: " + e.getMessage()));
|
||||
throw new SystemException("存储源 " + storageId + " 刷新令牌失败, 获取时发生异常.", e);
|
||||
}
|
||||
}
|
||||
@@ -715,8 +722,35 @@ public class GoogleDriveServiceImpl extends AbstractProxyTransferService<GoogleD
|
||||
}
|
||||
|
||||
private HttpRequest commonHttpRequest(HttpRequest httpRequest) {
|
||||
httpRequest.header("Authorization", "Bearer " + param.getAccessToken());
|
||||
String accessToken = checkExpiredAndGetAccessToken();
|
||||
httpRequest.bearerAuth(accessToken);
|
||||
return httpRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 AccessToken 是否过期,如果过期则刷新 AccessToken 并返回新的 AccessToken。
|
||||
*/
|
||||
private String checkExpiredAndGetAccessToken() {
|
||||
RefreshTokenCacheBO.RefreshTokenInfo refreshTokenInfo = RefreshTokenCacheBO.getRefreshTokenInfo(storageId);
|
||||
|
||||
if (refreshTokenInfo == null || refreshTokenInfo.isExpired()) {
|
||||
// 使用双重检查锁定机制,确保同一个 storageId 只会有一个线程在刷新 AccessToken
|
||||
synchronized (("storage-refresh-" + storageId).intern()) {
|
||||
// 双重检查,再次从缓存中获取,确认是否其他线程已经刷新过
|
||||
refreshTokenInfo = RefreshTokenCacheBO.getRefreshTokenInfo(storageId);
|
||||
if (refreshTokenInfo == null || refreshTokenInfo.isExpired()) {
|
||||
log.info("{} AccessToken 未获取或已过期, 尝试刷新.", getStorageSimpleInfo());
|
||||
refreshAccessToken();
|
||||
refreshTokenInfo = RefreshTokenCacheBO.getRefreshTokenInfo(storageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshTokenInfo == null) {
|
||||
throw new SystemException("存储源 " + storageId + " AccessToken 刷新失败: 未找到刷新令牌信息.");
|
||||
}
|
||||
|
||||
return refreshTokenInfo.getData().getAccessToken();
|
||||
}
|
||||
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
INSERT INTO
|
||||
storage_source_config (name, type, title, storage_id)
|
||||
SELECT
|
||||
'refreshTokenExpiredAt', type, '刷新令牌过期时间', storage_id
|
||||
FROM
|
||||
storage_source_config
|
||||
WHERE
|
||||
name = 'refreshToken';
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
INSERT INTO
|
||||
storage_source_config (name, type, title, storage_id)
|
||||
SELECT
|
||||
'refreshTokenExpiredAt', type, '刷新令牌过期时间', storage_id
|
||||
FROM
|
||||
storage_source_config
|
||||
WHERE
|
||||
name = 'refreshToken';
|
||||
Reference in New Issue
Block a user