
【基于 SwooleHyperf 的微服务实战】第九周·周四实现订单服务并通过网关聚合下单接口今天我们进入第九周周四核心目标是实现订单服务并通过网关聚合下单接口完成从客户端到订单、库存、商品服务的全链路贯通。昨天我们已经有了商品和库存服务今天将创建订单服务并让网关作为统一入口整合 JWT 鉴权和限流为明天的 Saga 分布式事务奠定基础。今日目标创建order-service项目配置数据库、RPC 客户端、Consul 注册。实现订单服务的CreateOrderRPC 接口内部通过 RPC 调用库存服务冻结库存调用商品服务获取商品信息调用用户服务校验用户。在网关中创建聚合下单接口POST /orders通过 RPC 客户端调用订单服务并在此应用JWT 鉴权和限流50 QPS。测试全链路注册用户 → 登录获取 Token → 通过网关下单 → 订单生成、库存冻结、商品信息正确。验证限流效果使用压测工具确认超过 50 QPS 后返回 429。一、创建订单服务并配置环境约 45 分钟1. 创建项目docker-composeexecswoolebashcd/var/www/servicescomposercreate-project hyperf/hyperf-skeleton ordercdordercomposerrequire hyperf/database hyperf/redis hyperf/json-rpc hyperf/service-governance-consul hyperf/guzzle设置端口9505避免与现有服务冲突数据库为order_db。2. 配置数据库和 RPC 客户端.env设置数据库连接指向mysql容器库order_db。config/autoload/databases.php按环境变量配置。config/autoload/server.php添加 JSON-RPC 服务端监听9505servers[[namejsonrpc,typeServer::SERVER_HTTP,host0.0.0.0,port9505,callbacks[Event::ON_REQUEST[\Hyperf\JsonRpc\HttpServer::class,onRequest],],],],config/autoload/services.php配置 Consul 注册和消费者。消费者需添加三个依赖服务的发现consumers[[nameUserService,registry[protocolconsul,addresshttp://consul:8500],],[nameProductService,registry[protocolconsul,addresshttp://consul:8500],],[nameInventoryService,registry[protocolconsul,addresshttp://consul:8500],],],3. 数据库表结构在order_db中创建orders表通过 SQL 文件或迁移CREATETABLEorders(idBIGINTUNSIGNEDAUTO_INCREMENTPRIMARYKEY,order_idVARCHAR(64)NOTNULLUNIQUE,user_idINTNOTNULL,product_idINTNOTNULL,amountDECIMAL(10,2)NOTNULL,statusENUM(pending,frozen,paid,cancelled)NOTNULLDEFAULTpending,created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP,updated_atTIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP);二、知识核心订单服务职责与同步调用流程约 1 小时订单服务的CreateOrder需要做几件事校验用户存在调用用户服务getUserById。校验商品存在且价格正确调用商品服务getProductById。调用库存服务freeze冻结库存。创建订单记录状态设为frozen。为什么不用 Saga今天先用同步调用实现基本流程保证核心链路可用。明天我们会引入 Saga 协调器将这些同步调用拆分为异步命令并增加补偿逻辑从而解决分布式事务问题。今天重点体验服务间协作和网关聚合。订单服务的接口定义我们将订单服务作为一个 RPC 服务暴露CreateOrder方法返回订单信息。也可以暴露GetOrder等。三、实战编写订单服务与网关聚合约 2.5 小时步骤 1定义订单服务契约在订单服务中创建app/JsonRpc/OrderServiceInterface.php?phpnamespaceApp\JsonRpc;useHyperf\RpcServer\Annotation\RpcService;#[RpcService(name:OrderService,protocol:jsonrpc-http,server:jsonrpc)]interfaceOrderServiceInterface{publicfunctioncreateOrder(int$userId,int$productId,float$amount):array;publicfunctiongetOrder(string$orderId):array;}步骤 2实现订单服务创建app/JsonRpc/OrderService.php?phpnamespaceApp\JsonRpc;useApp\Model\Order;useHyperf\Di\Annotation\Inject;useHyperf\RpcClient\Annotation\RpcClient;useApp\JsonRpc\UserServiceInterface;useApp\JsonRpc\ProductServiceInterface;useApp\JsonRpc\InventoryServiceInterface;useHyperf\Utils\Str;#[RpcService(name:OrderService,protocol:jsonrpc-http,server:jsonrpc)]classOrderServiceimplementsOrderServiceInterface{#[RpcClient(name:UserService)]privateUserServiceInterface$userService;#[RpcClient(name:ProductService)]privateProductServiceInterface$productService;#[RpcClient(name:InventoryService)]privateInventoryServiceInterface$inventoryService;publicfunctioncreateOrder(int$userId,int$productId,float$amount):array{// 1. 校验用户$user$this-userService-getUserById($userId);if(empty($user)){thrownew\InvalidArgumentException(用户不存在);}// 2. 校验商品$product$this-productService-getProductById($productId);if(empty($product)){thrownew\InvalidArgumentException(商品不存在);}// 金额可简单校验if($amount0){thrownew\InvalidArgumentException(金额错误);}// 3. 冻结库存数量暂定为1实际应参数化$frozen$this-inventoryService-freeze($productId,1);if(!$frozen){thrownew\RuntimeException(库存不足);}// 4. 创建订单$orderId(string)Str::uuid();Order::create([order_id$orderId,user_id$userId,product_id$productId,amount$amount,statusfrozen,]);return[order_id$orderId,user_id$userId,product_id$productId,amount$amount,statusfrozen,];}publicfunctiongetOrder(string$orderId):array{$orderOrder::where(order_id,$orderId)-first();return$order?$order-toArray():[];}}步骤 3创建模型和依赖绑定app/Model/Order.php继承Hyperf\DbConnection\Model\Model$table orders。config/autoload/dependencies.php绑定接口到实现。步骤 4启动订单服务并验证 RPC启动订单服务直接通过curl测试 RPC 接口curl-XPOST http://localhost:9505-HContent-Type: application/json\-d{jsonrpc:2.0,method:order/createOrder,params:[1,1,99.00],id:1}前提是用户 ID1 存在商品 ID1 存在且库存够应返回订单信息并检查库存服务的冻结量是否增加。步骤 5在网关中创建聚合下单接口网关需要暴露一个 RESTful 下单接口POST /orders内部调用订单服务的 RPC同时网关负责鉴权和限流。5.1 网关中增加订单服务消费者网关的config/autoload/services.php添加消费者[nameOrderService,registry[protocolconsul,addresshttp://consul:8500],],5.2 创建网关侧的订单聚合服务新建app/Service/OrderService.php或叫 OrderClientWrapper?phpnamespaceApp\Service;useHyperf\RpcClient\Annotation\RpcClient;useApp\JsonRpc\OrderServiceInterface;classOrderServiceWrapper{#[RpcClient(name:OrderService)]privateOrderServiceInterface$orderService;publicfunctioncreateOrder(int$userId,int$productId,float$amount):array{return$this-orderService-createOrder($userId,$productId,$amount);}}5.3 创建网关控制器新建app/Controller/OrderController.php?phpnamespaceApp\Controller;useApp\Service\OrderServiceWrapper;useHyperf\HttpServer\Annotation\Controller;useHyperf\HttpServer\Annotation\RequestMapping;useHyperf\Di\Annotation\Inject;#[Controller(prefix:/orders)]classOrderControllerextendsAbstractController{#[Inject]privateOrderServiceWrapper$orderService;#[RequestMapping(path:,methods:post)]publicfunctioncreate(){$userId$this-request-getAttribute(user_id);// 由 JWT 中间件注入if(!$userId){return$this-response-json([code401,message未登录])-withStatus(401);}$productId(int)$this-request-input(product_id);$amount(float)$this-request-input(amount);try{$order$this-orderService-createOrder($userId,$productId,$amount);return[code200,data$order];}catch(\Throwable$e){return$this-response-json([code400,message$e-getMessage()])-withStatus(400);}}}5.4 将/orders加入网关自身路由白名单避免被转发中间件代理。步骤 6配置限流中间件下单接口 50 QPS在网关的RateLimitMiddleware中为/orders路径设置独立限流。修改resolveLimiter方法privatefunctionresolveLimiter(string$uri):?string{if(str_starts_with($uri,/orders)){returnapi_order;}// ... 其他}并在rate_limit.php中添加api_order配置api_order[capacity50,rate50,],这样下单接口每秒最多允许 50 个请求令牌桶容量和速率都设 50可实现严格限流。步骤 7JWT 鉴权已在全局中间件中实现确保HybridAuthMiddleware或之前的JwtAuthMiddleware处于活跃状态且/orders不在白名单中因此只有携带有效 JWT 的请求才能到达控制器。四、全链路测试与压测验证约 1 小时1. 准备测试数据通过用户服务注册用户若之前未注册可 RPC 调用注册确保商品 ID1 存在库存stocks表有足够库存2. 流程测试# 登录获取 TokenTOKEN$(curl-s-XPOST http://localhost:9500/auth/login-HContent-Type: application/json-d{username:alice,password:123456}|jq-r.data.token)# 下单curl-XPOST http://localhost:9500/orders\-HAuthorization: Bearer$TOKEN\-HContent-Type: application/json\-d{product_id:1,amount:99.00}应返回订单信息状态frozen。3. 验证库存冻结curlhttp://localhost:9504-XPOST-HContent-Type: application/json\-d{jsonrpc:2.0,method:inventory/getStock,params:[1],id:1}frozen应为 1available减少。4. 限流测试使用ab或wrk以超过 50 QPS 的并发请求下单接口wrk-t4-c100-d10s--latency-HAuthorization: Bearer$TOKEN-spost_order.lua http://localhost:9500/orders需要写一个简单的 Lua 构造 POST body。观察部分请求返回 429并且后端订单服务压力可控。5. 未登录拦截测试不带 Token 请求下单接口应返回 401。五、今日作业与学习产出提交代码将order-service项目、网关聚合代码、限流配置提交到 Git。增强订单服务添加GetOrder接口供查询。记录订单操作日志事件监听写入日志或数据库。学习笔记绘制下单全链路时序图客户端 - 网关JWT限流- 订单服务 RPC - 用户/商品/库存服务 RPC - 数据库。思考同步调用库存冻结的潜在问题超时、失败为明天的 Saga 改造做铺垫。挑战任务为订单服务添加熔断器当库存服务连续失败时自动降级返回“系统繁忙”。使用 Redis 缓存商品信息减少订单服务对商品服务的 RPC 调用。今天你已经将电商系统的核心交易链路打通客户端可以安全、受控地下单库存得到实时冻结。明天我们将引入 Saga 分布式事务和延迟队列实现支付超时自动取消和补偿恢复让整个系统具备最终一致性能力。