using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
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);
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;
}
var exceptions = new List<Exception>();
for (var attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
await Task.Delay(retryInterval);
}
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>
/// 失败重试(无返回值)
/// </summary>
/// <param name="func"></param>
/// <param name="retryInterval">重试间隔</param>
/// <param name="maxAttemptCount">最大重试次数</param>
/// <param name="specificExceptionTypes">指定异常</param>
/// <exception cref="AggregateException"></exception>
public static async Task RetryAsync(
Func<Task> func,
TimeSpan retryInterval,
int maxAttemptCount = 3,
ICollection<Type>? specificExceptionTypes = null
)
{
_ = await RetryAsync(
async () =>
{
await func();
return 0;
},
retryInterval,
maxAttemptCount,
specificExceptionTypes
);
}
/// <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
#pragma warning disable CS0162 // 检测到不可到达的代码
return GetClientIp(request);
#pragma warning restore CS0162 // 检测到不可到达的代码
}
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";
}
#nullable enable
public static string GenerateSecret(int length = 32)
{
// ReSharper disable once StringLiteralTypo
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
ArgumentOutOfRangeException.ThrowIfLessThan(length, 1);
Span<byte> buffer = stackalloc byte[length];
RandomNumberGenerator.Fill(buffer);
var chars = new char[length];
for (var i = 0; i < length; i++)
{
chars[i] = alphabet[buffer[i] % alphabet.Length];
}
return new string(chars);
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个名为 ApplicationTools 的静态工具类,提供了多个实用的辅助方法。下面是详细的功能说明:
主要功能模块
1. 静态字段
StartTime: 应用程序的起始时间(2018年2月11日)MemoryStreamManager: 可回收的内存流管理器,用于高效管理内存流对象
2. 通配符匹配算法
这是最核心的功能模块,实现了支持 * 和 ? 的通配符匹配:
WildcardMatch: 主入口方法- 支持
*(匹配任意字符)和?(匹配单个字符) - 默认忽略大小写
- 内置缓存机制,使用
ConcurrentDictionary保证线程安全 - 缓存大小限制为 10,000 条,防止内存泄漏
- 支持
WildcardMatchCore: 核心匹配算法- 使用
ReadOnlySpan<char>提高性能 - 采用回溯算法处理
*通配符
- 使用
缓存管理方法:
ClearWildcardCache(): 清空缓存GetWildcardCacheSize(): 获取缓存大小ClearHalfWildcardCache(): 缓存满时清理一半数据
3. Markdown 转 HTML
MarkdownToHtml: 将 Markdown 文本转换为 HTML- 使用 Markdig 库
- 支持自动链接、表格、任务列表、强调扩展等
- 可选择是否禁用 HTML 标签
- 自动为所有链接添加
target="_blank"属性(在新窗口打开)
4. 重试机制
提供了两个异步重试方法:
RetryAsync<T>: 有返回值的重试RetryAsync: 无返回值的重试
特性:
- 可配置重试间隔和最大重试次数
- 支持指定不需要重试的特定异常类型
- 失败后抛出
AggregateException包含所有异常信息
5. 获取客户端 IP 地址
GetIpAddress: 从 HTTP 请求中提取客户端真实 IP- 优先级顺序:
Ali-Cdn-Real-Ip头(阿里云 CDN)X-Forwarded-For头X-Real-IP头- 连接的远程 IP 地址
- DEBUG 模式下返回硬编码的测试 IP
- 优先级顺序:
6. 密钥生成
GenerateSecret: 生成指定长度的随机字符串- 使用加密安全的
RandomNumberGenerator - 字符集包含大小写字母和数字
- 默认长度 32 字符
- 使用加密安全的
设计亮点
- 性能优化: 使用
Span<T>、ReadOnlySpan<char>减少内存分配 - 线程安全: 使用
ConcurrentDictionary实现线程安全的缓存 - 内存管理: 缓存有容量限制,防止内存泄漏
- 安全性: 使用
RandomNumberGenerator生成加密级别的随机数 - 可扩展性: 方法设计灵活,支持多种配置选项
这个工具类是一个典型的企业级应用辅助工具集,涵盖了字符串匹配、文本转换、网络请求、安全等多个常用场景。
评论加载中...