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);
}
}
}
注意事项
- ✅ 所有验证器类实现
IEntityAccessValidator接口 - ✅ 验证器通过
ITransientDependency自动注册 - ✅ 配置集中在模块启动类的
Configure<CommonAttachmentOptions>中 - ✅ 验证逻辑应抛出明确的异常信息
- ✅ 建议为每个验证器编写单元测试
- ⚠️ 不配置验证器的实体类型只做基本的类型检查,不做权限验证
总结
通过配置驱动 + 父类聚合的架构,我们实现了:
- ✨ 零子类创建 - 业务模块无需创建验证器子类
- ✨ 零修改扩展 - 添加新实体类型只需配置 + 实现验证器
- ✨ 灵活配置 - 可选配置权限验证器
- ✨ 跨模块复用 - 所有核心组件在基础设施层,所有模块可用
- ✨ 集中管理 - 所有配置在模块启动类中一目了然
- ✨ 符合SOLID原则 - 开闭原则、单一职责、依赖倒置
这是一个真正面向扩展开放、面向修改关闭的设计!🎉