前两篇聊了背单词小程序「优词记 Pro」的整体架构和请求层封装,今天说说后端一个比较激进的设计:整个项目的路由文件只有一行。
路由文件的痛
常规做法是每个接口注册一条路由。项目小的时候没问题,接口一多,路由文件就变成几百行的流水账:改个接口要同时动路由和控制器,命名风格慢慢漂移,review 时还得对着路由表找实现。既然我的接口 URL 本来就有强规律,为什么不让规律本身当路由?
于是全站只留一条通配路由,所有请求交给一个 Dispatcher 统一调度:
Router::addRoute(['GET','POST','PUT','DELETE'], '/{name:.+}', Dispatcher::class . '@handle');三条约定替代整张路由表
约定一:URL 前缀分端。/admin/* 走管理端控制器目录,/user/* 走小程序端,/common/* 两端通用。不同端的认证策略也随前缀确定——管理端 JWT 有效期 7 天,小程序端 365 天,通用接口两种身份都放行。
约定二:HTTP 方法映射方法前缀。GET → get*,POST → create*,PUT → update*,DELETE → delete*。语义直接编码在方法名里,看到 createOrder 就知道它只接受 POST。
约定三:URL 段转驼峰拼方法名。kebab-case 的 URL 段转成 camelCase 后匹配控制器和方法,比如 GET /admin/user/list-page 会命中 User::getListPage()。找不到控制器时还有一次目录归约兜底:Foo\Bar 未命中就回退找 Foo\Bar\Index。
| 请求 | 命中 |
|---|---|
GET /admin/user/list-page | AdminPort\User::getListPage() |
POST /user/collection | UserPort\Collection::create() |
DELETE /admin/advert | AdminPort\Advert::delete() |
新增一个接口,只需要在对应目录放好控制器、按前缀命名方法,路由自动就通了,git diff 里只有一个文件。
认证和权限也挂在约定上
Dispatcher 找到目标方法后,先过认证再执行。默认全部接口要求登录,例外用 #[NoNeedLogin] 注解显式声明;需要细粒度权限的方法加 #[Permission(['manage-advert-add'])],由注解在调度时统一校验。权限、日志这些横切逻辑都收敛在调度层,业务控制器保持干净。
代价与取舍
约定路由不是免费的。两点代价说在前面:一是新人要先学约定,不看文档很难从 URL 直接反查代码位置,所以约定必须写进 README 并且从不破例;二是灵活性受限,比如当前实现不允许向方法传位置参数,多余的 URL 段直接 404——这是有意为之,宁可严格也不要歧义。对单人或小团队项目,用这点学习成本换掉整张路由表的维护成本,我认为很值。
产品本体是个背单词小程序,微信搜「优词记 Pro」可以体验。明天写写这套后端更「离经叛道」的部分:不写 migration,直接从数据库 schema 反向生成 Entity 和 Model 代码,感兴趣的可以关注。



网友评论 0