跳到主要内容

共享功能

业务模块通过 src/BuildingBlocks/src/FreeKit/ 复用认证、数据访问、日志、缓存、事件和模块化能力。共享层提供的是组合积木,不替业务模块决定领域边界。

BuildingBlocks 边界

项目当前职责
IGeekFan.FreeKit.InfrastructureController/Application/Domain 基类,认证授权,中间件,DTO,异常、缓存和可观测性辅助类型
IGeekFan.FreeKit.WebUseFreeKit 组合入口,MVC/Newtonsoft.Json、Swagger、FreeSql、Redis、CAP、认证和宿主扩展
IGeekFan.FreeKit.Auth.ClientOAuth2/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.cs
  • src/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 / CurrentUnitOfWorkFreeSql 工作单元
CapPublisherCAP 事件发布
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 收集 moduleTypeMapextraAssemblies 中的程序集,然后注册:

  • 名称以 Service 结尾的公开类:按接口和自身注册为每生命周期实例,并启用工作单元拦截。
  • 实现 IDomainService 的公开类:按接口注册为每生命周期实例。
  • 实现 ITransientDependencyIScopedDependencyISingletonDependency 的类:按对应生命周期注册。

标记接口适用于不符合 *Service / IDomainService 约定的组件:

标记接口Autofac 生命周期
ITransientDependencyInstancePerDependency
IScopedDependencyInstancePerLifetimeScope
ISingletonDependencySingleInstance

需要 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。仓库中没有通用的 NotFoundExceptionForbiddenException 基类,不要在示例中引用它们。

MVC 边界由 AddNewtonsoftJson 配置,DTO 不应依赖 System.Text.Json 专属特性。外部 HTTP 客户端的内部模型仍可使用 System.Text.Json

新代码公共约定

  • 数据库存储时间使用 DateTime.UtcNow
  • Application、Domain、Infrastructure 中返回 Task 的业务方法以 Async 结尾;Controller Action 和框架 Handler 可按框架习惯命名。
  • 外部 HTTP 调用通过 IHttpClientFactory;需要重试/超时/熔断时使用 Polly V8 AddStandardResilienceHandler
  • Application 和 Controller 不新增裸 IFreeSql 依赖,优先使用 IAuditBaseRepository<TEntity[,TKey]> 或 Domain Manager。

详细规则见仓库的 .github/guidelines/conventions.md

源码索引

  • src/BuildingBlocks/IGeekFan.FreeKit.Web/DI/WebApplicationBuilderExtensions.cs
  • src/BuildingBlocks/IGeekFan.FreeKit.Web/DI/ContainerBuilderExtensions.cs
  • src/BuildingBlocks/IGeekFan.FreeKit.Infrastructure/Extensions/AutofacExtensions.cs
  • src/BuildingBlocks/IGeekFan.FreeKit.Web/Filters/HttpGlobalExceptionFilter.cs
  • src/FreeKit/src/IGeekFan.FreeKit.Extras/Dependency/FreeKitModule.cs
  • src/FreeKit/src/IGeekFan.FreeKit.Extras/Dependency/UnitOfWorkModule.cs

相关文档