跳到主要内容

CommonAttachment 架构演进 - 配置驱动的权限验证设计

🎯 设计目标

实现一个灵活、可扩展、配置驱动的文件附件系统,让不同的业务实体都能:

  1. ✅ 关联文件上传数据
  2. ✅ 配置独立的权限验证策略
  3. ✅ 零代码修改即可扩展新实体类型

📊 架构演进对比

阶段 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 (基础设施层实现) ↓

  1. ValidateEntityTypeAsync - 检查实体类型是否在 CommonAttachmentOptions 中配置 ↓
  2. 从 CommonAttachmentOptions.EntityTypes 获取定义 ↓
  3. 检查 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 ✨
开闭原则⚠️
配置驱动
可选验证器
跨模块复用⚠️
集中管理
易于测试

这是一个真正企业级的、可扩展的、配置驱动的权限验证架构! 🎊