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
    • 优先级顺序:
      1. Ali-Cdn-Real-Ip 头(阿里云 CDN)
      2. X-Forwarded-For
      3. X-Real-IP
      4. 连接的远程 IP 地址
    • DEBUG 模式下返回硬编码的测试 IP

6. 密钥生成

  • GenerateSecret: 生成指定长度的随机字符串
    • 使用加密安全的 RandomNumberGenerator
    • 字符集包含大小写字母和数字
    • 默认长度 32 字符

设计亮点

  1. 性能优化: 使用 Span<T>ReadOnlySpan<char> 减少内存分配
  2. 线程安全: 使用 ConcurrentDictionary 实现线程安全的缓存
  3. 内存管理: 缓存有容量限制,防止内存泄漏
  4. 安全性: 使用 RandomNumberGenerator 生成加密级别的随机数
  5. 可扩展性: 方法设计灵活,支持多种配置选项

这个工具类是一个典型的企业级应用辅助工具集,涵盖了字符串匹配、文本转换、网络请求、安全等多个常用场景。

评论加载中...