CommonAttachment 架构演进 - 配置驱动的权限验证设计
🎯 设计目标
实现一个灵活、可扩展、配置驱动的文件附件系统,让不同的业务实体都能:
- ✅ 关联文件上传数据
- ✅ 配置独立的权限验证策略
- ✅ 零代码修改即可扩展新实体类型
📊 架构演进对比
阶段 1: 硬编码方式(❌ 不推荐)
// 每添加新实体都需要修改这个 switch
public override async Task ValidateAccessAsync(string entityType, Guid entityId)
{
switch (entityType)
{
case nameof(Article):
// Article 验证逻辑
break;
case nameof(ShortMsg):
// ShortMsg 验证逻辑
break;
default:
throw new ArgumentException("未支持的实体类型");
}
}
缺点:
- ❌ 违反开闭原则
- ❌ 验证逻辑耦合在一起
- ❌ 难以测试和维护
阶段 2: 字典映射 + 策略模式(⚠️ 有改进空间)
// 验证器映射仍然硬编码
private static readonly Dictionary<string, Type> EntityTypeValidatorMap = new()
{
[nameof(Article)] = typeof(ArticleAccessValidator),
[nameof(ShortMsg)] = typeof(ShortMsgAccessValidator)
};
优点:
- ✅ 验证逻辑解耦
- ✅ 遵循单一职责原则
缺点:
- ❌ 映射关系仍然硬编码
- ❌ 添加新实体仍需修改验证器类
阶段 3: 配置驱动 + 父类聚合(✨ 最终方案)
// 配置中定义实体类型和验证器(无需子类)
services.Configure<CommonAttachmentOptions>(options =>
{
options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(Article))
.WithAccessValidator<ArticleAccessValidator>()
);
// 可选配置验证器
options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(PublicDocument))
// 不配置验证器,表示公开访问
);
});
优点:
- ✅ 完全配置驱动
- ✅ 验证器可选配置
- ✅ 符合开闭原则
- ✅ 跨模块复用
- ✅ 集中管理
- ✅ 无需创建验证器子类!
🏗️ 最终架构设计
核心层次
┌─────────────────────────────────────────────────────────┐
│ FreeKit.Identity.Infrastructure │
│ (基础设施层 - 提供完整实现) │
├─────────────────────────────────────────────────────────┤
│ │
│ IEntityAccessValidator (验证策略接口) │
│ └─ 业务模块实现具体验证逻辑 │
│ │
│ CommonAttachmentValidatorBase (完整实现类) │
│ ├─ ValidateEntityTypeAsync() │
│ ├─ ValidateAccessAsync() ← 动态解析验证器 │
│ ├─ GetAsync() │
│ └─ IsDefinedAsync() │
│ └─ 实现 ICommonAttachmentValidator │
│ │
│ CommonAttachmentOptions (通用配置类) │
│ └─ EntityTypes: List<CommonFileEntityTypeDefinition> │
│ │
│ CommonFileEntityTypeDefinition (配置定义类) │
│ ├─ AccessValidatorType: Type? │
│ └─ WithAccessValidator<T>() (流畅API) │
└─────────────────────────────────────────────────────────┘
▲
│ 使用
│
┌─────────────────────────────────────────────────────────┐
│ FreeKit.CmsKit │
│ (业务模块 - 仅配置和实现验证策略) │
├─────────────────────────────────────────────────────────┤
│ │
│ ArticleAccessValidator : IEntityAccessValidator │
│ ShortMsgAccessValidator : IEntityAccessValidator │
│ └─ 具体的权限验证逻辑 │
│ │
│ 配置(在 CmsKitModuleStartup 中): │
│ services.Configure<CommonAttachmentOptions>(options => │
│ options.EntityTypes.Add( │
│ new CommonFileEntityTypeDefinition(...) │
│ .WithAccessValidator<...>() │
│ ); │
│ ); │
│ │
│ ✨ 无需创建 CmsKitCommonAttachmentValidator! │
│ ✨ 直接使用父类 CommonAttachmentValidatorBase! │
└─────────────────────────────────────────────────────────┘
### 数据流
API 请求上传文件 ↓ CommonAttachmentService.AddAttachmentAsync(input) ↓ _attachmentValidator.ValidateAccessAsync(entityType, entityId) ↓ CommonAttachmentValidatorBase (基础设施层实现) ↓
- ValidateEntityTypeAsync - 检查实体类型是否在 CommonAttachmentOptions 中配置 ↓
- 从 CommonAttachmentOptions.EntityTypes 获取定义 ↓
- 检查 CommonFileEntityTypeDefinition.RequireAccessValidation ├─ true → 通过 IComponentContext 解析 AccessValidatorType │ ↓ │ validator.ValidateAsync(entityId) │ ↓ │ ArticleAccessValidator / ShortMsgAccessValidator │ (业务模块实现的具体验证策略) │ ↓ │ 检查用户权限 (CreateUserId, IsAdmin等) │ ↓ │ ├─ 通过 → 允许上传 │ └─ 失败 → 抛出 AuthenticationException │ └─ false → 跳过权限验证(公开访问) ↓ 允许上传
---
## 💡 关键设计模式
### 1. 策略模式 (Strategy Pattern)
```csharp
// 策略接口
public interface IEntityAccessValidator
{
Task ValidateAsync(Guid entityId);
}
// 具体策略
public class ArticleAccessValidator : IEntityAccessValidator { }
public class ShortMsgAccessValidator : IEntityAccessValidator { }
2. 流畅接口 (Fluent Interface)
new CommonFileEntityTypeDefinition(nameof(Article))
.WithAccessValidator<ArticleAccessValidator>()
3. 依赖注入 (Dependency Injection)
// 自动注册
public interface IEntityAccessValidator : ITransientDependency { }
// 动态解析
var validator = _componentContext.Resolve(validatorType);
4. 配置即代码 (Configuration as Code)
// 基础设施层配置(跨模块通用)
services.Configure<CommonAttachmentOptions>(options =>
{
options.EntityTypes.Add(...);
});
5. 模板方法模式 (Template Method + 父类聚合)
// 父类提供完整实现,子类无需重写
public class CommonAttachmentValidatorBase : ICommonAttachmentValidator
{
public virtual async Task ValidateAccessAsync(string entityType, Guid entityId)
{
// 通用验证流程,从配置中动态调度
}
}
🚀 使用场景示例
场景 1: 私有文章附件(需要权限)
// 配置(在 CmsKitModuleStartup 中)
services.Configure<CommonAttachmentOptions>(options =>
{
options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(Article))
.WithAccessValidator<ArticleAccessValidator>()
);
});
// 验证器实现
public class ArticleAccessValidator : IEntityAccessValidator
{
public async Task ValidateAsync(Guid entityId)
{
// 只有作者和管理员可以上传附件
if (createUserId != _currentUser.FindUserId() && !IsAdmin)
throw new AuthenticationException();
}
}
场景 2: 公开文档(无需权限)
// 配置 - 不调用 WithAccessValidator
services.Configure<CommonAttachmentOptions>(options =>
{
options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(PublicDocument))
);
});
// 系统自动跳过权限验证 ✅
场景 3: 复杂权限(组合验证)
// 验证器
public class OrderAccessValidator : IEntityAccessValidator
{
public async Task ValidateAsync(Guid entityId)
{
// 1. 检查订单所有者
// 2. 检查订单状态
// 3. 检查用户角色
// 4. 检查时间范围
}
}
📦 跨模块复用
现在所有模块都使用统一的基础设施配置,无需依赖特定模块!
Mall 模块使用示例
// 1. 创建验证器(在 Mall 模块中)
namespace FreeKit.Mall.Domain.Files;
public class ProductAccessValidator : IEntityAccessValidator
{
public async Task ValidateAsync(Guid entityId)
{
// 产品附件权限逻辑
}
}
// 2. 配置(在 MallModuleStartup 中)
services.Configure<CommonAttachmentOptions>(options =>
{
options.EntityTypes.Add(
new CommonFileEntityTypeDefinition(nameof(Product))
.WithAccessValidator<ProductAccessValidator>()
);
});
// ✅ 自动使用基础设施层的 CommonAttachmentValidatorBase
// ✅ 无需创建 MallCommonAttachmentValidator 子类!
完全无需修改基础设施代码!
✅ 验证清单
使用此架构时,请确保:
- 实体类型已在模块配置中注册
- 验证器类实现了
IEntityAccessValidator接口 - 验证器类继承了
ITransientDependency(自动注册) - 配置中使用
.WithAccessValidator<T>()关联验证器 - 验证逻辑抛出明确的异常(如
AuthenticationException) - 编写了验证器的单元测试
🎉 总结
通过三次迭代演进,我们实现了一个:
| 特性 | 阶段1 | 阶段2 | 阶段3 ✨ |
|---|---|---|---|
| 开闭原则 | ❌ | ⚠️ | ✅ |
| 配置驱动 | ❌ | ❌ | ✅ |
| 可选验证器 | ❌ | ❌ | ✅ |
| 跨模块复用 | ❌ | ⚠️ | ✅ |
| 集中管理 | ❌ | ❌ | ✅ |
| 易于测试 | ❌ | ✅ | ✅ |
这是一个真正企业级的、可扩展的、配置驱动的权限验证架构! 🎊