C# 签名 SDK 接入指南

功能说明

C# 签名 SDK 用于通过 AK/SK 鉴权调用美图开放平台 API。SDK 根据请求方法、URL、查询参数、请求头和请求正文生成 SDK-HMAC-SHA256 签名,并返回可由 HttpClient 发送的 HttpRequestMessage

SDK 负责签名和创建请求;具体接口地址、HTTP 方法及业务字段以目标能力 API 文档为准。

环境要求

  • .NET 6.0
  • Visual Studio 2022 或 .NET 6 SDK
  • 示例工程依赖 System.Net.Http 4.3.4

下载与引入

  1. 下载并解压 C# SDK 与 Demo
  2. signer/Signer.cs 复制到业务项目。
  3. 在调用代码中引入命名空间:
using Signature;

示例工程的目标框架和包依赖如下:

<TargetFramework>net6.0</TargetFramework>
<PackageReference Include="System.Net.Http" Version="4.3.4" />

接口说明

SDK 的公开接口如下:

public Signer(string key, string secret);

public HttpRequestMessage Sign(
    string url,
    HttpMethod method,
    Dictionary<string, string> headers,
    string body);

参数说明:

参数说明
keyAccess Key(AK)
secretSecret Key(SK)
url最终请求 URL,包含完整路径和查询参数
methodHTTP 方法,例如 HttpMethod.PostHttpMethod.Get
headers参与签名的请求头;至少应正确设置 Host,JSON 请求设置 Content-Type
body最终请求正文;无正文时传空字符串 "",不能传 null

返回值为已经包含签名请求头和请求正文的 HttpRequestMessageSign 会在传入的 headers 中写入当前 UTC 时间对应的 X-Sdk-Date 和生成的 Authorization

常用请求头常量:

常量请求头
Signer.HeaderHostHost
Signer.HeaderContentTypeContent-Type
Signer.HeaderContentSha256X-Sdk-Content-Sha256
Signer.HeaderXDateX-Sdk-Date
Signer.HeaderAuthorizationAuthorization

POST 完整调用示例

下面示例调用正式同步任务提交地址:

https://openapi.meitu.com/api/v1/sdk/sync/push

请求体顶层包含 tasktask_typeinit_imagesparamssync_timeout。其中 params 必须是 JSON 字符串。示例中的任务、图片和参数值仅用于展示结构,请按目标能力 API 文档替换。

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Signature;

namespace MainNamespace
{
    class Program
    {
        private static readonly HttpClient Client = new HttpClient
        {
            Timeout = TimeSpan.FromSeconds(60)
        };

        static async Task<int> Main()
        {
            var accessKey = Environment.GetEnvironmentVariable("AIGCP_ACCESS_KEY");
            var secretKey = Environment.GetEnvironmentVariable("AIGCP_SECRET_KEY");

            if (string.IsNullOrWhiteSpace(accessKey) ||
                string.IsNullOrWhiteSpace(secretKey))
            {
                Console.Error.WriteLine(
                    "AIGCP_ACCESS_KEY and AIGCP_SECRET_KEY are required");
                return 2;
            }

            var endpoint = new Uri("https://openapi.meitu.com/api/v1/sdk/sync/push");
            var payload = new
            {
                task = "/v1/replace-with-product-task",
                task_type = "formula",
                init_images = new[]
                {
                    new
                    {
                        url = "https://example.com/input.jpg",
                        profile = new
                        {
                            media_profiles = new
                            {
                                media_data_type = "url"
                            },
                            version = "v1"
                        }
                    }
                },
                @params = JsonSerializer.Serialize(new
                {
                    parameter = new
                    {
                        rsp_media_type = "url"
                    }
                }),
                sync_timeout = 30
            };

            var body = JsonSerializer.Serialize(payload);
            var headers = new Dictionary<string, string>
            {
                { Signer.HeaderContentType, "application/json" },
                { Signer.HeaderHost, endpoint.Authority }
            };

            var signer = new Signer(accessKey, secretKey);

            try
            {
                using (var request = signer.Sign(
                    endpoint.AbsoluteUri, HttpMethod.Post, headers, body))
                using (var response = await Client.SendAsync(request))
                {
                    var responseBody = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"status={(int)response.StatusCode} {response.StatusCode}");
                    Console.WriteLine(responseBody);
                    return response.IsSuccessStatusCode ? 0 : 1;
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"request failed: {ex.Message}");
                return 1;
            }
        }
    }
}

运行示例:

export AIGCP_ACCESS_KEY='<your-access-key>'
export AIGCP_SECRET_KEY='<your-secret-key>'
dotnet run --project csharp.csproj

代码只序列化一次最终 body,并将同一个字符串传给 Sign。SDK 使用该字符串的 UTF-8 字节计算摘要,同时使用它创建实际发送的 StringContent,确保签名正文与发送正文一致。tasktask_type、图片、paramssync_timeout: 30 的实际取值均以目标能力 API 文档为准。

GET 调用示例

/api/v1/sdk/sync/push 是 POST 接口,不能改为 GET。下面代码仅适用于目标 API 文档中明确声明为 GET 的接口;请替换为该接口的真实路径和查询参数。

var endpoint = new Uri("https://openapi.meitu.com/replace-with-get-api-path?page=1");
var headers = new Dictionary<string, string>
{
    { Signer.HeaderContentType, "application/json" },
    { Signer.HeaderHost, endpoint.Authority }
};

var signer = new Signer(accessKey, secretKey);

using (var request = signer.Sign(endpoint.AbsoluteUri, HttpMethod.Get, headers, ""))
using (var response = await Client.SendAsync(request))
{
    var responseBody = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"status={(int)response.StatusCode} {response.StatusCode}");
    Console.WriteLine(responseBody);
}

GET 查询参数必须在调用 Sign 前写入最终 URL。无正文时传空字符串 ""

注意事项

  • 生产请求使用目标 API 文档提供的最终 HTTPS URL,并确保 Host 与 URL 的 Authority 一致。
  • 在签名前完成方法、URL、查询参数、请求头和正文的构造;签名后不要修改这些内容。
  • 当前 SDK 使用 UTF-8 StringContent 并发送 Content-Type: application/json,请求正文应为最终 JSON 字符串。
  • 每次请求或重试都使用新的 headers、重新调用 Sign,并发送新的 HttpRequestMessage
  • 保持服务器时间同步,确保 SDK 生成的 X-Sdk-Date 有效。
  • 仅当目标接口明确支持时,才可在签名前设置 X-Sdk-Content-Sha256: UNSIGNED-PAYLOAD;否则使用默认正文摘要。
  • SK 只保存在受信任的服务端,不要写入桌面端、移动端或浏览器代码。

设置 UNSIGNED-PAYLOAD 的方式:

headers[Signer.HeaderContentSha256] = "UNSIGNED-PAYLOAD";

常见错误

现象检查项
鉴权失败或签名不匹配AK/SK 是否正确;签名后 URL、方法、headers 或 body 是否变化
Host 不匹配Signer.HeaderHost 是否使用最终 URL 的 endpoint.Authority
时间错误服务器时间是否同步;是否复用了旧请求或旧签名
POST 正文验签失败是否只生成一次最终 JSON,并将同一个 body 传给 Sign
GET 签名失败查询参数是否在签名前写入 URL;无正文时是否传入 ""
Content-Type 不一致是否使用 application/json,正文是否为 UTF-8 JSON
UNSIGNED-PAYLOAD 失败目标接口是否支持该模式,请求头名称和值是否准确
请求超时或非 2xx检查 HTTPS 地址、网络、超时配置,并查看接口返回的状态码和响应体