按数据表开发业务(实战)
从一张空表到管理后台里可用的功能页面,完整走一遍。本文用「商品品牌管理」作为例子(表 brands,模块 brand),你可以照着换成自己的业务表。
前置:已按 MCP 开发助手 完成客户端接入,并执行过一次 init_client_assets。没有 MCP 也能按本文做,只是每一步都要自己动手,AI 帮不上忙。
先记住分工,这决定了成品质量
基础 CRUD 由生成器出,业务逻辑由 AI 加。 框架规范明确要求:基础增删改查必须先用 php think make:crud 生成骨架,AI 只在骨架之上做增量(审核流转、状态机、批量操作、跨表编排)。让 AI 从零手写整套 CRUD 是错误姿势——它绕过了生成器已经保证正确的分层、命名、路由和前端联动,反而更容易出错。
1. 设计并创建数据表
字段命名遵循框架约定(详见业务模块规范 第 2 章):时间戳固定 created_at / updated_at / deleted_at,状态字段固定 status(1 启用 / 0 禁用)。
CREATE TABLE `brands` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '品牌ID',
`name` varchar(64) NOT NULL DEFAULT '' COMMENT '品牌名称',
`logo` varchar(255) NOT NULL DEFAULT '' COMMENT '品牌LOGO',
`initial` char(1) NOT NULL DEFAULT '' COMMENT '首字母',
`website` varchar(255) NOT NULL DEFAULT '' COMMENT '官网地址',
`description` varchar(500) NOT NULL DEFAULT '' COMMENT '品牌简介',
`sort` int NOT NULL DEFAULT '0' COMMENT '排序,越大越靠前',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1启用 0禁用',
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime DEFAULT NULL COMMENT '更新时间',
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
KEY `idx_status_sort` (`status`, `sort`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品品牌表';字段注释直接决定生成质量
COMMENT 不是写给人看的装饰。make:crud 的类型推断、表单控件映射、搜索条件判定全部读取真实表结构和注释;AI 也靠注释理解字段语义。注释缺失时,生成器只能按类型兜底成普通输入框,AI 则只能靠字段名猜——initial 这种字段没有注释,谁都不知道它是「首字母」。
建完表后同步写入 server/public/install/data/schema.sql,否则全新安装会缺表。
2. 让 AI 先认识这张表
打开 Cursor / Kimi Code / Claude Code,直接说:
看一下 brands 表结构,说说这个模块该怎么建助手会调用 show_table_schema 读出真实字段,再结合 get_conventions 的框架规范给出方案。这一步的价值是校对:如果它复述的字段和你的设计有出入,说明表还没建好或改动没生效,此时发现比生成完代码再发现便宜得多。
推导规则也在这一步确认。表名 brands → 模块目录 brand → 主类名 Brand(复数转单数、全小写拼接、PascalCase 类名),生成的文件会落在:
server/app/model/brand/Brand.php
server/app/repository/brand/BrandRepository.php
server/app/service/brand/BrandService.php
server/app/adminapi/controller/v1/brand/BrandController.php
server/app/adminapi/validate/v1/brand/BrandValidate.php
server/app/adminapi/route/brand.php
admin/src/api/brand.ts
admin/src/views/.../brand/index.vue、components/BrandForm.vue3. 生成基础骨架
cd server
php think make:crud brands --module=brand --model=Brand或在管理后台「开发工具 → 代码生成器」里可视化操作。产物清单、字段类型映射规则见 CRUD 生成规范。
这一步产出的是能跑通的完整基础 CRUD:列表分页、搜索、新增、编辑、删除、状态切换,前后端都有。此时刷新后台(配置好菜单后,见第 6 节)就已经能用了。
4. 用 AI 做业务增量
骨架不覆盖的业务逻辑交给 AI。以「批量启用 / 禁用品牌」为例,在对话里说:
给 brand 模块增加批量启用和批量禁用功能,接收 ids 数组装了引导资产的助手会按标准流程自己执行:读现有 BrandController / BrandService / 路由文件学风格 → 查 get_conventions 的 route 主题确认批量动作怎么命名 → 写代码 → 预览 → 落盘 → 校验。
它应该产出这样的路由(框架规范:多词动作用连字符,对应方法名用 camelCase):
Route::group('brand', function () {
Route::get('list', 'v1.brand.BrandController/list');
Route::post('batch-enable', 'v1.brand.BrandController/batchEnable');
Route::post('batch-disable', 'v1.brand.BrandController/batchDisable');
})->middleware(['admin_auth', 'admin_permission', 'admin_log']);业务方法追加在 Service 上,查询仍然下沉到 Repository:
public function batchUpdateStatus(array $ids, int $status): bool
{
$result = $this->brandRepository->batchUpdate($ids, ['status' => $status]);
$this->trigger('brand.status_changed', ['ids' => $ids, 'status' => $status]);
return $result;
}增量的边界
在骨架文件上追加方法,不要改动骨架已有方法的签名,也不要把新逻辑写进 Controller。副作用(日志、通知、缓存清理)用 $this->trigger() 交给 Listener,别内联。完整的增量原则见 CRUD 生成规范 第 4、5 章。
5. 预览、落盘、校验
AI 写盘走的是固定三步,你只需要在第二步点确认:
apply_patch(dry_run=true) → 列出将新增/覆盖哪些文件、各多少行
↓ 你确认
apply_patch(dry_run=false) → 真正写入磁盘
↓
run_check → php -l 语法检查 + 路由注册验证run_check 出现 ✗ 时,把报错原文丢回对话即可——错误信息里带着具体行号和路由注册失败堆栈,助手能据此定位修正,然后重新落盘、重新校验,直到全绿。
不要把「已生成」当成「已完成」
run_check 只保证语法正确、路由能注册,不保证业务逻辑对。生成完仍要自己跑一遍接口、看一眼数据。
6. 配置菜单与权限
代码就位后,功能还进不了后台导航——菜单和权限节点是数据,不是代码。写入 server/public/install/data/init.sql 的 menus 表:
- 二级菜单(品牌列表页)挂在对应一级菜单下,按
x00段惯例分配 ID; - 三级记录是按钮权限(新增 / 编辑 / 删除 / 批量操作),
permission字段与 Controller 上的权限标注对应。
插入前务必先查已用 ID
menus.id 是全局主键,一级、二级、按钮 ID 混在一起排号,直接猜必然冲突。查已用 ID 的命令、各模块的号段惯例、实地核实过的一级菜单清单,全部在业务模块规范 第 3 章——照着做,别让 AI 凭印象填 ID。
前端源码改动后需要构建,产物是 git 跟踪的:
cd admin && pnpm run build7. 完整链路回顾
设计表(注释写全)
→ schema.sql 同步
→ make:crud 出骨架
→ AI 增量(读现有实现 + 查规范 → 写 → 预览 → 落盘 → run_check)
→ init.sql 配菜单权限
→ admin 构建
→ 验收8. AI 写歪了怎么办
按出现频率排列的常见偏差,以及纠正话术:
| 现象 | 原因 | 纠正 |
|---|---|---|
字段名对不上(写了 create_time) | 没读真实表结构,用了通用 ThinkPHP 习惯 | 「先调用 show_table_schema 看真实字段再改」 |
Controller 里直接写 Db:: 查询 | 跨层了 | 「查 get_conventions 的 architecture 主题,按分层改」 |
路由动作乱命名(brandList) | 没遵循路由细则 | 「查 get_conventions 的 route 主题,按动作命名规范改」 |
| 手写了整套基础 CRUD | 绕过了生成器 | 「基础 CRUD 用 make:crud 生成,你只做增量」 |
| 改完不校验就说完成了 | 流程没走完 | 「跑 run_check,有 ✗ 修到全绿」 |
这些偏差在装了引导资产(init_client_assets)之后会明显减少——规约常驻在助手上下文里,标准流程也写进了 skills。仍然出现时,多半是资产版本过时,用 get_project_info 看一眼 client_assets 是否显示 outdated。