Skip to content

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');
});

命名规则:

  • 关联资源:使用简化命名(refundsgoodsdelivery
  • 独立资源:使用完整命名(order-logsorder-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 是冗余的)
  • ✅ 与其他独立资源保持一致(logsrolesmenus 都是独立资源)

完整路径示例:

  • 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方法路径格式可恢复性
单个硬删除DELETEresource/:id❌ 不可恢复
批量硬删除DELETEresource/batch❌ 不可恢复
单个软删除PATCHresource/:id/soft-delete✅ 可恢复
批量软删除POSTresource/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/batchgoods/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 路由匹配是从上到下的顺序,更具体的路由必须放在更通用的路由之前。

规则

  1. 多段路径优先于少段路径
  2. 具体路径优先于动态路径
  3. 特殊操作优先于通用操作

示例

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 使用复数形式
  • ✅ 独立资源 logsrolesmenus 使用简化命名(不是 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;  // 从路径参数获取
}

最佳实践总结

  1. 使用名词,不使用动词:路径应该表示资源,而不是操作
  2. 使用复数形式:集合资源使用复数,单个资源通过ID标识
  3. 使用正确的 HTTP 方法:GET查询、POST创建、PUT完整更新、PATCH部分更新、DELETE删除
  4. 保持路由顺序:具体路径在前,动态路径在后
  5. 使用路径参数:资源ID应该放在路径中,而不是请求体
  6. 添加注释说明:对于重要的路由顺序,添加注释说明原因
  7. 独立资源 vs 嵌套资源:根据业务场景选择合适的资源组织方式
  8. 路由分组:使用业务模块名称作为分组,而不是资源名称
  9. 分组内资源命名:关联资源使用简化命名,独立资源使用完整命名

参考资源


最后更新:2024-01-20
维护者:DSPlatform技术团队(德尚网络)

获取帮助

如果您在使用过程中遇到问题,可以通过以下方式获取帮助:

  • 官方网站https://www.csdeshang.com
  • 电话咨询:15364080101(微信同号)
  • QQ咨询:858761000
  • 邮箱咨询:858761000@qq.com
  • 工作时间:工作日 9:00-18:00
  • 微信咨询:扫码添加微信
微信二维码

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