go-zero-looklook 订单服务实战解析:从下单 API 到 Asynq 延迟关单的完整链路
发布时间:2026/10/13 9:33:04 锦皓数字建站

后端微服务【免费下载链接】go-zero-looklook基于go-zero(go zero) 微服务全技术栈开发最佳实践项目。Develop best practice projects based on the full technology stack of go zero (go zero) microservices.项目地址https://gitcode.com/gh_mirrors/go/go-zero-looklook点击查看免费下载本文以 go-zero-looklook 微服务项目中的订单服务order为讲解核心完整拆解其业务架构、服务依赖关系以及用户下单 → 订单创建 → 延迟关单任务投递 → 异步关闭未支付订单的完整实现链路。阅读本文后你将掌握 go-zero 项目中 api/rpc 分层协作的标准写法、基于 asynq 的延迟任务队列在订单场景中的落地方式以及订单状态机校验的实现细节可直接复用到自己的微服务订单模块中。一、订单服务业务架构在 go-zero-looklook 中订单服务位于 app/order 目录是整个民宿预订业务的核心交易模块。它与用户服务usercenter、民宿服务travel、支付服务payment以及消息队列服务mqueue协同工作共同完成一次完整的民宿预订闭环。从订单服务的架构图可以看到订单服务内部又进一步拆分为 api 与 rpc 两层遵循 go-zero 微服务的最佳实践order-apiapp/order/cmd/api对外暴露 HTTP 接口负责参数解析、JWT 鉴权、会话上下文读取是客户端小程序/App访问的入口order-rpcapp/order/cmd/rpc对内提供 gRPC 服务承载订单创建、查询、状态流转等核心业务逻辑并直接操作 MySQL 与 Redis 缓存。二、服务依赖关系原文档对订单服务的依赖关系做了精炼概括结合源码可以进一步落实到具体配置上order-api订单 api依赖 order-rpc订单 rpc、payment-rpc支付 rpc、travel-rpc民宿 rpcorder-rpc订单 rpc依赖 travel-rpc民宿 rpc这两条依赖关系在配置文件中均有明确证据app/order/cmd/api/etc/order.yaml 中同时配置了OrderRpcConf127.0.0.1:2001、PaymentRpcConf127.0.0.1:2002、TravelRpcConf127.0.0.1:2003三个 RPC 客户端即 order-api 同时调用订单、支付、民宿三个 RPC 服务app/order/cmd/rpc/internal/svc/serviceContext.go 中order-rpc 的ServiceContext持有TravelRpc travel.Travel通过travel.NewTravel(zrpc.MustNewClient(c.TravelRpcConf))创建民宿 RPC 客户端同时持有AsynqClientasynq 延迟队列客户端与HomestayOrderModel订单数据模型对应配置见 app/order/cmd/rpc/etc/order.yaml。依赖关系可以总结为下表服务依赖的 RPC 服务依赖用途order-apiorder-rpc创建订单、订单列表、订单明细order-apitravel-rpc校验民宿是否存在order-apipayment-rpc查询订单支付信息order-rpctravel-rpc拉取民宿详情用于组装订单快照order-rpcRedisasynq投递延迟关单任务三、订单举例完整业务链路实战3.1 下订单创建民宿订单用户在 travel 服务浏览民宿时选定入住日期后即可下单一次下单请求会串联起 api 层与 rpc 层的多个环节。第一步定义 API 接口接口定义位于 app/order/cmd/api/desc/order/order.api业务类型定义与 app/order/cmd/api/desc/order.api服务路由定义原文档摘录的核心片段如下//订单模块v1版本的接口 server( prefix: order/v1 group: homestayOrder ) service order { doc 创建民宿订单 handler createHomestayOrder post /homestayOrder/createHomestayOrder (CreateHomestayOrderReq) returns (CreateHomestayOrderResp) ..... }其中CreateHomestayOrderReq的完整字段定义在 order.api 中为type ( CreateHomestayOrderReq { HomestayId int64 json:homestayId IsFood bool json:isFood LiveStartTime int64 json:liveStartTime LiveEndTime int64 json:liveEndTime LivePeopleNum int64 json:livePeopleNum Remark string json:remark } CreateHomestayOrderResp { OrderSn string json:orderSn } )可以看到下单请求携带民宿 ID、是否需要餐食、入住起止时间Unix 时间戳、入住人数与备注响应则返回订单号orderSn。此外路由定义中服务组还标注了jwt: JwtAuth说明订单相关接口全部需要登录鉴权。第二步order-api 调用 order-rpc请求进入 CreateHomestayOrderHandler 完成参数解析后由 createHomestayOrderLogic.go 执行业务userId : ctxdata.GetUidFromCtx(l.ctx) // 从JWT上下文中取出登录用户ID resp, err : l.svcCtx.OrderRpc.CreateHomestayOrder(l.ctx, order.CreateHomestayOrderReq{ HomestayId: req.HomestayId, IsFood: req.IsFood, LiveStartTime: req.LiveStartTime, LiveEndTime: req.LiveEndTime, UserId: userId, LivePeopleNum: req.LivePeopleNum, Remark: req.Remark, })其中ctxdata.GetUidFromCtx是项目公共包 common/ctxdata/ctxData.go 提供的用户身份获取工具。调用关系如下图所示第三步order-rpc 校验条件、创建订单并投递延迟关单任务order-rpc 侧的核心逻辑位于 app/order/cmd/rpc/internal/logic/createHomestayOrderLogic.go原文档摘录的延迟任务部分如下// CreateHomestayOrder. func (l *CreateHomestayOrderLogic) CreateHomestayOrder(in *pb.CreateHomestayOrderReq) (*pb.CreateHomestayOrderResp, error) { ..... //2、Delayed closing of order tasks. payload, err : json.Marshal(jobtype.DeferCloseHomestayOrderPayload{Sn: order.Sn}) if err ! nil { logx.WithContext(l.ctx).Errorf(create defer close order task json Marshal fail err :%v , sn : %s,err,order.Sn) }else{ _, err l.svcCtx.AsynqClient.Enqueue(asynq.NewTask(jobtype.DeferCloseHomestayOrder, payload), asynq.ProcessIn(CloseOrderTimeMinutes * time.Minute)) if err ! nil { logx.WithContext(l.ctx).Errorf(create defer close order task insert queue fail err :%v , sn : %s,err,order.Sn) } } return pb.CreateHomestayOrderResp{ Sn: order.Sn, }, nil }结合完整源码order-rpc 创建订单的完整流程为参数校验若in.LiveEndTime in.LiveStartTime直接返回错误Stay at least one night查询民宿详情调用TravelRpc.HomestayDetail拉取民宿信息travel-rpc若民宿不存在则报错组装订单快照将民宿的标题、副标题、封面Banner 第一张、介绍、价格等字段冗余写入订单表形成订单快照封面通过strings.Split(resp.Homestay.Banner, ,)[0]截取生成订单号调用公共包uniqueid.GenSn(uniqueid.SN_PREFIX_HOMESTAY_ORDER)生成以HSO为前缀的订单号见 common/uniqueid/sn.go格式为前缀 时间戳 8 位随机数字计算价格liveDays通过入住结束时间减去开始时间除以 86400 秒得到民宿总价 民宿单价 × 天数若选择餐食IsFood餐食总价 餐食单价 × 入住人数 × 天数订单总价 民宿总价 餐食总价。注意源码中价格均以分为单位的 int64 存储写入数据库HomestayOrderModel.Insert落库初始状态为HomestayOrderTradeStateWaitPay待支付值为 0见 app/order/model/vars.go并生成 8 位随机TradeCode交易码tool.Krand(8, tool.KC_RAND_KIND_ALL)投递延迟关单任务将订单号Sn序列化为 asynq 任务负载投递到 Redis 延迟队列延迟时间为CloseOrderTimeMinutes分钟。其中第 7 步是以延迟队列替代定时任务轮询关单的关键设计。常量定义在文件顶部const CloseOrderTimeMinutes 30 //defer close order time即订单创建后30 分钟原文档正文提到 20 分钟当前仓库源码实际为 30 分钟请以源码为准未支付将由延迟任务自动关闭。asynq 客户端在 app/order/cmd/rpc/internal/svc/asynqClient.go 中创建底层直接连接order.yaml中配置的 Redis//create asynq client. func newAsynqClient(c config.Config) *asynq.Client { return asynq.NewClient(asynq.RedisClientOpt{Addr: c.Redis.Host, Password: c.Redis.Pass}) }第四步mqueue-job 消费延迟任务、关闭未支付订单延迟任务的消费方是消息队列服务中的 job 模块 app/mqueue/cmd/job。原文档指出该目录下 closeOrder.go 定义了 asynq 的延迟关闭订单任务核心代码如下// defer close no pay homestayOrder : if return err ! nil , asynq will retry func (l *CloseHomestayOrderHandler) ProcessTask(ctx context.Context, t *asynq.Task) error { var p jobtype.DeferCloseHomestayOrderPayload if err : json.Unmarshal(t.Payload(), p); err ! nil { return errors.Wrapf(ErrCloseOrderFal, closeHomestayOrderStateMqHandler payload err:%v, payLoad:%v, err, t.Payload()) } resp, err : l.svcCtx.OrderRpc.HomestayOrderDetail(ctx, order.HomestayOrderDetailReq{ Sn: p.Sn, }) if err ! nil || resp.HomestayOrder nil { return errors.Wrapf(ErrCloseOrderFal, closeHomestayOrderStateMqHandler get order fail or order no exists err:%v, sn:%s ,HomestayOrder : %v, err, p.Sn, resp.HomestayOrder) } if resp.HomestayOrder.TradeState model.HomestayOrderTradeStateWaitPay { _, err : l.svcCtx.OrderRpc.UpdateHomestayOrderTradeState(ctx, order.UpdateHomestayOrderTradeStateReq{ Sn: p.Sn, TradeState: model.HomestayOrderTradeStateCancel, }) if err ! nil { return errors.Wrapf(ErrCloseOrderFal, CloseHomestayOrderHandler close order fail err:%v, sn:%s , err, p.Sn) } } return nil }该处理器的工作逻辑非常清晰反序列化任务负载取出订单号Sn调用 order-rpc 的HomestayOrderDetail查询订单当前状态只有当订单仍处于待支付HomestayOrderTradeStateWaitPay时才将其更新为已取消HomestayOrderTradeStateCancel若订单已支付则直接忽略避免误关已支付订单返回nil表示任务成功返回错误时 asynq 会自动重试代码注释明确说明if return err ! nil , asynq will retry。任务路由的注册在 app/mqueue/cmd/job/internal/logic/routes.go 中完成//register job func (l *CronJob) Register() *asynq.ServeMux { mux : asynq.NewServeMux() //scheduler job mux.Handle(jobtype.ScheduleSettleRecord,NewSettleRecordHandler(l.svcCtx)) //defer job mux.Handle(jobtype.DeferCloseHomestayOrder,NewCloseHomestayOrderHandler(l.svcCtx)) //queue job , asynq support queue job // wait you fill.. return mux }任务类型常量定义在 app/mqueue/cmd/job/jobtype/jobtype.go// DeferCloseHomestayOrder 延迟关闭订单 const DeferCloseHomestayOrder defer:homestay_order:close对应负载结构在 jobpayload.go// DeferCloseHomestayOrderPayload defer close homestay order type DeferCloseHomestayOrderPayload struct { Sn string }启动 mqueue-job 时asynq server 即被加载并注册上述路由server 配置见 app/mqueue/cmd/job/internal/svc/asynqServer.go并发数为 20。当之前投递的延迟任务到达 30 分钟时就会自动执行关闭订单逻辑未支付则关闭订单已支付则忽略。这样就不必使用定时任务轮询来关闭订单既避免了轮询带来的数据库压力又保证了关单的实时性——这正是原文档所强调的核心设计价值。3.2 订单列表订单列表接口定义同样位于 order.api//订单模块v1版本的接口 server( prefix: order/v1 group: homestayOrder ) service order { doc 用户订单列表 handler userHomestayOrderList post /homestayOrder/userHomestayOrderList (UserHomestayOrderListReq) returns (UserHomestayOrderListResp) }该接口的请求与响应类型type ( UserHomestayOrderListView { Sn string json:sn //单号 Title string json:title //标题 SubTitle string json:subTitle //副标题 HomestayId int64 json:homestayId //民宿id Cover string json:cover //封面 OrderTotalPrice float64 json:orderTotalPrice //订单总价格 CreateTime int64 json:createTime //下单时间 TradeState int64 json:tradeState //-1: 已取消 0:待支付 1:未使用 2:已使用 3:已退款 4:已过期 LiveStartDate int64 json:liveStartDate //入驻开始日期 LiveEndDate int64 json:liveEndDate //入驻结束日期 TradeCode string json:tradeCode //交易码 } UserHomestayOrderListReq { LastId int64 json:lastId PageSize int64 json:pageSize TradeState int64 json:tradeState //-99:全部 0:代支付订单 1:有效订单 } UserHomestayOrderListResp { List []UserHomestayOrderListView json:list } )原文档指出该接口没啥逻辑就是查出来显示。实际从源码看api 层 userHomestayOrderListLogic.go 会做两件事从 JWT 上下文取当前登录用户 ID调用 order-rpc 的UserHomestayOrderList通过copier.Copy将 RPC 返回结果拷贝到 api 层类型并用公共工具tool.Fen2Yuan将分转换为元见 common/tool/coinconvert.go。rpc 层 userHomestayOrderListLogic.go 则使用 squirrel 构建条件查询实现基于游标LastId的分页whereBuilder : l.svcCtx.HomestayOrderModel.SelectBuilder().Where(squirrel.Eq{user_id: in.UserId}) //There are supported states in the filter, otherwise return all if in.TraderState model.HomestayOrderTradeStateCancel in.TraderState model.HomestayOrderTradeStateExpire { whereBuilder whereBuilder.Where(squirrel.Eq{trade_state: in.TraderState}) } list, err : l.svcCtx.HomestayOrderModel.FindPageListByIdDESC(l.ctx, whereBuilder, in.LastId, in.PageSize)FindPageListByIdDESC是 go-zero 缓存模型生成的游标分页方法见 app/order/model/homestayOrderModel_gen.go 的FindPageListByIdDESC仅当preMinId 0时附加id ?条件按id DESC排序并限制pageSize条数属于典型的加载更多式滚动分页。3.3 订单明细订单明细接口定义//订单模块v1版本的接口 server( prefix: order/v1 group: homestayOrder ) service order { doc 用户订单明细 handler userHomestayOrderDetail post /homestayOrder/userHomestayOrderDetail (UserHomestayOrderDetailReq) returns (UserHomestayOrderDetailResp) }虽然原文档同样以没啥逻辑一笔带过但从源码看订单明细实际上承载了两个值得注意的细节见 userHomestayOrderDetailLogic.go数据归属校验RPC 返回订单后api 层会校验resp.HomestayOrder.UserId userId防止越权查看他人订单支付信息联动当订单状态不是已取消且不是待支付即已经进入交易流程时会调用payment-rpc的GetPaymentSuccessRefundByOrderSn查询支付时间与支付方式填充到明细的PayTime、PayType字段——这正体现了 order-api 依赖 payment-rpc 的实际场景。rpc 层 homestayOrderDetailLogic.go 的实现则通过HomestayOrderModel.FindOneBySn按订单号查询走 Redis 缓存缓存键前缀为cache:looklookOrder:homestayOrder:sn:见 homestayOrderModel_gen.go。四、补充订单状态机与状态流转约束虽然原文档未展开但订单服务的状态流转校验是理解整个模块的关键值得补充。订单交易状态在 app/order/model/vars.go 中以常量的形式定义var HomestayOrderTradeStateCancel int64 -1 // 已取消 var HomestayOrderTradeStateWaitPay int64 0 // 待支付 var HomestayOrderTradeStateWaitUse int64 1 // 未使用 var HomestayOrderTradeStateUsed int64 2 // 已使用 var HomestayOrderTradeStateRefund int64 3 // 已退款 var HomestayOrderTradeStateExpire int64 4 // 已过期订单状态的更新统一收敛在 order-rpc 的 updateHomestayOrderTradeStateLogic.go。该方法在更新前会调用verifyOrderTradeState校验状态迁移的合法性新状态允许的旧状态说明待支付0不允许任何状态迁移至此防止回退到待支付已取消-1仅待支付0只有待支付订单才能被取消未使用1仅待支付0支付成功后才可变为未使用已使用2仅未使用1未使用的订单才可使用已退款3仅未使用1未使用的订单才可退款已过期4仅未使用1未使用的订单才可过期同时该方法是延迟关单任务的最终落点UpdateHomestayOrderTradeState内部通过UpdateWithVersion乐观锁更新version字段自增更新影响行数为 0 时返回ErrNoRowsUpdate见 homestayOrderModel_gen.go从机制上避免并发场景下的状态覆盖。此外状态更新为未使用1即支付成功后还会顺带投递一个MsgPaySuccessNotifyUser的即时队列任务msg:pay_success:notify_user用于通知用户支付成功对应消费逻辑在 app/mqueue/cmd/job/internal/logic/paySuccessNotifyUser.go。由此可见订单服务的状态变更并不只是简单的数据库 UPDATE而是与延迟队列、即时队列深度联动。五、小结与下一步至此go-zero-looklook 订单服务的完整脉络已经清晰order-api 对外提供 JWT 保护的 HTTP 接口order-rpc 承载核心下单逻辑与状态机借助 asynq 延迟队列在 30 分钟后自动关闭未支付订单再由 mqueue-job 异步消费任务完成关单。这一延迟队列替代定时轮询的设计加上价格以分为单位存储、订单快照冗余、乐观锁更新、游标分页等细节构成了一个可直接借鉴的生产级订单模块范式。下了订单当然就要支付了——订单创建后返回的orderSn将作为支付请求的核心参数。下一篇我们将进入支付服务看订单如何与微信支付打通以及支付成功回调后订单状态又是如何从待支付流转为未使用的。赞分享后端微服务【免费下载链接】go-zero-looklook基于go-zero(go zero) 微服务全技术栈开发最佳实践项目。Develop best practice projects based on the full technology stack of go zero (go zero) microservices.项目地址https://gitcode.com/gh_mirrors/go/go-zero-looklook点击查看免费下载相关推荐Accelerate 故障排查实战指南日志、卡死、OOM 与可复现性问题定位Accelerate 故障排查实战指南日志、卡死、OOM 与可复现性问题定位 本指南面向使用 Hugging Face Accelerate 进行单机/多机分后端微服务vLLM-Omni 离线推理 Step-Audio2 完整指南从 Thinker 到 Token2Wav 的两阶段音频推理实战vLLM Omni 离线推理 Step Audio2 完整指南从 Thinker 到 Token2Wav 的两阶段音频推理实战 本文是 vLLM Omni 仓后端微服务Modern C Tutorial 第 2 章精讲C11 到 C17 语言可用性增强constexpr、类型推导、结构化绑定与模板Modern C Tutorial 第 2 章精讲C11 到 C17 语言可用性增强constexpr、类型推导、结构化绑定与模板 语言可用性后端微服务上一篇无线音频传输与跨设备协作告别线缆的Android与电脑音频无缝互联新方案下一篇3步掌握Android音频跨设备无缝流转工具全攻略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。