|
|
|
@ -10,6 +10,7 @@ import javax.crypto.spec.SecretKeySpec;
|
|
|
|
|
import java.io.InputStream;
|
|
|
|
|
import java.io.OutputStream;
|
|
|
|
|
import java.net.HttpURLConnection;
|
|
|
|
|
import java.net.URI;
|
|
|
|
|
import java.net.URL;
|
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
|
import java.nio.charset.Charset;
|
|
|
|
@ -18,53 +19,50 @@ import java.security.MessageDigest;
|
|
|
|
|
import java.text.SimpleDateFormat;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 即梦API通用工具类
|
|
|
|
|
* 用于处理火山引擎API的请求签名和发送
|
|
|
|
|
*/
|
|
|
|
|
public class JmCommon {
|
|
|
|
|
// 请求域名
|
|
|
|
|
public static String host = "visual.volcengineapi.com";
|
|
|
|
|
public static String path = "/"; // 路径,不包含 Query// 请求接口信息
|
|
|
|
|
public static String service = "cv";
|
|
|
|
|
public static String region = "cn-north-1";
|
|
|
|
|
public static String schema = "https";
|
|
|
|
|
public static String version = "2022-08-31";
|
|
|
|
|
public static String path = "/"; // 路径,不包含 Query
|
|
|
|
|
// 请求接口信息
|
|
|
|
|
public static String service = "cv"; // 服务名称,固定为cv
|
|
|
|
|
public static String region = "cn-north-1"; // 区域,固定为cn-north-1
|
|
|
|
|
public static String schema = "https"; // 协议,使用https
|
|
|
|
|
public static String version = "2022-08-31"; // API版本号
|
|
|
|
|
|
|
|
|
|
// URL编码相关常量
|
|
|
|
|
private static final BitSet URLENCODER = new BitSet(256);
|
|
|
|
|
private static final String CONST_ENCODE = "0123456789ABCDEF";
|
|
|
|
|
public static final Charset UTF_8 = StandardCharsets.UTF_8;
|
|
|
|
|
protected static final String ak;
|
|
|
|
|
protected static final String sk;
|
|
|
|
|
public static Prop PropKit;
|
|
|
|
|
|
|
|
|
|
// API访问凭证
|
|
|
|
|
protected static final String ak; // Access Key
|
|
|
|
|
protected static final String sk; // Secret Key
|
|
|
|
|
public static Prop PropKit; // 配置文件工具
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 静态初始化块
|
|
|
|
|
* 加载配置文件并初始化URL编码器
|
|
|
|
|
*/
|
|
|
|
|
static {
|
|
|
|
|
//加载配置文件
|
|
|
|
|
String configFile = "application_{?}.yaml".replace("{?}", getEnvPrefix());
|
|
|
|
|
PropKit = new YamlProp(configFile);
|
|
|
|
|
System.out.println("当前环境: " + getEnvPrefix());
|
|
|
|
|
// 火山官网密钥信息, 注意sk结尾有==
|
|
|
|
|
ak = PropKit.get("JiMeng.ak");
|
|
|
|
|
sk = PropKit.get("JiMeng.sk");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 功能:获取是否为开发环境
|
|
|
|
|
*
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static String getEnvPrefix() {
|
|
|
|
|
String myEnvVar = System.getenv("WORKING_ENV");
|
|
|
|
|
if (myEnvVar == null) {
|
|
|
|
|
myEnvVar = "dev";
|
|
|
|
|
}
|
|
|
|
|
return myEnvVar;
|
|
|
|
|
}
|
|
|
|
|
ak = PropKit.get("JiMeng.ak"); // 从配置文件获取Access Key
|
|
|
|
|
sk = PropKit.get("JiMeng.sk"); // 从配置文件获取Secret Key
|
|
|
|
|
|
|
|
|
|
static {
|
|
|
|
|
// 初始化URL编码器,设置不需要编码的字符
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 97; i <= 122; ++i) {
|
|
|
|
|
for (i = 97; i <= 122; ++i) { // a-z
|
|
|
|
|
URLENCODER.set(i);
|
|
|
|
|
}
|
|
|
|
|
for (i = 65; i <= 90; ++i) {
|
|
|
|
|
for (i = 65; i <= 90; ++i) { // A-Z
|
|
|
|
|
URLENCODER.set(i);
|
|
|
|
|
}
|
|
|
|
|
for (i = 48; i <= 57; ++i) {
|
|
|
|
|
for (i = 48; i <= 57; ++i) { // 0-9
|
|
|
|
|
URLENCODER.set(i);
|
|
|
|
|
}
|
|
|
|
|
URLENCODER.set('-');
|
|
|
|
@ -73,28 +71,57 @@ public class JmCommon {
|
|
|
|
|
URLENCODER.set('~');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取当前运行环境前缀
|
|
|
|
|
*
|
|
|
|
|
* @return 环境前缀,默认为"dev"
|
|
|
|
|
*/
|
|
|
|
|
public static String getEnvPrefix() {
|
|
|
|
|
String myEnvVar = System.getenv("WORKING_ENV");
|
|
|
|
|
if (myEnvVar == null) {
|
|
|
|
|
myEnvVar = "dev"; // 默认为开发环境
|
|
|
|
|
}
|
|
|
|
|
return myEnvVar;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 发送HTTP请求到火山引擎API
|
|
|
|
|
*
|
|
|
|
|
* @param method 请求方法,如GET、POST
|
|
|
|
|
* @param queryList 查询参数列表
|
|
|
|
|
* @param body 请求体数据
|
|
|
|
|
* @param action API操作名称
|
|
|
|
|
* @return 响应体字符串
|
|
|
|
|
* @throws Exception 请求过程中的异常
|
|
|
|
|
*/
|
|
|
|
|
protected static String doRequest(String method, Map<String, String> queryList, byte[] body, String action) throws Exception {
|
|
|
|
|
Date date = new Date();
|
|
|
|
|
if (body == null) {
|
|
|
|
|
body = new byte[0];
|
|
|
|
|
}
|
|
|
|
|
// 计算请求体的SHA256哈希值
|
|
|
|
|
String xContentSha256 = hashSHA256(body);
|
|
|
|
|
|
|
|
|
|
// 格式化日期时间,用于请求头
|
|
|
|
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
|
|
|
|
|
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
|
|
|
|
|
String xDate = sdf.format(date);
|
|
|
|
|
String shortXDate = xDate.substring(0, 8);
|
|
|
|
|
String contentType = "application/json";
|
|
|
|
|
String signHeader = "host;x-date;x-content-sha256;content-type";
|
|
|
|
|
String shortXDate = xDate.substring(0, 8); // 提取日期部分,用于凭证范围
|
|
|
|
|
|
|
|
|
|
String contentType = "application/json"; // 内容类型
|
|
|
|
|
String signHeader = "host;x-date;x-content-sha256;content-type"; // 签名头部列表
|
|
|
|
|
|
|
|
|
|
// 构建查询参数字符串
|
|
|
|
|
SortedMap<String, String> realQueryList = new TreeMap<>(queryList);
|
|
|
|
|
realQueryList.put("Action", action);
|
|
|
|
|
realQueryList.put("Version", version);
|
|
|
|
|
realQueryList.put("Action", action); // 添加Action参数
|
|
|
|
|
realQueryList.put("Version", version); // 添加Version参数
|
|
|
|
|
StringBuilder querySB = new StringBuilder();
|
|
|
|
|
for (String key : realQueryList.keySet()) {
|
|
|
|
|
querySB.append(signStringEncoder(key)).append("=").append(signStringEncoder(realQueryList.get(key))).append("&");
|
|
|
|
|
}
|
|
|
|
|
querySB.deleteCharAt(querySB.length() - 1);
|
|
|
|
|
querySB.deleteCharAt(querySB.length() - 1); // 删除最后一个&符号
|
|
|
|
|
|
|
|
|
|
// 构建规范请求字符串,用于签名
|
|
|
|
|
String canonicalStringBuilder = method + "\n" + path + "\n" + querySB + "\n" +
|
|
|
|
|
"host:" + host + "\n" +
|
|
|
|
|
"x-date:" + xDate + "\n" +
|
|
|
|
@ -103,16 +130,25 @@ public class JmCommon {
|
|
|
|
|
"\n" +
|
|
|
|
|
signHeader + "\n" +
|
|
|
|
|
xContentSha256;
|
|
|
|
|
|
|
|
|
|
// 计算规范请求字符串的哈希值
|
|
|
|
|
String hashcanonicalString = hashSHA256(canonicalStringBuilder.getBytes());
|
|
|
|
|
|
|
|
|
|
// 构建凭证范围和签名字符串
|
|
|
|
|
String credentialScope = shortXDate + "/" + region + "/" + service + "/request";
|
|
|
|
|
String signString = "HMAC-SHA256" + "\n" + xDate + "\n" + credentialScope + "\n" + hashcanonicalString;
|
|
|
|
|
|
|
|
|
|
// 生成签名密钥并计算签名
|
|
|
|
|
byte[] signKey = genSigningSecretKeyV4(shortXDate, region, service);
|
|
|
|
|
String signature = HexFormat.of().formatHex(hmacSHA256(signKey, signString));
|
|
|
|
|
URL url = new URL(schema + "://" + host + path + "?" + querySB);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 构建URL并创建HTTP连接
|
|
|
|
|
URI uri = new URI(schema, "//" + host + path, "?" + querySB.toString());
|
|
|
|
|
URL url = uri.toURL();
|
|
|
|
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
|
|
|
|
conn.setRequestMethod(method);
|
|
|
|
|
|
|
|
|
|
// 设置请求头
|
|
|
|
|
conn.setRequestProperty("Host", host);
|
|
|
|
|
conn.setRequestProperty("X-Date", xDate);
|
|
|
|
|
conn.setRequestProperty("X-Content-Sha256", xContentSha256);
|
|
|
|
@ -121,6 +157,8 @@ public class JmCommon {
|
|
|
|
|
" Credential=" + ak + "/" + credentialScope +
|
|
|
|
|
", SignedHeaders=" + signHeader +
|
|
|
|
|
", Signature=" + signature);
|
|
|
|
|
|
|
|
|
|
// 如果不是GET请求,写入请求体
|
|
|
|
|
if (!Objects.equals(conn.getRequestMethod(), "GET")) {
|
|
|
|
|
conn.setDoOutput(true);
|
|
|
|
|
OutputStream os = conn.getOutputStream();
|
|
|
|
@ -128,22 +166,29 @@ public class JmCommon {
|
|
|
|
|
os.flush();
|
|
|
|
|
os.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 建立连接并获取响应
|
|
|
|
|
conn.connect();
|
|
|
|
|
|
|
|
|
|
int responseCode = conn.getResponseCode();
|
|
|
|
|
|
|
|
|
|
InputStream is;
|
|
|
|
|
if (responseCode == 200) {
|
|
|
|
|
is = conn.getInputStream();
|
|
|
|
|
is = conn.getInputStream(); // 成功响应
|
|
|
|
|
} else {
|
|
|
|
|
is = conn.getErrorStream();
|
|
|
|
|
is = conn.getErrorStream(); // 错误响应
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 读取响应内容并关闭流
|
|
|
|
|
String responseBody = new String(ByteStreams.toByteArray(is));
|
|
|
|
|
is.close();
|
|
|
|
|
|
|
|
|
|
return responseBody;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* URL编码字符串,用于签名
|
|
|
|
|
*
|
|
|
|
|
* @param source 源字符串
|
|
|
|
|
* @return 编码后的字符串
|
|
|
|
|
*/
|
|
|
|
|
protected static String signStringEncoder(String source) {
|
|
|
|
|
if (source == null) {
|
|
|
|
|
return null;
|
|
|
|
@ -153,10 +198,13 @@ public class JmCommon {
|
|
|
|
|
while (bb.hasRemaining()) {
|
|
|
|
|
int b = bb.get() & 255;
|
|
|
|
|
if (URLENCODER.get(b)) {
|
|
|
|
|
// 不需要编码的字符直接添加
|
|
|
|
|
buf.append((char) b);
|
|
|
|
|
} else if (b == 32) {
|
|
|
|
|
// 空格编码为%20
|
|
|
|
|
buf.append("%20");
|
|
|
|
|
} else {
|
|
|
|
|
// 其他字符编码为%XX格式
|
|
|
|
|
buf.append("%");
|
|
|
|
|
char hex1 = CONST_ENCODE.charAt(b >> 4);
|
|
|
|
|
char hex2 = CONST_ENCODE.charAt(b & 15);
|
|
|
|
@ -168,6 +216,13 @@ public class JmCommon {
|
|
|
|
|
return buf.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 计算内容的SHA256哈希值
|
|
|
|
|
*
|
|
|
|
|
* @param content 要计算哈希的内容
|
|
|
|
|
* @return 十六进制表示的哈希值
|
|
|
|
|
* @throws Exception 计算过程中的异常
|
|
|
|
|
*/
|
|
|
|
|
protected static String hashSHA256(byte[] content) throws Exception {
|
|
|
|
|
try {
|
|
|
|
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
|
|
|
@ -177,6 +232,14 @@ public class JmCommon {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 使用HMAC-SHA256算法计算消息认证码
|
|
|
|
|
*
|
|
|
|
|
* @param key 密钥
|
|
|
|
|
* @param content 内容
|
|
|
|
|
* @return 计算结果
|
|
|
|
|
* @throws Exception 计算过程中的异常
|
|
|
|
|
*/
|
|
|
|
|
protected static byte[] hmacSHA256(byte[] key, String content) throws Exception {
|
|
|
|
|
try {
|
|
|
|
|
Mac mac = Mac.getInstance("HmacSHA256");
|
|
|
|
@ -187,6 +250,16 @@ public class JmCommon {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成V4版本的签名密钥
|
|
|
|
|
* 使用AWS签名V4算法的派生密钥过程
|
|
|
|
|
*
|
|
|
|
|
* @param date 日期字符串
|
|
|
|
|
* @param region 区域
|
|
|
|
|
* @param service 服务名称
|
|
|
|
|
* @return 派生的签名密钥
|
|
|
|
|
* @throws Exception 生成过程中的异常
|
|
|
|
|
*/
|
|
|
|
|
protected static byte[] genSigningSecretKeyV4(String date, String region, String service) throws Exception {
|
|
|
|
|
byte[] kDate = hmacSHA256((JmCommon.sk).getBytes(), date);
|
|
|
|
|
byte[] kRegion = hmacSHA256(kDate, region);
|
|
|
|
|