using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Security.Cryptography;
using System.Threading;
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
#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

    private static readonly ConcurrentDictionary<
        Type,
        ConcurrentDictionary<string, PropertyInfo?>
    > PropertyCache = new();

    private static class MemberAccessor<T>
    {
        internal static readonly ConcurrentDictionary<
            string,
            Lazy<Action<T, object?>>
        > SetterCache = new();
        internal static readonly ConcurrentDictionary<string, Lazy<Func<T, object?>>> GetterCache =
            new();
    }

    public static Action<T, object?>? GetSetter<T>(string propertyName)
    {
        var property = GetProperty<T>(propertyName);
        if (property == null || !property.CanWrite)
        {
            return null;
        }

        if (MemberAccessor<T>.SetterCache.TryGetValue(propertyName, out var cacheValue))
        {
            return cacheValue.Value;
        }

        var lazyValue = MemberAccessor<T>.SetterCache.GetOrAdd(
            propertyName,
            _ => new Lazy<Action<T, object?>>(
                () => CreateSetterExpressionTree<T>(property),
                LazyThreadSafetyMode.ExecutionAndPublication
            )
        );

        return lazyValue.Value;
    }

    public static Func<T, object?>? GetGetter<T>(string propertyName)
    {
        var property = GetProperty<T>(propertyName);
        if (property == null || !property.CanRead)
        {
            return null;
        }

        if (MemberAccessor<T>.GetterCache.TryGetValue(propertyName, out var cacheValue))
        {
            return cacheValue.Value;
        }

        var lazyValue = MemberAccessor<T>.GetterCache.GetOrAdd(
            propertyName,
            _ => new Lazy<Func<T, object?>>(
                () => CreateGetterExpressionTree<T>(property),
                LazyThreadSafetyMode.ExecutionAndPublication
            )
        );

        return lazyValue.Value;
    }

    private static Action<T, object?> CreateSetterExpressionTree<T>(PropertyInfo property)
    {
        var target = Expression.Parameter(typeof(T), "__q");
        var value = Expression.Parameter(typeof(object), "__q2");
        var convert = Expression.Convert(value, property.PropertyType);
        var propertyAccess = Expression.Property(target, property);
        var assign = Expression.Assign(propertyAccess, convert);
        var lambda = Expression.Lambda<Action<T, object?>>(assign, target, value);
        return lambda.Compile();
    }

    private static Func<T, object?> CreateGetterExpressionTree<T>(PropertyInfo property)
    {
        var target = Expression.Parameter(typeof(T), "__q");
        var propertyAccess = Expression.Property(target, property);
        var convert = Expression.Convert(propertyAccess, typeof(object));
        var lambda = Expression.Lambda<Func<T, object?>>(convert, target);
        return lambda.Compile();
    }

    private static PropertyInfo? GetProperty<T>(string propertyName)
    {
        var typeProperties = PropertyCache.GetOrAdd(
            typeof(T),
            _ => new ConcurrentDictionary<string, PropertyInfo?>()
        );

        if (typeProperties.TryGetValue(propertyName, out var propertyInfo))
        {
            return propertyInfo;
        }

        var property = typeof(T).GetProperty(
            propertyName,
            BindingFlags.Public | BindingFlags.Instance
        );
        typeProperties.TryAdd(propertyName, property);

        return property;
    }

    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,集中放置了常用的辅助方法和缓存/工厂(如通配符匹配、Markdown 转 HTML、重试逻辑、IP 获取、反射访问器缓存、随机密钥生成等)。
  • 线程安全性上大量使用了 ConcurrentDictionary、Lazy 和表达式树编译后缓存来提高并发下的性能。

常量与字段

  • StartTime:类中定义的一个固定 DateTime(初始化时间)。
  • ChatGptUserId:一个固定的字符串常量(默认 ChatGPT 用户 ID)。
  • MemoryStreamManager:Microsoft.IO.RecyclableMemoryStreamManager 的单例,用于内存流复用,减少 GC 和大对象分配。

通配符匹配(WildcardMatch)

  • 功能:支持 '*'(匹配任意数量字符)和 '?'(匹配单个字符)的通配符匹配;默认忽略大小写。
  • 缓存:匹配结果使用 ConcurrentDictionary<(string input, string pattern), bool> 缓存以加速重复匹配。缓存上限 MaxWildcardCacheSize = 10000,超过时调用 ClearHalfWildcardCache() 清理一半条目(或者失败时清空全部)。
  • 算法:WildcardMatchCore 使用经典的线性算法(保存最近 '*' 的位置并回溯),时间复杂度为 O(n+m)(n 为输入长度,m 为模式长度)。
  • 辅助:CharEquals 支持忽略大小写比较。
  • 可用方法:WildcardMatch、ClearWildcardCache、GetWildcardCacheSize。
  • 注意事项/潜在问题:
    • 缓存键在 ignoreCase = true 时对输入和模式都做了 ToLowerInvariant(),会产生分配;但缓存命中能弥补开销。
    • ClearHalfWildcardCache 通过 Keys.Take(N/2) 删除一半,未采用 LRU 策略,可能删除近期使用的项。
    • MaxWildcardCacheSize >=10 会被设置为 3 的地方是针对 RetryAsync,而不是这里(见下文关于重试的说明)。

Markdown 转 HTML(MarkdownToHtml)

  • 使用 Markdig 构建 pipeline:启用 AutoLinks、PipeTables、TaskLists、EmphasisExtras、AutoIdentifiers。
  • disableHtml 参数(默认 true)会调用 pipelineBuild.DisableHtml(),以禁用原生 HTML 渲染(提高安全性)。
  • 解析后遍历文档,将所有 LinkInline 和 AutolinkInline 添加 target="_blank"(打开新标签)。
  • 最后将文档转换为 HTML 返回。
  • 注意:调用 document.Descendants() 来修改属性,然后 document.ToHtml(pipeline)。

