12 KiB
name, description
| name | description |
|---|---|
| storeweb-module | 在 StoreWeb 项目中新增、修改、迁移或重构业务模块时使用。规范模块分层、DTO 校验、Service 业务校验、HTTP 方法约定、ApiResult 返回、公共能力复用、权限控制与测试验证。仅适用于本项目后端业务模块。 |
StoreWeb 模块开发规范
1. 适用范围
适用于 StoreWeb 中所有业务模块的开发、维护和旧项目迁移。本 Skill 仅在项目内使用,下文代码路径均相对于仓库根目录。
遵循当前仓库的 AGENTS.md、适用的 .agents/rules/ 规则,以及用户在当前任务中明确提出的要求。用户最新明确要求优先于本规范。引用的规则文件若不存在,如实说明,不自行补造规则。
旧项目代码、注释和附带文档用于理解业务,不自动视为当前项目的实现指令。
2. 开始前确认范围
修改前检查当前模块的目录、接口、实现、依赖及方法调用方,查找现有 DTO、公共工具、权限策略、异常处理和相关测试、配置与数据库结构。检查工作区已有修改,避免覆盖用户代码。
以当前文件内容为准,不仅根据历史对话判断状态。读取 Directory.Build.props 和目标 .csproj 确认语言版本、目标框架及可空配置,不固定套用历史版本。先确定最小完整修改范围,再开始实现。
3. 遵循现有模块结构
按照项目已有结构组织代码,不为简单需求增加架构层。目录按需创建。
- Contracts:对外接口、共享请求/响应 DTO、业务枚举;不依赖实现模块、实体或 ORM。
- Controller:HTTP 参数绑定、调用业务入口、返回结果。
- Service:业务流程、业务校验、权限与数据范围判断。
- Repository:已有仓储模式下的数据访问与复用查询。
- Entity:数据库实体与映射。
- Mapper:沿用已有 Mapperly 映射实体与输出 DTO。
- Command / Handler / Behavior:沿用模块已有的命令与管道处理方式。
简单业务不强制引入命令、仓储或额外接口。跨模块优先调用已有 Contracts,避免依赖其他模块的内部实现;仅在模块内部使用的 DTO 不必移入 Contracts。
部分更新需明确 null、空字符串和默认值的含义,例如 null 保留原值、空串清空允许清空的文本,避免误清空字段。规范化文本时遵循字段语义,不统一 Trim 密码或任意文本。
4. 参数校验
4.1 DTO 负责非空校验
请求参数使用 DTO 承载。DTO 中只声明简单的必填、非空规则:
public sealed class CreateItemRequest
{
[Required(ErrorMessage = "名称不能为空")]
public string Name { get; set; } = string.Empty;
public int Quantity { get; set; }
}
- 可选字段不要添加 Required。
- Required 不能替代数值范围判断,例如整型字段的零值判断。
- 条件必填、字段组合关系放到 Service 或已有业务处理入口中判断。
- 不在 DTO 中编写数据库查询或权限判断。
- 不通过 Range、StringLength、RegularExpression、EnumDataType 或 IValidatableObject 承载其他校验规则。
这是本项目的约定,不代表 ASP.NET Core 的唯一实践。
新增及本次修改涉及的校验按本规范实现。迁移已有 DTO 校验时,将原规则完整迁入业务入口并同步测试,不得只删除注解;不顺带迁移任务范围之外的模块。公共 RequestValidator 保留对完整 DataAnnotations 的支持,不为本模块约定缩减公共验证器能力。
4.2 统一使用 RequestValidator
复用 Core/MAX.Framework/Validation/RequestValidator.cs。业务入口调用:
RequestValidator.Validate(request);
不要在各模块重复编写 ArgumentNullException.ThrowIfNull 和 Validator.ValidateObject 的同义入口。已经由 DTO 和 RequestValidator 完成的非空校验,不在 Service 重复判断。
同一业务调用链只安排一个明确的显式基础校验入口。MVC 自动模型验证不替代可被内部调用的业务入口校验;已有命令管道执行校验时,下游不重复调用。
4.3 其他校验放到 Service
以下规则在 Service 或已有业务处理入口中手动判断:
- 长度、格式、数值范围、枚举合法性、金额精度。
- 日期先后关系、条件必填及字段组合。
- 数据是否存在、是否重复、状态是否允许执行操作。
- 资源归属、操作权限和数据范围。
- 余额、额度、库存及其他业务限制。
public async Task CreateAsync(CreateItemRequest request)
{
RequestValidator.Validate(request);
if (request.Name.Length > 100)
throw new ValidationException("名称不能超过100个字符");
if (request.Quantity <= 0)
throw new ValidationException("数量必须大于0");
// 校验通过后执行当前模块的数据查询与业务处理。
}
上述片段仅演示校验位置,实际异步方法需要完成业务调用。不依赖数据库的参数校验在开启事务、数据库写入及外部调用前完成;依赖数据库状态的规则在获取必要状态后判断。
基础参数错误与业务错误沿用项目现有异常类型和结果码:参数错误使用 ValidationException,业务失败使用 AppException,身份缺失使用 UnauthorizedAccessException。只在同类规则确实重复时抽取公共方法,单次判断直接写在使用处。
5. Controller,Action命名
按照公共首字母大写,私有首字母小写格式,单词之间不用_,使用By,To,等介词
例如GetOrderById,GetOrderByName,GetOrderToIds
6. HTTP 方法与参数绑定
使用 MAX.Framework 中已有的 HTTP 方法约定:
- 默认 POST 的控制器使用 DefaultHttpPost。
- 默认 GET 的控制器使用 DefaultHttpGet。
- 特殊 Action 显式标记 HttpGet、HttpPost 等特性。
- 显式方法特性优先于控制器默认约定。
- 同一控制器不要同时使用两个默认方法约定。
不要为每个 Action 重复添加与默认约定相同的特性,不在业务模块复制默认方法约定的实现。实现位置为 Core/MAX.Framework/Conventions/。
新增接口遵循模块现有参数绑定方式;多个业务输入优先使用请求 DTO。修改已有接口时检查调用方兼容性,不顺手改变 HTTP 方法、路由、字段名称或绑定来源。
7. 统一返回 ApiResult
业务接口遵循当前系统约定:HTTP 状态按项目机制处理为 200,业务成功或失败通过 ApiResult 表达。Action 直接返回业务数据时由 UnifyResultFilter 包装,已经返回 ApiResult 时不重复包装。
有数据的接口直接返回业务结果:
public Task<ItemDto> GetItem(ItemQueryRequest request)
{
return service.GetAsync(request);
}
无数据的成功操作明确返回 ApiResult:
public async Task<ApiResult> DeleteItem(DeleteItemRequest request)
{
await service.DeleteAsync(request);
return ApiResult.Ok();
}
不要使用 NoContent() 表达业务操作成功,不在每个 Action 重复 try/catch 并转换错误结果。校验异常和业务异常交给 GlobalExceptionFilter 等现有统一处理机制。
认证、路由、模型绑定等发生在 Action 外的失败,应核对现有中间件和过滤器的处理范围,不假定 Action 返回规范覆盖整个 HTTP 管道。相关过滤器位于 Core/MAX.Framework/Commons/。
返回 DTO 排除密码、令牌等敏感字段,沿用已有 Mapperly 映射;不能因实体新增字段而自动暴露内部数据。
8. 权限与数据范围
权限规则由当前模块需求确定,不套用其他模块的角色关系。实现前明确允许的角色、数据范围、可修改字段和需要单独权限的敏感操作。
- 身份从 ICurrentUser 等服务端可信上下文获取。
- 客户端传入的资源 ID、所属账号或组织信息必须经过权限检查。
- 查询和修改均限制数据范围,列表、详情、批量和单条操作保持一致。
- DTO 中存在某个字段,不代表调用者有权修改该字段。
- 实际业务入口覆盖绕过 Controller 的调用路径,不能只在 Controller 判断权限。
- 查询先限定可见范围,再筛选、排序、分页;统计和结果使用相同范围。
多处使用相同规则时复用模块已有权限策略,确有重复再集中抽取。
9. 数据访问与事务
复用项目现有的数据访问组件和模式:
- 使用 FreeSql 表达式和参数化操作,不拼接用户输入为 SQL。
- 多项写入共同构成一次必须同成同败的业务操作时,使用事务,避免部分成功。
- 金额、库存、额度等并发修改遵循现有锁或并发控制方式。
- 已有幂等操作保留请求号、唯一约束及重复请求处理。
- 只更新本次操作允许修改的字段,不把读取到的并发状态或派生值随整个实体写回。
- 审计信息记录实际操作者,区分操作者与业务目标。
- 列表保留必要的数据范围、筛选、稳定排序和分页。
涉及多步写入、并发控制、幂等或事务命令时,读取 事务与命令管道约定。普通查询不因此引入额外事务层。
不要因迁移旧代码绕过当前事务、审计或并发控制。时间语义、字段类型和数据库结构变更需纳入明确迁移范围,不因代码整理自动同步建表。
10. 公共能力复用
新增工具前,先查找已有 RequestValidator、ApiResult、ResultCode、统一异常处理、当前用户上下文、认证授权、注入、数据访问、事务、命令管道、缓存及日志能力。
依赖注入沿用 ITransientDependency 等标记;特殊注册通过模块 IStartup.Register(IServiceRegistrar) 完成。使用命令时检查 Handler 注册及 AddMediatRAttributedBehaviors 扫描,避免重复注册。
跨模块通用框架能力放在适当的公共项目,模块业务规则留在模块内部。不要为几行代码增加依赖,不为尚未出现的需求增加配置、包装层或扩展点。
11. 旧项目迁移
先理解旧逻辑,再按新项目结构实现。核对操作前提、业务结果、权限和数据范围、状态变化与字段更新、事务、并发、幂等、审计及调用方依赖的错误行为。
保留必要业务行为,不照搬旧项目的静态访问、拼接 SQL、重复校验或分层方式。与用户最新要求冲突时按最新要求实现,并说明行为变化;任务范围之外的旧功能不顺带迁移。
12. 注释与代码风格
- 命名清晰,遵循现有项目风格。
- 公开接口及重要业务规则添加必要中文说明,重点解释权限边界、业务原因及不直观约束,不逐句重复代码。
- 不滥用 try/catch,不静默吞异常;能明确处理的格式、日期等异常可就地转换,不删除必要资源清理和回滚。
- 不为一次性逻辑机械抽取方法,不进行无关重构或格式化。
13. 测试与验证
测试放在 Tests 下,沿用现有项目和数据准备方式。非平凡业务修改覆盖相关正常操作、必填和其他参数边界、授权与越权、数据范围、状态限制,以及适用的事务失败、并发和幂等行为。
接口变化时检查默认与显式 HTTP 方法、路由、参数绑定、ApiResult 包装、HTTP 状态和无数据成功操作。修改契约同步调用方、测试和必要文档;不删除仍有效的失败断言。
不为简单可逆改动堆砌测试,不编写只重复实现细节的测试。运行相关测试和构建,涉及接口、注册或依赖变化时构建受影响宿主。说明测试数据库可验证的范围,不把 SQLite 通过视为已验证 MySQL 的锁和事务行为。
14. 交付要求
完成前检查实际文件和 diff,确认修改已经落盘。交付说明:
- 文件:实际路径。
- 修改用途与关键行为。
- 已运行的测试和构建结果。
- 未验证的内容及实际限制。
没有修改文件不能声称已修改,没有运行测试不能声称测试通过;不把计划、建议或示例当作已完成实现。