Java 签名 SDK 接入指南

功能说明

Java 签名 SDK 使用 AK/SK 为美图开放平台 API 请求生成签名。调用 Signer.sign 后,请求头中会包含:

  • X-Sdk-Date:UTC 签名时间。
  • AuthorizationSDK-HMAC-SHA256 签名结果。

SDK 负责签名,HTTP 请求可使用 JDK 标准库或业务项目现有的 HTTP 客户端发送。本文示例使用 Java 标准库,可直接应用于服务端项目。

环境要求

  • JDK 8 或更高版本。
  • 已获取可用的 Access Key(AK)和 Secret Key(SK)。
  • 服务运行环境可通过 HTTPS 访问 openapi.meitu.com
  • 已确认目标能力文档中的请求地址、task、图片字段和业务参数。

下载与引入

下载 Java SDK 1.0.3

下载包解压后的主要文件:

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)
参数类型说明
keyStringAccess Key(AK)
secretStringSecret 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 方法,例如 GETPOST
headers可修改的请求头 Map;至少包含与 URL 一致的 Host
body最终发送的请求体;无请求体时传 null

sign 会直接更新传入的 headers,加入 X-Sdk-DateAuthorization,并返回同一个 Map:

Map<String, String> signedHeaders = signer.sign(url, method, headers, body);

常用请求头常量:

常量请求头说明
Signer.HeaderHostHost请求域名,值为 openapi.meitu.com
Signer.HeaderXDateX-Sdk-Date可省略,由 SDK 自动生成当前 UTC 时间
Signer.HeaderContentSha256X-Sdk-Content-Sha256目标接口支持时可设置为 UNSIGNED-PAYLOAD
Signer.HeaderAuthorizationAuthorization由 SDK 自动生成

默认情况下,SDK 对 body 计算 SHA-256 并纳入签名。bodynull 时按空字符串计算。若目标接口明确支持 body 不参与签名,可在调用 sign 前设置:

headers.put(Signer.HeaderContentSha256, "UNSIGNED-PAYLOAD");

POST 完整调用示例

以下示例调用正式同步任务接口 https://openapi.meitu.com/api/v1/sdk/sync/push。请求体包含同步接口的五个顶层字段:tasktask_typeinit_imagesparamssync_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();
    }
}

注意事项

  1. urlmethod、headers 和 body 使用最终发送值进行签名;签名完成后保持这些内容不变。
  2. Host 只填写域名,并与 URL 的主机一致;正式请求使用 HTTPS。
  3. POST body 使用 UTF-8 发送,发送内容与传入 sign 的字符串完全一致。
  4. 查询参数在签名前写入 URL;参数发生变化时重新签名。
  5. X-Sdk-Date 通常由 SDK 自动生成,运行环境保持系统时钟准确。
  6. 每次请求和重试均重新调用 sign,生成新的时间与签名。
  7. 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 可访问性和接口处理时间,并按业务需要调整连接与读取超时