跳到主要内容

CommonAttachment 实体访问验证器 - 完全配置驱动实现

架构概述

本方案使用策略模式 + 配置驱动 + 父类聚合,实现了完全可配置的实体类型及其权限验证器。

核心特点

  • ✅ 所有验证逻辑在基础设施层 CommonAttachmentValidatorBase 中实现
  • ✅ 业务模块无需创建验证器子类
  • ✅ 仅需配置 CommonAttachmentOptions 和实现具体的 IEntityAccessValidator

核心设计理念

📌 配置即代码

通过配置定义实体类型及其验证器,无需修改验证器类即可扩展新的实体类型。

📌 完全解耦

  • 实体类型定义 - 通过 CommonFileEntityTypeDefinition 配置
  • 验证器实现 - 独立的验证器类实现 IEntityAccessValidator
  • 验证逻辑调度 - CommonAttachmentValidatorBase 从配置中读取并动态调度

📌 父类聚合

  • 所有通用逻辑在 CommonAttachmentValidatorBase(基础设施层)中实现
  • 业务模块只需配置,无需继承或重写

核心组件

1. IEntityAccessValidator 接口

位于 FreeKit.Identity.Infrastructure.Domain.CommonAttachments.Contracts,可被所有模块复用:

public interface IEntityAccessValidator : ITransientDependency
{
/// <summary>
/// 验证用户是否有权限访问指定实体
/// </summary>
Task ValidateAsync(Guid entityId);
}

2. CommonFileEntityTypeDefinition 配置类

位于 FreeKit.Identity.Infrastructure.Domain.Common,支持流畅 API:

public class CommonFileEntityTypeDefinition : EntityTypeDefinition
{
public Type? AccessValidatorType { get; set; }
public bool RequireAccessValidation => AccessValidatorType != null;

public CommonFileEntityTypeDefinition WithAccessValidator<TValidator>()
where TValidator : IEntityAccessValidator
{
AccessValidatorType = typeof(TValidator);
return this;
}
}

3. 具体验证器实现(业务模块)

  • ArticleAccessValidator: 验证 Article 实体的访问权限
  • ShortMsgAccessValidator: 验证 ShortMsg 实体的访问权限

每个验证器封装了特定实体的权限验证逻辑。

4. CommonAttachmentValidatorBase 完整实现(基础设施层)

位于 FreeKit.Identity.Infrastructure.Domain.CommonAttachments,提供完整实现:

public class CommonAttachmentValidatorBase : ICommonAttachmentValidator
{
private readonly CommonAttachmentOptions _options;
private readonly IComponentContext _componentContext;

public virtual async Task ValidateAccessAsync(string entityType, Guid entityId)
{
// 1. 验证实体类型是否配置
await ValidateEntityTypeAsync(entityType);

// 2. 从配置中获取实体类型定义
var definition = _options.EntityTypes.SingleOrDefault(...);

// 3. 如果配置了访问验证器,则动态解析并执行验证
if (definition is CommonFileEntityTypeDefinition fileDefinition &&
fileDefinition.RequireAccessValidation)
{
var validator = _componentContext.Resolve(fileDefinition.AccessValidatorType);
await validator.ValidateAsync(entityId);
}
}
}

✨ 所有逻辑已在父类实现,业务模块无需继承!

配置示例

在模块启动类 CmsKitModuleStartup.cs 中配置:

// 使用基础设施层的 CommonAttachmentOptions(而不是 CmsKitOptions)
services.Configure<CommonAttachmentOptions>(options =>
{
// 配置带权限验证的实体类型
options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(Article))
.WithAccessValidator<ArticleAccessValidator>()
);

options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(ShortMsg))
.WithAccessValidator<ShortMsgAccessValidator>()
);

// 配置不需要权限验证的实体类型(可选)
options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(PublicDocument))
// 不调用 WithAccessValidator,表示不需要权限验证
);
});

// ✨ 无需注册 CmsKitCommonAttachmentValidator!
// ✨ CommonAttachmentValidatorBase 实现了 ICommonAttachmentValidator
// ✨ 接口继承了 ITransientDependency,自动注册到 DI 容器

工作流程

用户请求 → ValidateAccessAsync(entityType, entityId)

CommonAttachmentValidatorBase (基础设施层完整实现)

1. ValidateEntityTypeAsync - 验证实体类型是否在 CommonAttachmentOptions 中配置

2. 从 CommonAttachmentOptions.EntityTypes 获取配置

3. 检查 CommonFileEntityTypeDefinition.RequireAccessValidation
├─ true → 通过 IComponentContext.Resolve 获取验证器实例
│ ↓
│ validator.ValidateAsync(entityId) 执行具体验证逻辑
│ ↓
│ ArticleAccessValidator / ShortMsgAccessValidator
│ (业务模块实现的具体策略)

## 优势对比

