From 08a56f782af0018a7f40759f795226aecd830546 Mon Sep 17 00:00:00 2001 From: yanlongqi Date: Sat, 22 Nov 2025 15:33:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DHTTP=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E6=B3=84=E9=9C=B2=E5=92=8CToken=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E7=BA=BF=E7=A8=8B=E5=AE=89=E5=85=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复HTTP连接资源管理问题 * 所有HTTP请求方法使用try-with-resources确保InputStream自动关闭 * 添加EntityUtils.consume确保HTTP响应实体被完全消费 * 引入必要的Apache HttpClient工具类 - 修复Token缓存线程安全问题 * 使用computeIfAbsent确保首次创建token的原子性操作 * 实现双重检查锁定机制避免重复获取token * 提取createNewTokenInfo方法提高代码可读性和复用性 - 性能和稳定性提升 * 消除HTTP连接泄露风险,提高连接池利用率 * 解决多线程环境下的token竞争问题 * 减少重复token请求,提升高并发场景下系统稳定性 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../sdk/openapi/api/JHRequestExecution.java | 65 ++++++++++++------- .../sdk/openapi/client/JHApiClient.java | 21 +++--- .../openapi/client/JHApiHttpClientImpl.java | 5 +- 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/api/JHRequestExecution.java b/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/api/JHRequestExecution.java index f7df942..a9034de 100644 --- a/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/api/JHRequestExecution.java +++ b/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/api/JHRequestExecution.java @@ -142,37 +142,58 @@ public class JHRequestExecution { if (StringUtils.isBlank(username)) { throw new ArgsException("用户名称不能为空!"); } - TokenInfo tokenInfo = TOKEN_INFO_MAP.get(username); // 防止因为服务器时间的问题二导致token不可用,可以通过此配置提前获取token int tokenEffectiveTime = (tokenTimeout - tokenResidueTime) * 60 * 1000; - // 如果是强制获取用户令牌为空、用户令牌不存在、用户令牌过期等,则获取令牌 - boolean isGetToken = isForceGetToken || tokenInfo == null || System.currentTimeMillis() - tokenInfo.getCurrentTimestamp() > tokenEffectiveTime; - if (isGetToken) { - Map params = new HashMap<>(2); - params.put("timeout", tokenTimeout); - String currentTimeMillis = getCurrentTimeMillis(); - String beforeEncryption = String.format(CommonConstant.TokenUserFormat, username, currentTimeMillis); - try { - SecretKeySpec secretKey = new SecretKeySpec( - CommonConstant.DEFAULT_AES_KEY.getBytes(StandardCharsets.UTF_8), CommonConstant.AES_ALGORITHM); - Cipher cipher = Cipher.getInstance(CommonConstant.AES_ECB_PADDING); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] encryptBytes = cipher.doFinal(beforeEncryption.getBytes(StandardCharsets.UTF_8)); - params.put("username", Base64.getEncoder().encodeToString(encryptBytes)); - } catch (Exception e) { - throw new ClientException("AES加密失败,失败原因:" + e.getMessage(), e); + // 使用computeIfAbsent确保原子性操作,避免重复获取token + TokenInfo tokenInfo = TOKEN_INFO_MAP.computeIfAbsent(username, this::createNewTokenInfo); + + // 检查token是否过期,如果过期则创建新token + long currentTime = System.currentTimeMillis(); + if (isForceGetToken || currentTime - tokenInfo.getCurrentTimestamp() > tokenEffectiveTime) { + synchronized (this) { + // 双重检查锁定,确保在同步块中再次检查 + tokenInfo = TOKEN_INFO_MAP.get(username); + if (tokenInfo == null || currentTime - tokenInfo.getCurrentTimestamp() > tokenEffectiveTime || isForceGetToken) { + tokenInfo = createNewTokenInfo(username); + TOKEN_INFO_MAP.put(username, tokenInfo); + } } - tokenInfo = new TokenInfo(); - tokenInfo.setUserName(username); - tokenInfo.setToken(requestToken(params)); - tokenInfo.setCurrentTimestamp(System.currentTimeMillis()); - TOKEN_INFO_MAP.put(username, tokenInfo); } + return tokenInfo.getToken(); } + /** + * 创建新的TokenInfo + * + * @param username 用户名 + * @return 新的TokenInfo + */ + private TokenInfo createNewTokenInfo(String username) { + Map params = new HashMap<>(2); + params.put("timeout", tokenTimeout); + String currentTimeMillis = getCurrentTimeMillis(); + String beforeEncryption = String.format(CommonConstant.TokenUserFormat, username, currentTimeMillis); + try { + SecretKeySpec secretKey = new SecretKeySpec( + CommonConstant.DEFAULT_AES_KEY.getBytes(StandardCharsets.UTF_8), CommonConstant.AES_ALGORITHM); + Cipher cipher = Cipher.getInstance(CommonConstant.AES_ECB_PADDING); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + byte[] encryptBytes = cipher.doFinal(beforeEncryption.getBytes(StandardCharsets.UTF_8)); + params.put("username", Base64.getEncoder().encodeToString(encryptBytes)); + } catch (Exception e) { + throw new ClientException("AES加密失败,失败原因:" + e.getMessage(), e); + } + + TokenInfo tokenInfo = new TokenInfo(); + tokenInfo.setUserName(username); + tokenInfo.setToken(requestToken(params)); + tokenInfo.setCurrentTimestamp(System.currentTimeMillis()); + return tokenInfo; + } + /** * 获得当前的时间 * diff --git a/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/client/JHApiClient.java b/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/client/JHApiClient.java index ac3bd3d..9013f12 100644 --- a/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/client/JHApiClient.java +++ b/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/client/JHApiClient.java @@ -87,8 +87,7 @@ public class JHApiClient { if (StringUtils.isBlank(path)) { throw new ArgsException("url不能为空"); } - try { - InputStream content = apiHttpClient.get(getUrl(path), headers); + try (InputStream content = apiHttpClient.get(getUrl(path), headers)) { return mapper.readValue(content, type); } catch (IOException e) { throw new ClientException(e.getMessage(), e); @@ -179,8 +178,9 @@ public class JHApiClient { if (body != null) { bodyStr = mapper.writeValueAsString(body); } - InputStream content = apiHttpClient.post(getUrl(path), bodyStr, headers); - return mapper.readValue(content, type); + try (InputStream content = apiHttpClient.post(getUrl(path), bodyStr, headers)) { + return mapper.readValue(content, type); + } } catch (IOException e) { throw new ClientException(e.getMessage(), e); } @@ -208,8 +208,9 @@ public class JHApiClient { if (body != null) { bodyStr = mapper.writeValueAsString(body); } - InputStream content = apiHttpClient.put(getUrl(path), bodyStr, headers); - return mapper.readValue(content, type); + try (InputStream content = apiHttpClient.put(getUrl(path), bodyStr, headers)) { + return mapper.readValue(content, type); + } } catch (IOException e) { throw new ClientException(e.getMessage(), e); } @@ -272,8 +273,7 @@ public class JHApiClient { if (StringUtils.isBlank(path)) { throw new ArgsException("path不能为空"); } - try { - InputStream content = apiHttpClient.delete(getUrl(path), headers); + try (InputStream content = apiHttpClient.delete(getUrl(path), headers)) { return mapper.readValue(content, type); } catch (IOException e) { throw new ClientException(e.getMessage(), e); @@ -314,8 +314,9 @@ public class JHApiClient { throw new ArgsException("path不能为空"); } try { - InputStream content = apiHttpClient.upload(getUrl(path), keyName, fileName, is, body, headers); - return mapper.readValue(content, type); + try (InputStream content = apiHttpClient.upload(getUrl(path), keyName, fileName, is, body, headers)) { + return mapper.readValue(content, type); + } } catch (IOException e) { throw new ClientException(e.getMessage(), e); } diff --git a/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/client/JHApiHttpClientImpl.java b/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/client/JHApiHttpClientImpl.java index 42720cd..80274c9 100644 --- a/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/client/JHApiHttpClientImpl.java +++ b/jhinno-openapi-java-sdk/src/main/java/com/jhinno/sdk/openapi/client/JHApiHttpClientImpl.java @@ -26,6 +26,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.util.EntityUtils; import java.io.IOException; import java.io.InputStream; @@ -146,7 +147,9 @@ public class JHApiHttpClientImpl implements JHApiHttpClient { try { HttpResponse response = closeableHttpClient.execute(httpRequest); int statusCode = response.getStatusLine().getStatusCode(); - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + if (statusCode != HttpStatus.SC_OK) { + // 确保响应实体被完全消费以释放连接 + EntityUtils.consume(response.getEntity()); httpRequest.releaseConnection(); throw new ClientException("发送HTTP请求失败,请求码:" + statusCode, ClientErrorCode.REQUEST_ERROR); }