Appearance
RESTful 路由设计规范
本文档定义了 DSPlatform 项目中 RESTful API 路由设计的标准和最佳实践,确保 API 设计的一致性、可维护性和符合 RESTful 规范。
目录
核心原则
1. 使用名词,不使用动词
❌ 错误示例:
php
Route::post('user/balance/modifyUserBalance', ...);
Route::post('order/cancel', ...);
Route::get('goods/getGoodsList', ...);✅ 正确示例:
php
Route::put('users/:id/balance', ...);
Route::post('orders/:id/cancel', ...);
Route::get('goods/pages', ...);2. 使用复数形式表示资源集合
❌ 错误示例:
php
Route::get('user/pages', ...);
Route::get('order/:id', ...);
Route::get('goods/:id', ...);✅ 正确示例:
php
Route::get('users/pages', ...);
Route::get('orders/:id', ...);
Route::get('goods/:id', ...);3. 使用正确的 HTTP 方法
| HTTP 方法 | 用途 | 示例 |
|---|---|---|
| GET | 查询资源 | GET /users/:id |
| POST | 创建资源 | POST /users |
| PUT | 完整更新资源 | PUT /users/:id |
| PATCH | 部分更新资源 | PATCH /users/:id/status |
| DELETE | 删除资源 | DELETE /users/:id |
路由分组规范
分组命名规范
路由分组应该使用业务模块名称,而不是资源名称:
php
// ✅ 正确:使用业务模块名称
Route::group('tbl-order', function () {
Route::get('orders/pages', ...);
Route::get('refunds/pages', ...); // 简化命名
Route::get('goods/pages', ...); // 简化命名
});
Route::group('tbl-goods', function () {
Route::get('goods/pages', ...);
Route::get('categories/tree', ...); // 简化命名
Route::get('brands/tree', ...); // 简化命名
});
// ❌ 错误:使用资源名称作为分组
Route::group('orders', function () {
Route::get('pages', ...);
});分组内的资源命名策略
在路由分组下,优先使用简化命名,分组已提供上下文:
php
Route::group('tbl-order', function () {
// 主资源:订单
Route::get('orders/pages', 'tblOrder.tblOrder/getTblOrderPages');
Route::get('orders/:id', 'tblOrder.tblOrder/getTblOrderInfo');
// 关联资源:使用简化命名(推荐)
Route::get('refunds/pages', 'tblOrder.TblOrderRefund/getTblOrderRefundPages');
Route::get('refunds/:id', 'tblOrder.TblOrderRefund/getTblOrderRefundInfo');
Route::get('refunds/:id/logs', 'tblOrder.TblOrderRefund/getTblOrderRefundLogList');
Route::get('goods/pages', 'tblOrder.tblOrder/getTblOrderGoodsPages');
Route::get('delivery/pages', 'tblOrder.TblOrderDelivery/getTblOrderDeliveryPages');
// 独立资源:使用完整命名(避免混淆)
Route::get('order-logs', 'tblOrder.tblOrder/getTblOrderLogList');
Route::get('order-pay-logs', 'tblOrder.tblOrder/getTblOrderPayLogList');
});命名规则:
- ✅ 关联资源:使用简化命名(
refunds、goods、delivery) - ✅ 独立资源:使用完整命名(
order-logs、order-pay-logs) - ✅ 避免歧义:如果资源可能跨分组使用,使用完整命名
路由组内独立资源的命名
对于路由组内的独立资源,如果它们与主资源没有直接的父子关系,应该使用简化命名:
php
Route::group('admin', function () {
// 主资源:管理员(复数)
Route::get('admins/pages', 'admin.Admin/getAdminPages');
Route::get('admins/:id', 'admin.Admin/getAdminInfo');
// 独立资源:使用简化命名(推荐)
// 这些资源独立存在,不是管理员的子资源
Route::get('logs/pages', 'admin.AdminLogs/getAdminLogsPages'); // 管理员日志
Route::get('roles/list', 'admin.AdminRole/getAdminRoleList'); // 角色
Route::get('menus/tree', 'admin.AdminMenu/getAdminMenuTree'); // 菜单
Route::get('menus/:id', 'admin.AdminMenu/getAdminMenuInfo');
});为什么使用简化命名?
- ✅ 路由组
admin已经提供了上下文(/adminapi/admin/...) - ✅ 这些资源是独立管理的,不是
admins的子资源 - ✅ 避免路径冗余(
/adminapi/admin/admins/menus是冗余的) - ✅ 与其他独立资源保持一致(
logs、roles、menus都是独立资源)
完整路径示例:
GET /adminapi/admin/admins/pages- 管理员列表GET /adminapi/admin/logs/pages- 管理员日志列表GET /adminapi/admin/roles/list- 角色列表GET /adminapi/admin/menus/tree- 菜单树
资源命名规范
1. 集合资源使用复数
所有表示资源集合的路径都应该使用复数形式:
php
// ✅ 正确
Route::get('users/pages', ...); // 用户列表
Route::get('orders/pages', ...); // 订单列表
Route::get('goods/pages', ...); // 商品列表
Route::get('categories/tree', ...); // 分类树
Route::get('brands/tree', ...); // 品牌树
// ❌ 错误
Route::get('user/pages', ...);
Route::get('order/pages', ...);
Route::get('category/tree', ...);2. 复合资源命名
对于复合资源名称,使用连字符连接:
php
// ✅ 正确
Route::get('order-refunds/pages', ...); // 订单退款(独立资源)
Route::get('order-deliveries/pages', ...); // 订单交付(独立资源)
Route::get('points-goods/pages', ...); // 积分商品
Route::get('distributor-orders/pages', ...); // 分销商订单
// ❌ 错误
Route::get('orderRefunds/pages', ...); // 驼峰命名
Route::get('order_refunds/pages', ...); // 下划线命名3. 避免路径冗余
❌ 错误示例:
php
Route::get('user/user/info', ...); // 冗余:user 重复
Route::get('order/order/pages', ...); // 冗余:order 重复✅ 正确示例:
php
Route::get('users/:id', ...); // 简洁明了
Route::get('orders/pages', ...); // 简洁明了HTTP 方法使用规范
GET - 查询操作
用于查询资源,不应该有副作用:
php
// ✅ 正确
Route::get('users/pages', ...); // 分页列表
Route::get('users/:id', ...); // 单个资源
Route::get('users/:id/balance', ...); // 资源属性
Route::get('categories/tree', ...); // 特殊查询(树形结构)
// ❌ 错误:使用 POST 进行查询
Route::post('users/info', ...);POST - 创建资源
用于创建新资源:
php
// ✅ 正确
Route::post('users', ...); // 创建用户
Route::post('orders', ...); // 创建订单
Route::post('refunds', ...); // 创建退款
// ❌ 错误:使用 GET 创建资源
Route::get('users/create', ...);PUT - 完整更新资源
用于完整更新资源的所有字段:
php
// ✅ 正确
Route::put('users/:id', ...); // 更新用户完整信息
Route::put('users/:id/balance', ...); // 更新用户余额(完整更新)
Route::put('orders/:id', ...); // 更新订单完整信息
// ❌ 错误:使用 POST 进行更新
Route::post('users/:id/update', ...);PATCH - 部分更新资源
用于部分更新资源的特定字段:
php
// ✅ 正确
Route::patch('users/:id/status', ...); // 更新用户状态
Route::patch('goods/:id/sys-status', ...); // 更新商品系统状态
Route::patch('bloggers/:id/toggle-field', ...); // 切换字段状态
Route::patch('merchants/:id/audit', ...); // 审核商户
// ❌ 错误:使用 PUT 进行部分更新
Route::put('users/:id/status', ...);DELETE - 删除资源
用于删除资源:
php
// ✅ 正确
Route::delete('users/:id', ...); // 删除用户
Route::delete('orders/:id', ...); // 删除订单
Route::delete('tracks/:rider_id/all', ...); // 清空轨迹(特殊删除操作)
// ❌ 错误:使用 POST 进行删除
Route::post('users/:id/delete', ...);删除操作规范
硬删除(物理删除)
硬删除直接从数据库中移除资源记录,使用 DELETE 方法:
php
// ✅ 正确:单个硬删除
Route::delete('users/:id', ...); // 删除用户
Route::delete('orders/:id', ...); // 删除订单
// ✅ 正确:批量硬删除
Route::delete('carts/batch', ...); // 批量删除购物车
Route::delete('articles/batch', ...); // 批量删除文章特点:
- HTTP 方法:
DELETE - 单个删除:
DELETE resource/:id - 批量删除:
DELETE resource/batch - 数据操作:从数据库中物理移除,无法恢复
软删除(逻辑删除)
软删除通过设置标志位(如 is_deleted)标记资源为已删除,数据仍保留在数据库中:
php
// ✅ 正确:单个软删除
Route::patch('goods/:id/soft-delete', ...); // 单个软删除商品
Route::patch('orders/:id/soft-delete', ...); // 单个软删除订单
// ✅ 正确:批量软删除
Route::post('goods/batch-soft-delete', ...); // 批量软删除商品
Route::post('orders/batch-soft-delete', ...); // 批量软删除订单特点:
- HTTP 方法:
- 单个软删除:
PATCH - 批量软删除:
POST
- 单个软删除:
- 路径格式:
- 单个软删除:
PATCH resource/:id/soft-delete - 批量软删除:
POST resource/batch-soft-delete
- 单个软删除:
- 数据操作:设置标志位(如
is_deleted = 1),数据保留在数据库中,可以恢复
删除操作规范总结
| 操作类型 | HTTP方法 | 路径格式 | 可恢复性 |
|---|---|---|---|
| 单个硬删除 | DELETE | resource/:id | ❌ 不可恢复 |
| 批量硬删除 | DELETE | resource/batch | ❌ 不可恢复 |
| 单个软删除 | PATCH | resource/:id/soft-delete | ✅ 可恢复 |
| 批量软删除 | POST | resource/batch-soft-delete | ✅ 可恢复 |
路由顺序注意事项
批量操作路径必须在单个资源路径之前:
php
Route::group('goods', function () {
// ✅ 正确顺序:批量操作(2段)必须在动态路径之前
Route::post('goods/batch-soft-delete', ...); // 批量软删除
Route::delete('goods/batch', ...); // 批量硬删除
Route::get('goods/pages', ...); // 列表
// 单个资源操作(动态路径)
Route::patch('goods/:id/soft-delete', ...); // 单个软删除(3段)
Route::get('goods/:id', ...); // 详情
Route::delete('goods/:id', ...); // 单个硬删除
});原因: goods/:id 会匹配 goods/batch 和 goods/batch-soft-delete,所以具体路径必须在动态路径之前。
嵌套资源 vs 独立资源
何时使用嵌套资源
嵌套资源适用于必须通过父资源访问的场景:
php
// 示例:退款日志必须通过退款访问
Route::get('refunds/:id/logs', ...); // 获取退款的日志列表
Route::get('orders/:id/goods', ...); // 获取订单的商品列表何时使用独立资源
独立资源适用于可以独立管理和查询的场景:
php
// 示例:退款可以独立管理
Route::group('tbl-order', function () {
// 退款作为独立资源,可以独立查询和管理
Route::get('refunds/pages', ...); // 退款列表(可通过 order_id 参数筛选)
Route::get('refunds/:id', ...); // 退款详情(直接通过退款ID访问)
Route::post('refunds/:id/retry', ...); // 重新发起退款
Route::get('refunds/:id/logs', ...); // 退款日志(嵌套资源)
});判断标准
使用独立资源,如果:
- ✅ 资源有独立的业务标识(如退款单号)
- ✅ 资源可以独立查询和管理(如退款管理页面)
- ✅ 资源详情可以直接通过资源ID访问
- ✅ 资源有独立的生命周期和状态管理
使用嵌套资源,如果:
- ✅ 资源必须通过父资源访问
- ✅ 资源没有独立的业务标识
- ✅ 资源只是父资源的属性或子集
路由顺序规范
重要性
ThinkPHP 路由匹配是从上到下的顺序,更具体的路由必须放在更通用的路由之前。
规则
- 多段路径优先于少段路径
- 具体路径优先于动态路径
- 特殊操作优先于通用操作
示例
php
Route::group('user', function () {
// ✅ 正确顺序
// 1. 多段路径(3段)必须在少段路径(2段)前面
Route::put('users/:id/balance', 'user.UserBalance/modifyUserBalance');
Route::put('users/:id/points', 'user.UserPoints/modifyUserPoints');
// 2. 具体路径(2段)必须在动态路径(2段)前面
Route::get('users/pages', 'user.User/getUserPages');
Route::get('users/relation/list', 'user.User/getUserRelationList');
// 3. 动态路径(2段)
Route::get('users/:id', 'user.User/getUserInfo');
Route::put('users/:id', 'user.User/updateUser');
Route::post('users', 'user.User/createUser');
});添加注释说明
为了代码可维护性,应该添加注释说明路由顺序的重要性:
php
Route::group('user', function () {
// users/:id/balance (3段) 必须在 users/:id (2段) 前面 否则 PUT /users/123/balance 会被 users/:id 匹配
Route::put('users/:id/balance', 'user.UserBalance/modifyUserBalance');
// users/pages (2段) 必须在 users/:id (2段) 前面 否则 GET /users/pages 会被 users/:id 匹配
Route::get('users/pages', 'user.User/getUserPages');
Route::get('users/:id', 'user.User/getUserInfo');
});路径参数规范
使用路径参数而非请求体
❌ 错误示例:
php
// 路由定义
Route::put('users/balance', 'user.UserBalance/modifyUserBalance');
// 控制器
public function modifyUserBalance() {
$user_id = input('param.user_id'); // 从请求体获取
// ...
}✅ 正确示例:
php
// 路由定义
Route::put('users/:id/balance', 'user.UserBalance/modifyUserBalance');
// 控制器
public function modifyUserBalance($id) {
$user_id = $id; // 从路径参数获取
// ...
}路径参数命名
使用简洁的标识符名称:
php
// ✅ 正确
Route::get('users/:id', ...); // 用户ID
Route::get('orders/:id', ...); // 订单ID
Route::get('goods/:id', ...); // 商品ID
// 特殊场景:需要区分时使用完整名称
Route::delete('tracks/:rider_id/all', ...); // 骑手ID(避免与轨迹ID混淆)实际案例
案例1:用户管理
php
Route::group('user', function () {
// 用户推广关系(特殊路径,放在前面)
Route::get('users/relation/list', 'user.User/getUserRelationList');
// 用户余额、积分、成长值(3段路径,放在前面)
Route::put('users/:id/balance', 'user.UserBalance/modifyUserBalance');
Route::put('users/:id/points', 'user.UserPoints/modifyUserPoints');
Route::put('users/:id/growth', 'user.UserGrowth/modifyUserGrowth');
// 用户列表(2段路径,放在前面)
Route::get('users/pages', 'user.User/getUserPages');
// 用户详情和操作(2段动态路径)
Route::get('users/:id', 'user.User/getUserInfo');
Route::put('users/:id', 'user.User/updateUser');
Route::post('users', 'user.User/createUser');
// 关联资源:余额日志
Route::get('balance-log/pages', 'user.UserBalance/getUserBalanceLogPages');
});案例2:商品管理
php
Route::group('tbl-goods', function () {
// 商品特殊操作(3段路径)
Route::patch('goods/:id/sys-status', 'tblGoods.tblGoods/updateTblGoodsSysStatus');
Route::patch('goods/:id/sys-recommend', 'tblGoods.tblGoods/updateSysRecommend');
// 商品列表(2段路径)
Route::get('goods/pages', 'tblGoods.tblGoods/getTblGoodsPages');
Route::get('goods/list', 'tblGoods.tblGoods/getTblGoodsList');
// 商品详情和操作(2段动态路径)
Route::get('goods/:id', 'tblGoods.tblGoods/getTblGoodsInfo');
Route::put('goods/:id', 'tblGoods.tblGoods/updateTblGoods');
// 关联资源:商品评论(简化命名)
Route::patch('comments/:id/toggle-field', 'tblGoods.tblGoodsComment/toggleTblGoodsCommentField');
Route::get('comments/pages', 'tblGoods.tblGoodsComment/getTblGoodsCommentPages');
// 关联资源:商品分类(简化命名)
Route::get('categories/tree', 'tblGoods.tblGoodsCategory/getTblGoodsCategoryTree');
Route::get('categories/:id', 'tblGoods.tblGoodsCategory/getTblGoodsCategoryInfo');
Route::post('categories', 'tblGoods.tblGoodsCategory/createTblGoodsCategory');
Route::put('categories/:id', 'tblGoods.tblGoodsCategory/updateTblGoodsCategory');
Route::delete('categories/:id', 'tblGoods.tblGoodsCategory/deleteTblGoodsCategory');
// 关联资源:品牌(简化命名)
Route::get('brands/tree', 'tblGoods.tblGoodsBrand/getTblGoodsBrandTree');
Route::get('brands/:id', 'tblGoods.tblGoodsBrand/getTblGoodsBrandInfo');
Route::post('brands', 'tblGoods.tblGoodsBrand/createTblGoodsBrand');
Route::put('brands/:id', 'tblGoods.tblGoodsBrand/updateTblGoodsBrand');
Route::delete('brands/:id', 'tblGoods.tblGoodsBrand/deleteTblGoodsBrand');
});案例3:订单管理
php
Route::group('tbl-order', function () {
// 订单列表(2段路径,放在前面)
Route::get('orders/pages', 'tblOrder.tblOrder/getTblOrderPages');
// 订单详情(2段动态路径)
Route::get('orders/:id', 'tblOrder.tblOrder/getTblOrderInfo');
// 关联资源:订单商品(简化命名)
Route::get('goods/pages', 'tblOrder.tblOrder/getTblOrderGoodsPages');
Route::get('goods/list', 'tblOrder.tblOrder/getTblOrderGoodsList');
// 关联资源:订单退款(简化命名)
Route::get('refunds/pages', 'tblOrder.TblOrderRefund/getTblOrderRefundPages');
Route::get('refunds/list', 'tblOrder.TblOrderRefund/getTblOrderRefundList');
Route::get('refunds/:id', 'tblOrder.TblOrderRefund/getTblOrderRefundInfo');
Route::post('refunds/:id/retry', 'tblOrder.TblOrderRefund/retryTblOrderRefund');
Route::get('refunds/:id/logs', 'tblOrder.TblOrderRefund/getTblOrderRefundLogList');
// 关联资源:订单交付(简化命名)
Route::get('delivery/pages', 'tblOrder.TblOrderDelivery/getTblOrderDeliveryPages');
// 独立资源:订单日志(完整命名,避免混淆)
Route::get('order-logs', 'tblOrder.tblOrder/getTblOrderLogList');
// 独立资源:订单支付记录(完整命名,避免混淆)
Route::get('order-pay-logs', 'tblOrder.tblOrder/getTblOrderPayLogList');
});案例4:管理员管理
php
Route::group('admin', function () {
// 当前管理员信息(特殊路径,放在前面)
Route::get('current/info', 'admin.CurrentAdmin/getCurrentAdminInfo');
Route::get('current/menus', 'admin.CurrentAdmin/getCurrentAdminMenus');
Route::put('current/password', 'admin.CurrentAdmin/editCurrentAdminPassword');
// 管理员管理(主资源,复数形式)
// admins/pages (2段) 必须在 admins/:id (2段) 前面
Route::get('admins/pages', 'admin.Admin/getAdminPages');
Route::get('admins/:id', 'admin.Admin/getAdminInfo');
Route::post('admins', 'admin.Admin/createAdmin');
Route::put('admins/:id', 'admin.Admin/updateAdmin');
Route::delete('admins/:id', 'admin.Admin/deleteAdmin');
// 独立资源:管理员日志(简化命名)
Route::get('logs/pages', 'admin.AdminLogs/getAdminLogsPages');
Route::get('logs/:id', 'admin.AdminLogs/getAdminLogsInfo');
// 独立资源:角色(简化命名)
Route::get('roles/list', 'admin.AdminRole/getAdminRoleList');
Route::get('roles/:id', 'admin.AdminRole/getAdminRoleInfo');
Route::post('roles', 'admin.AdminRole/createAdminRole');
// roles/:id/rules (3段) 必须在 roles/:id (2段) 前面
Route::put('roles/:id/rules', 'admin.AdminRole/updateAdminRoleRules');
Route::put('roles/:id', 'admin.AdminRole/updateAdminRole');
Route::delete('roles/:id', 'admin.AdminRole/deleteAdminRole');
// 独立资源:菜单(简化命名)
// menus/tree (2段) 必须在 menus/:id (2段) 前面
Route::get('menus/tree', 'admin.AdminMenu/getAdminMenuTree');
Route::get('menus/options', 'admin.AdminMenu/getAdminMenuOptions');
Route::get('menus/:id', 'admin.AdminMenu/getAdminMenuInfo');
Route::post('menus', 'admin.AdminMenu/createAdminMenu');
Route::put('menus/:id', 'admin.AdminMenu/updateAdminMenu');
Route::delete('menus/:id', 'admin.AdminMenu/deleteAdminMenu');
});设计说明:
- ✅ 主资源
admins使用复数形式 - ✅ 独立资源
logs、roles、menus使用简化命名(不是admins/logs) - ✅ 路由组
admin提供上下文,完整路径为/adminapi/admin/... - ✅ 避免路径冗余(
/adminapi/admin/admins/menus是冗余的)
常见错误和避免方法
错误1:路径中包含动词
❌ 错误:
php
Route::post('user/balance/modifyUserBalance', ...);
Route::post('order/cancel', ...);
Route::get('goods/getGoodsList', ...);✅ 正确:
php
Route::put('users/:id/balance', ...);
Route::post('orders/:id/cancel', ...);
Route::get('goods/pages', ...);错误2:使用单数而非复数
❌ 错误:
php
Route::get('user/pages', ...);
Route::get('order/:id', ...);
Route::get('category/tree', ...);✅ 正确:
php
Route::get('users/pages', ...);
Route::get('orders/:id', ...);
Route::get('categories/tree', ...);错误3:使用错误的 HTTP 方法
❌ 错误:
php
Route::post('users/info', ...); // 查询应该用 GET
Route::post('users/:id/update', ...); // 更新应该用 PUT
Route::post('users/:id/delete', ...); // 删除应该用 DELETE
Route::put('users/:id/status', ...); // 部分更新应该用 PATCH✅ 正确:
php
Route::get('users/:id', ...); // 查询用 GET
Route::put('users/:id', ...); // 完整更新用 PUT
Route::delete('users/:id', ...); // 删除用 DELETE
Route::patch('users/:id/status', ...); // 部分更新用 PATCH错误4:路由顺序错误
❌ 错误:
php
Route::group('user', function () {
Route::get('users/:id', ...); // 会匹配 users/pages
Route::get('users/pages', ...); // 永远不会匹配到
});✅ 正确:
php
Route::group('user', function () {
Route::get('users/pages', ...); // 具体路径在前
Route::get('users/:id', ...); // 动态路径在后
});错误5:路径参数放在请求体中
❌ 错误:
php
// 路由
Route::put('users/balance', ...);
// 控制器
public function modifyUserBalance() {
$user_id = input('param.user_id'); // 从请求体获取
}✅ 正确:
php
// 路由
Route::put('users/:id/balance', ...);
// 控制器
public function modifyUserBalance($id) {
$user_id = $id; // 从路径参数获取
}最佳实践总结
- 使用名词,不使用动词:路径应该表示资源,而不是操作
- 使用复数形式:集合资源使用复数,单个资源通过ID标识
- 使用正确的 HTTP 方法:GET查询、POST创建、PUT完整更新、PATCH部分更新、DELETE删除
- 保持路由顺序:具体路径在前,动态路径在后
- 使用路径参数:资源ID应该放在路径中,而不是请求体
- 添加注释说明:对于重要的路由顺序,添加注释说明原因
- 独立资源 vs 嵌套资源:根据业务场景选择合适的资源组织方式
- 路由分组:使用业务模块名称作为分组,而不是资源名称
- 分组内资源命名:关联资源使用简化命名,独立资源使用完整命名
参考资源
最后更新:2024-01-20
维护者:DSPlatform技术团队(德尚网络)
获取帮助
如果您在使用过程中遇到问题,可以通过以下方式获取帮助:
- 官方网站:https://www.csdeshang.com
- 电话咨询:15364080101(微信同号)
- QQ咨询:858761000
- 邮箱咨询:858761000@qq.com
- 工作时间:工作日 9:00-18:00
- 微信咨询:扫码添加微信

版权所有 © 2014-至今 德尚网络