307 lines
12 KiB
C#
307 lines
12 KiB
C#
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");
|
||
}
|
||
}
|
||
}
|