2026-05-22 14:29:22 +08:00
|
|
|
|
namespace FileShare_Services.Core
|
2026-05-22 11:18:47 +08:00
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Matches unified endpoint patterns and extracts simple route values.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
internal static class ServiceEndpointPatternMatcher
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Match literal segments and single-segment route parameters such as {id} or {id:int}.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static bool TryMatch(
|
|
|
|
|
|
string pattern,
|
|
|
|
|
|
string path,
|
|
|
|
|
|
out Dictionary<string, string> routeValues)
|
|
|
|
|
|
{
|
|
|
|
|
|
routeValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
|
|
|
|
|
|
|
var patternSegments = SplitSegments(pattern);
|
|
|
|
|
|
var pathSegments = SplitSegments(path);
|
|
|
|
|
|
if (patternSegments.Length != pathSegments.Length)
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (var index = 0; index < patternSegments.Length; index++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var patternSegment = patternSegments[index];
|
|
|
|
|
|
var pathSegment = pathSegments[index];
|
|
|
|
|
|
|
|
|
|
|
|
if (TryGetParameterName(patternSegment, out var parameterName))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!MatchesConstraint(patternSegment, pathSegment))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
routeValues[parameterName] = Uri.UnescapeDataString(pathSegment);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!string.Equals(patternSegment, pathSegment, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 14:45:07 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将路径按 <c>/</c> 拆分为非空片段数组。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="value">路径字符串。</param>
|
|
|
|
|
|
/// <returns>过滤空条目后的片段数组。</returns>
|
2026-05-22 11:18:47 +08:00
|
|
|
|
private static string[] SplitSegments(string value)
|
|
|
|
|
|
{
|
|
|
|
|
|
return value.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 14:45:07 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 尝试从 <c>{name}</c> 或 <c>{name:constraint}</c> 格式的片段中提取参数名称。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="segment">模式片段。</param>
|
|
|
|
|
|
/// <param name="parameterName">提取出的参数名称。</param>
|
|
|
|
|
|
/// <returns>该片段是路由参数时返回 true,否则 false。</returns>
|
2026-05-22 11:18:47 +08:00
|
|
|
|
private static bool TryGetParameterName(string segment, out string parameterName)
|
|
|
|
|
|
{
|
|
|
|
|
|
parameterName = string.Empty;
|
|
|
|
|
|
if (segment.Length < 3 || segment[0] != '{' || segment[^1] != '}')
|
|
|
|
|
|
{
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var token = segment[1..^1];
|
|
|
|
|
|
var constraintIndex = token.IndexOf(':');
|
|
|
|
|
|
parameterName = constraintIndex >= 0 ? token[..constraintIndex] : token;
|
|
|
|
|
|
return !string.IsNullOrWhiteSpace(parameterName);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-22 14:45:07 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 检查路由值是否满足片段中的类型约束(目前仅支持 <c>:int</c>)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="segment">包含约束的模式片段。</param>
|
|
|
|
|
|
/// <param name="value">实际路径值。</param>
|
|
|
|
|
|
/// <returns>满足约束时返回 true。</returns>
|
2026-05-22 11:18:47 +08:00
|
|
|
|
private static bool MatchesConstraint(string segment, string value)
|
|
|
|
|
|
{
|
|
|
|
|
|
return !segment.EndsWith(":int}", StringComparison.OrdinalIgnoreCase)
|
|
|
|
|
|
|| int.TryParse(value, out _);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|