失败重试(RetryAsync)

  • 功能:对给定异步方法(Func<Task>)进行重试。
  • 参数:retryInterval(重试间隔,首次失败后等待),maxAttemptCount(最大尝试次数,默认 3),specificExceptionTypes(如果异常类型在其中,则立即抛出,不再重试)。
  • 实现细节:
    • 如果 maxAttemptCount <= 0,则置为 3。
    • 如果 maxAttemptCount >= 10,也把它强制设为 3 —— 这是有问题或可疑的逻辑(通常应允许更大尝试次数或限制到某个合理上限,但将 >=10 直接强制为 3 很可能是 bug 或硬编码误写)。
    • 每次重试前如果 attempted > 0,会等待 retryInterval,并把 retryInterval 累加 TimeSpan.FromSeconds(attempted + 1)(使后续等待时间变长)。
    • 捕获所有异常并累积到 exceptions 列表,直到达到最大尝试次数后抛出 AggregateException。
    • 如果捕获的异常类型在 specificExceptionTypes 中,则立即 rethrow,不计入重试。
  • 注意事项:
    • 将 maxAttemptCount >=10 强制为 3 很可能不符合预期,应检查或移除这行。
    • retryInterval 的累加方式会使等待时间呈递增(指数/线性增长取决实现),注意可能导致长时间等待。
    • 捕获 Exception 会把 ThreadAbort/OutOfMemory 等严重异常也捕获,若希望排除某些严重异常应另作判断。

IP 获取(GetIpAddress / GetClientIp)

  • GetIpAddress 扩展 HttpRequest:在 DEBUG 编译符下直接返回固定 IP(113.110.235.52),其他情况下调用 GetClientIp。
  • GetClientIp 检查请求头(优先顺序):
    • "Ali-Cdn-Real-Ip"
    • "X-Forwarded-For"(取第一个逗号分隔的 IP)
    • "X-Real-IP"
    • 最后 fallback 到 request.HttpContext.Connection.RemoteIpAddress
  • 返回 "unknown" 作为缺省值。
  • 注意:DEBUG 下硬编码 IP 是便于本地开发,但在某些场景下可能产生误导,生产环境编译时不会包含该分支。

反射属性访问缓存(GetSetter / GetGetter / Create* / PropertyCache)

  • 目的:通过表达式树动态生成 getter/setter 委托并缓存,减少反射调用的开销,提高运行时性能。
  • 结构:
    • PropertyCache: ConcurrentDictionary<Type, ConcurrentDictionary<string, PropertyInfo?>>:缓存类型到属性信息的查找。
    • MemberAccessor 内部静态类维护针对 T 的 SetterCache 和 GetterCache(ConcurrentDictionary<string, Lazy<...>>),Lazy 用于延迟线程安全创建委托。
  • CreateSetterExpressionTree 与 CreateGetterExpressionTree 使用 System.Linq.Expressions 构造表达式树并 Compile() 成为委托:
    • Setter: Action<T, object?>,在执行时会把 object 转为属性类型再赋值。
    • Getter: Func<T, object?>,读取属性并装箱为 object。
  • 优点:运行时调用委托比反射快很多,并且线程安全地缓存委托实例。
  • 注意:GetProperty 仅查找 public instance 属性(BindingFlags.Public | BindingFlags.Instance)。

生成密钥(GenerateSecret)

  • 使用 cryptographically secure RandomNumberGenerator.Fill 填充一个 stackalloc 的 byte span,避免额外堆分配。
  • 从 alphabet 字符集中按 byte % alphabet.Length 映射生成指定长度的随机字符串(默认 32)。
  • 参数检查使用 ArgumentOutOfRangeException.ThrowIfLessThan(length, 1)。
  • 生成的字符串字符集为 62 字母数字字符(大小写字母 + 数字)。

其他

  • #nullable enable 开启了可空引用类型检查。
  • 使用 Microsoft.IO.RecyclableMemoryStreamManager 可减少内存分配(适用于频繁创建 MemoryStream 场景)。

潜在问题与改进建议

  • RetryAsync 中把 maxAttemptCount >= 10 强制设为 3,很可能是 bug,应修正或更明确地设定上限。
  • WildcardMatchCache 的简单清理策略(删除前半)不是 LRU,可能删除热数据;可考虑使用更成熟的缓存策略(LRU、带过期时间的缓存等)。
  • WildcardMatchCache 的键在 ignoreCase 时先 ToLowerInvariant 会产生临时字符串分配;如果匹配非常频繁可以考虑用更节省分配的 key(例如规范化后缓存或直接缓存 pattern 的编译结构)。
  • RetryAsync 捕获所有 Exception,可能需要排除不可恢复异常(如 OutOfMemoryException、StackOverflowException 等)。
  • MarkdownToHtml 使用 DisableHtml 默认是安全的,但若需要允许部分 HTML,应谨慎处理 XSS。
  • GetIpAddress 在 DEBUG 环境返回固定 IP,用于开发调试,但要注意不要在生产环境误用。

总结

  • 这是一个实用的工具集合,覆盖文本处理(Markdown)、字符串匹配、反射性能优化、重试机制、IP 获取和安全随机字符串生成等常见功能。
  • 设计上注重并发安全和性能(缓存、表达式树、stackalloc、RecyclableMemoryStreamManager)。
  • 有少量可疑或可改进的地方(如重试次数限制、缓存清理策略),建议在使用前根据具体需求调整。
评论加载中...