## 项目最高优先级规则
- 默认使用中文回答。
- 优先遵守本文件规则;不得用通用最佳实践覆盖项目既有风格。
- 修改前必须先阅读相关现有模块,优先参照 `HotWordsService` / `ProductTagsService` / `ContactUsService`。
- 浮点类型默认是2位小数,除非是我的特殊要求
- 不随意重构既有代码,不顺手改无关变量名、格式、结构、字段名或路由。
- 如认为需要抽方法、加常量、改字段名、兼容字段、调整结构,必须先说明原因并征求确认。
## 实现前必须先做
- 判断该需求是否适合成熟开源方案;适合时列出最流行 3 个并对比优缺点。
- 简单修 bug、字段修正、项目风格收敛等不适合开源方案时,说明“不适用”的原因即可。
- 实现前列出最可能出 bug 的 5 个边界或异常场景。
- 只为边界条件和异常场景写测试,不测试正常流程。
- 写代码前先按本文件做一遍硬约束检查,避免事后返工。
## 字段与命名
- 定义字段禁止使用 `_` 开头。
- 该规则适用于数据库字段、接口字段、内部数组键、临时结构键、测试断言字段。
- 错误结构使用 `error`、`message` 等明确字段,不使用 `_error`。
- 字段名必须前后端统一,不做别名兼容,不引入多个同义字段。
- 不擅自改字段名;确需修改时先说明影响并征求确认。
## 后端 MVC 规范
- 新增后端模块 route 使用 RESTful 风格。
- 维护旧模块时,不擅自改动既有路由结构。
- Controller 保持轻量,只读取 `Request` 参数并调用 Service,不写业务逻辑。
- Controller 参数读取规则:
- 列表:`$params = Request::get();`
- 新增:`$data = Request::post();`
- 更新:`$data = Request::param();`
- 删除/详情:`$id = Request::param('id');`
## Service 编写规范
- Service 按业务流程顺序直写:参数校验、数据查询、业务判断、保存、返回结果。
- Service 方法不要写参数类型和返回类型,例如 `public function update($data)`。
- PHP 不写强类型参数和返回类型,不使用 `(int)`、`(string)` 等强转处理业务字段。
- 不使用 `throw new \InvalidArgumentException` 做业务校验,统一返回 `HttpRequest::error()`。
- 不为了“复用感”强行拆 helper。
- 单调用方法默认禁止拆出,应直接写在当前业务流程内。
- 只有多处真实复用、且用户确认后,才允许新增 helper。
- 不使用常量包装简单业务值,直接使用数据库约定值,并在必要注释或返回文案中说明含义。
- 字典映射优先使用当前方法内局部数组,例如 `$typeArr = ["产品"=>1,"分类"=>2,"搜索词"=>3];`。
## 防御性代码与边界输入
- 不为了让局部代码“看起来更稳”而到处写空值兜底、默认值、trim()、try/catch。
- 防御性代码只能写在真实边界处,例如 Request 入参、导入文件、外部接口返回、环境变量、配置文件、数据库查询结果。
- 已经由上游明确校验过的字段,在当前业务流程中按契约使用,不重复兜底;如认为上游契约不可信,必须先说明原因并征求确认。
- 核心业务逻辑中不要静默吞掉异常、不要把错误值转成空字符串/null/弱默认值继续执行。
- 可恢复失败使用项目统一结构返回,例如业务校验返回 `HttpRequest::error()`。
- 不可恢复错误应 fail fast,不要伪装成正常业务返回;例如启动配置缺失、关键依赖不可用、数据结构与代码契约冲突。
- try/catch 只包围需要兜底的业务入口;catch 中必须返回明确错误,不允许吞掉错误后继续执行。
- 不要用 `?? ''`、`?: []`、默认状态值等方式掩盖必填字段缺失,除非该默认值是当前业务明确约定。
- 不要为了兼容不确定输入而引入别名字段、同义字段或 `_error` 等临时字段;字段名必须前后端统一。
## 环境变量与配置读取
- 环境变量、配置文件属于边界输入,读取时必须集中解析、校验和说明默认值来源。
- 可以有默认值的配置必须是业务明确允许的,例如本地开发端口、非关键开关。
- 生产必填配置禁止默认值,例如密钥、Token、数据库密码、JWT_SECRET、第三方回调签名密钥。
- secret 类配置保持原样,不要偷偷 trim、大小写转换或格式重写;只能校验为空、长度、格式明显异常等问题。
- 普通 URL、域名、端口等可按业务需要 trim 或规范化,但必须在边界处完成,不要散落到业务代码中。
- 非法配置应在启动或初始化阶段失败,不要运行到业务流程里再返回空字符串、null 或弱默认值。
- 配置解析结果进入业务代码后,应被视为可信契约,不在每个使用点重复兜底。
## AI 代码审查重点
- 检查 AI 是否把真正应该暴露的问题悄悄吞掉。
- 检查 AI 是否把必填字段写成了弱默认值。
- 检查 AI 是否在非边界位置重复 trim、isset、empty、try/catch。
- 检查 AI 是否为了“兼容”引入多个同义字段或别名字段。
- 检查 AI 是否把业务错误、配置错误、系统异常混成一种兜底返回。
## 查询与数据处理
- 简单列表查询使用 `$where = [];` 后逐步追加条件,再 `where($where)->order(...)->select()->toArray()`。
- 小数据列表不强制分页;是否分页按现有模块或需求决定,不擅自改成交互不同的分页结构。
- 查询数据超过 2W 时,使用延迟关联。
- 展示字段补充可在列表循环中直接处理,例如 `target_name`、`use_count`。
- 简单关联数据优先用 `column()` 提前取映射表,避免循环里重复查询。
## 新增、更新、删除
- 新增/更新校验直接在当前方法内使用 `Validate`,不要额外抽校验方法。
- 唯一性校验直接写在 `create` / `update` 方法里,更新时用 `where('id', '<>', $data['id'])` 排除自身。
- 保存使用对应 Model 的 `saveAction()`,删除使用 `deleteAction()`,保持审计字段风格一致。
- 保存前常用写法为 `$saveData = $data; unset($saveData['id']);`,再调用 `(new Model())->saveAction(...)`。
- 删除前的占用检查直接写在 `delete` 方法里,例如被产品使用则直接返回错误。
- 业务限制直接写在对应 `create` / `update` / `import` 方法中,例如查重、上线数量限制、状态判断。
- 业务联动动作允许直接写在 `create` / `update` 内,不为复用感强行抽方法。
## 导入导出
- 导入逻辑按行处理,遇到错误直接 `HttpRequest::error()` 返回。
- 不做复杂错误聚合,除非用户明确要求。
- 导入等需要排查的问题可以在 catch 中 `Log::error()`。
- 导出逻辑可以直接组装 `$sheetData[]`,再调用 `exploadApi()`。
## 错误处理与注释
- try/catch 只包围需要兜底的业务入口,catch 中返回统一错误结构。
- 注释只写关键业务原因,不写空泛解释;简单代码不要堆注释。
- 保留项目现有缩进、空行和写法习惯,不为了“规范化”大面积格式化旧代码。
## 数据库、OpenAPI 与测试
- 后端如果是新逻辑,需要生成数据库迁移文件;迁移文件版本号后追加 4 位当前时分。
- 后端接口编写完成后,生成对应 OpenAPI 文件。
- OpenAPI 需要包含请求示例和响应数据注释。
- 不更新总的 `openapi.json` 文件,除非用户明确要求。
- 代码编写完成后,生成单元测试示例。
- 修改代码后必须运行相关测试。
## 完成前验收清单
- 是否读取并对齐参考模块风格。
- 是否按照原型图时间格式
- 是否存在单调用 helper,若存在必须说明用户已确认。
- 是否存在 `_` 开头字段或内部数组键。
- 是否存在强类型参数、返回类型或业务字段强转。
- 是否只测试边界和异常场景。
- 是否运行相关测试。
- 是否需要迁移文件和 OpenAPI;不需要时说明原因。
- 汇总改了哪些文件、覆盖了哪些异常场景、运行了哪些测试。