网站首页 网站源码
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Markdig;
using Markdig.Renderers.Html;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using Microsoft.AspNetCore.Http;
using Microsoft.IO;
namespace Dpz.Core.Infrastructure;
public static class ApplicationTools
{
/// <summary>
/// 起始时间
/// </summary>
public static readonly DateTime StartTime = new(2018, 2, 11, 13, 56, 24, DateTimeKind.Local);
/// <summary>
/// ChatGPT 默认用户ID
/// </summary>
public const string ChatGptUserId = "e0a82f97-d01d-43c0-a257-97998003e8b9";
public static readonly RecyclableMemoryStreamManager MemoryStreamManager =
new RecyclableMemoryStreamManager();
#region 通配符匹配算法
// 缓存匹配结果以提高性能,使用ConcurrentDictionary保证线程安全
private static readonly ConcurrentDictionary<
(string input, string pattern),
bool
> WildcardMatchCache = new();
// 缓存大小限制,避免内存泄漏
private const int MaxWildcardCacheSize = 10000;
/// <summary>
/// 通配符匹配
/// 支持 * (匹配任意字符) 和 ? (匹配单个字符)
/// 默认忽略大小写,包含缓存机制以提高重复匹配的性能
/// </summary>
/// <param name="input">要匹配的输入字符串</param>
/// <param name="pattern">通配符模式</param>
/// <param name="ignoreCase">是否忽略大小写,默认为 true</param>
/// <returns>是否匹配成功</returns>
/// <example>
/// <code>
/// bool result1 = ApplicationTools.WildcardMatch("/api/users/123", "/api/*"); // true
/// bool result2 = ApplicationTools.WildcardMatch("file.txt", "*.txt"); // true
/// bool result3 = ApplicationTools.WildcardMatch("test", "t??t"); // true
/// bool result4 = ApplicationTools.WildcardMatch("ABC", "abc"); // true (默认忽略大小写)
/// bool result5 = ApplicationTools.WildcardMatch("ABC", "abc", false); // false (区分大小写)
/// </code>
/// </example>
public static bool WildcardMatch(string input, string pattern, bool ignoreCase = true)
{
if (string.IsNullOrEmpty(input) && string.IsNullOrEmpty(pattern))
{
return true;
}
if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(pattern))
{
// 空输入只能匹配全为 * 的模式
if (string.IsNullOrEmpty(input) && !string.IsNullOrEmpty(pattern))
{
return pattern.All(c => c == '*');
}
return false;
}
// 检查缓存
var cacheKey = ignoreCase
? (input.ToLowerInvariant(), pattern.ToLowerInvariant())
: (input, pattern);
if (WildcardMatchCache.TryGetValue(cacheKey, out var cachedResult))
{
return cachedResult;
}
// 执行匹配
var result = WildcardMatchCore(input.AsSpan(), pattern.AsSpan(), ignoreCase);
// 缓存结果(有大小限制)
if (WildcardMatchCache.Count < MaxWildcardCacheSize)
{
WildcardMatchCache.TryAdd(cacheKey, result);
}
else if (WildcardMatchCache.Count >= MaxWildcardCacheSize)
{
// 当缓存满时,清理一半的缓存
ClearHalfWildcardCache();
WildcardMatchCache.TryAdd(cacheKey, result);
}
return result;
}
/// <summary>
/// 清空通配符匹配缓存
/// 在内存压力较大或模式变化频繁时可以调用此方法
/// </summary>
public static void ClearWildcardCache()
{
WildcardMatchCache.Clear();
}
/// <summary>
/// 获取当前通配符匹配缓存的大小
/// </summary>
/// <returns>缓存中的项目数量</returns>
public static int GetWildcardCacheSize()
{
return WildcardMatchCache.Count;
}
/// <summary>
/// 核心通配符匹配算法实现
/// </summary>
private static bool WildcardMatchCore(
ReadOnlySpan<char> input,
ReadOnlySpan<char> pattern,
bool ignoreCase
)
{
// 处理空字符串的特殊情况
if (pattern.Length == 0)
{
return input.Length == 0;
}
if (input.Length == 0)
{
// 输入为空,只有模式全部是 '*' 才匹配
for (var i = 0; i < pattern.Length; i++)
{
if (pattern[i] != '*')
{
return false;
}
}
return true;
}
var inputIndex = 0;
var patternIndex = 0;
var starIndex = -1;
var match = 0;
while (inputIndex < input.Length)
{
// 如果模式字符是 '?' 或者字符匹配
if (
patternIndex < pattern.Length
&& (
pattern[patternIndex] == '?'
|| CharEquals(input[inputIndex], pattern[patternIndex], ignoreCase)
)
)
{
inputIndex++;
patternIndex++;
}
// 如果模式字符是 '*'
else if (patternIndex < pattern.Length && pattern[patternIndex] == '*')
{
starIndex = patternIndex;
match = inputIndex;
patternIndex++;
}
// 如果之前遇到过 '*',回溯
else if (starIndex != -1)
{
patternIndex = starIndex + 1;
match++;
inputIndex = match;
}
// 不匹配
else
{
return false;
}
}
// 跳过模式末尾的 '*'
while (patternIndex < pattern.Length && pattern[patternIndex] == '*')
{
patternIndex++;
}
return patternIndex == pattern.Length;
}
/// <summary>
/// 字符比较,支持大小写敏感/不敏感
/// </summary>
private static bool CharEquals(char a, char b, bool ignoreCase)
{
return ignoreCase ? char.ToLowerInvariant(a) == char.ToLowerInvariant(b) : a == b;
}
/// <summary>
/// 清理一半的缓存以避免内存过度使用
/// </summary>
private static void ClearHalfWildcardCache()
{
try
{
var keysToRemove = WildcardMatchCache.Keys.Take(WildcardMatchCache.Count / 2).ToList();
foreach (var key in keysToRemove)
{
WildcardMatchCache.TryRemove(key, out _);
}
}
catch
{
// 如果清理失败,清空所有缓存
WildcardMatchCache.Clear();
}
}
#endregion
/// <summary>
/// Markdown转为Html
/// </summary>
/// <param name="markdown"></param>
/// <param name="disableHtml">是否禁用html(默认禁用)</param>
/// <returns></returns>
public static string MarkdownToHtml(this string markdown, bool disableHtml = true)
{
var pipelineBuild = new MarkdownPipelineBuilder()
.UseAutoLinks()
.UsePipeTables()
.UseTaskLists()
.UseEmphasisExtras()
.UseAutoIdentifiers();
if (disableHtml)
{
pipelineBuild.DisableHtml();
}
var pipeline = pipelineBuild.Build();
var document = Markdown.Parse(markdown, pipeline);
foreach (var link in document.Descendants<LinkInline>())
{
link.GetAttributes().AddPropertyIfNotExist("target", "_blank");
}
foreach (var link in document.Descendants<AutolinkInline>())
{
link.GetAttributes().AddPropertyIfNotExist("target", "_blank");
}
return document.ToHtml(pipeline);
}
/// <summary>
/// 失败重试
/// </summary>
/// <param name="func"></param>
/// <param name="retryInterval">重试间隔</param>
/// <param name="maxAttemptCount">最大重试次数</param>
/// <param name="specificExceptionTypes">指定异常</param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="AggregateException"></exception>
public static async Task<T> RetryAsync<T>(
Func<Task<T>> func,
TimeSpan retryInterval,
int maxAttemptCount = 3,
ICollection<Type>? specificExceptionTypes = null
)
{
if (maxAttemptCount <= 0)
{
maxAttemptCount = 3;
}
if (maxAttemptCount >= 10)
{
maxAttemptCount = 3;
}
var exceptions = new List<Exception>();
for (var attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
await Task.Delay(retryInterval);
retryInterval += TimeSpan.FromSeconds(attempted + 1);
}
return await func();
}
catch (Exception ex)
{
if (specificExceptionTypes != null && specificExceptionTypes.Contains(ex.GetType()))
{
throw;
}
exceptions.Add(ex);
}
}
throw new AggregateException(
$"this task has been repeated {maxAttemptCount} times and has failed",
exceptions
);
}
/// <summary>
/// 获取IP地址
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static string GetIpAddress(this HttpRequest? request)
{
if (request == null)
{
return "unknown";
}
#if DEBUG
return "113.110.235.52";
#endif
return GetClientIp(request);
}
private static string GetClientIp(HttpRequest request)
{
// 优先从 Ali-Cdn-Real-Ip 中获取真实IP
var aliCdnRealIp = request.Headers["Ali-Cdn-Real-Ip"].FirstOrDefault();
if (!string.IsNullOrEmpty(aliCdnRealIp))
{
var ip = aliCdnRealIp.Trim();
if (
!string.IsNullOrEmpty(ip)
&& !ip.Equals("unknown", StringComparison.OrdinalIgnoreCase)
)
{
return ip;
}
}
var forwardedFor = request.Headers["X-Forwarded-For"].FirstOrDefault();
if (!string.IsNullOrEmpty(forwardedFor))
{
var ip = forwardedFor.Split(',')[0].Trim();
if (
!string.IsNullOrEmpty(ip)
&& !ip.Equals("unknown", StringComparison.OrdinalIgnoreCase)
)
{
return ip;
}
}
var realIp = request.Headers["X-Real-IP"].FirstOrDefault();
if (!string.IsNullOrEmpty(realIp))
{
return realIp;
}
return request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
}
}
这段代码定义了一个名为 ApplicationTools
的静态类,提供了一些实用工具方法,主要用于处理 Markdown 文本和实现失败重试机制。以下是代码的主要功能和组成部分的详细解释:
StartTime: 记录了一个固定的起始时间(2018年2月11日13:56:24),可以用于计算应用程序的运行时间或其他时间相关的逻辑。
ChatGptUserId: 定义了一个常量字符串,表示 ChatGPT 的默认用户 ID。
MemoryStreamManager: 使用 RecyclableMemoryStreamManager
来管理内存流,优化内存使用,避免频繁的内存分配和释放。
MarkdownToHtml: 这是一个扩展方法,用于将 Markdown 格式的字符串转换为 HTML。它接受两个参数:
markdown
: 要转换的 Markdown 字符串。disableHtml
: 一个布尔值,指示是否禁用 HTML 标签(默认为禁用)。方法的实现步骤如下:
MarkdownPipelineBuilder
,配置 Markdown 解析的选项(如自动链接、管道表、任务列表等)。disableHtml
为 true
,则禁用 HTML 标签。LinkInline
和 AutolinkInline
)添加 target="_blank"
属性,以便在新标签页中打开链接。RetryAsync: 这是一个异步方法,用于执行一个可能会失败的操作,并在失败时进行重试。它接受以下参数:
func
: 一个返回 Task<T>
的函数,表示要执行的操作。retryInterval
: 重试之间的时间间隔。maxAttemptCount
: 最大重试次数(默认为 3 次)。specificExceptionTypes
: 可选的异常类型集合,指定在重试时需要处理的特定异常。方法的实现逻辑如下:
maxAttemptCount
小于等于 0 或大于等于 10,则将其重置为 3。func
,最多尝试 maxAttemptCount
次。specificExceptionTypes
包含该异常类型,则抛出该异常。AggregateException
,包含所有失败的异常。整体而言,这段代码提供了 Markdown 文本处理和异步操作的重试机制,适用于需要将 Markdown 转换为 HTML 的应用程序,以及需要处理可能失败的异步操作的场景。