首页/新闻资讯/正文详情

命令模式实战:把“下单后的动作“打包成可执行的任务

发布时间:2026/9/26 1:29:49 来源:云帆数科 栏目:资讯中心
命令模式实战:把“下单后的动作“打包成可执行的任务
一句话定位将一个请求封装为一个对象从而可用不同的请求对客户进行参数化对请求排队或记录请求日志以及支持可撤销的操作。一、实战场景用户下单成功后系统要做一连串后置动作发送订单确认短信/邮件发放积分更新会员等级通知仓储系统备货记录运营埋点。如果直接在主流程里逐个调用publicasyncTaskOnOrderCreatedAsync(Orderorder){await_smsService.SendAsync(order.CustomerPhone,下单成功……);await_pointsService.GrantAsync(order.CustomerId,100);await_memberService.UpgradeAsync(order.CustomerId);await_warehouseService.NotifyAsync(order);await_trackingService.TrackAsync(order.created,order);// 每加一个动作这里就多一行失败处理也各不相同……}问题主流程被业务动作塞满订单核心逻辑和后置动作耦合在一起这些动作往往需要异步执行、失败重试、排队削峰直接内联调用做不到想批量重放比如系统恢复后重跑某天的后置动作无从下手每个动作的失败处理策略不同短信失败可忽略、积分发放失败要重试散落在主流程里。命令模式的解法把每个动作封装成一个命令对象实现统一接口主流程只负责创建命令并提交。命令可以被排队、被记录、被重放、被批量执行——动作本身变成了数据。二、设计思路调用方下单服务 │ 创建并提交 ▼ ┌─────────────────────────────┐ │ CommandQueue命令队列 │ │ 排队 → 逐个执行 → 失败重试 │ └──────────────┬──────────────┘ ▼ 执行Invoke ┌─────────────────────────────┐ │ IOrderCommand命令接口 │ │ ├─ SendSmsCommand │ │ ├─ GrantPointsCommand │ │ ├─ UpgradeMemberCommand │ │ ├─ NotifyWarehouseCommand │ │ └─ TrackEventCommand │ └─────────────────────────────┘命令模式的精髓做什么被封装成对象可以存储、传输、排队、重放。三、代码实现.NET 10 / C# 143.1 命令接口与执行上下文namespaceOrders.AfterOrder;publicinterfaceIOrderCommand{stringName{get;}TaskExecuteAsync(OrderContextcontext,CancellationTokenctdefault);TaskUndoAsync(OrderContextcontext,CancellationTokenctdefault);// 可选的补偿}publicsealedrecordOrderContext(OrderSnapshotOrder);publicsealedrecordOrderSnapshot(stringOrderId,stringCustomerId,stringCustomerPhone,decimalAmount);3.2 具体命令们每个动作一个命令namespaceOrders.AfterOrder.Commands;// 命令1发短信publicsealedclassSendSmsCommand(ISmsSendersms):IOrderCommand{publicstringNameSendSms;publicTaskExecuteAsync(OrderContextctx,CancellationTokenctdefault)sms.SendAsync(ctx.Order.CustomerPhone,$您已成功下单{ctx.Order.OrderId}金额{ctx.Order.Amount:C},ct);publicTaskUndoAsync(OrderContextctx,CancellationTokenctdefault)Task.CompletedTask;// 短信无法撤回}// 命令2发积分失败可重试publicsealedclassGrantPointsCommand(IPointsServicepoints):IOrderCommand{publicstringNameGrantPoints;publicTaskExecuteAsync(OrderContextctx,CancellationTokenctdefault)points.GrantAsync(ctx.Order.CustomerId,(int)(ctx.Order.Amount/10),ct);publicTaskUndoAsync(OrderContextctx,CancellationTokenctdefault)points.RecoverAsync(ctx.Order.CustomerId,(int)(ctx.Order.Amount/10),ct);}// 命令3通知仓储publicsealedclassNotifyWarehouseCommand(IWarehouseServicewarehouse):IOrderCommand{publicstringNameNotifyWarehouse;publicTaskExecuteAsync(OrderContextctx,CancellationTokenctdefault)warehouse.NotifyAsync(ctx.Order.OrderId,ct);publicTaskUndoAsync(OrderContextctx,CancellationTokenctdefault)Task.CompletedTask;}// 命令4运营埋点可忽略失败publicsealedclassTrackEventCommand(ITrackingServicetracking):IOrderCommand{publicstringNameTrackEvent;publicTaskExecuteAsync(OrderContextctx,CancellationTokenctdefault)tracking.TrackAsync(order.created,ctx.Order,ct);publicTaskUndoAsync(OrderContextctx,CancellationTokenctdefault)Task.CompletedTask;}3.3 执行器命令队列排队、重试、记录namespaceOrders.AfterOrder;publicsealedclassCommandQueue(ILoggerlogger){privatereadonlyQueueIOrderCommand_commandsnew();publicvoidEnqueue(IOrderCommandcommand)_commands.Enqueue(command);publicasyncTaskIReadOnlyListCommandExecutionResultFlushAsync(CancellationTokenctdefault){varresultsnewListCommandExecutionResult();while(_commands.Count0){varcommand_commands.Dequeue();varswStopwatch.StartNew();try{awaitExecuteWithRetryAsync(command,ct);results.Add(newCommandExecutionResult(command.Name,true,sw.ElapsedMilliseconds,null));}catch(Exceptionex){logger.LogError(ex,命令 {Name} 执行失败,command.Name);results.Add(newCommandExecutionResult(command.Name,false,sw.ElapsedMilliseconds,ex.Message));}}returnresults;}privateasyncTaskExecuteWithRetryAsync(IOrderCommandcommand,CancellationTokenct,intmaxRetries3){for(varattempt1;;attempt){try{awaitcommand.ExecuteAsync(CommandContext.Current,ct);return;}catchwhen(attemptmaxRetries){awaitTask.Delay(TimeSpan.FromSeconds(attempt),ct);// 指数退避的简化版}}}}publicsealedrecordCommandExecutionResult(stringName,boolSuccess,longElapsedMs,string?Error);// 简单模拟全局执行上下文真实项目可用 AsyncLocal / DI ScopepublicstaticclassCommandContext{publicstaticOrderContextCurrent{get;set;}default!;}3.4 主流程下单后把动作打包提交namespaceOrders;publicsealedclassOrderService(CommandQueuequeue,SendSmsCommandsmsCommand,GrantPointsCommandpointsCommand,NotifyWarehouseCommandwarehouseCommand,TrackEventCommandtrackCommand){publicasyncTaskOrderSnapshotCreateOrderAsync(CreateOrderRequestreq,CancellationTokenctdefault){// 1. 核心下单逻辑校验、扣库存、建单……省略varordernewOrderSnapshot(OrderId:$ORD-{Guid.NewGuid():N}[..18],CustomerId:req.CustomerId,CustomerPhone:req.Phone,Amount:req.Amount);// 2. 后置动作全部打包成命令进队列CommandContext.CurrentnewOrderContext(order);queue.Enqueue(smsCommand);queue.Enqueue(pointsCommand);queue.Enqueue(warehouseCommand);queue.Enqueue(trackCommand);// 3. 统一执行真实项目队列可换成 Hangfire / RabbitMQ实现异步削峰varresultsawaitqueue.FlushAsync(ct);returnorderwith{/* 可附加 results 状态 */};}}3.5 组装与运行// Program.cs.NET 10 顶层语句usingOrders;usingOrders.AfterOrder;usingOrders.AfterOrder.Commands;varbuilderWebApplication.CreateBuilder(args);builder.Services.AddLogging();// 注册各种服务与命令builder.Services.AddSingletonISmsSender,FakeSmsSender();builder.Services.AddSingletonIPointsService,FakePointsService();builder.Services.AddSingletonIWarehouseService,FakeWarehouseService();builder.Services.AddSingletonITrackingService,FakeTrackingService();builder.Services.AddSingletonSendSmsCommand();builder.Services.AddSingletonGrantPointsCommand();builder.Services.AddSingletonNotifyWarehouseCommand();builder.Services.AddSingletonTrackEventCommand();builder.Services.AddSingletonCommandQueue();builder.Services.AddScopedOrderService();varappbuilder.Build();app.MapPost(/orders,async(CreateOrderRequestreq,OrderServiceorders){varorderawaitorders.CreateOrderAsync(req);returnResults.Ok(new{order.OrderId,Statuscreated});});app.Run();// 各 Fake 服务的实现演示用真实项目替换为实际客户端publicinterfaceISmsSender{TaskSendAsync(stringphone,stringtext,CancellationTokenct);}publicsealedclassFakeSmsSender:ISmsSender{publicTaskSendAsync(stringphone,stringtext,CancellationTokenct){Console.WriteLine($[短信]{phone}:{text});returnTask.CompletedTask;}}publicinterfaceIPointsService{TaskGrantAsync(stringcustomer,intpoints,CancellationTokenct);TaskRecoverAsync(stringcustomer,intpoints,CancellationTokenct);}publicsealedclassFakePointsService:IPointsService{publicTaskGrantAsync(stringcustomer,intpoints,CancellationTokenct){Console.WriteLine($[积分] 用户{customer}{points});returnTask.CompletedTask;}publicTaskRecoverAsync(stringcustomer,intpoints,CancellationTokenct)Task.CompletedTask;}publicinterfaceIWarehouseService{TaskNotifyAsync(stringorderId,CancellationTokenct);}publicsealedclassFakeWarehouseService:IWarehouseService{publicTaskNotifyAsync(stringorderId,CancellationTokenct){Console.WriteLine($[仓储] 备货通知{orderId});returnTask.CompletedTask;}}publicinterfaceITrackingService{TaskTrackAsync(stringevt,objectpayload,CancellationTokenct);}publicsealedclassFakeTrackingService:ITrackingService{publicTaskTrackAsync(stringevt,objectpayload,CancellationTokenct){Console.WriteLine($[埋点]{evt});returnTask.CompletedTask;}}publicsealedrecordCreateOrderRequest(stringCustomerId,stringPhone,decimalAmount);四、应用要点与注意事项命令模式的三大价值解耦主流程不再知道后置动作的实现、可排队命令是对象能进队列、异步执行、削峰、可重放命令可记录、可批量重跑、可做补偿。与消息队列的关系命令模式是代码层面的封装消息队列是执行层面的载体。两者常组合使用——把命令序列化成消息投递到 RabbitMQ/Kafka消费者反序列化后执行。理解命令模式就理解了任务系统的核心。Undo 是可选能力不是所有命令都能撤销短信发出去撤不回。UndoAsync返回空实现即可但设计命令接口时保留这个钩子未来做补偿/回滚时不用改接口。区分命令 vs 事件命令是明确的意图期望被执行发积分事件是已经发生的事实可以触发副作用订单已创建。下单后置动作既可以用命令模式也可以用观察者模式事件驱动实现——命令模式更强调可排队、可重放事件驱动更强调解耦发布订阅。两者可以并存。每个命令保持单一职责一个命令只做一件事失败重试策略可以在执行器层面统一如示例的ExecuteWithRetryAsync不要让命令内部各自实现重试。五、小结命令模式把动作从代码升级成了数据可排队、可记录、可重放、可补偿。下单后置动作、定时任务、批量操作、撤销重做都是它的经典舞台。配合消息队列、Hangfire 这类执行载体它几乎构成了现代后端异步任务体系的全部雏形。

相关推荐

酒店管理系统C#源码与文档:从需求到部署的完整实战指南
酒店管理系统C#源码与文档:从需求到部署的完整实战指南

上周还有人在后台问我:课程设计选了酒店管理系统,指定用 C# 写,有没有现成的源码+文档可以参考?我翻了一下电脑,正好手边就有一套自己整理过的完整项目。这类题目我前前后后带过不少,结论是&… · 2026/9/26 1:29:49

Python打卡第27天
Python打卡第27天

浙大疏锦行 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 # This Python 3 environment comes with many helpful analytics libraries installed # It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python # For example, heres… · 2026/9/26 1:29:49

广东搜宇科技有限公司和同行哪家好?3个对比维度解析
广东搜宇科技有限公司和同行哪家好?3个对比维度解析

智能安防物联网领域,广东搜宇科技有限公司与海康、大华等同行无绝对优劣,选谁看预算、定制和交付。广东搜宇科技有限公司与同行相比,核心差异是什么核心差异在定制灵活度、价格和生态成熟度。说实话,这三项直接决定项目落地成本和… · 2026/9/26 1:29:42

为什么图纸问题总到施工时才暴露?
为什么图纸问题总到施工时才暴露?

标高冲突、预留洞口对不上、节点做法现场现编——这些问题几乎从不在图纸阶段冒头,专挑施工时炸。是图纸质量真没问题吗?不是。是发现它们的时机被推后了。问题其实一直都在,只是三个环节没人兜住。 环节一:会前没人真看图。 会审… · 2026/9/26 2:17:17

ElastAlert 规则过滤器(filter)编写指南:从 Query DSL 到 Kibana 导入的完整实战
ElastAlert 规则过滤器(filter)编写指南:从 Query DSL 到 Kibana 导入的完整实战

告警异常检测 【免费下载链接】elastalert Easy & Flexible Alerting With ElasticSearch 项目地址: https://gitcode.com/gh_mirrors/el/elastalert 点击查看 免费下载 ElastAlert 的每条规则都通过 filter 字段定义"什么样的日志事件需要被处理"&a… · 2026/9/26 2:17:11

蘑菇检测数据集与YOLO训练实战:从标注到部署避坑指南
蘑菇检测数据集与YOLO训练实战:从标注到部署避坑指南

简介:面向深度学习目标检测任务的蘑菇检测数据集,适合正在学习YOLO系列模型或需要目标检测训练数据的开发者使用。数据已按YOLO格式整理,包含训练集与验证集、对应标签文件以及类别说明class文件,标签由labelme标注完成&#xff0… · 2026/9/26 2:17:11

使用AI生成C/C++头文件(.h)函数注释的方法和步骤:TaoToken统一API接入与配置骨架
使用AI生成C/C++头文件(.h)函数注释的方法和步骤:TaoToken统一API接入与配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 2:17:05

ESP32双模智能家居DIY:WiFi与BLE协同开发实战指南
ESP32双模智能家居DIY:WiFi与BLE协同开发实战指南

1. 为什么ESP32成了智能家居DIY的甜点级芯片如果你最近在折腾智能家居,大概率绕不开ESP32这颗芯片。它火到什么程度?随便打开一个创客社区,ESP32相关的项目帖能占掉半壁江山。原因其实不复杂:一颗十几块钱的芯片,同时集… · 2026/9/26 2:16:59

Word Shift+F3快捷键深度解析:文本大小写原子级控制
Word Shift+F3快捷键深度解析:文本大小写原子级控制

1. 这个快捷键不是“按一下就完事”,而是Word里最被低估的文本格式切换开关很多人在Word里遇到大小写混杂的英文段落,第一反应是手动选中、右键、点“更改大小写”——这动作看似简单,但每天重复十几次,一年下来就是近两万次鼠标悬… · 2026/9/26 2:16:59

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码