跳到主要内容

开发规范

本文整理的是当前仓库可执行的开发约定。涉及目录和注册方式时,以 src/Services/Host/FreeKit.Host/Program.cs 及各模块的 *ModuleStartup.cs 为准;历史代码仍有少量例外,不应把例外复制到新代码。

分层与命名

主业务模块采用务实的 DDD 分层,而不是要求每个子域都有完全相同的目录:

职责命名
Domain/聚合、领域规则和需要持久化协作的领域服务IXxxManager / XxxManager
Application/用例编排、权限、DTO 转换、事务协作IXxxService / XxxService
Controllers/*.HttpApiHTTP 协议、路由和响应包装XxxController
Infrastructure/数据库迁移、外部实现和基础设施适配按能力命名

面向用户的 API 不直接暴露 Domain 内部服务。Controller 调用 Application Service;复杂查询和持久化通过 Domain Manager 或仓储封装。

Controller 保持薄

  • 继承 FreeKitController;它默认带授权要求。
  • 只有明确允许游客访问的 Action 才标记 [AllowAnonymous]
  • Controller 只处理路由参数、协议转换和 ApiResponse,业务规则留在 Application / Domain。
  • Controller Action 是异步命名的例外,可以不使用 Async 后缀;其他返回 Task 的业务方法应使用 Async
[Area("cms")]
[Route("api/cms/articles")]
[ApiController]
public class ArticleController(IArticleService articleService) : FreeKitController
{
[HttpGet("{id:guid}")]
public Task<ArticleDto> Get(Guid id) => articleService.GetAsync(id);
}

Application Service 基类

Application Service 继承 ApplicationService。基类采用延迟解析,已经提供:

  • CurrentUserPermissionStoreAuthorizationService
  • Logger / LoggerFactory
  • CapPublisherMediator
  • UnitOfWorkManager / CurrentUnitOfWork
  • 本地化访问器 L

因此不要仅为了取得这些对象再次注入同一依赖。对“查看本人或凭权限查看指定用户”的列表类接口,可使用基类的 GetEffectiveUserIdAsync(permission, queryUserId);资源级修改仍必须根据实体所有者单独校验,不能把列表权限检查当成资源授权。

数据访问边界

Application 和 Controller 新代码不得依赖 FreeSql.IFreeSql,也不得直接写 fsql.Select<T>()。优先使用:

IAuditBaseRepository<TEntity>
IAuditBaseRepository<TEntity, TKey>

需要跨聚合规则或复杂查询时,在仓储之上建立 Domain Manager 或专用查询对象。仓库中仍有少量直连 IFreeSql 的历史债务;它们是迁移清单,不是示例。PlatformArchitectureTests 会守护 Platform 的大部分 Application / Controller 边界,Holiday 暂列为显式豁免。

异常与设置

场景异常
业务规则不满足、资源不存在、业务状态不允许BusinessException
服务未注册、系统配置缺失或程序状态不可能成立InvalidOperationException

设置项在 *SettingDefinitionProvider.cs 定义,通过 ISettingManager.GetSystemOrNullAsync(name) 读取系统值。数据库设置应按请求读取以支持热更新,不要复制到静态缓存。JSON 设置可用 WithJsonSchemaType(typeof(TSettings)) 提供编辑模型,但不要标记为加密,否则保存后的密文不能直接反序列化为 JSON。

外部 HTTP 与序列化

外部调用使用 AddHttpClient<TClient, TImplementation>(),并通过 AddStandardResilienceHandler 配置超时、重试和熔断。Polly V8 要求熔断采样窗口 SamplingDuration 至少是单次尝试超时的两倍;长耗时操作还要避免一次完整超时后再次产生昂贵请求。Platform.TTSMimoTtsClient 是当前参考实现。

API 边界由 Web 构建块统一使用 Newtonsoft.Json;API DTO 不应依赖仅 System.Text.Json 能识别的特性。仅用于外部 HTTP 的内部模型可以使用 System.Text.Json。

时间与提交前检查

新写入的数据库时间戳使用 DateTime.UtcNow,展示时再转换时区。不要一次性替换历史 DateTime.Now,因为这可能改变既有数据含义。

提交前至少确认:

  1. Controller 没有业务规则或 ORM 查询。
  2. Application / Domain 的异步方法以 Async 结尾。
  3. 资源级读写同时检查身份、权限和所有权。
  4. 新配置没有静态缓存,敏感值没有写进示例文档。
  5. 外部 HTTP 有统一弹性管线。
  6. 运行与改动范围相匹配的构建和测试;参见测试与验证