更新C#14扩展属性

This commit is contained in:
LanZhan
2026-01-20 20:04:19 +08:00
parent b06fa59dd6
commit 607e0ef4cf
14 changed files with 622 additions and 950 deletions

3
.csharpierignore Normal file
View File

@@ -0,0 +1,3 @@
App.xaml.cs
**/*.xaml
**/*.csproj

View File

@@ -6,23 +6,17 @@ public static class ResourceExtensions
{
private static readonly ResourceLoader _resourceLoader = new();
public static string GetLocalized(this string resourceKey) =>
_resourceLoader.GetString(resourceKey);
extension(string resourceKey)
{
public string GetLocalized() => _resourceLoader.GetString(resourceKey);
public static string GetLocalizedWithReplace(
this string resourceKey,
string placeholder,
string value
)
public string GetLocalizedWithReplace(string placeholder, string value)
{
var template = _resourceLoader.GetString(resourceKey);
return template.Replace(placeholder, value);
}
public static string GetLocalizedWithReplace(
this string resourceKey,
IDictionary<string, string> replacements
)
public string GetLocalizedWithReplace(IDictionary<string, string> replacements)
{
var template = _resourceLoader.GetString(resourceKey);
foreach (var (placeholder, value) in replacements)
@@ -32,3 +26,4 @@ public static class ResourceExtensions
return template;
}
}
}

View File

