1. 项目背景与核心需求
去年接手了一个餐饮连锁企业的数字化改造项目,他们急需一套能够打通线上线下点餐流程的系统。经过多方对比,最终选择了ASP.NET Core作为技术栈,主要看中其高性能、跨平台特性和丰富的生态系统。这个点餐系统需要满足以下核心需求:
- 支持微信小程序和网页端双渠道点餐
- 实时同步后厨订单数据
- 会员积分与优惠券系统
- 智能推荐菜品功能
- 多门店库存管理
2. 技术架构设计
2.1 整体架构方案
系统采用经典的三层架构:
- 表现层:微信小程序+管理后台网页
- 业务逻辑层:ASP.NET Core Web API
- 数据访问层:Entity Framework Core + SQL Server
特别加入了SignalR实现实时通信,当后厨完成菜品制作时,前台和大屏能实时收到通知。数据库方面采用读写分离设计,主库负责订单写入,从库处理查询请求。
2.2 关键技术选型
- 跨域处理:使用EnableCorsAttribute配置
services.AddCors(options => { options.AddPolicy("AllowAll", builder => { builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); });实际生产环境中不建议使用AllowAnyOrigin(),我们后期改为指定具体域名
- ORM选择:Entity Framework Core 6.0
- 缓存方案:Redis缓存热门菜品数据
- 文件存储:阿里云OSS存储菜品图片
3. 核心功能实现
3.1 微信小程序接入
小程序端采用uniapp框架开发,与ASP.NET后端通过JWT进行认证。关键点在于处理微信支付回调:
[HttpPost] [Route("api/payment/notify")] public async Task<IActionResult> WechatPayNotify() { using var reader = new StreamReader(Request.Body); var xmlData = await reader.ReadToEndAsync(); // 验证签名逻辑 if(!VerifyWechatSign(xmlData)) { return BadRequest(); } // 更新订单状态 var orderId = GetOrderIdFromXml(xmlData); await _orderService.CompletePaymentAsync(orderId); return Ok("<xml><return_code>SUCCESS</return_code></xml>"); }3.2 实时订单处理
使用SignalR实现订单状态实时推送:
public class OrderHub : Hub { public async Task JoinKitchenGroup() { await Groups.AddToGroupAsync(Context.ConnectionId, "Kitchen"); } } // 订单状态变更时通知 await _hubContext.Clients.Group("Kitchen") .SendAsync("OrderUpdated", orderId, status);前端通过监听事件更新UI:
connection.on("OrderUpdated", (orderId, status) => { const orderElement = document.getElementById(`order-${orderId}`); orderElement.classList.add(`status-${status}`); });4. 数据库设计要点
4.1 主要表结构
CREATE TABLE [dbo].[Dishes] ( [Id] INT IDENTITY PRIMARY KEY, [Name] NVARCHAR(100) NOT NULL, [Price] DECIMAL(10,2) NOT NULL, [ImageUrl] NVARCHAR(255), [IsRecommend] BIT DEFAULT 0 ); CREATE TABLE [dbo].[Orders] ( [Id] UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(), [OrderNo] VARCHAR(20) NOT NULL, [TotalAmount] DECIMAL(10,2) NOT NULL, [Status] TINYINT NOT NULL DEFAULT 0, [CreateTime] DATETIME2 DEFAULT SYSDATETIME() );4.2 性能优化措施
- 为高频查询字段添加索引:
CREATE INDEX IX_Orders_Status ON Orders(Status); CREATE INDEX IX_Orders_CreateTime ON Orders(CreateTime);- 使用表分区处理历史订单数据
- 引入Redis缓存菜单数据
5. 踩坑经验分享
5.1 跨域问题排查
开发初期遇到CORS问题,发现即使配置了EnableCorsAttribute("","","*"),某些请求仍然失败。后来发现:
- 预检请求(OPTIONS)需要单独处理
- 带凭证的请求不能使用*
- 某些特殊Header需要显式暴露
最终解决方案:
services.AddCors(options => { options.AddPolicy("ProductionPolicy", builder => { builder.WithOrigins("https://client.domain.com") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials() .SetPreflightMaxAge(TimeSpan.FromHours(1)) .WithExposedHeaders("X-Custom-Header"); }); });5.2 并发订单处理
高峰期出现超卖问题,通过三种方案对比测试:
- 乐观并发控制(EF Core原生支持)
try { var dish = await _context.Dishes.FindAsync(id); dish.Stock -= quantity; await _context.SaveChangesAsync(); } catch(DbUpdateConcurrencyException) { // 处理冲突 }- 使用数据库事务
using var transaction = await _context.Database.BeginTransactionAsync(); // 业务逻辑 await transaction.CommitAsync();- Redis分布式锁(最终采用方案)
using var redLock = await _redLockFactory.CreateLockAsync( $"dish_{dishId}_lock", TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(0.5) ); if(redLock.IsAcquired) { // 处理库存 }6. 部署与监控
6.1 容器化部署
使用Docker编排服务:
FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app COPY ./publish . ENTRYPOINT ["dotnet", "RestaurantSystem.dll"]配合docker-compose.yml管理多个服务:
version: '3.8' services: web: build: . ports: - "5000:80" environment: - ConnectionStrings__Default=Server=db;Database=Restaurant;User=sa;Password=yourpassword; db: image: mssql/server:2019-latest environment: - ACCEPT_EULA=Y - SA_PASSWORD=yourpassword6.2 监控配置
- 健康检查端点:
app.MapHealthChecks("/health", new HealthCheckOptions { ResponseWriter = async (context, report) => { context.Response.ContentType = "application/json"; await context.Response.WriteAsync(/* 自定义格式 */); } });- 使用Application Insights收集日志
- 配置警报规则监控订单失败率
这个项目从零开始到最终上线历时4个月,期间遇到了各种技术挑战,特别是高并发场景下的数据一致性问题。建议后续可以考虑引入领域驱动设计(DDD)来更好地处理复杂业务逻辑,同时将部分服务拆分为微服务架构以提升扩展性。