chore: 同步当前开发内容

This commit is contained in:
2025-11-23 01:25:20 +08:00
parent ddf584f212
commit 1169e1f220
58 changed files with 1886 additions and 82 deletions

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using TakeoutSaaS.Module.Authorization.Policies;
namespace TakeoutSaaS.Module.Authorization.Attributes;
/// <summary>
/// 权限校验特性
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public sealed class PermissionAuthorizeAttribute : AuthorizeAttribute
{
public PermissionAuthorizeAttribute(params string[] permissions)
{
ArgumentNullException.ThrowIfNull(permissions);
var normalized = permissions
.Where(p => !string.IsNullOrWhiteSpace(p))
.Select(p => p.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
if (normalized.Length == 0)
{
throw new ArgumentException("至少需要一个权限标识", nameof(permissions));
}
Permissions = normalized;
Policy = PermissionAuthorizationPolicyProvider.BuildPolicyName(normalized);
}
/// <summary>
/// 所需权限集合
/// </summary>
public IReadOnlyCollection<string> Permissions { get; }
}

View File

@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using TakeoutSaaS.Module.Authorization.Policies;
namespace TakeoutSaaS.Module.Authorization.Extensions;
/// <summary>
/// 权限授权注入扩展
/// </summary>
public static class AuthorizationServiceCollectionExtensions
{
/// <summary>
/// 启用自定义权限策略提供者与处理器
/// </summary>
public static IServiceCollection AddPermissionAuthorization(this IServiceCollection services)
{
services.AddSingleton<IAuthorizationPolicyProvider, PermissionAuthorizationPolicyProvider>();
services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
return services;
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
namespace TakeoutSaaS.Module.Authorization.Policies;
/// <summary>
/// 权限校验处理器
/// </summary>
public sealed class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
public const string PermissionClaimType = "permission";
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
if (context.User?.Identity?.IsAuthenticated != true)
{
return Task.CompletedTask;
}
var userPermissions = context.User
.FindAll(PermissionClaimType)
.Select(claim => claim.Value)
.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => value.Trim())
.ToHashSet(StringComparer.OrdinalIgnoreCase);
if (userPermissions.Count == 0)
{
return Task.CompletedTask;
}
if (requirement.Permissions.Any(userPermissions.Contains))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
namespace TakeoutSaaS.Module.Authorization.Policies;
/// <summary>
/// 权限策略提供者(按需动态构建策略)
/// </summary>
public sealed class PermissionAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
public const string PolicyPrefix = "PERMISSION:";
private readonly AuthorizationOptions _options;
public PermissionAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options)
: base(options)
{
_options = options.Value;
}
public override Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(PolicyPrefix, StringComparison.OrdinalIgnoreCase))
{
var existingPolicy = _options.GetPolicy(policyName);
if (existingPolicy != null)
{
return Task.FromResult(existingPolicy);
}
var permissions = ParsePermissions(policyName);
if (permissions.Length == 0)
{
return Task.FromResult<AuthorizationPolicy?>(null);
}
var policy = new AuthorizationPolicyBuilder()
.AddRequirements(new PermissionRequirement(permissions))
.Build();
_options.AddPolicy(policyName, policy);
return Task.FromResult<AuthorizationPolicy?>(policy);
}
return base.GetPolicyAsync(policyName);
}
/// <summary>
/// 根据权限集合构建策略名称
/// </summary>
public static string BuildPolicyName(IEnumerable<string> permissions)
=> $"{PolicyPrefix}{string.Join('|', NormalizePermissions(permissions))}";
private static string[] ParsePermissions(string policyName)
{
var raw = policyName[PolicyPrefix.Length..];
return NormalizePermissions(raw.Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries));
}
private static string[] NormalizePermissions(IEnumerable<string> permissions)
=> permissions
.Where(p => !string.IsNullOrWhiteSpace(p))
.Select(p => p.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;
namespace TakeoutSaaS.Module.Authorization.Policies;
/// <summary>
/// 权限要求
/// </summary>
public sealed class PermissionRequirement : IAuthorizationRequirement
{
public PermissionRequirement(IReadOnlyCollection<string> permissions)
{
Permissions = permissions ?? throw new ArgumentNullException(nameof(permissions));
}
public IReadOnlyCollection<string> Permissions { get; }
}

View File

@@ -4,8 +4,11 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,23 +0,0 @@
namespace TakeoutSaaS.Module.Identity.Abstractions;
/// <summary>
/// 微信登录服务抽象code2Session
/// </summary>
public interface IWeChatAuthService
{
/// <summary>
/// 使用小程序登录 code 换取 openid/unionid/session_key
/// </summary>
Task<WeChatSessionInfo> Code2SessionAsync(string code, CancellationToken cancellationToken = default);
}
/// <summary>
/// 微信会话信息
/// </summary>
public sealed class WeChatSessionInfo
{
public string OpenId { get; init; } = string.Empty;
public string? UnionId { get; init; }
public string SessionKey { get; init; } = string.Empty;
}

View File

@@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\TakeoutSaaS.Shared.Abstractions\TakeoutSaaS.Shared.Abstractions.csproj" />
</ItemGroup>
</Project>