2025-11-16 19:33:01 +08:00

307 lines
12 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using CrazyStudio.Core.Common;
using CrazyStudio.Core.Common.Eitities;
using Microsoft.AspNetCore.DataProtection;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Web;
using XPrintServer.Api.Entities.Vol;
namespace XPrintServer.Api.Tools
{
public class RequestInfo
{
public string Url { get; set; } = "";
public string Version { get; set; } = "";
public string Action { get; set; } = "";
public string ServiceName { get; set; } = "";
public string Region { get; set; } = "";
public object Data { get; set; } = null!;
public string Method { get; set; } = "POST";
public Action<Exception> Fail { get; set; } = (error) => { };
}
public class HttpVolTool
{
/// <summary>
/// 不参与加签过程的 header key
/// </summary>
private static readonly HashSet<string> HEADER_KEYS_TO_IGNORE = new HashSet<string>
{
"authorization",
"content-type",
"content-length",
"user-agent",
"presigned-expires",
"expect"
};
///// <summary>
///// 测试Get请求
///// </summary>
//private void TestGet()
//{
// string temp = "HMAC-SHA256 Credential=AKLTYWViMTVmZGYzM2E0NDI5Mzk2MDZjNjFmMjc2MjRjMzg/20250329/cn-beijing/billing/request, SignedHeaders=host;x-date, Signature=1eda9e7e6b1728151a8e8791fdaf67cfbd28bd5c80d0fce2eb208746cf483105";
// string HTTPRequestMethod = "GET";
// var CanonicalQueryString = new Dictionary<string, string>
//{
// { "Version", "2022-01-01" },
// { "Action", "QueryBalanceAcct" }
//};
// var Headers = new Dictionary<string, string>
//{
// { "host", "billing.volcengineapi.com" },
// { "x-date", "20250329T180937Z" }
//};
// string RequestPayload = "";
// //地域
// string region = "cn-beijing";
// string result = Sign(HTTPRequestMethod, CanonicalQueryString, Headers, RequestPayload, AccessKeyID, SecretAccessKey, region);
// if (temp == result)
// {
// Debug.Log("Get ok");
// }
//}
///// <summary>
///// 测试Post请求
///// </summary>
//private void TestPost()
//{
// string temp = "HMAC-SHA256 Credential=AKLTYWViMTVmZGYzM2E0NDI5Mzk2MDZjNjFmMjc2MjRjMzg/20250329/cn-beijing/billing/request, SignedHeaders=host;x-date, Signature=5e8480ceea12d0000a23c054151c50dd02c1a7dec835004057d19f13d53a7658";
// string HTTPRequestMethod = "POST";
// var CanonicalQueryString = new Dictionary<string, string> { { "Version", "2022-01-01" }, { "Action", "ListBill" } };
// var Headers = new Dictionary<string, string> { { "host", "billing.volcengineapi.com" }, { "x-date", "20250329T180937Z" } };
// var obj = new { Limit = 10, BillPeriod = "2023-08" };
// string RequestPayload = JsonSerializer.Serialize(obj);
// //地域
// string region = "cn-beijing";
// string result = Sign(HTTPRequestMethod, CanonicalQueryString, Headers, RequestPayload, AccessKeyID, SecretAccessKey, region);
// if (temp == result)
// {
// Debug.WriteLine("Post ok");
// }
//}
/// <summary>
/// 火山引擎API调用
/// </summary>
public static async Task<T?> RequestVol<T>(RequestInfo requestInfo, VolInfos volInfos)
{
var canonicalQueryString = new Dictionary<string, string> { { "Version", requestInfo.Version }, { "Action", requestInfo.Action } };
var headers = new Dictionary<string, string> { { "host", requestInfo.Url.Replace("https://", "") }, { "x-date", GetDateTimeNow() } };
string requestPayload = JsonSerializer.Serialize(requestInfo.Data);
var authorization = Sign(requestInfo.Method, requestInfo.ServiceName, canonicalQueryString, headers, requestPayload, volInfos.AccessKeyId, volInfos.SecretAccessKey, requestInfo.Region);
var queryString = string.Join("&", canonicalQueryString
.Where(kv => kv.Value != null)
.Select(kv => $"{UriEscape(kv.Key)}={UriEscape(kv.Value)}"));
var client = new HttpClient();
var request = new HttpRequestMessage
{
Method = new HttpMethod(requestInfo.Method),
RequestUri = new Uri($"{requestInfo.Url}?{queryString}"),
Content = new StringContent(JsonSerializer.Serialize(requestInfo.Data),
Encoding.UTF8, "application/json")
};
foreach (var header in headers)
{
request.Headers.Add(header.Key, header.Value);
}
request.Headers.TryAddWithoutValidation("Authorization", authorization!);
try
{
var response = await client.SendAsync(request);
var result = await response.Content.ReadFromJsonAsync<T>();
return result;
}
catch (Exception ex)
{
requestInfo.Fail(ex);
}
return default;
}
/// <summary>
/// 获取签名
/// </summary>
/// <param name="Method"> HTTP 请求方法GET 或 POST</param>
/// <param name="QueryString">查询字符串</param>
/// <param name="Headers">请求头</param>
/// <param name="RequestPayload">请求体</param>
/// <param name="ak"> AccessKeyID </param>
/// <param name="sk"> SecretAccessKey </param>
/// <returns></returns>
public static string Sign(string Method,
string serviceName,
Dictionary<string, string> QueryString,
Dictionary<string, string> Headers,
string RequestPayload,
string ak, string sk, string Region)
{
string HTTPRequestMethod = Method;
string CanonicalURI = "/";
var sortedParams = QueryString.OrderBy(p => p.Key, StringComparer.Ordinal);
string CanonicalQueryString = string.Join("&", sortedParams.Select(p =>
$"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"));
var sortedHeaders = Headers.OrderBy(h => h.Key, StringComparer.OrdinalIgnoreCase);
string CanonicalHeaders = string.Join("\n", sortedHeaders.Select(h =>
$"{h.Key.ToLower()}:{h.Value.Trim()}"));
string SignedHeaders = string.Join(";", sortedHeaders.Select(h => h.Key.ToLower()));
string CanonicalRequest =
HTTPRequestMethod + '\n' +
CanonicalURI + '\n' +
CanonicalQueryString + '\n' +
CanonicalHeaders + '\n' +
'\n' +
SignedHeaders + '\n' +
HexEncode(Hash(RequestPayload));
string Algorithm = "HMAC-SHA256";
string RequestDate = Headers["x-date"];
string CredentialScope = $"{RequestDate.Substring(0, 8)}/{Region}/{serviceName}/request";
string StringToSign =
Algorithm + '\n' +
RequestDate + '\n' +
CredentialScope + '\n' +
HexEncode(Hash(CanonicalRequest));
string kSecret = sk;
string kDate = HmacSecretKey(kSecret, RequestDate.Substring(0, 8));
string kRegion = Hmac(kDate, Region);
string kService = Hmac(kRegion, serviceName);
string kSigning = Hmac(kService, "request");
string Signature = Hmac(kSigning, StringToSign);
string Credential = $"{ak}/{CredentialScope}";
string Authorization = $"{Algorithm} Credential={Credential}, SignedHeaders={SignedHeaders}, Signature={Signature}";
return Authorization;
}
// SHA256 哈希算法
private static byte[] Hash(string input)
{
using (System.Security.Cryptography.SHA256 sha256 = System.Security.Cryptography.SHA256.Create())
{
return sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
}
}
// 16 进制编码
private static string HexEncode(byte[] data)
{
return BitConverter.ToString(data).Replace("-", "").ToLower();
}
private static string HmacSecretKey(string key, string message)
{
byte[] keyBytes = Encoding.UTF8.GetBytes(key);
using System.Security.Cryptography.HMACSHA256 hmac = new System.Security.Cryptography.HMACSHA256(keyBytes);
byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return HexEncode(hashBytes);
}
//Hmac_SHA256 哈希算法
private static string Hmac(string key, string message)
{
// 将16进制字符串转换为字节数组
byte[] keyBytes = new byte[key.Length / 2];
for (int i = 0; i < keyBytes.Length; i++)
{
keyBytes[i] = Convert.ToByte(key.Substring(i * 2, 2), 16);
}
using System.Security.Cryptography.HMACSHA256 hmac = new System.Security.Cryptography.HMACSHA256(keyBytes);
byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return HexEncode(hashBytes);
}
private static string QueryParamsToString(Dictionary<string, string> paramsDict)
{
return string.Join("&", paramsDict
.OrderBy(kv => kv.Key)
.Select(kv =>
{
var val = kv.Value;
if (val == null) return null;
var escapedKey = UriEscape(kv.Key);
if (string.IsNullOrEmpty(escapedKey)) return null;
return $"{escapedKey}={UriEscape(val)}";
})
.Where(v => v != null));
}
private static (string signedHeaders, string canonicalHeaders) GetSignHeaders(
Dictionary<string, string> originHeaders, List<string> needSignHeaders)
{
string TrimHeaderValue(object header)
{
return header?.ToString()?.Trim()?.Replace(" ", " ") ?? "";
}
var h = originHeaders.Keys.ToList();
// 根据 needSignHeaders 过滤
if (needSignHeaders.Any())
{
var needSignSet = new HashSet<string>(
needSignHeaders.Concat(new[] { "x-date", "host" })
.Select(k => k.ToLower()));
h = h.Where(k => needSignSet.Contains(k.ToLower())).ToList();
}
// 根据 ignore headers 过滤
h = h.Where(k => !HEADER_KEYS_TO_IGNORE.Contains(k.ToLower())).ToList();
var signedHeaderKeys = string.Join(";",
h.Select(k => k.ToLower()).OrderBy(k => k));
var canonicalHeaders = string.Join("\n",
h.OrderBy(k => k.ToLower())
.Select(k => $"{k.ToLower()}:{TrimHeaderValue(originHeaders[k])}"));
return (signedHeaderKeys, canonicalHeaders);
}
private static string UriEscape(string str)
{
try
{
return HttpUtility.UrlEncode(str)?.Replace("*", "%2A")!;
}
catch
{
return "";
}
}
private static string GetDateTimeNow()
{
return DateTime.UtcNow.ToString("yyyyMMddTHHmmssZ");
}
}
}