简介在构建现代Web应用时选择合适的技术栈是项目成功的关键。ASP.NET Core MVC框架凭借其清晰的MVC架构、强大的依赖注入容器和高性能的Kestrel服务器为开发者提供了坚实的开发骨架。关系型数据库特别是SQL Server以其强大的ACID事务处理能力和对复杂查询的优异支持成为处理电商等强一致性业务场景的可靠选择。这套技术组合的价值在于它能高效支撑从商品管理、订单处理到用户认证的全链路业务逻辑尤其适合需要深度定制和高稳定性的企业级应用场景。本文以构建一个现代电商平台为例深入探讨了如何利用ASP.NET Core MVC与SQL Server的组合拳解决库存并发控制、订单状态机设计等核心难题并分享了Entity Framework Core的优化实践与部署调优要点。1. 项目概述从零构建一个现代电商平台的技术栈选择最近几年无论是创业公司还是传统企业搭建自己的在线商城系统都是一个绕不开的需求。市面上虽然有SaaS化的解决方案但对于需要深度定制、掌控核心数据和业务流程的团队来说自己动手构建一套商城系统依然是性价比和灵活性最高的选择。今天我想和大家聊聊如果现在要启动这样一个项目为什么我会坚定地选择ASP.NET Core MVC SQL Server这套技术组合以及这套组合拳在实际开发中究竟能带来哪些实实在在的好处。简单来说这个项目就是利用 ASP.NET Core MVC 框架作为后端和前端视图的引擎SQL Server 作为数据存储的核心来构建一个功能完整、性能可靠、易于维护的 B2C 或 B2B 商城系统。它要解决的核心问题是为开发者提供一个从商品展示、用户管理、购物车、订单处理到支付集成的全链路开发范本。这套方案特别适合那些熟悉 .NET 技术栈、追求开发效率与系统稳定性的团队无论是经验丰富的老手还是希望系统学习企业级应用开发的新手都能从中找到清晰的路径和可复用的代码。2. 技术栈深度解析为什么是 ASP.NET Core MVC 与 SQL Server2.1 ASP.NET Core MVC现代Web开发的坚实骨架选择 ASP.NET Core MVC绝非仅仅因为它是微软的“亲儿子”。经过多年的迭代特别是跨平台和开源之后它已经脱胎换骨成为构建高性能、可测试企业级应用的首选框架之一。首先清晰的关注点分离SoC是MVC模式的核心优势。在商城系统中业务逻辑极其复杂。商品模块要处理SKU、库存、价格策略订单模块涉及状态流转、库存扣减、物流跟踪用户模块则关乎身份认证、权限和数据安全。MVC模式强制性地将模型Model即业务逻辑和数据、视图View即用户界面和控制器Controller即请求处理逻辑分开。这意味着负责商品管理的开发人员可以专注于ProductService类的业务规则而前端工程师则可以并行开发商品列表页的Razor视图两者通过定义良好的接口和模型进行交互极大提升了团队协作效率和代码的可维护性。当需要修改商品详情页的展示样式时你几乎不需要触碰后端的C#代码。其次内置的强大基础设施减少了大量重复劳动。ASP.NET Core 提供了开箱即用的依赖注入DI容器。在商城系统中我们会创建诸如IProductRepository、IOrderService、IPaymentGateway等大量服务接口及其实现。通过依赖注入我们可以轻松地在Startup.cs或Program.cs中注册这些服务然后在控制器或其它服务中通过构造函数注入使用。这不仅使单元测试变得异常简单可以轻松注入Mock对象也让代码结构更加清晰、松耦合。例如今天你用A支付渠道明天想换B只需要替换IPaymentGateway的实现并重新注册所有调用它的地方都无需修改。再者其高性能和跨平台能力是应对商城高并发的底气。ASP.NET Core 的Kestrel服务器性能卓越配合异步编程模型async/await能够高效处理商城大促时瞬间涌入的海量请求比如秒杀场景下的下单请求。同时跨平台特性允许你将应用部署在Linux服务器上通常能获得比Windows Server更优的性价比和资源利用率这对于控制项目运营成本至关重要。2.2 SQL Server关系型数据库的“定海神针”在数据库选型上面对NoSQL的冲击我仍然为商城系统首选SQL Server原因在于商城业务本质上是强一致性、强事务性的。核心优势在于其强大的事务处理能力和数据一致性保障。想象一下用户下单的瞬间需要从库存中扣除商品数量、在订单表中创建记录、在订单明细表中插入商品项、可能还要更新用户的积分。这些操作必须作为一个原子单元全部成功或全部失败。SQL Server提供了完善的ACID原子性、一致性、隔离性、持久性事务支持通过BEGIN TRANSACTION、COMMIT、ROLLBACK可以轻松实现复杂的业务事务。这是许多NoSQL数据库难以媲美的对于确保资金和库存数据绝对准确是底线要求。其次成熟的T-SQL语言和丰富的功能特性能应对复杂业务查询。商城后台经常需要运行复杂的报表查询例如“统计过去一个月每个品类的销售额并按地区分组”。这种涉及多表连接JOIN、分组GROUP BY、聚合SUM和筛选WHERE的操作正是SQL Server这类关系数据库的强项。利用存储过程或优化后的视图可以将这些复杂逻辑封装在数据库层提高执行效率。此外像全文搜索、行版本控制用于乐观并发控制防止超卖、地理空间数据支持等特性在特定场景下也非常有用。与.NET生态的无缝集成大幅提升开发体验。使用Entity Framework CoreEF Core作为ORM框架时SQL Server有着最好的支持。Visual Studio和命令行工具提供了完美的数据库迁移Migration体验你可以用C#代码定义数据模型然后一键生成或更新数据库结构这极大地简化了数据库的版本管理。对于团队中的.NET开发者来说这套工具链的学习成本和开发效率是最优的。注意虽然SQL Server Express版免费但对于正式上线的商城建议至少使用Standard版。Express版有10GB的数据库大小限制和有限的性能在业务增长后可能成为瓶颈。开发阶段可以用Express或Developer版免费但生产环境规划需提前考虑。3. 商城系统核心模块设计与实现要点一个完整的商城系统可以拆解为若干高内聚、低耦合的模块。下面我以几个核心模块为例拆解其设计思路和用ASP.NET Core MVC实现的关键点。3.1 商品与库存管理模块这是商城的基础。其核心模型Product商品和ProductSKU商品规格的设计至关重要。数据模型设计通常采用主从表结构。Product表存储商品通用信息如名称、分类、主图、详情等。ProductSKU表则存储具体规格如“iPhone 15 Pro 256GB 深空黑”包含唯一SKU编码、价格、库存、规格属性JSON格式或关联规格属性表。这种设计平衡了灵活性与查询效率。// Product 实体模型示例 public class Product { public int Id { get; set; } public string Name { get; set; } public int CategoryId { get; set; } public virtual Category Category { get; set; } // 导航属性 public string Description { get; set; } public string MainImageUrl { get; set; } public bool IsActive { get; set; } public DateTime CreatedTime { get; set; } // 商品关联的SKU集合 public virtual ICollectionProductSKU SKUs { get; set; } } // ProductSKU 实体模型示例 public class ProductSKU { public int Id { get; set; } public int ProductId { get; set; } public virtual Product Product { get; set; } // 导航属性 public string SKUCode { get; set; } // 唯一库存单元编码 public string Attributes { get; set; } // 规格属性如 {Color: Black, Storage: 256GB} public decimal Price { get; set; } public int StockQuantity { get; set; } // 库存数量 }库存扣减的并发控制这是商城系统的经典难题尤其在秒杀场景。绝对不要简单地用UPDATE ProductSKU SET StockQuantity StockQuantity - Quantity WHERE Id Id然后在代码里判断更新后的值是否大于0。在高并发下会出现超卖。推荐方案是使用乐观并发控制或悲观锁。乐观并发在ProductSKU实体中添加一个byte[] RowVersion属性时间戳。EF Core会在更新时自动检查此版本如果数据已被其他事务修改则抛出DbUpdateConcurrencyException你可以在捕获异常后重试或提示用户。public async Taskbool ReduceStockAsync(int skuId, int quantity) { var sku await _context.ProductSKUs.FindAsync(skuId); if (sku null || sku.StockQuantity quantity) return false; sku.StockQuantity - quantity; try { await _context.SaveChangesAsync(); return true; } catch (DbUpdateConcurrencyException) { // 重试逻辑或返回失败 return false; } }悲观锁对于极端高并发场景可以在事务中使用UPDLOCK提示通过原生SQL执行在查询库存时即锁定该行数据直到事务结束。var sql SELECT StockQuantity FROM ProductSKU WITH (UPDLOCK) WHERE Id Id; var currentStock await _context.Database.ExecuteSqlRawAsync(sql, skuId); // ... 判断并更新3.2 购物车与订单模块购物车Cart通常设计为临时性数据而订单Order则是核心业务数据设计上需严谨。购物车设计考虑到用户体验购物车数据通常存储在用户会话Session或浏览器本地存储LocalStorage中对于已登录用户可以同步到服务器端的数据库。一个简单的CartItem模型包含SKU ID和数量即可。在ASP.NET Core中你可以将购物车对象序列化为JSON存入Session或分布式缓存如Redis以实现横向扩展。订单流程与状态机订单是商城最复杂的实体之一涉及Order订单主表、OrderItem订单项、OrderStatusHistory状态历史等多张表。订单状态OrderStatus的设计是关键它定义了订单的生命周期。一个典型的状态流转可能是Pending待支付 -Paid已支付 -Shipped已发货 -Delivered已送达 -Completed已完成。也可能有Cancelled已取消、Refunded已退款等状态。强烈建议使用状态模式State Pattern或至少一个明确的OrderService来封装状态流转逻辑。不要在控制器或各处散落if (order.Status ...)这样的代码。例如public class OrderService : IOrderService { private readonly AppDbContext _context; public OrderService(AppDbContext context) _context context; public async Taskbool ShipOrderAsync(int orderId, string trackingNumber) { var order await _context.Orders.Include(o o.Items).FirstOrDefaultAsync(o o.Id orderId); if (order null || order.Status ! OrderStatus.Paid) return false; // 状态流转核心逻辑 order.Status OrderStatus.Shipped; order.ShippingTime DateTime.UtcNow; order.TrackingNumber trackingNumber; // 记录状态历史 _context.OrderStatusHistories.Add(new OrderStatusHistory { OrderId orderId, Status OrderStatus.Shipped, Remark $发货物流单号{trackingNumber}, CreatedTime DateTime.UtcNow }); await _context.SaveChangesAsync(); // 可触发发货邮件通知等事件 return true; } }3.3 用户认证与授权ASP.NET Core Identity 是一个功能强大、可扩展的会员系统框架对于商城来说它是处理用户注册、登录、双因素认证、外部登录如微信、支付宝的绝佳起点。基础集成通过脚手架可以快速生成Identity相关的页面和代码。但商城用户通常需要扩展更多字段如手机号、头像、收货地址等。这时需要继承IdentityUser创建自定义用户类。public class ApplicationUser : IdentityUser { public string? AvatarUrl { get; set; } public string? NickName { get; set; } // 导航属性一个用户有多个收货地址 public virtual ICollectionUserAddress Addresses { get; set; } }然后在Startup.cs或Program.cs中配置services.AddDefaultIdentityApplicationUser(options options.SignIn.RequireConfirmedAccount true) .AddEntityFrameworkStoresAppDbContext();策略授权Policy-Based Authorization商城后台管理需要精细的权限控制。Identity支持基于角色的授权[Authorize(Roles Admin)]但更灵活的是策略授权。例如定义一个“商品管理”策略services.AddAuthorization(options { options.AddPolicy(ProductManagement, policy policy.RequireAssertion(context context.User.IsInRole(Admin) || context.User.HasClaim(Permission, Product.Create) || context.User.HasClaim(Permission, Product.Edit))); });然后在商品管理的控制器或Action上使用[Authorize(Policy ProductManagement)]即可。4. 数据访问层与Entity Framework Core实战EF Core是我们操作SQL Server的利器但要用好它避免性能陷阱需要遵循一些最佳实践。4.1 数据库上下文DbContext与仓储模式DbContext配置在AppDbContext中除了继承IdentityDbContextApplicationUser更重要的是在OnModelCreating方法中配置实体关系、索引和种子数据。protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 调用基类方法以配置Identity // 配置Product和Category的多对一关系 modelBuilder.EntityProduct() .HasOne(p p.Category) .WithMany(c c.Products) .HasForeignKey(p p.CategoryId) .OnDelete(DeleteBehavior.Restrict); // 防止级联删除 // 为商品名称和SKU编码创建索引以提高查询速度 modelBuilder.EntityProduct() .HasIndex(p p.Name); modelBuilder.EntityProductSKU() .HasIndex(sku sku.SKUCode) .IsUnique(); // 唯一索引 // 种子初始数据如管理员角色、商品分类等 modelBuilder.EntityCategory().HasData( new Category { Id 1, Name 手机数码, ParentId null }, new Category { Id 2, Name 电脑办公, ParentId null } ); }是否使用仓储模式这是一个经典争议。对于中型以上、业务复杂的商城项目我推荐使用。仓储模式Repository Pattern将数据访问逻辑抽象出来使业务层如ProductService不直接依赖EF Core而是依赖IProductRepository接口。这带来了两大好处一是使单元测试更容易可以Mock仓储二是如果未来需要更换数据访问技术概率极低但架构上更干净业务层几乎不受影响。一个简单的仓储接口和实现public interface IProductRepository { TaskProduct? GetByIdAsync(int id); TaskIEnumerableProduct GetActiveProductsByCategoryAsync(int categoryId); Task AddAsync(Product product); // ... 其他方法 } public class ProductRepository : IProductRepository { private readonly AppDbContext _context; public ProductRepository(AppDbContext context) _context context; public async TaskProduct? GetByIdAsync(int id) { // 使用Include进行贪婪加载避免后续的N1查询问题 return await _context.Products .Include(p p.Category) .Include(p p.SKUs) .FirstOrDefaultAsync(p p.Id id); } // ... 实现其他方法 }4.2 查询优化与性能陷阱规避EF Core使用不当很容易产生性能问题尤其是在商城列表页这种需要关联查询多张表的地方。N1查询问题这是最常见的问题。例如循环遍历所有商品然后访问每个商品的分类名称var products await _context.Products.ToListAsync(); // 1次查询 foreach (var p in products) { var categoryName p.Category?.Name; // 每次循环都可能触发1次查询N次 }解决方案是使用Include或投影Select进行贪婪加载// 方法1Include var products await _context.Products .Include(p p.Category) .ToListAsync(); // 1次查询通过JOIN获取分类数据 // 方法2投影只获取所需字段更高效 var productInfos await _context.Products .Select(p new ProductListDto { Id p.Id, Name p.Name, CategoryName p.Category.Name // 在Select中访问导航属性EF Core会智能生成JOIN }) .ToListAsync();分页查询商品列表必须分页。务必使用Skip和Take在数据库层面分页而不是先取全部数据到内存再分页。public async TaskPagedListProduct GetProductsAsync(int categoryId, int pageIndex, int pageSize) { var query _context.Products.AsNoTracking().Where(p p.IsActive); // AsNoTracking提升只读查询性能 if (categoryId 0) query query.Where(p p.CategoryId categoryId); var totalCount await query.CountAsync(); // 获取总数 var items await query.OrderBy(p p.CreatedTime) .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .Include(p p.Category) .ToListAsync(); return new PagedListProduct(items, pageIndex, pageSize, totalCount); }5. 前端视图与Razor Pages实战ASP.NET Core MVC使用Razor语法在服务器端渲染视图这对于需要SEO友好的商城首页、商品详情页来说非常合适。5.1 布局Layout与视图组件View Component共享布局在Views/Shared/_Layout.cshtml中定义网站的整体框架头部导航、页脚、侧边栏等。通过RenderBody()来渲染各个页面的独特内容。视图组件用于创建可重用的UI部件例如“热门商品推荐”、“购物车摘要”。它比局部视图Partial View更强大因为它有自己的逻辑。创建一个视图组件// Components/TopProductsViewComponent.cs public class TopProductsViewComponent : ViewComponent { private readonly IProductRepository _productRepo; public TopProductsViewComponent(IProductRepository productRepo) _productRepo productRepo; public async TaskIViewComponentResult InvokeAsync(int count 5) { var products await _productRepo.GetTopSellingProductsAsync(count); return View(products); } }然后在对应的视图文件Views/Shared/Components/TopProducts/Default.cshtml中编写HTML。在任何视图中调用它await Component.InvokeAsync(TopProducts, new { count 5 })。5.2 表单处理与模型绑定商品添加/编辑页是典型的表单场景。MVC的模型绑定和验证特性让这变得简单。模型类添加数据注解public class ProductCreateViewModel { [Required(ErrorMessage 商品名称不能为空)] [StringLength(100)] public string Name { get; set; } [Required] [Display(Name 商品分类)] public int CategoryId { get; set; } [DataType(DataType.MultilineText)] public string Description { get; set; } [Range(0.01, 10000, ErrorMessage 价格必须在0.01到10000之间)] public decimal Price { get; set; } // ... 其他属性 }控制器Action处理[HttpGet] public IActionResult Create() { ViewBag.Categories _categoryService.GetAll(); // 用于下拉框绑定 return View(); } [HttpPost] [ValidateAntiForgeryToken] // 防止跨站请求伪造 public async TaskIActionResult Create(ProductCreateViewModel model) { if (ModelState.IsValid) // 自动验证数据注解 { // 将ViewModel转换为实体并保存 var product _mapper.MapProduct(model); // 可使用AutoMapper await _productService.CreateAsync(product); return RedirectToAction(nameof(Index)); } // 验证失败返回视图并显示错误信息 ViewBag.Categories _categoryService.GetAll(); return View(model); }在视图中使用Tag Helper可以生成简洁且安全的HTMLform asp-actionCreate methodpost div classform-group label asp-forName/label input asp-forName classform-control / span asp-validation-forName classtext-danger/span /div div classform-group label asp-forCategoryId/label select asp-forCategoryId asp-itemsViewBag.Categories classform-control option value-- 请选择分类 --/option /select span asp-validation-forCategoryId classtext-danger/span /div !-- 其他字段 -- button typesubmit classbtn btn-primary提交/button /form6. 部署、配置与性能调优开发完成只是第一步如何让系统稳定、高效地跑起来是另一个关键。6.1 部署到IIS或Linux部署到Windows IIS在项目上发布为“文件夹”目标。在IIS中创建网站指向发布文件夹。确保服务器已安装对应的**.NET Core运行时和Windows Server Hosting Bundle**。配置应用程序池为“无托管代码”并设置正确的身份。部署到Linux如Ubuntu在服务器上安装.NET运行时。使用dotnet publish -c Release发布项目。可以使用Nginx作为反向代理转发请求到运行在Kestrel上的ASP.NET Core应用。Nginx处理静态文件效率更高还能做负载均衡。使用systemd创建服务文件来管理应用进程实现开机自启和故障重启。6.2 配置管理与环境变量不要将数据库连接字符串等敏感信息硬编码在appsettings.json里。ASP.NET Core支持多层次配置。标准做法appsettings.json存放通用、非敏感配置。appsettings.Development.json开发环境特定配置。appsettings.Production.json生产环境特定配置。环境变量或Azure Key Vault/其他密钥管理服务存放连接字符串、API密钥等敏感信息。在Program.cs中配置读取var builder WebApplication.CreateBuilder(args); // 从环境变量中读取优先级高于JSON文件 var connectionString builder.Configuration.GetConnectionString(DefaultConnection);在生产服务器上通过系统环境变量设置ConnectionStrings:DefaultConnection即可。6.3 性能与缓存策略商城首页、商品分类页等读多写少的场景必须引入缓存。内存缓存IMemoryCache适合单服务器部署存储一些不常变的数据如商品分类菜单。public class CategoryService : ICategoryService { private readonly IMemoryCache _cache; public CategoryService(IMemoryCache cache) _cache cache; public async TaskListCategory GetAllAsync() { // 尝试从缓存获取 if (!_cache.TryGetValue(AllCategories, out ListCategory categories)) { // 缓存中没有则从数据库读取 categories await _context.Categories.ToListAsync(); // 设置缓存过期时间10分钟 _cache.Set(AllCategories, categories, TimeSpan.FromMinutes(10)); } return categories; } }分布式缓存IDistributedCache当应用部署在多台服务器如负载均衡后时必须使用分布式缓存如Redis、SQL Server来共享缓存数据。否则用户第一次请求打到服务器A缓存了数据第二次请求打到服务器B缓存就失效了。ASP.NET Core提供了统一的IDistributedCache接口只需在Program.cs中切换服务注册如services.AddStackExchangeRedisCache业务代码几乎不用改。响应缓存中间件对于完全静态或更新不频繁的页面如关于我们可以使用[ResponseCache]特性让浏览器或代理服务器缓存整个页面输出。7. 常见问题排查与调试技巧实录在实际开发中总会遇到各种“坑”。这里记录几个我踩过且具有代表性的问题。7.1 SQL Server连接失败与性能问题问题本地开发连接正常部署到服务器后出现“无法连接到数据库”或连接超时。排查检查连接字符串确认服务器地址、实例名、用户名密码是否正确。生产环境连接字符串通常与开发环境不同。检查SQL Server服务确保SQL Server服务正在运行。在服务器上打开“SQL Server配置管理器”检查“SQL Server服务”状态。检查网络与防火墙确保应用服务器能通过网络访问数据库服务器的1433端口默认。在数据库服务器防火墙中添加入站规则允许TCP端口1433。检查SQL Server身份验证模式如果使用SQL Server身份验证即用户名密码需确保SQL Server实例已启用“混合模式身份验证”并且sa账户已启用并设置了强密码。问题某个商品列表查询突然变得很慢。排查使用SQL Server Profiler或扩展事件捕获应用发送到数据库的实际SQL语句和执行计划。这是定位慢查询最直接的方法。检查缺失的索引在查询条件WHERE、连接条件JOIN和排序字段ORDER BY上创建索引能极大提升速度。可以使用SQL Server Management Studio的“数据库引擎优化顾问”来获取索引建议。分析EF Core生成的SQL在开发环境可以在DbContext配置中启用敏感数据记录和详细日志查看EF Core实际生成的SQL。services.AddDbContextAppDbContext(options options.UseSqlServer(connectionString) .LogTo(Console.WriteLine, LogLevel.Information) // 输出日志到控制台 .EnableSensitiveDataLogging()); // 在日志中显示参数值仅开发环境7.2 ASP.NET Core MVC 常见陷阱问题表单提交后模型验证总是失败但前端显示的数据看起来没问题。排查检查模型属性名称和类型确保表单字段的name属性与模型属性名完全一致包括大小写。对于复杂类型如集合需要正确的索引格式如nameItems[0].ProductId。检查模型绑定前缀如果Action参数名与模型类名不同或者是在嵌套场景下可能需要使用[Bind(Prefix ...)]特性。查看ModelState错误详情在POST的Action中设置断点检查ModelState.Values和ModelState.Keys查看具体的错误信息这是最直接的调试方式。问题静态文件CSS JS 图片在IIS上不加载返回404。排查检查静态文件中间件确保Program.cs中调用了app.UseStaticFiles()。检查文件路径和权限发布时静态文件是否被正确复制到了输出目录如wwwroot文件夹下IIS应用程序池账户如IIS_IUSRS是否有读取该文件夹的权限检查MIME类型对于自定义扩展名的文件IIS可能不认识。需要在web.config中为其添加MIME类型映射。7.3 并发与事务处理中的坑问题用户同时提交两个相同的订单或者库存扣减出现负数。解决方案回顾与深化数据库层面唯一约束对于订单号等需要全局唯一的字段在数据库层面设置唯一索引UNIQUE CONSTRAINT这是最后也是最坚固的防线。乐观并发控制如前所述使用RowVersion字段。这是处理并发冲突的推荐做法因为它避免了长期锁表提高了吞吐量。业务逻辑去重在用户点击“提交订单”后前端可以禁用按钮后端可以通过生成一个基于“用户时间商品”的临时令牌在短时间内防止重复提交。关键操作加锁对于像“秒杀”这种极端场景可能需要更严格的锁甚至引入分布式锁如基于Redis的RedLock在服务层确保同一时刻只有一个请求能处理某个商品的库存扣减。但这会极大影响性能需谨慎评估。问题跨多个服务的操作如扣库存、创建订单、调用支付如何保证数据一致性解决方案对于简单的、在同一个数据库内的操作使用EF Core的DbContext.Database.BeginTransaction()来开启一个显式事务。 对于复杂的、涉及外部API调用如支付网关的分布式操作本地数据库事务无法保证外部操作的一致性。这时需要引入最终一致性模式例如消息队列与补偿事务Saga模式将整个流程拆分成多个步骤每个步骤完成后发送消息触发下一步。如果某一步失败则发送补偿消息来回滚之前的操作。例如先扣库存本地事务成功后发消息“库存已扣”订单服务监听消息并创建订单本地事务如果创建订单失败则发消息“补偿库存”库存服务收到后回滚库存。这种模式实现复杂但对于构建高可靠、可扩展的商城系统至关重要是微服务架构下处理分布式事务的常用手段。本文还有配套的精品资源点击获取