Files
ks/ruoyi-bussiness/src/main/java/com/ruoyi/cms/handler/AliyunNlsTokenUtil.java

138 lines
6.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.ruoyi.cms.handler;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AliyunNlsTokenUtil {
private static final Logger logger = LoggerFactory.getLogger(AliyunNlsTokenUtil.class);
// Nginx 代理配置(与 Nginx 一致)
private static final String PROXY_HOST = "192.168.2.102";
private static final int PROXY_PORT = 10044;
// Token 接口代理地址(通过 Nginx 转发)
private static final String TOKEN_PROXY_URL = "http://" + PROXY_HOST + ":" + PROXY_PORT + "/";
// 单例 OkHttp 客户端(带代理,全局复用)
private static OkHttpClient proxyOkHttpClient;
static {
// 初始化带代理的 OkHttp 客户端(仅初始化一次)
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_HOST, PROXY_PORT));
proxyOkHttpClient = new OkHttpClient.Builder()
.proxy(proxy)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
}
/**
* 手动生成 Token绕开 SDK 无代理逻辑)
* @param accessKeyId 阿里云 AccessKeyId
* @param accessKeySecret 阿里云 AccessKeySecret
* @param accessToken SDK 的 AccessToken 对象(用于设置 token 和 expireTime
*/
public static void generateToken(String accessKeyId, String accessKeySecret, com.alibaba.nls.client.AccessToken accessToken) throws Exception {
// 1. 生成阿里云 API 签名HMAC-SHA1
String timestamp = getIso8601UtcTimestamp();
String nonce = String.valueOf(System.currentTimeMillis());
String signature = generateSignature(accessKeyId, accessKeySecret, timestamp, nonce);
// 2. 构造请求 URL带签名参数
String requestUrl = TOKEN_PROXY_URL +
"?Action=CreateToken" +
"&Format=JSON" +
"&Version=2019-02-28" +
"&AccessKeyId=" + URLEncoder.encode(accessKeyId, "UTF-8") +
"&Timestamp=" + URLEncoder.encode(timestamp, "UTF-8") +
"&SignatureNonce=" + URLEncoder.encode(nonce, "UTF-8") +
"&Signature=" + URLEncoder.encode(signature, "UTF-8") +
"&SignatureMethod=HMAC-SHA1" +
"&SignatureVersion=1.0";
// 打印完整请求 URL用于排查参数问题
logger.info("Token 生成请求 URL{}", requestUrl);
// 3. 发送 GET 请求(走 Nginx 代理)
Request request = new Request.Builder().url(requestUrl).build();
try (Response response = proxyOkHttpClient.newCall(request).execute()) {
String responseBody = response.body() != null ? response.body().string() : "无响应内容";
logger.info("Token 接口响应 - 状态码:{},响应体:{}", response.code(), responseBody);
if (!response.isSuccessful()) {
throw new RuntimeException("Token 生成失败,状态码:" + response.code() + ",错误信息:" + responseBody);
}
com.alibaba.fastjson.JSONObject json = com.alibaba.fastjson.JSON.parseObject(responseBody);
// 4. 提取 Token 和过期时间
String token = json.getJSONObject("Token").getString("Id");
long expireTime = json.getJSONObject("Token").getLong("ExpireTime");
// 5. 反射设置到 SDK 的 AccessToken 对象
setAccessTokenField(accessToken, "token", token);
setAccessTokenField(accessToken, "expireTime", expireTime);
logger.info("Token 生成成功,过期时间:{}", expireTime);
}
}
/**
* 生成阿里云 API 签名(遵循阿里云规范)
*/
private static String generateSignature(String accessKeyId, String accessKeySecret, String timestamp, String nonce) throws Exception {
// 1. 按字典序拼接参数(必须严格排序)
StringBuilder paramBuilder = new StringBuilder();
paramBuilder.append("AccessKeyId=").append(URLEncoder.encode(accessKeyId, "UTF-8"))
.append("&Action=").append(URLEncoder.encode("CreateToken", "UTF-8"))
.append("&Format=").append(URLEncoder.encode("JSON", "UTF-8"))
.append("&SignatureMethod=").append(URLEncoder.encode("HMAC-SHA1", "UTF-8"))
.append("&SignatureNonce=").append(URLEncoder.encode(nonce, "UTF-8"))
.append("&SignatureVersion=").append(URLEncoder.encode("1.0", "UTF-8"))
.append("&Timestamp=").append(URLEncoder.encode(timestamp, "UTF-8"))
.append("&Version=").append(URLEncoder.encode("2019-02-28", "UTF-8"));
// 2. 构造签名原文Method + & + 编码后的URL + & + 编码后的参数)
String method = "GET";
String encodedUrl = URLEncoder.encode("/", "UTF-8"); // 根路径编码
String encodedParams = URLEncoder.encode(paramBuilder.toString(), "UTF-8");
String signatureText = method + "&" + encodedUrl + "&" + encodedParams;
// 调试日志:对比服务器端预期的签名原文
logger.info("本地计算的签名原文:{}", signatureText);
// 3. HMAC-SHA1 加密 + Base64 编码
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec((accessKeySecret + "&").getBytes("UTF-8"), "HmacSHA1"));
byte[] signatureBytes = mac.doFinal(signatureText.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(signatureBytes);
}
/**
* 生成 ISO8601 格式的 UTC 时间戳2025-11-24T10:00:00Z
*/
private static String getIso8601UtcTimestamp() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("UTC")); // 强制使用 UTC 时区
return sdf.format(new Date());
}
/**
* 反射设置 AccessToken 的私有字段
*/
private static void setAccessTokenField(com.alibaba.nls.client.AccessToken accessToken, String fieldName, Object value) throws Exception {
java.lang.reflect.Field field = com.alibaba.nls.client.AccessToken.class.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(accessToken, value);
}
}