豆包生成C#即梦API HTTP调用实例代码
本文介绍了使用C#调用即梦API的过程。作者通过火山引擎开通即梦API服务后,利用豆包AI将官方Python示例代码转换为C#代码。在调试过程中发现签名计算问题,最终确定是Content-Type头信息设置不当导致。文章详细说明了签名生成方法,并提供了完整的C#客户端实现代码,包括请求构建、签名计算和参数处理等关键步骤,最后成功实现了API调用。
最近玩即梦AI,文生图,文生视频等等很多玩法都很强大。即梦本身页提供了API。官方文档里有Java, Golang, Python, PHP的SDK,官方也推荐使用SDK,调用SDK会比较省事儿。官方也提供了HTTP请求示例代码,但是也只包括Java, Golang, Python, PHP,没有C#。所以就尝试写个C#调用即梦API。调用的难点在于火山引擎API的签名生成。下面介绍一下即梦API的调用过程和使用豆包将Python HTTP示例代码转成C#代码。
1. 注册火山引擎账号,并开通即梦API图片生成服务,开通方式选择免费试用。免费调用时长限额是500秒,并发限额是1。参考新手指南--AI中台公用文档-火山引擎。这个文档中还包含了API访问密钥的生成方式,这个也是使用API必不可少的步骤。注意这个密钥自己妥善保管,不能泄露。
2. 账号开通完成,API访问密钥创建完成就可以开始代码了。我参考了Python的代码,Python代码下载下来,需要替换access_key和secret_key。本地装有Python环境,可以直接跑通。
3. 使用豆包的AI编程,把官方Python代码,贴进豆包提问,请豆包转成C#代码,稍等片刻,C#代码生成完毕。
4. 把C#代码Copy到VS,开始调试。我遇到的问题是:“The request signature we calculated does not match the signature you provided. Check your Secret Access Key and signing method. Consult the service documentation for details.”。是说,request签名,C#里计算的结果和火山引擎服务器计算的结果不一致。
5. 继续调试,通过和Python的签名结果对比,C#函数计算的签名其实是没有问题的,那么问题出在Http request。
6. 进一步调试,发现,这个API的POST request的Header里应该要包含如下内容,这里主要注意Content-Type。
POST
Host: billing.volcengineapi.com
Content-Type: application/json
X-Date: 20250329T180937Z
Authorization: HMAC-SHA256 Credential=AKLTYWViMTVmZGYzM2E0NDI5Mzk2MDZjNjFmMjc2MjRjMzg/20250329/cn-beijing/billing/request, SignedHeaders=host;x-date, Signature=5e8480ceea12d0000a23c054151c50dd02c1a7dec835004057d19f13d53a7658
7. 往Http request加header,是通过下面的代码实现的。需要注意的是注释的部分,我调试的错误就是因为AI的代码额外加了Content的UTF8编码,导致服务器端计算的签名和C#计算的不匹配。
var headers = new Dictionary<string, string>
{
{"X-Date", currentDate},
{"Authorization", authorizationHeader},
{"X-Content-Sha256", payloadHash},
{"Content-Type", contentType}
};
.......
foreach (var header in headers)
{
// 这里加header,Content-Type是加不进去的,它应该要被放在Content的header
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
.......
// 加到这里,注意这里一定要只包含Content-Type,不要包含任何编码格式
// AI生成的代码,可能会加上编码格式,导致,发送往服务器的Header Content-Type不完全是 “application/json”
request.Content = new StringContent(formattedBody, new System.Net.Http.Headers.MediaTypeHeaderValue(contentType));
8. 最终的调用类完整代码如下。
using Newtonsoft.Json;
using System.Security.Cryptography;
using System.Text;
internal class VolcengineApiClient
{
private readonly string _accessKey;
private readonly string _secretKey;
private readonly string _host;
private readonly string _region;
private readonly string _service;
private readonly string _endpoint;
private readonly HttpClient _httpClient;
/// <summary>
/// 初始化火山引擎API客户端
/// </summary>
/// <param name="accessKey">访问密钥</param>
/// <param name="secretKey">密钥</param>
/// <param name="host">API主机地址</param>
/// <param name="region">区域</param>
/// <param name="service">服务名称</param>
public VolcengineApiClient(string accessKey, string secretKey,
string host = "visual.volcengineapi.com",
string region = "cn-north-1",
string service = "cv")
{
_accessKey = accessKey ?? throw new ArgumentNullException(nameof(accessKey));
_secretKey = secretKey ?? throw new ArgumentNullException(nameof(secretKey));
_host = host ?? throw new ArgumentNullException(nameof(host));
_region = region ?? throw new ArgumentNullException(nameof(region));
_service = service ?? throw new ArgumentNullException(nameof(service));
_endpoint = $"https://{_host}";
_httpClient = new HttpClient();
}
/// <summary>
/// 发送API请求
/// </summary>
/// <param name="method">HTTP方法</param>
/// <param name="action">API动作</param>
/// <param name="version">API版本</param>
/// <param name="bodyParams">请求体参数</param>
/// <param name="extraQueryParams">额外的查询参数</param>
/// <returns>响应结果</returns>
public async Task<string> SendRequestAsync(string method = "POST",
string action = "CVProcess",
string version = "2022-08-31",
object bodyParams = null,
Dictionary<string, string> extraQueryParams = null)
{
// 准备查询参数
var queryParams = new Dictionary<string, string>
{
{"Action", action},
{"Version", version}
};
// 添加额外的查询参数
if (extraQueryParams != null)
{
foreach (var param in extraQueryParams)
{
queryParams[param.Key] = param.Value;
}
}
// 格式化查询参数
string formattedQuery = FormatQueryParameters(queryParams);
// 准备请求体
string formattedBody = bodyParams != null ? JsonConvert.SerializeObject(bodyParams) : "{}";
// 生成时间戳
DateTime utcNow = DateTime.UtcNow;
string currentDate = utcNow.ToString("yyyyMMddTHHmmssZ");
string dateStamp = utcNow.ToString("yyyyMMdd");
// 计算 payload hash
string payloadHash = ComputeSha256Hash(formattedBody);
// 构建规范请求
string canonicalUri = "/";
string contentType = "application/json";
string canonicalHeaders =
$"content-type:{contentType}\n" +
$"host:{_host}\n" +
$"x-content-sha256:{payloadHash}\n" +
$"x-date:{currentDate}\n";
string signedHeaders = "content-type;host;x-content-sha256;x-date";
string canonicalRequest = $"{method}\n{canonicalUri}\n{formattedQuery}\n{canonicalHeaders}\n{signedHeaders}\n{payloadHash}";
// 计算签名
string algorithm = "HMAC-SHA256";
string credentialScope = $"{dateStamp}/{_region}/{_service}/request";
string stringToSign = $"{algorithm}\n{currentDate}\n{credentialScope}\n{ComputeSha256Hash(canonicalRequest)}";
byte[] signingKey = GetSignatureKey(_secretKey, dateStamp, _region, _service);
string signature = ComputeHmacSha256(signingKey, stringToSign);
// 构建授权头
string authorizationHeader = $"{algorithm} Credential={_accessKey}/{credentialScope}, " +
$"SignedHeaders={signedHeaders}, " +
$"Signature={signature}";
// 准备请求头
var headers = new Dictionary<string, string>
{
{"X-Date", currentDate},
{"Authorization", authorizationHeader},
{"X-Content-Sha256", payloadHash},
{"Content-Type", contentType}
};
// 构建请求URL
string requestUrl = $"{_endpoint}?{formattedQuery}";
Console.WriteLine("\nBEGIN REQUEST++++++++++++++++++++++++++++++++++++");
Console.WriteLine($"Request URL = {requestUrl}");
// 发送请求
HttpResponseMessage response;
try
{
using (var request = new HttpRequestMessage(new HttpMethod(method), requestUrl))
{
// 添加请求头
foreach (var header in headers)
{
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
// 添加请求体
if (method.Equals("POST", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(formattedBody))
{
request.Content = new StringContent(formattedBody, new System.Net.Http.Headers.MediaTypeHeaderValue(contentType));
}
response = await _httpClient.SendAsync(request);
}
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine("\nRESPONSE++++++++++++++++++++++++++++++++++++");
Console.WriteLine($"Response code: {(int)response.StatusCode}");
Console.WriteLine($"Response body: {responseBody.Replace("\\u0026", "&")}\n");
return responseBody;
}
catch (Exception ex)
{
Console.WriteLine($"Error occurred: {ex.Message}");
throw;
}
}
/// <summary>
/// 格式化查询参数
/// </summary>
private string FormatQueryParameters(Dictionary<string, string> parameters)
{
if (parameters == null || parameters.Count == 0)
return "";
// 按键名排序并拼接
var sortedParams = parameters.OrderBy(p => p.Key);
return string.Join("&", sortedParams.Select(p => $"{p.Key}={p.Value}"));
}
/// <summary>
/// 计算SHA256哈希
/// </summary>
private string ComputeSha256Hash(string input)
{
// 创建SHA256实例
using (SHA256 sha256Hash = SHA256.Create())
{
// 将输入字符串转换为UTF8字节数组
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
// 计算哈希值
byte[] hashBytes = sha256Hash.ComputeHash(inputBytes);
// 将字节数组转换为十六进制字符串
StringBuilder builder = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
builder.Append(hashBytes[i].ToString("x2")); // "x2"确保每个字节用两位十六进制表示
}
return builder.ToString();
}
}
/// <summary>
/// 计算HMAC-SHA256
/// </summary>
private string ComputeHmacSha256(byte[] key, string input)
{
using (HMACSHA256 hmac = new HMACSHA256(key))
{
byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(input));
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
/// <summary>
/// 获取签名密钥
/// </summary>
private byte[] GetSignatureKey(string key, string dateStamp, string regionName, string serviceName)
{
byte[] kDate = ComputeHmacSha256Bytes(Encoding.UTF8.GetBytes(key), dateStamp);
byte[] kRegion = ComputeHmacSha256Bytes(kDate, regionName);
byte[] kService = ComputeHmacSha256Bytes(kRegion, serviceName);
return ComputeHmacSha256Bytes(kService, "request");
}
/// <summary>
/// 计算HMAC-SHA256字节数组
/// </summary>
private byte[] ComputeHmacSha256Bytes(byte[] key, string input)
{
using (HMACSHA256 hmac = new HMACSHA256(key))
{
return hmac.ComputeHash(Encoding.UTF8.GetBytes(input));
}
}
}
9. 调用代码
private static async Task Main(string[] args)
{
try
{
// 火山官网密钥信息, 注意sk结尾有==
string accessKey = "AKL...jI";
string secretKey = "WVR..==";
// 创建客户端实例
var client = new VolcengineApiClient(accessKey, secretKey);
// 准备请求参数
var bodyParams = new
{
req_key = "jimeng_high_aes_general_v21_L",
prompt = "stand in forest",
return_url = true
};
// 发送请求
string response = await client.SendRequestAsync(
action: "CVProcess",
version: "2022-08-31",
bodyParams: bodyParams
);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
if (ex.InnerException != null)
{
Console.WriteLine($"Inner Exception: {ex.InnerException.Message}");
}
}
}
10. 调用输出
至此,即梦API调用成功。
更多推荐
所有评论(0)