共享功能
业务模块通过 src/BuildingBlocks/ 和 src/FreeKit/ 复用认证、数据访问、日志、缓存、事件和模块化能力。共享层提供的是组合积木,不替业务模块决定领域边界。
BuildingBlocks 边界
| 项目 | 当前职责 |
|---|---|
IGeekFan.FreeKit.Infrastructure | Controller/Application/Domain 基类,认证授权,中间件,DTO,异常、缓存和可观测性辅助类型 |
IGeekFan.FreeKit.Web | UseFreeKit 组合入口,MVC/Newtonsoft.Json、Swagger、FreeSql、Redis、CAP、认证和宿主扩展 |
IGeekFan.FreeKit.Auth.Client | OAuth2/SSO 客户端能力 |
IGeekFan.FreeKit.CLI | 命令行与脚手架能力 |
FreeKit 子模块中的 IGeekFan.FreeKit.Extras 提供审计实体、仓储、工作单元和依赖注入标记;IGeekFan.FreeKit.Modularity 提供模块启动协议。
HTTP 基类
真实继承关系很克制:
[ApiController]
public abstract class KitApiControllerBase : ControllerBase;
[Authorize]
public abstract class FreeKitController : KitApiControllerBase;
源码位于:
src/BuildingBlocks/IGeekFan.FreeKit.Infrastructure/Application/KitApiControllerBase.cssrc/BuildingBlocks/IGeekFan.FreeKit.Infrastructure/Application/FreeKitController.cs
因此:
FreeKitController默认要求认证,具体 scheme 由 DefaultPolicy 决定。- Controller 上的
[AllowAnonymous]可以开放某个 Action。 FreeKitController不提供CurrentUser、仓储、日志或异常处理属性;这些能力属于应用服务或 ASP.NET Core 本身。- 全局模型验证和异常过滤器由
IGeekFan.FreeKit.Web注册。
推荐的 Controller 只负责 HTTP 语义并转发到应用服务:
[ApiExplorerSettings(GroupName = "plat")]
[Route("api/plat/todo")]
[ApiController]
[Authorize]
public sealed class ToDoController(IToDoService service) : FreeKitController
{
[HttpGet]
public Task<PagedResultDto<ToDoDto>> GetListAsync(
[FromQuery] ToDoQuery query)
{
return service.GetListAsync(query);
}
}
对应实现是 src/Services/Platform/FreeKit.Platform.HttpApi/ToDo/Controllers/ToDoController.cs。
Application Service
需要当前用户、授权、CAP、MediatR、工作单元、日志或本地化时,应用服务继承 ApplicationService:
public sealed class ExampleService : ApplicationService, IExampleService
{
public async Task<Guid> ExecuteAsync()
{
Guid userId = CurrentUser.FindUserId();
bool allowed = await PermissionStore.CheckPermissionAsync(
"Platform.Example");
if (!allowed)
{
throw new BusinessException("Permission denied")
.WithStatusCode(StatusCodes.Status403Forbidden);
}
Logger.LogInformation("User {UserId} executed example", userId);
return userId;
}
}
ApplicationService 的真实公共能力包括:
| 属性 | 用途 |
|---|---|
CurrentUser | 当前用户、租户和角色 |
AuthorizationService / PermissionStore | 策略或权限检查 |
UnitOfWorkManager / CurrentUnitOfWork | FreeSql 工作单元 |
CapPublisher | CAP 事件发布 |
Mediator | 进程内 MediatR 调度 |
Logger | 当前服务类型日志 |
L | 模块本地化资源 |
实现位于 src/BuildingBlocks/IGeekFan.FreeKit.Infrastructure/Application/AppliactionService.cs。文件名保留了历史拼写,但类型名是 ApplicationService。
通用 CRUD 可继承 CrudAppService<...>。它直接接收 IBaseRepository<TEntity,TKey>,并提供分页、映射和基础 CRUD 扩展点;复杂业务规则仍应进入具体 Service 或 Domain Manager。
Domain Service
领域服务接口继承 IDomainService,实现通常继承 DomainService:
public interface IArticleManager : IDomainService
{
Task UpdateLikeQuantityAsync(Guid articleId, int delta);
}
public sealed class ArticleManager(
IAuditBaseRepository<Article> repository)
: DomainService, IArticleManager
{
public async Task UpdateLikeQuantityAsync(Guid articleId, int delta)
{
Article article = await repository.Select
.Where(x => x.Id == articleId)
.FirstAsync();
article.UpdateLikeQuantity(delta);
await repository.UpdateAsync(article);
}
}
源码范本是 FreeKit.CmsKit.Domain.Articles.ArticleManager。Manager 负责业务不变量和跨实体规则,Controller 不应直接调用 Manager。
Autofac 自动注册
UseFreeKit 收集 moduleTypeMap 和 extraAssemblies 中的程序集,然后注册:
- 名称以
Service结尾的公开类:按接口和自身注册为每生命周期实例,并启用工作单元拦截。 - 实现
IDomainService的公开类:按接口注册为每生命周期实例。 - 实现
ITransientDependency、IScopedDependency、ISingletonDependency的类:按对应生命周期注册。
标记接口适用于不符合 *Service / IDomainService 约定的组件:
| 标记接口 | Autofac 生命周期 |
|---|---|
ITransientDependency | InstancePerDependency |
IScopedDependency | InstancePerLifetimeScope |
ISingletonDependency | SingleInstance |
需要 keyed service、替换默认实现或依赖明确初始化顺序时,应在模块 Startup 中显式注册。CmsKit HttpApi 使用 services.Replace(...) 将空实时通知实现替换为 SignalR 实现,就是这种场景。
Web 管线顺序
主宿主的认证相关顺序为:
app.UseRequestLocalization();
app.UseAuthentication();
app.UseMiddleware<EncryptionMiddleware>()
.UseCurrentUserAccessor();
app.UseRouting()
.UseRateLimiter()
.UseAuthorization()
.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHealthChecks("/health");
});
UseCurrentUserAccessor() 必须在认证之后、需要 CurrentUser 的业务处理之前执行。模块的 Configure 在主 Host 中由 app.ConfigureModules().Init() 更早执行,用于初始化和端点映射,不应假设它拥有独立管线。
异常与 API JSON
业务校验失败使用真实的 BusinessException:
throw new BusinessException("Article not found")
.WithStatusCode(StatusCodes.Status404NotFound);
HttpGlobalExceptionFilter 将业务异常转换为统一 ApiResponse,其他异常记录日志并返回 500。仓库中没有通用的 NotFoundException 或 ForbiddenException 基类,不要在示例中引用它们。
MVC 边界由 AddNewtonsoftJson 配置,DTO 不应依赖 System.Text.Json 专属特性。外部 HTTP 客户端的内部模型仍可使用 System.Text.Json。
新代码公共约定
- 数据库存储时间使用
DateTime.UtcNow。 - Application、Domain、Infrastructure 中返回
Task的业务方法以Async结尾;Controller Action 和框架 Handler 可按框架习惯命名。 - 外部 HTTP 调用通过
IHttpClientFactory;需要重试/超时/熔断时使用 Polly V8AddStandardResilienceHandler。 - Application 和 Controller 不新增裸
IFreeSql依赖,优先使用IAuditBaseRepository<TEntity[,TKey]>或 Domain Manager。
详细规则见仓库的 .github/guidelines/conventions.md。
源码索引
src/BuildingBlocks/IGeekFan.FreeKit.Web/DI/WebApplicationBuilderExtensions.cssrc/BuildingBlocks/IGeekFan.FreeKit.Web/DI/ContainerBuilderExtensions.cssrc/BuildingBlocks/IGeekFan.FreeKit.Infrastructure/Extensions/AutofacExtensions.cssrc/BuildingBlocks/IGeekFan.FreeKit.Web/Filters/HttpGlobalExceptionFilter.cssrc/FreeKit/src/IGeekFan.FreeKit.Extras/Dependency/FreeKitModule.cssrc/FreeKit/src/IGeekFan.FreeKit.Extras/Dependency/UnitOfWorkModule.cs