Java 签名 SDK 接入指南
功能说明
Java 签名 SDK 使用 AK/SK 为美图开放平台 API 请求生成签名。调用 Signer.sign 后,请求头中会包含:
X-Sdk-Date:UTC 签名时间。Authorization:SDK-HMAC-SHA256签名结果。
SDK 负责签名,HTTP 请求可使用 JDK 标准库或业务项目现有的 HTTP 客户端发送。本文示例使用 Java 标准库,可直接应用于服务端项目。
环境要求
- JDK 8 或更高版本。
- 已获取可用的 Access Key(AK)和 Secret Key(SK)。
- 服务运行环境可通过 HTTPS 访问
openapi.meitu.com。 - 已确认目标能力文档中的请求地址、
task、图片字段和业务参数。
下载与引入
下载包解压后的主要文件:
AIGCP-API-java-sdk-1.0.3/
├── pom.xml
└── src/main/java/com/meitu/openai/common/Signer.java以下载包中的 1.0.3 源码和 pom.xml 为准,在 SDK 根目录执行:
mvn clean install构建完成后,在业务项目的 pom.xml 中加入:
<dependency>
<groupId>com.meitu.openai</groupId>
<artifactId>openai-common-signer</artifactId>
<version>1.0.3</version>
</dependency>SDK 没有第三方运行时依赖。也可将 Signer.java 放入业务项目的相同包路径。调用代码使用以下导入:
import com.meitu.openai.common.Signer;接口说明
创建签名器
public Signer(String key, String secret)| 参数 | 类型 | 说明 |
|---|---|---|
key | String | Access Key(AK) |
secret | String | Secret Key(SK) |
示例:
Signer signer = new Signer(accessKey, secretKey);请求签名
public Map<String, String> sign(
String url,
String method,
Map<String, String> headers,
String body
) throws URISyntaxException| 参数 | 说明 |
|---|---|
url | 最终请求 URL,包含路径和查询参数 |
method | 最终 HTTP 方法,例如 GET、POST |
headers | 可修改的请求头 Map;至少包含与 URL 一致的 Host |
body | 最终发送的请求体;无请求体时传 null |
sign 会直接更新传入的 headers,加入 X-Sdk-Date 和 Authorization,并返回同一个 Map:
Map<String, String> signedHeaders = signer.sign(url, method, headers, body);常用请求头常量:
| 常量 | 请求头 | 说明 |
|---|---|---|
Signer.HeaderHost | Host | 请求域名,值为 openapi.meitu.com |
Signer.HeaderXDate | X-Sdk-Date | 可省略,由 SDK 自动生成当前 UTC 时间 |
Signer.HeaderContentSha256 | X-Sdk-Content-Sha256 | 目标接口支持时可设置为 UNSIGNED-PAYLOAD |
Signer.HeaderAuthorization | Authorization | 由 SDK 自动生成 |
默认情况下,SDK 对 body 计算 SHA-256 并纳入签名。body 为 null 时按空字符串计算。若目标接口明确支持 body 不参与签名,可在调用 sign 前设置:
headers.put(Signer.HeaderContentSha256, "UNSIGNED-PAYLOAD");POST 完整调用示例
以下示例调用正式同步任务接口 https://openapi.meitu.com/api/v1/sdk/sync/push。请求体包含同步接口的五个顶层字段:task、task_type、init_images、params 和 sync_timeout。
示例中的 task、图片 URL、图片 profile 和 params 为占位数据,接入时按目标能力文档替换;params 保持为 JSON 字符串,sync_timeout 的取值范围以目标接口文档为准。
import com.meitu.openai.common.Signer;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public final class Main {
public static void main(String[] args) throws Exception {
String accessKey = requireEnv("MEITU_OPENAPI_AK");
String secretKey = requireEnv("MEITU_OPENAPI_SK");
String url = "https://openapi.meitu.com/api/v1/sdk/sync/push";
String method = "POST";
String body = "{"
+ "\"task\":\"/v1/replace-with-product-task\","
+ "\"task_type\":\"formula\","
+ "\"init_images\":[{"
+ "\"url\":\"https://example.com/input.jpg\","
+ "\"profile\":{"
+ "\"media_profiles\":{\"media_data_type\":\"url\"},"
+ "\"version\":\"v1\""
+ "}"
+ "}],"
+ "\"params\":\"{\\\"parameter\\\":{\\\"rsp_media_type\\\":\\\"url\\\"}}\","
+ "\"sync_timeout\":30"
+ "}";
URI uri = new URI(url);
Map<String, String> headers = new HashMap<>();
headers.put(Signer.HeaderHost, uri.getHost());
headers.put("Content-Type", "application/json; charset=UTF-8");
Signer signer = new Signer(accessKey, secretKey);
Map<String, String> signedHeaders = signer.sign(url, method, headers, body);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setRequestMethod(method);
connection.setConnectTimeout(10_000);
connection.setReadTimeout(60_000);
connection.setDoOutput(true);
for (Map.Entry<String, String> entry : signedHeaders.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
connection.setFixedLengthStreamingMode(bodyBytes.length);
try (OutputStream output = connection.getOutputStream()) {
output.write(bodyBytes);
}
int status = connection.getResponseCode();
InputStream responseStream = status >= 400
? connection.getErrorStream()
: connection.getInputStream();
String responseBody = readUtf8(responseStream);
connection.disconnect();
System.out.println("HTTP " + status);
System.out.println(responseBody);
}
private static String requireEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty()) {
throw new IllegalStateException("Missing environment variable: " + name);
}
return value;
}
private static String readUtf8(InputStream input) throws Exception {
if (input == null) {
return "";
}
StringBuilder result = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(input, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append('\n');
}
}
return result.toString();
}
}示例通过环境变量读取凭证:
export MEITU_OPENAPI_AK='<ACCESS_KEY>'
export MEITU_OPENAPI_SK='<SECRET_KEY>'GET 调用示例
GET 请求将查询参数写入最终 URL,并使用 null body。以下示例调用任务状态查询接口;task_id 替换为实际任务 ID。
import com.meitu.openai.common.Signer;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public final class GetExample {
public static void main(String[] args) throws Exception {
String accessKey = requireEnv("MEITU_OPENAPI_AK");
String secretKey = requireEnv("MEITU_OPENAPI_SK");
String url = "https://openapi.meitu.com/api/v1/sdk/status?task_id=replace-with-task-id";
String method = "GET";
String body = null;
URI uri = new URI(url);
Map<String, String> headers = new HashMap<>();
headers.put(Signer.HeaderHost, uri.getHost());
Signer signer = new Signer(accessKey, secretKey);
Map<String, String> signedHeaders = signer.sign(url, method, headers, body);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setRequestMethod(method);
connection.setConnectTimeout(10_000);
connection.setReadTimeout(60_000);
for (Map.Entry<String, String> entry : signedHeaders.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
int status = connection.getResponseCode();
InputStream responseStream = status >= 400
? connection.getErrorStream()
: connection.getInputStream();
String responseBody = readUtf8(responseStream);
connection.disconnect();
System.out.println("HTTP " + status);
System.out.println(responseBody);
}
private static String requireEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty()) {
throw new IllegalStateException("Missing environment variable: " + name);
}
return value;
}
private static String readUtf8(InputStream input) throws Exception {
if (input == null) {
return "";
}
StringBuilder result = new StringBuilder();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(input, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append('\n');
}
}
return result.toString();
}
}注意事项
url、method、headers 和 body 使用最终发送值进行签名;签名完成后保持这些内容不变。Host只填写域名,并与 URL 的主机一致;正式请求使用 HTTPS。- POST body 使用 UTF-8 发送,发送内容与传入
sign的字符串完全一致。 - 查询参数在签名前写入 URL;参数发生变化时重新签名。
X-Sdk-Date通常由 SDK 自动生成,运行环境保持系统时钟准确。- 每次请求和重试均重新调用
sign,生成新的时间与签名。 - SK 仅保存在受信任的服务端,不写入客户端或源代码。
常见错误
| 现象 | 处理方法 |
|---|---|
package com.meitu.openai.common does not exist | 确认已执行 mvn clean install,业务项目依赖版本为 1.0.3,或 Signer.java 位于正确包路径 |
URISyntaxException | 检查 URL 是否为完整 HTTPS URL,并对路径和查询参数中的特殊字符正确编码 |
| 签名校验失败 | 核对 AK/SK、HTTP 方法、完整 URL、Host、已签请求头和实际发送 body 是否一致,并确认系统时间准确 |
| 请求体参数错误 | 确认 body 包含接口要求的字段,params 为 JSON 字符串,图片字段和 task 来自目标能力文档 |
| HTTP 401 或 403 | 确认凭证有效,并确认应用已开通目标能力和调用权限 |
| 请求超时 | 检查网络连通性、图片 URL 可访问性和接口处理时间,并按业务需要调整连接与读取超时 |