| 维度 | 传统方式 | 配置驱动 + 父类聚合 |
|------|---------|---------------------|
| **扩展性** | 需修改 Validator 类的 switch/字典 | 仅需配置,无需修改代码 ✅ |
| **灵活性** | 所有实体必须有验证器 | 可选配置验证器 ✅ |
| **维护性** | 验证器映射分散在代码中 | 配置集中在模块启动类 ✅ |
| **子类需求** | 必须创建子类重写方法 | **无需创建子类** ✅ |
| **可测试性** | 需要测试映射逻辑 | 映射逻辑由配置保证 ✅ |
| **开闭原则** | ❌ 违反(需修改现有代码) | ✅ 符合(通过配置扩展) |
| **跨模块** | 每个模块创建自己的验证器类 | 统一使用基础设施层实现 ✅ |

## 添加新实体类型

### 场景 1: 需要权限验证的实体

#### 步骤 1: 创建验证器类

```csharp
// BlogAccessValidator.cs
using FreeKit.CmsKit.Domain.Blogs;
using FreeKit.Identity.Infrastructure.Domain.CommonAttachments.Contracts;

namespace FreeKit.CmsKit.Domain.Files;

public class BlogAccessValidator : IEntityAccessValidator
{
private readonly IAuditBaseRepository<Blog> _blogRepository;
private readonly ICurrentUser _currentUser;

public BlogAccessValidator(
IAuditBaseRepository<Blog> blogRepository,
ICurrentUser currentUser)
{
_blogRepository = blogRepository;
_currentUser = currentUser;
}

/// <inheritdoc />
public async Task ValidateAsync(Guid entityId)
{
var blog = await _blogRepository.Select
.Where(x => x.Id == entityId)
.FirstAsync();

if (blog.CreateUserId != _currentUser.FindUserId() &&
!_currentUser.IsInRole(RoleEnum.Admin.ToString()))
{
throw new AuthenticationException("无权访问该 Blog 的附件");
}
}
}

步骤 2: 配置实体类型

步骤 2: 配置实体类型

services.Configure<CommonAttachmentOptions>(options =>
{
options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(Blog))
.WithAccessValidator<BlogAccessValidator>()
);
});

完成! 无需创建验证器子类,验证器会自动通过 ITransientDependency 接口注册到 DI 容器。

场景 2: 不需要权限验证的公共实体

services.Configure<CommonAttachmentOptions>(options =>
{
// 公共文档,任何人都可以访问
options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(PublicDocument))
// 不调用 WithAccessValidator
);
});

跨模块复用

在其他模块(如 Mall)中使用

1. 创建验证器

// Mall 模块
using FreeKit.Identity.Infrastructure.Domain.CommonAttachments.Contracts;

namespace FreeKit.Mall.Domain.Files;

public class ProductAccessValidator : IEntityAccessValidator
{
public async Task ValidateAsync(Guid entityId)
{
// 产品文件验证逻辑
}
}

2. 配置(在 Mall 模块启动类中)

services.Configure<CommonAttachmentOptions>(options =>
{
options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(Product))
.WithAccessValidator<ProductAccessValidator>()
);
});

高级场景

使用 Autofac 键化服务(可选)

如果希望更明确的服务注册,可以手动注册:

builder.RegisterType<ArticleAccessValidator>()
.Keyed<IEntityAccessValidator>(nameof(Article))
.InstancePerLifetimeScope();

然后在配置中引用键:

options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(Article))
.WithAccessValidatorKey(nameof(Article))
);

组合验证器

创建复合验证器处理复杂权限逻辑:

public class CompositeAccessValidator : IEntityAccessValidator
{
private readonly IEnumerable<IEntityAccessValidator> _validators;

public CompositeAccessValidator(IEnumerable<IEntityAccessValidator> validators)
{
_validators = validators;
}

public async Task ValidateAsync(Guid entityId)
{
foreach (var validator in _validators)
{
await validator.ValidateAsync(entityId);
}
}
}

注意事项

  1. ✅ 所有验证器类实现 IEntityAccessValidator 接口
  2. ✅ 验证器通过 ITransientDependency 自动注册
  3. ✅ 配置集中在模块启动类的 Configure<CommonAttachmentOptions>
  4. ✅ 验证逻辑应抛出明确的异常信息
  5. ✅ 建议为每个验证器编写单元测试
  6. ⚠️ 不配置验证器的实体类型只做基本的类型检查,不做权限验证

总结

通过配置驱动 + 父类聚合的架构,我们实现了:

  • 零子类创建 - 业务模块无需创建验证器子类
  • 零修改扩展 - 添加新实体类型只需配置 + 实现验证器
  • 灵活配置 - 可选配置权限验证器
  • 跨模块复用 - 所有核心组件在基础设施层,所有模块可用
  • 集中管理 - 所有配置在模块启动类中一目了然
  • 符合SOLID原则 - 开闭原则、单一职责、依赖倒置

这是一个真正面向扩展开放、面向修改关闭的设计!🎉