@@ -7,23 +7,17 @@ public static class SettingsStorageExtensions
{
private const string FileExtension = ".json";
public static bool IsRoamingStorageAvailable(this ApplicationData appData)
extension(ApplicationData appData)
{
public bool IsRoamingStorageAvailable()
{
return appData.RoamingStorageQuota == 0;
}
public static async Task SaveAsync<T>(this StorageFolder folder, string name, T content)
{
var file = await folder.CreateFileAsync(
GetFileName(name),
CreationCollisionOption.ReplaceExisting
);
var fileContent = await Json.StringifyAsync(content!);
await FileIO.WriteTextAsync(file, fileContent);
}
public static async Task<T?> ReadAsync<T>(this StorageFolder folder, string name)
extension(StorageFolder folder)
{
public async Task<T?> ReadAsync<T>(string name)
{
if (!File.Exists(Path.Combine(folder.Path, GetFileName(name))))
{
@@ -36,34 +30,32 @@ public static class SettingsStorageExtensions
return await Json.ToObjectAsync<T>(fileContent);
}
public static async Task SaveAsync<T>(
this ApplicationDataContainer settings,
string key,
T value
)
public async Task SaveAsync<T>(string name, T content)
{
settings.SaveString(key, await Json.StringifyAsync(value!));
var file = await folder.CreateFileAsync(
GetFileName(name),
CreationCollisionOption.ReplaceExisting
);
var fileContent = await Json.StringifyAsync(content!);
await FileIO.WriteTextAsync(file, fileContent);
}
public static void SaveString(this ApplicationDataContainer settings, string key, string value)
public async Task<byte[]?> ReadFileAsync(string fileName)
{
settings.Values[key] = value;
var item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
if ((item is not null) && item.IsOfType(StorageItemTypes.File))
{
var storageFile = await folder.GetFileAsync(fileName);
var content = await storageFile.ReadBytesAsync();
return content;
}
public static async Task<T?> ReadAsync<T>(this ApplicationDataContainer settings, string key)
{
object? obj;
if (settings.Values.TryGetValue(key, out obj))
{
return await Json.ToObjectAsync<T>((string)obj);
return null;
}
return default;
}
public static async Task<StorageFile> SaveFileAsync(
this StorageFolder folder,
public async Task<StorageFile> SaveFileAsync(
byte[] content,
string fileName,
CreationCollisionOption options = CreationCollisionOption.ReplaceExisting
@@ -83,22 +75,11 @@ public static class SettingsStorageExtensions
await FileIO.WriteBytesAsync(storageFile, content);
return storageFile;
}
public static async Task<byte[]?> ReadFileAsync(this StorageFolder folder, string fileName)
{
var item = await folder.TryGetItemAsync(fileName).AsTask().ConfigureAwait(false);
if ((item is not null) && item.IsOfType(StorageItemTypes.File))
{
var storageFile = await folder.GetFileAsync(fileName);
var content = await storageFile.ReadBytesAsync();
return content;
}
return null;
}
public static async Task<byte[]?> ReadBytesAsync(this StorageFile file)
extension(StorageFile file)
{
public async Task<byte[]?> ReadBytesAsync()
{
if (file is not null)
{
@@ -112,6 +93,32 @@ public static class SettingsStorageExtensions
return null;
}
}
extension(ApplicationDataContainer settings)
{
public async Task<T?> ReadAsync<T>(string key)
{
object? obj;
if (settings.Values.TryGetValue(key, out obj))
{
return await Json.ToObjectAsync<T>((string)obj);
}
return default;
}
public async Task SaveAsync<T>(string key, T value)
{
settings.SaveString(key, await Json.StringifyAsync(value!));
}
public void SaveString(string key, string value)
{
settings.Values[key] = value;
}
}
private static string GetFileName(string name)
{

View File

@@ -1,17 +1,17 @@
#pragma warning disable
using System.Text;
namespace UntamedMusicPlayer.OnlineAPIs.CloudMusicAPI.Extensions;
internal static class ExceptionExtensions
{
extension(Exception exception)
{
/// <summary>
/// 获取最内层异常
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
public static Exception GetInmostException(this Exception exception)
public Exception GetInmostException()
{
ArgumentNullException.ThrowIfNull(exception);
@@ -25,7 +25,7 @@ internal static class ExceptionExtensions
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
public static string ToFullString(this Exception exception)
public string ToFullString()
{
ArgumentNullException.ThrowIfNull(exception);
@@ -33,6 +33,7 @@ internal static class ExceptionExtensions
DumpException(exception, sb);
return sb.ToString();
}
}
private static void DumpException(Exception exception, StringBuilder sb)
{

View File

@@ -1,5 +1,3 @@
#pragma warning disable
using System.Net.Http.Headers;
using System.Text;
@@ -7,36 +5,33 @@ namespace UntamedMusicPlayer.OnlineAPIs.CloudMusicAPI.Extensions;
internal static class HttpClientExtensions
{
public static Task<HttpResponseMessage> SendAsync(
this HttpClient client,
HttpMethod method,
string url
) => client.SendAsync(method, url, null, null);
extension(HttpClient client)
{
public Task<HttpResponseMessage> SendAsync(HttpMethod method, string? url) =>
client.SendAsync(method, url, null, null);
public static Task<HttpResponseMessage> SendAsync(
this HttpClient client,
public Task<HttpResponseMessage> SendAsync(
HttpMethod method,
string url,
IEnumerable<KeyValuePair<string, string>> queries,
IEnumerable<KeyValuePair<string, string>> headers
string? url,
IEnumerable<KeyValuePair<string, string>>? queries,
IEnumerable<KeyValuePair<string, string>>? headers
) =>
client.SendAsync(
method,
url,
queries,
headers,
(byte[])null,
(byte[]?)null,
"application/x-www-form-urlencoded"
);
public static Task<HttpResponseMessage> SendAsync(
this HttpClient client,
public Task<HttpResponseMessage> SendAsync(
HttpMethod method,
string url,
IEnumerable<KeyValuePair<string, string>> queries,
IEnumerable<KeyValuePair<string, string>> headers,
string content,
string contentType
string? url,
IEnumerable<KeyValuePair<string, string>>? queries,
IEnumerable<KeyValuePair<string, string>>? headers,
string? content,
string? contentType
) =>
client.SendAsync(
method,
@@ -47,18 +42,16 @@ internal static class HttpClientExtensions
contentType
);
public static Task<HttpResponseMessage> SendAsync(
this HttpClient client,
public Task<HttpResponseMessage> SendAsync(
HttpMethod method,
string url,
IEnumerable<KeyValuePair<string, string>> queries,
IEnumerable<KeyValuePair<string, string>> headers,
byte[] content,
string contentType
string? url,
IEnumerable<KeyValuePair<string, string>>? queries,
IEnumerable<KeyValuePair<string, string>>? headers,
byte[]? content,
string? contentType
)
{
ArgumentNullException.ThrowIfNull(client);
ArgumentNullException.ThrowIfNull(method);
if (string.IsNullOrEmpty(url))
@@ -71,15 +64,12 @@ internal static class HttpClientExtensions
throw new ArgumentNullException(nameof(contentType));
}
UriBuilder uriBuilder;
var uriBuilder = new UriBuilder(url);
HttpRequestMessage request;
uriBuilder = new UriBuilder(url);
if (queries is not null)
{
string query;
query = queries.ToQueryString();
var query = queries.ToQueryString();
if (!string.IsNullOrEmpty(query))
{
if (string.IsNullOrEmpty(uriBuilder.Query))
@@ -102,10 +92,7 @@ internal static class HttpClientExtensions
request.Content = new FormUrlEncodedContent(queries);
}
if (request.Content is not null)
{
request.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
}
request.Content?.Headers.ContentType = new MediaTypeHeaderValue(contentType);
if (headers is not null)
{
@@ -118,3 +105,4 @@ internal static class HttpClientExtensions
return client.SendAsync(request);
}
}
}

View File

@@ -2,13 +2,18 @@ namespace UntamedMusicPlayer.OnlineAPIs.CloudMusicAPI.Extensions;
internal static class HttpExtensions
{
public static string ToQueryString(this IEnumerable<KeyValuePair<string, string>> queries)
extension(IEnumerable<KeyValuePair<string, string>> queries)
{
public string ToQueryString()
{
ArgumentNullException.ThrowIfNull(queries);
return string.Join(
"&",
queries.Select(t => Uri.EscapeDataString(t.Key) + "=" + Uri.EscapeDataString(t.Value))
queries.Select(t =>
Uri.EscapeDataString(t.Key) + "=" + Uri.EscapeDataString(t.Value)
)
);
}
}
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable
using System.Net;
using System.Text;
using System.Text.Json.Nodes;
@@ -75,22 +74,21 @@ public sealed partial class NeteaseCloudMusicApi : IDisposable
)
{
ArgumentNullException.ThrowIfNull(provider);
ArgumentNullException.ThrowIfNull(queries);
if (provider == CloudMusicApiProviders.CheckMusic)
{
return HandleCheckMusicAsync(queries);
}
else if (provider == CloudMusicApiProviders.Login)
if (provider == CloudMusicApiProviders.Login)
{
return HandleLoginAsync(queries);
}
else if (provider == CloudMusicApiProviders.LoginStatus)
if (provider == CloudMusicApiProviders.LoginStatus)
{
return HandleLoginStatusAsync();
}
else if (provider == CloudMusicApiProviders.RelatedPlaylist)
if (provider == CloudMusicApiProviders.RelatedPlaylist)
{
return HandleRelatedPlaylistAsync(queries);
}
@@ -111,17 +109,11 @@ public sealed partial class NeteaseCloudMusicApi : IDisposable
)
{
ArgumentNullException.ThrowIfNull(method);
ArgumentNullException.ThrowIfNull(url);
ArgumentNullException.ThrowIfNull(data);
ArgumentNullException.ThrowIfNull(options);
bool isOk;
JsonObject json;
(isOk, json) = await Request.CreateRequest(_client, method, url, data, options);
var (isOk, json) = await Request.CreateRequest(_client, method, url, data, options);
json = (JsonObject)json["body"];
if (!isOk && (int?)json["code"] == 301)
{
@@ -131,16 +123,13 @@ public sealed partial class NeteaseCloudMusicApi : IDisposable
return (isOk, json);
}
private async Task<(bool, JsonObject)> HandleCheckMusicAsync(Dictionary<string, string> queries)
private async Task<(bool, JsonObject?)> HandleCheckMusicAsync(
Dictionary<string, string> queries
)
{
NeteaseCloudMusicApiProvider provider;
bool isOk;
JsonObject json;
JsonObject result;
bool playable;
var provider = CloudMusicApiProviders.CheckMusic;
provider = CloudMusicApiProviders.CheckMusic;
(isOk, json) = await RequestAsync(
var (isOk, json) = await RequestAsync(
provider.Method,
provider.Url(queries),
provider.Data(queries),
@@ -151,12 +140,12 @@ public sealed partial class NeteaseCloudMusicApi : IDisposable
return (false, null);
}
playable =
var playable =
(int?)json["code"] == 200
&& json["data"] is JsonArray dataArray
&& dataArray.Count > 0
&& (int?)dataArray[0]?["code"] == 200;
result = new JsonObject
var result = new JsonObject
{
{ "success", playable },
{ "message", playable ? "ok" : "亲爱的,暂无版权" },
@@ -164,14 +153,11 @@ public sealed partial class NeteaseCloudMusicApi : IDisposable
return (true, result);
}
private async Task<(bool, JsonObject)> HandleLoginAsync(Dictionary<string, string> queries)
private async Task<(bool, JsonObject?)> HandleLoginAsync(Dictionary<string, string> queries)
{
NeteaseCloudMusicApiProvider provider;
bool isOk;
JsonObject json;
var provider = CloudMusicApiProviders.Login;
provider = CloudMusicApiProviders.Login;
(isOk, json) = await RequestAsync(
var (isOk, json) = await RequestAsync(
provider.Method,
provider.Url(queries),
provider.Data(queries),
@@ -197,27 +183,21 @@ public sealed partial class NeteaseCloudMusicApi : IDisposable
private async Task<(bool, JsonObject)> HandleLoginStatusAsync()
{
HttpResponseMessage response;
response = null;
HttpResponseMessage? response = null;
try
{
const string GUSER = "GUser=";
const string GBINDS = "GBinds=";
string s;
int index;
JsonObject json;
response = await _client.GetAsync("https://music.163.com");
s = Encoding.UTF8.GetString(await response.Content.ReadAsByteArrayAsync());
index = s.IndexOf(GUSER, StringComparison.Ordinal);
var s = Encoding.UTF8.GetString(await response.Content.ReadAsByteArrayAsync());
var index = s.IndexOf(GUSER, StringComparison.Ordinal);
if (index == -1)
{
goto errorExit;
}
json = new JsonObject { { "code", 200 } };
var json = new JsonObject { { "code", 200 } };
var profileJson = JsonNode.Parse(s[(index + GUSER.Length)..]);
if (profileJson is not null)
{
@@ -254,24 +234,18 @@ public sealed partial class NeteaseCloudMusicApi : IDisposable
Dictionary<string, string> queries
)
{
HttpResponseMessage response;
response = null;
HttpResponseMessage? response = null;
try
{
string s;
MatchCollection matchs;
JsonArray playlists;
response = await _client.SendAsync(
HttpMethod.Get,
"https://music.163.com/playlist",
new QueryCollection { { "id", queries["id"] } },
new QueryCollection { { "User-Agent", Request.ChooseUserAgent("pc") } }
);
s = Encoding.UTF8.GetString(await response.Content.ReadAsByteArrayAsync());
matchs = MyRegex().Matches(s);
playlists = new JsonArray();
var s = Encoding.UTF8.GetString(await response.Content.ReadAsByteArrayAsync());
var matchs = MyRegex().Matches(s);
var playlists = new JsonArray();
matchs
.Cast<Match>()
.Select(match => new JsonObject

View File

@@ -1,6 +1,6 @@
#pragma warning disable
using System.Net;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.RegularExpressions;
using UntamedMusicPlayer.OnlineAPIs.CloudMusicAPI.Utils;
@@ -83,14 +83,13 @@ public sealed class NeteaseCloudMusicApiProvider
private IEnumerable<KeyValuePair<string, string>> GetData(Dictionary<string, string> queries)
{
QueryCollection data;
var data = new QueryCollection();
if (_parameterInfos.Length == 0)
{
return _emptyData;
}
data = [];
foreach (var parameterInfo in _parameterInfos)
{
switch (parameterInfo.Type)
@@ -1903,7 +1902,10 @@ public static partial class CloudMusicApiProviders
],
BuildOptions(
"linuxapi",
[new("os", "pc"), new("_ntes_nuid", new Random().RandomBytes(16).ToHexStringLower())]
[
new("os", "pc"),
new("_ntes_nuid", RandomNumberGenerator.GetBytes(16).ToHexStringLower()),
]
)
);
@@ -2321,10 +2323,10 @@ public static partial class CloudMusicApiProviders
options = new Options
{
crypto = crypto,
cookie = cookieCollection,
ua = ua,
url = url,
Crypto = crypto,
Cookie = cookieCollection,
UA = ua,
Url = url,
};
return options;
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
@@ -16,18 +15,14 @@ internal static class Crypto
private const string publicKey =
"-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----";
private static readonly byte[] eapiKey = Encoding.ASCII.GetBytes("e82ckenh8dichen8");
private static RSAParameters? _cachedPublicKey;
public static Dictionary<string, string> WEApi(object @object)
public static Dictionary<string, string> WEApi(object obj)
{
string text;
byte[] secretKey;
text = JsonSerializer.Serialize(@object);
secretKey = new Random().RandomBytes(16);
var text = JsonSerializer.Serialize(obj);
var secretKey = RandomNumberGenerator.GetBytes(16);
secretKey = [.. secretKey.Select(n => (byte)base62[n % 62])];
return new Dictionary<string, string>
return new()
{
{
"params",
@@ -41,22 +36,14 @@ internal static class Crypto
)
.ToBase64String()
},
{
"encSecKey",
RsaEncrypt(
[.. secretKey.AsEnumerable().Reverse()] /*, publicKey*/
)
.ToHexStringLower()
},
{ "encSecKey", RsaEncrypt([.. secretKey.Reverse()]).ToHexStringLower() },
};
}
public static Dictionary<string, string> LinuxApi(object @object)
public static Dictionary<string, string> LinuxApi(object obj)
{
string text;
text = JsonSerializer.Serialize(@object);
return new Dictionary<string, string>
var text = JsonSerializer.Serialize(obj);
return new()
{
{
"eparams",
@@ -66,18 +53,13 @@ internal static class Crypto
};
}
public static Dictionary<string, string> EApi(string url, object @object)
public static Dictionary<string, string> EApi(string url, object obj)
{
string text;
string message;
string digest;
string data;
text = JsonSerializer.Serialize(@object);
message = $"nobody{url}use{text}md5forencrypt";
digest = message.ToByteArrayUtf8().ComputeMd5().ToHexStringLower();
data = $"{url}-36cd479b6b5-{text}-36cd479b6b5-{digest}";
return new Dictionary<string, string>
var text = JsonSerializer.Serialize(obj);
var message = $"nobody{url}use{text}md5forencrypt";
var digest = message.ToByteArrayUtf8().ComputeMd5().ToHexStringLower();
var data = $"{url}-36cd479b6b5-{text}-36cd479b6b5-{digest}";
return new()
{
{
"params",
@@ -89,45 +71,40 @@ internal static class Crypto
public static byte[] Decrypt(byte[] cipherBuffer) =>
AesDecrypt(cipherBuffer, CipherMode.ECB, eapiKey, null);
private static byte[] AesEncrypt(byte[] buffer, CipherMode mode, byte[] key, byte[] iv)
private static byte[] AesEncrypt(byte[] buffer, CipherMode mode, byte[] key, byte[]? iv)
{
using var aes = Aes.Create();
aes.BlockSize = 128;
aes.Key = key;
if (iv is not null)
return mode switch
{
aes.IV = iv;
CipherMode.CBC => aes.EncryptCbc(buffer, iv!, PaddingMode.PKCS7),
CipherMode.ECB => aes.EncryptEcb(buffer, PaddingMode.PKCS7),
_ => throw new NotSupportedException($"Mode {mode} is not supported."),
};
}
aes.Mode = mode;
using var cryptoTransform = aes.CreateEncryptor();
return cryptoTransform.TransformFinalBlock(buffer, 0, buffer.Length);
}
private static byte[] AesDecrypt(byte[] buffer, CipherMode mode, byte[] key, byte[] iv)
private static byte[] AesDecrypt(byte[] buffer, CipherMode mode, byte[] key, byte[]? iv)
{
using var aes = Aes.Create();
aes.BlockSize = 128;
aes.Key = key;
if (iv is not null)
return mode switch
{
aes.IV = iv;
CipherMode.CBC => aes.DecryptCbc(buffer, iv!, PaddingMode.PKCS7),
CipherMode.ECB => aes.DecryptEcb(buffer, PaddingMode.PKCS7),
_ => throw new NotSupportedException($"Mode {mode} is not supported."),
};
}
aes.Mode = mode;
using var cryptoTransform = aes.CreateDecryptor();
return cryptoTransform.TransformFinalBlock(buffer, 0, buffer.Length);
private static byte[] RsaEncrypt(byte[] buffer)
{
if (_cachedPublicKey == null)
{
using var rsa = RSA.Create();
rsa.ImportFromPem(publicKey);
_cachedPublicKey = rsa.ExportParameters(false);
}
private static byte[] RsaEncrypt(
byte[] buffer /*, string key*/
)
{
RSAParameters rsaParameters;
_cachedPublicKey ??= ParsePublicKey(publicKey);
rsaParameters = _cachedPublicKey.Value;
var rsaParameters = _cachedPublicKey.Value;
return BigInteger
.ModPow(
new BigInteger(buffer, true, true),
@@ -136,128 +113,4 @@ internal static class Crypto
)
.ToByteArray(true, true);
}
private static RSAParameters ParsePublicKey(string _publicKey)
{
_publicKey = _publicKey.Replace("\n", string.Empty);
_publicKey = _publicKey[26..^24];
using var _stream = new MemoryStream(Convert.FromBase64String(_publicKey));
using var _reader = new BinaryReader(_stream);
ushort _i16;
byte[] _oid;
byte _i8;
byte _low;
byte _high;
int _modulusLength;
byte[] _modulus;
int _exponentLength;
byte[] _exponent;
_i16 = _reader.ReadUInt16();
if (_i16 == 0x8130)
{
_reader.ReadByte();
}
else if (_i16 == 0x8230)
{
_reader.ReadInt16();
}
else
{
throw new ArgumentException(null, nameof(_publicKey));
}
_oid = _reader.ReadBytes(15);
if (
!_oid.SequenceEqual(
new byte[]
{
0x30,
0x0D,
0x06,
0x09,
0x2A,
0x86,
0x48,
0x86,
0xF7,
0x0D,
0x01,
0x01,
0x01,
0x05,
0x00,
}
)
)
{
throw new ArgumentException(null, nameof(_publicKey));
}
_i16 = _reader.ReadUInt16();
if (_i16 == 0x8103)
{
_reader.ReadByte();
}
else if (_i16 == 0x8203)
{
_reader.ReadInt16();
}
else
{
throw new ArgumentException(null, nameof(_publicKey));
}
_i8 = _reader.ReadByte();
if (_i8 != 0x00)
{
throw new ArgumentException(null, nameof(_publicKey));
}
_i16 = _reader.ReadUInt16();
if (_i16 == 0x8130)
{
_reader.ReadByte();
}
else if (_i16 == 0x8230)
{
_reader.ReadInt16();
}
else
{
throw new ArgumentException(null, nameof(_publicKey));
}
_i16 = _reader.ReadUInt16();
if (_i16 == 0x8102)
{
_high = 0;
_low = _reader.ReadByte();
}
else if (_i16 == 0x8202)
{
_high = _reader.ReadByte();
_low = _reader.ReadByte();
}
else
{
throw new ArgumentException(null, nameof(_publicKey));
}
_modulusLength = BitConverter.ToInt32([_low, _high, 0x00, 0x00], 0);
if (_reader.PeekChar() == 0x00)
{
_reader.ReadByte();
_modulusLength -= 1;
}
_modulus = _reader.ReadBytes(_modulusLength);
if (_reader.ReadByte() != 0x02)
{
throw new ArgumentException(null, nameof(_publicKey));
}
_exponentLength = _reader.ReadByte();
_exponent = _reader.ReadBytes(_exponentLength);
return new RSAParameters { Modulus = _modulus, Exponent = _exponent };
}
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable
using System.Security.Cryptography;
using System.Text;
@@ -7,36 +6,30 @@ namespace UntamedMusicPlayer.OnlineAPIs.CloudMusicAPI.Utils;
internal static class Extensions
{
private static readonly MD5 _md5 = MD5.Create();
public static byte[] ToByteArrayUtf8(this string value) => Encoding.UTF8.GetBytes(value);
public static string ToHexStringLower(this byte[] value)
extension(string value)
{
return Convert.ToHexString(value).ToLowerInvariant();
public byte[] ToByteArrayUtf8() => Encoding.UTF8.GetBytes(value);
}
public static string ToHexStringUpper(this byte[] value)
extension(byte[] value)
{
return Convert.ToHexString(value);
public string ToHexStringLower() => Convert.ToHexString(value).ToLowerInvariant();
public string ToHexStringUpper() => Convert.ToHexString(value);
public string ToBase64String() => Convert.ToBase64String(value);
public byte[] ComputeMd5() => MD5.HashData(value);
}
public static string ToBase64String(this byte[] value) => Convert.ToBase64String(value);
public static byte[] ComputeMd5(this byte[] value) => _md5.ComputeHash(value);
public static byte[] RandomBytes(this Random random, int length)
extension(Random random)
{
byte[] buffer;
buffer = new byte[length];
random.NextBytes(buffer);
return buffer;
public byte[] RandomBytes(int length) => RandomNumberGenerator.GetBytes(length);
}
public static TValue GetValueOrDefault<TKey, TValue>(
this Dictionary<TKey, TValue> dictionary,
TKey key,
TValue defaultValue
) => dictionary.TryGetValue(key, out var value) ? value : defaultValue;
extension<TKey, TValue>(Dictionary<TKey, TValue> dictionary)
{
public TValue GetValueOrDefault(TKey key, TValue defaultValue) =>
dictionary.TryGetValue(key, out var value) ? value : defaultValue;
}
}

View File

@@ -1,13 +1,11 @@
#pragma warning disable
using System.Net;
namespace UntamedMusicPlayer.OnlineAPIs.CloudMusicAPI.Utils;
internal sealed class Options
{
public string crypto;
public CookieCollection cookie;
public string ua;
public string url;
public string Crypto { get; set; } = null!;
public CookieCollection Cookie { get; set; } = null!;
public string UA { get; set; } = null!;
public string Url { get; set; } = null!;
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable
using System.IO.Compression;
using System.Net;
using System.Text;
@@ -14,31 +13,31 @@ internal static partial class Request
{
private static readonly string[] userAgentList =
[
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6.1 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_7_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 EdgiOS/134.3124.77 Mobile/15E148 Safari/605.1.15",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/134.0.6998.99 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Linux; Android 16; AGT-AN00; HMSCore 6.14.0.309; GMSCore 25.45.34) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.196 HuaweiBrowser/16.0.9.303 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 16; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.135 Mobile Safari/537.36 EdgA/134.0.3124.68",
"Mozilla/5.0 (Linux; Android 16; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.135 Mobile Safari/537.36 EdgA/134.0.3124.68",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Mobile/14F89;GameHelper",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (iPad; CPU OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:130.0) Gecko/20100101 Firefox/130.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Edg/139.0.0.0",
"Mozilla/5.0 (iPad; CPU OS 17_7_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.7; rv:136.0) Gecko/20100101 Firefox/136.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0",
];
public static string ChooseUserAgent(string ua)
public static string ChooseUserAgent(string UA)
{
return ua switch
return UA switch
{
"mobile" => userAgentList[(int)Math.Floor(new Random().NextDouble() * 7)],
"pc" => userAgentList[(int)Math.Floor(new Random().NextDouble() * 5) + 8],
_ => string.IsNullOrEmpty(ua)
? userAgentList[(int)Math.Floor(new Random().NextDouble() * userAgentList.Length)]
: ua,
"mobile" => userAgentList[Random.Shared.Next(8)],
"pc" => userAgentList[Random.Shared.Next(6) + 8],
_ => string.IsNullOrEmpty(UA)
? userAgentList[Random.Shared.Next(userAgentList.Length)]
: UA,
};
}
@@ -51,27 +50,18 @@ internal static partial class Request
)
{
ArgumentNullException.ThrowIfNull(client);
ArgumentNullException.ThrowIfNull(method);
ArgumentNullException.ThrowIfNull(url);
ArgumentNullException.ThrowIfNull(data_);
ArgumentNullException.ThrowIfNull(options);
Dictionary<string, string> headers;
Dictionary<string, string> data;
JsonObject answer;
HttpResponseMessage response;
headers = new Dictionary<string, string>
var headers = new Dictionary<string, string>
{
["User-Agent"] = ChooseUserAgent(options.ua),
["User-Agent"] = ChooseUserAgent(options.UA),
["Cookie"] = string.Join(
"; ",
options
.cookie.Cast<Cookie>()
.Cookie.Cast<Cookie>()
.Select(t => Uri.EscapeDataString(t.Name) + "=" + Uri.EscapeDataString(t.Value))
),
};
@@ -85,17 +75,17 @@ internal static partial class Request
headers["Referer"] = "https://music.163.com";
}
data = [];
var data = new Dictionary<string, string>();
foreach (var item in data_)
{
data.Add(item.Key, item.Value);
}
switch (options.crypto)
switch (options.Crypto)
{
case "weapi":
{
data["csrf_token"] = options.cookie["__csrf"]?.Value ?? string.Empty;
data["csrf_token"] = options.Cookie["__csrf"]?.Value ?? string.Empty;
data = Crypto.WEApi(data);
url = MyRegex1().Replace(url, "weapi");
break;
@@ -111,7 +101,7 @@ internal static partial class Request
}
);
headers["User-Agent"] =
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36";
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36";
url = "https://music.163.com/api/linux/forward";
break;
}
@@ -122,7 +112,7 @@ internal static partial class Request
Dictionary<string, string> header;
cookie = [];
foreach (Cookie item in options.cookie)
foreach (Cookie item in options.Cookie)
{
cookie.Add(new Cookie(item.Name, item.Value));
}
@@ -135,14 +125,17 @@ internal static partial class Request
{ "appver", cookie["appver"]?.Value ?? "6.1.1" }, // app版本
{ "versioncode", cookie["versioncode"]?.Value ?? "140" }, // 版本号
{ "mobilename", cookie["mobilename"]?.Value ?? string.Empty }, // 设备model
{ "buildver", cookie["buildver"]?.Value ?? $"{GetCurrentTotalSeconds()}" },
{
"buildver",
cookie["buildver"]?.Value ?? $"{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}"
},
{ "resolution", cookie["resolution"]?.Value ?? "1920x1080" }, // 设备分辨率
{ "__csrf", csrfToken },
{ "os", cookie["os"]?.Value ?? "android" },
{ "channel", cookie["channel"]?.Value ?? string.Empty },
{
"requestId",
$"{GetCurrentTotalMilliseconds()}_{$"{Math.Floor(new Random().NextDouble() * 1000)}".PadLeft(4, '0')}"
$"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}_{Random.Shared.Next(1000):D4}"
},
};
if (cookie["MUSIC_U"] is not null)
@@ -162,21 +155,21 @@ internal static partial class Request
)
);
data["header"] = JsonSerializer.Serialize(header);
data = Crypto.EApi(options.url, data);
data = Crypto.EApi(options.Url, data);
url = MyRegex1().Replace(url, "eapi");
break;
}
}
answer = new JsonObject
var answer = new JsonObject
{
{ "status", 500 },
{ "body", null },
{ "cookie", null },
};
response = null;
HttpResponseMessage? response = null;
try
{
IEnumerable<string> temp1;
JsonValue temp2;
int temp3;
@@ -193,7 +186,7 @@ internal static partial class Request
throw new HttpRequestException();
}
if (!response.Headers.TryGetValues("set-cookie", out temp1))
if (!response.Headers.TryGetValues("set-cookie", out var temp1))
{
temp1 = [];
}
@@ -205,28 +198,23 @@ internal static partial class Request
.ToList()
.ForEach(x => cookieArray.Add(x));
answer["cookie"] = cookieArray;
if (options.crypto == "eapi")
if (options.Crypto == "eapi")
{
DeflateStream stream;
byte[] buffer;
stream = null;
try
{
stream = new DeflateStream(
using var stream = new DeflateStream(
await response.Content.ReadAsStreamAsync(),
CompressionMode.Decompress
);
buffer = ReadStream(stream);
using var ms = new MemoryStream();
stream.CopyTo(ms);
buffer = ms.ToArray();
}
catch
{
buffer = await response.Content.ReadAsByteArrayAsync();
}
finally
{
stream?.Dispose();
}
try
{
answer["body"] = JsonObject.Parse(
@@ -266,53 +254,6 @@ internal static partial class Request
{
response?.Dispose();
}
ulong GetCurrentTotalSeconds()
{
TimeSpan _timeSpan;
_timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1);
return (ulong)_timeSpan.TotalSeconds;
}
ulong GetCurrentTotalMilliseconds()
{
TimeSpan _timeSpan;
_timeSpan = DateTime.UtcNow - new DateTime(1970, 1, 1);
return (ulong)_timeSpan.TotalMilliseconds;
}
byte[] ReadStream(Stream _stream)
{
byte[] _buffer;
List<byte> _byteList;
_buffer = new byte[0x1000];
_byteList = [];
for (var i = 0; i < int.MaxValue; i++)
{
int count;
count = _stream.Read(_buffer, 0, _buffer.Length);
if (count == 0x1000)
{
_byteList.AddRange(_buffer);
}
else if (count == 0)
{
return [.. _byteList];
}
else
{
for (var j = 0; j < count; j++)
{
_byteList.Add(_buffer[j]);
}
}
}
throw new OutOfMemoryException();
}
}
[GeneratedRegex(@"\w*api", RegexOptions.Compiled)]

View File

@@ -2,6 +2,7 @@ using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.Extensions.Logging;
using Microsoft.Graphics.Canvas;
using UntamedMusicPlayer.Contracts.Services;
using Windows.Foundation;
using Windows.Graphics;
using Windows.Storage.Streams;
using Windows.UI;
@@ -145,12 +146,7 @@ public sealed class ColorExtractionService : IColorExtractionService
{
using var renderTarget = new CanvasRenderTarget(device, newSize.Width, newSize.Height, 96);
using var session = renderTarget.CreateDrawingSession();
session.DrawImage(
original,
new Windows.Foundation.Rect(0, 0, newSize.Width, newSize.Height)
);
session.DrawImage(original, new Rect(0, 0, newSize.Width, newSize.Height));
return CanvasBitmap.CreateFromDirect3D11Surface(device, renderTarget);
}
@@ -252,7 +248,10 @@ public sealed class ColorExtractionService : IColorExtractionService
foreach (var child in node.Children)
{
child?.Let(c => CollectColors(c, colorStats));
if (child is not null)
{
CollectColors(child, colorStats);
}
}
}
@@ -352,11 +351,3 @@ internal class OctreeNode
/// 渐变配置
/// </summary>
public sealed record GradientConfig(List<Color> Colors, double Angle);
/// <summary>
/// 扩展方法
/// </summary>
internal static class Extensions
{
public static void Let<T>(this T obj, Action<T> action) => action(obj);
}

View File

@@ -10,23 +10,21 @@ namespace UntamedMusicPlayer.Services;
/// 使用ZLogger的零分配特性和结构化日志
/// </summary>
public static class ZLoggerExtensions
{
extension(ILogger logger)
{
// 应用程序生命周期日志
public static void ApplicationStarting(this ILogger logger) =>
logger.ZLogInformation($"应用程序正在启动");
public void ApplicationStarting() => logger.ZLogInformation($"应用程序正在启动");
public static void ApplicationStarted(this ILogger logger) =>
logger.ZLogInformation($"应用程序启动完成");
public void ApplicationStarted() => logger.ZLogInformation($"应用程序启动完成");
public static void ApplicationShuttingDown(this ILogger logger) =>
logger.ZLogInformation($"应用程序正在关闭");
public void ApplicationShuttingDown() => logger.ZLogInformation($"应用程序正在关闭");
// 音乐库相关日志
public static void SavingLibraryData(this ILogger logger, string path) =>
public void SavingLibraryData(string path) =>
logger.ZLogInformation($"正在保存音乐库数据到: {path}");
public static void LibraryDataSaved(
this ILogger logger,
public void LibraryDataSaved(
string path,
double elapsedMs,
int songCount,
@@ -36,11 +34,10 @@ public static class ZLoggerExtensions
$"音乐库数据已保存到: {path}, 耗时: {elapsedMs}ms, 歌曲数: {songCount}, 专辑数: {albumCount}"
);
public static void LoadingLibraryData(this ILogger logger, string path) =>
public void LoadingLibraryData(string path) =>
logger.ZLogInformation($"正在加载音乐库数据从: {path}");
public static void LibraryDataLoaded(
this ILogger logger,
public void LibraryDataLoaded(
string path,
double elapsedMs,
int songCount,
@@ -50,201 +47,130 @@ public static class ZLoggerExtensions
$"音乐库数据已加载从: {path}, 耗时: {elapsedMs}ms, 歌曲数: {songCount}, 专辑数: {albumCount}"
);
public static void LibraryScanning(
this ILogger logger,
int processedCount,
double progressPercent
) =>
public void LibraryScanning(int processedCount, double progressPercent) =>
logger.ZLogDebug(
$"正在扫描音乐库: 已处理 {processedCount} 个文件, 进度: {progressPercent}%"
);
// 播放器相关日志
public static void SongStartedPlaying(this ILogger logger, string title, string artist) =>
public void SongStartedPlaying(string title, string artist) =>
logger.ZLogInformation($"开始播放歌曲: {title} - {artist}");
public static void SongPlaybackError(
this ILogger logger,
string title,
Exception? exception = null
) =>
public void SongPlaybackError(string title, Exception? exception = null) =>
logger.ZLogError(
exception,
$"{"Error_SongPlayback".GetLocalizedWithReplace("{title}", title)}"
);
public static void PlaybackDeviceBusy(this ILogger logger, Exception? exception = null) =>
public void PlaybackDeviceBusy(Exception? exception = null) =>
logger.ZLogError(exception, $"{"Error_PlaybackDeviceBusy".GetLocalized()}");
public static void SongPlaybackPosition(this ILogger logger, string title, long positionMs) =>
public void SongPlaybackPosition(string title, long positionMs) =>
logger.ZLogTrace($"歌曲播放位置更新: {title}, 位置: {positionMs}ms");
// 下载相关日志
public static void DownloadStarted(this ILogger logger, string title, string url) =>
public void DownloadStarted(string title, string url) =>
logger.ZLogInformation($"开始下载: {title}, URL: {url}");
public static void DownloadCompleted(
this ILogger logger,
string title,
double elapsedMs,
long fileSizeBytes
) =>
public void DownloadCompleted(string title, double elapsedMs, long fileSizeBytes) =>
logger.ZLogInformation(
$"下载完成: {title}, 耗时: {elapsedMs}ms, 文件大小: {fileSizeBytes} 字节"
);
public static void DownloadFailed(
this ILogger logger,
string title,
string error,
Exception? exception = null
) => logger.ZLogError(exception, $"下载失败: {title}, 错误: {error}");
public void DownloadFailed(string title, string error, Exception? exception = null) =>
logger.ZLogError(exception, $"下载失败: {title}, 错误: {error}");
public static void DownloadProgress(this ILogger logger, string title, int progressPercent) =>
public void DownloadProgress(string title, int progressPercent) =>
logger.ZLogTrace($"下载进度: {title}, 进度: {progressPercent}%");
// 用户界面相关日志
public static void NavigationOccurred(this ILogger logger, string pageName) =>
public void NavigationOccurred(string pageName) =>
logger.ZLogDebug($"页面导航: {pageName}");
public static void UserAction(this ILogger logger, string actionName) =>
logger.ZLogDebug($"用户操作: {actionName}");
public void UserAction(string actionName) => logger.ZLogDebug($"用户操作: {actionName}");
// 性能相关日志
public static void PerformanceMetric(this ILogger logger, string metricName, double value) =>
public void PerformanceMetric(string metricName, double value) =>
logger.ZLogInformation($"性能指标: {metricName}, 值: {value}ms");
public static void MemoryUsage(this ILogger logger, long memoryBytes, int generation) =>
public void MemoryUsage(long memoryBytes, int generation) =>
logger.ZLogInformation($"内存使用情况: {memoryBytes} 字节, GC代数: {generation}");
// 错误和异常日志
public static void UnexpectedException(
this ILogger logger,
string message,
Exception exception
) => logger.ZLogError(exception, $"未处理的异常: {message}");
public void UnexpectedException(string message, Exception exception) =>
logger.ZLogError(exception, $"未处理的异常: {message}");
public static void OperationFailed(
this ILogger logger,
public void OperationFailed(
string operationName,
string error,
Exception? exception = null
) => logger.ZLogError(exception, $"操作失败: {operationName}, 错误: {error}");
public static void CriticalError(
this ILogger logger,
string message,
Exception? exception = null
) => logger.ZLogCritical(exception, $"严重错误: {message}");
public void CriticalError(string message, Exception? exception = null) =>
logger.ZLogCritical(exception, $"严重错误: {message}");
// 歌曲信息编辑日志
public static void EditingSongInfoIO(this ILogger logger, string title) =>
logger.ZLogError($"{"Error_EditingSongInfoIO".GetLocalizedWithReplace("{title}", title)}");
public void EditingSongInfoIO(string title) =>
logger.ZLogError(
$"{"Error_EditingSongInfoIO".GetLocalizedWithReplace("{title}", title)}"
);
public static void EditingSongInfoOther(
this ILogger logger,
string title,
Exception exception
) =>
public void EditingSongInfoOther(string title, Exception exception) =>
logger.ZLogError(
exception,
$"{"Error_EditingSongInfoOther".GetLocalizedWithReplace("{title}", title)}"
);
// 播放列表相关日志
public static void SamePlaylistName(this ILogger logger) =>
public void SamePlaylistName() =>
logger.ZLogError($"{"Error_SamePlaylistName".GetLocalized()}");
// 高性能性能监控日志
public static void LogPerformanceStart(
this ILogger logger,
string operationName,
int operationId
) => logger.ZLogDebug($"[{operationId:X8}] 开始执行: {operationName}");
public void LogPerformanceStart(string operationName, int operationId) =>
logger.ZLogDebug($"[{operationId:X8}] 开始执行: {operationName}");
public static void LogPerformanceEnd(
this ILogger logger,
string operationName,
int operationId,
double elapsedMs
) =>
public void LogPerformanceEnd(string operationName, int operationId, double elapsedMs) =>
logger.ZLogInformation(
$"[{operationId:X8}] 完成执行: {operationName}, 耗时: {elapsedMs}ms"
);
public static void LogPerformanceEnd(
this ILogger logger,
string operationName,
int operationId,
TimeSpan elapsed
) =>
public void LogPerformanceEnd(string operationName, int operationId, TimeSpan elapsed) =>
logger.ZLogInformation(
$"[{operationId:X8}] 完成执行: {operationName}, 耗时: {elapsed.TotalMilliseconds}ms"
);
// 音乐播放专用高性能日志
public static void PlaybackStateChanged(
this ILogger logger,
string songTitle,
string newState
) => logger.ZLogDebug($"播放状态变更: {songTitle} -> {newState}");
public void PlaybackStateChanged(string songTitle, string newState) =>
logger.ZLogDebug($"播放状态变更: {songTitle} -> {newState}");
public static void AudioStreamCreated(
this ILogger logger,
string filePath,
int streamHandle,
double durationSeconds
) =>
public void AudioStreamCreated(string filePath, int streamHandle, double durationSeconds) =>
logger.ZLogDebug(
$"音频流已创建: {filePath}, 句柄: {streamHandle}, 时长: {durationSeconds}s"
);
public static void AudioStreamReleased(this ILogger logger, int streamHandle) =>
public void AudioStreamReleased(int streamHandle) =>
logger.ZLogDebug($"音频流已释放: 句柄 {streamHandle}");
public static void PlaybackBufferUnderrun(
this ILogger logger,
string songTitle,
int bufferLevel
) => logger.ZLogWarning($"播放缓冲区不足: {songTitle}, 缓冲区水平: {bufferLevel}%");
public void PlaybackBufferUnderrun(string songTitle, int bufferLevel) =>
logger.ZLogWarning($"播放缓冲区不足: {songTitle}, 缓冲区水平: {bufferLevel}%");
// 网络请求日志
public static void HttpRequestStarted(
this ILogger logger,
string method,
string url,
int requestId
) => logger.ZLogDebug($"[{requestId:X8}] HTTP请求开始: {method} {url}");
public void HttpRequestStarted(string method, string url, int requestId) =>
logger.ZLogDebug($"[{requestId:X8}] HTTP请求开始: {method} {url}");
public static void HttpRequestCompleted(
this ILogger logger,
int requestId,
int statusCode,
double elapsedMs
) =>
logger.ZLogInformation($"[{requestId:X8}] HTTP请求完成: {statusCode}, 耗时: {elapsedMs}ms");
public void HttpRequestCompleted(int requestId, int statusCode, double elapsedMs) =>
logger.ZLogInformation(
$"[{requestId:X8}] HTTP请求完成: {statusCode}, 耗时: {elapsedMs}ms"
);
public static void HttpRequestFailed(
this ILogger logger,
int requestId,
string error,
Exception? exception = null
) => logger.ZLogError(exception, $"[{requestId:X8}] HTTP请求失败: {error}");
public void HttpRequestFailed(int requestId, string error, Exception? exception = null) =>
logger.ZLogError(exception, $"[{requestId:X8}] HTTP请求失败: {error}");
// 文件操作日志
public static void FileOperationStarted(
this ILogger logger,
string operation,
string filePath,
int operationId
) => logger.ZLogDebug($"[{operationId:X8}] 文件操作开始: {operation} -> {filePath}");
public void FileOperationStarted(string operation, string filePath, int operationId) =>
logger.ZLogDebug($"[{operationId:X8}] 文件操作开始: {operation} -> {filePath}");
public static void FileOperationCompleted(
this ILogger logger,
int operationId,
double elapsedMs,
long? fileSize = null
)
public void FileOperationCompleted(int operationId, double elapsedMs, long? fileSize = null)
{
if (fileSize.HasValue)
{
@@ -258,22 +184,17 @@ public static class ZLoggerExtensions
}
}
public static void FileOperationFailed(
this ILogger logger,
public void FileOperationFailed(
int operationId,
string error,
Exception? exception = null
) => logger.ZLogError(exception, $"[{operationId:X8}] 文件操作失败: {error}");
// 数据库操作日志
public static void DatabaseOperationStarted(
this ILogger logger,
string operation,
int operationId
) => logger.ZLogDebug($"[{operationId:X8}] 数据库操作开始: {operation}");
public void DatabaseOperationStarted(string operation, int operationId) =>
logger.ZLogDebug($"[{operationId:X8}] 数据库操作开始: {operation}");
public static void DatabaseOperationCompleted(
this ILogger logger,
public void DatabaseOperationCompleted(
int operationId,
double elapsedMs,
int? affectedRows = null
@@ -291,13 +212,13 @@ public static class ZLoggerExtensions
}
}
public static void DatabaseOperationFailed(
this ILogger logger,
public void DatabaseOperationFailed(
int operationId,
string error,
Exception? exception = null
) => logger.ZLogError(exception, $"[{operationId:X8}] 数据库操作失败: {error}");
}
}
/// <summary>
/// 高性能性能监控辅助类