从零构建一个企业级 ERP 系统:.NET 8 + Vue 全栈实战指南
这不是一篇普通的 CRUD 教程而是一个完整的技术成长实录——从建表到分布式锁从三层架构到接口解耦从审计日志到熔断降级每一步都是真实踩坑后的沉淀。一、写在前面为什么要造这个轮子很多 .NET 开发者工作多年依然停留在“复制粘贴 Controller-Service-Repository”的层面对事务、并发、业务闭环、架构演进缺乏系统性认知。我决定从零写一个 Mini ERP 系统目标不是商业落地而是用代码打通整个后端知识体系。技术栈选型如下后端ASP.NET Core 8 EF Core SQL Server前端Vue 3 Element Plus辅助展示架构三层架构Domain / Application / Infrastructure / API 依赖注入解耦高级特性分布式锁Redis、审计日志Interceptor、发件箱模式Outbox、熔断重试Polly、CQRS 读写分离、容器化部署二、第一阶段打好地基三层架构 DI 解耦2.1 项目分层不是文件夹是类库很多初学者把三层架构做成“文件夹”那是伪分层。真正的分层必须通过项目引用强制隔离textMyERP.sln ├── MyERP.Domain # 实体、枚举纯 C#无任何外部依赖 ├── MyERP.Infrastructure # DbContext、Repository、EF Core 迁移 ├── MyERP.Application # DTO、Service 业务逻辑、接口定义 └── MyERP.API # Controller、JWT 鉴权、Program.cs 启动项依赖方向API → Application → Infrastructure → Domain依赖倒置2.2 最重要的一步面向接口编程DI 解耦绝大多数初学者写的 Service 是“裸类”csharp// ❌ 错误写法没有接口 public class InventoryService { ... } public class InventoryController(InventoryService svc) { ... }一旦需要换成另一个实现比如从 SQL Server 换成 Redis 缓存所有 Controller 都要改。正确写法提取接口csharp// ✅ 接口定义 public interface IInventoryService { Taskstring StockInAsync(StockInRequest request); Taskstring StockOutAsync(StockOutRequest request); } // ✅ 实现类 public class InventoryService : IInventoryService { ... } // ✅ Controller 依赖接口 public class InventoryController(IInventoryService inventoryService) { ... } // ✅ DI 注册 builder.Services.AddScopedIInventoryService, InventoryService();价值换实现只需改一行注册代码符合依赖倒置原则DIP同时也是单元测试 Mock 的基础。三、第二阶段核心业务闭环ERP 的灵魂3.1 数据库设计核心表表名作用关键字段Base_Item物料成品/原材料ItemCode,ItemTypeBase_Warehouse仓库WarehouseCodeBase_Partner客户/供应商PartnerCode,TypeInv_Stock实时库存快照AvailableQty,LockedQty,Version乐观锁Inv_Transaction库存流水只增不改Direction,BeforeQty,AfterQtySales_Order销售订单Status待审核/已审核/生产中/已发货/已完成Production_Order生产工单Status待发料/进行中/已完工/已入库Production_OrderDetail工单 BOM 明细RequiredQty,IssuedQty3.2 业务闭环流程核心中的核心这是整个 ERP 最有价值的部分——需求驱动生产text销售下单待审核 ↓ 审核通过 → 自动生成生产工单待发料 ↓ 生产领料 → 扣减原材料库存工单状态变为“进行中” ↓ 生产完工 → 成品入库工单状态变为“已完工”关键代码片段销售审核自动生成工单csharppublic async Taskbool ApproveAsync(long salesOrderId) { using var transaction await _context.Database.BeginTransactionAsync(); try { // 1. 更新销售订单状态 order.Status SalesOrderStatus.Approved; // 2. 自动生成生产工单 var prodOrder new ProductionOrder { OrderNo GenerateOrderNo(), ItemId order.ItemId, Quantity order.TotalQty, Status ProductionOrderStatus.PendingMaterial }; _context.ProductionOrders.Add(prodOrder); // 3. 展开 BOM 生成工单明细 foreach (var bom in GetBOM(order.ItemId)) { _context.ProductionOrderDetails.Add(new ProductionOrderDetail { ProductionOrderId prodOrder.Id, ItemId bom.MaterialItemId, RequiredQty bom.RequiredQty * order.TotalQty }); } await _context.SaveChangesAsync(); await transaction.CommitAsync(); return true; } catch { await transaction.RollbackAsync(); throw; } }3.3 库存扣减乐观锁防并发超卖这是 ERP 最核心的技术难点——多个请求同时扣同一个物料的库存。csharppublic async Taskstring StockOutAsync(StockOutRequest request) { // 1. 查询库存并记录旧版本号 var stock await _context.InvStocks.FindAsync(request.ItemId, request.WarehouseId); var oldVersion stock.Version; var beforeQty stock.AvailableQty; var afterQty stock.AvailableQty - request.Quantity; // 2. 乐观锁更新WHERE 条件带上 Version var rows await _context.InvStocks .Where(s s.Id stock.Id s.Version oldVersion) .ExecuteUpdateAsync(setters setters .SetProperty(s s.AvailableQty, afterQty) .SetProperty(s s.Version, oldVersion 1) ); // 3. 如果影响行数为 0说明被其他请求修改了 if (rows 0) throw new Exception(库存已被修改请重试); // 4. 写入流水审计 _context.InvTransactions.Add(new InvTransaction { ItemId request.ItemId, Direction Direction.Out, BeforeQty beforeQty, AfterQty afterQty }); await _context.SaveChangesAsync(); }面试常问为什么不用悲观锁SELECT ... FOR UPDATE因为乐观锁在冲突率低的场景下性能更好且不会产生死锁。四、第三阶段架构进阶从“能用”到“好用”4.1 审计日志利用 EF Core Interceptor 自动记录不写任何业务代码自动捕获所有实体变更csharppublic class AuditInterceptor : SaveChangesInterceptor { public override async ValueTaskInterceptionResultint SavingChangesAsync( DbContextEventData eventData, InterceptionResultint result, CancellationToken cancellationToken default) { var entries eventData.Context.ChangeTracker.Entries() .Where(e e.State EntityState.Modified || e.State EntityState.Added); foreach (var entry in entries) { // 记录旧值、新值、操作人、时间戳 var audit new AuditLog { TableName entry.Entity.GetType().Name, Action entry.State.ToString(), OldValues JsonSerializer.Serialize(entry.OriginalValues.Properties.ToDictionary(...)), NewValues JsonSerializer.Serialize(entry.CurrentValues.Properties.ToDictionary(...)), ChangedBy GetCurrentUserId(), ChangedAt DateTime.UtcNow }; eventData.Context.Add(audit); } return await base.SavingChangesAsync(eventData, result, cancellationToken); } }价值财务/老板最看重的功能——数据追溯。谁在什么时间改了哪个字段一目了然。4.2 分布式锁 幂等性Redis乐观锁解决了并发冲突但如果请求重试会重复扣库存。需要幂等令牌csharppublic async Taskstring StockOutAsync(StockOutRequest request) { // 1. 幂等性检查 var idempotentKey $Idempotent:{request.RequestId}; if (await _redis.StringGetAsync(idempotentKey) 1) return await GetCachedResult(request.RequestId); // 2. 分布式锁Key 物料仓库 var lockKey $Lock:Inventory:{request.ItemId}:{request.WarehouseId}; using var redisLock await _redis.LockTakeAsync(lockKey, ...); if (!redisLock.Acquired) throw new Exception(系统繁忙请稍后重试); // 3. 执行扣库存... await ExecuteStockOutAsync(request); // 4. 标记幂等 await _redis.StringSetAsync(idempotentKey, 1, TimeSpan.FromHours(24)); }价值工业级扣库存方案 幂等性 分布式锁 乐观锁三重保障。4.3 熔断与重试Polly当系统调用外部 API第三方物流、短信网关时必须加保护csharpbuilder.Services.AddHttpClient(ExternalApi, client ...) .AddPolicyHandler(HttpPolicyExtensions .HandleTransientHttpError() .WaitAndRetryAsync(3, retry TimeSpan.FromSeconds(Math.Pow(2, retry)))) // 指数退避 .AddPolicyHandler(PolicyHttpResponseMessage .HandleException() .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30))); // 连续失败5次熔断30秒面试高频题熔断和重试的区别重试是“再试一次”熔断是“别试了赶紧失败”防止级联故障。4.4 发件箱模式Outbox Pattern—— 最终一致性如果用 MediatR 解耦业务可能出现“订单状态改了但事件没发出去”的问题。解决方案把“发事件”变成数据库事务的一部分sql-- 在同一事务中 UPDATE Sales_Order SET Status Approved WHERE Id 1; INSERT INTO Outbox_Messages (EventType, Payload, CreatedAt) VALUES (OrderApproved, {OrderId:1}, GETDATE());后台轮询扫描Outbox_Messages表发布成功后标记ProcessedAt。价值微服务最终一致性的黄金标准保证 100% 数据不丢。五、第四阶段数据库与性能优化5.1 CQRS 读写分离报表查询Group By几百万条流水会阻塞主库事务。引入 CQRS操作数据库ORM写入Command主库MasterEF Core读取Query只读副本SlaveDapper 原生 SQLcsharp// 写入走 EF Core public class InventoryService(AppDbContext context) : IInventoryService // 查询走 Dapper public class ReportService(IDbConnection slaveConnection) : IReportService { public async TaskListStockLedger GetLedgerAsync(...) { return await slaveConnection.QueryAsyncStockLedger(sql, param); } }5.2 冷热数据分离Inv_Transaction会无限增长超过千万级后报表超时。自动归档策略保留 2 年在线数据每月凌晨将过期数据迁移到Inv_Transaction_Archive分区表报表查询时根据日期范围自动路由六、第五阶段容器化与可观测性6.1 Docker Compose 一键启动yamlversion: 3.8 services: sqlserver: image: mcr.microsoft.com/mssql/server:2022-latest environment: SA_PASSWORD: Your_password123 ACCEPT_EULA: Y ports: - 1433:1433 redis: image: redis:alpine ports: - 6379:6379 myerp-api: build: . ports: - 8080:8080 depends_on: - sqlserver - redis6.2 可观测性三件套Metrics使用 Prometheus OpenTelemetry 收集接口耗时、错误率Tracing给每个请求生成 TraceId跨服务串联日志HealthCheck暴露/health接口配合云服务商实现自动告警七、写在最后我的技术栈清单CSDN 读者可自检分类技能点状态基础架构三层架构 DI 接口解耦✅数据库EF Core SQL Server 迁移管理✅认证授权JWT 角色权限 动态用户上下文✅并发控制乐观锁 分布式锁(Redis) 幂等性✅业务闭环销售→工单→领料→完工入库✅审计追溯EF Core Interceptor 自动审计日志✅系统弹性Polly 熔断/重试/超时✅最终一致性发件箱模式Outbox✅性能优化CQRS 读写分离 冷热数据归档✅测试Testcontainers 集成测试✅部署Docker K8s 编排✅可观测性OpenTelemetry Prometheus HealthCheck✅前端Vue 3 Element Plus✅八、给读者的建议先跑通业务闭环不要上来就搞分布式锁先把销售→生产→入库跑通。接口先行先写接口定义IxxxService再写实现养成面向接口编程的习惯。事务是底线涉及多表操作永远用BeginTransaction包裹。日志是命根子审计日志 操作日志 异常日志一个都不能少。不要过度设计小型项目不需要 CQRS 和发件箱但在架构层面知道它们的存在是进阶的起点。

相关新闻

Linux Ubuntu与安卓设备文件传输全方案:从USB到无线实战指南

Linux Ubuntu与安卓设备文件传输全方案:从USB到无线实战指南

1. 跨系统文件交换:一个高频且真实的痛点如果你同时使用Linux Ubuntu桌面系统和安卓手机,那么在两台设备之间传文件这件事,大概率是你日常工作中的高频操作。可能是把手机拍的照片、录的视频传到电脑上剪辑处理,也可能是把电脑上写…

2026/8/2 10:53:51 阅读更多 →
华为TCX转换器:打破运动数据孤岛的高效实用工具

华为TCX转换器:打破运动数据孤岛的高效实用工具

华为TCX转换器:打破运动数据孤岛的高效实用工具 【免费下载链接】Huawei-TCX-Converter A makeshift python tool that generates TCX files from Huawei HiTrack files 项目地址: https://gitcode.com/gh_mirrors/hu/Huawei-TCX-Converter 华为TCX转换器是一…

2026/8/2 10:53:51 阅读更多 →
解码生命暗物质:从无序蛋白到人类IDRome的分子语法与实战分析

解码生命暗物质:从无序蛋白到人类IDRome的分子语法与实战分析

1. 项目概述:从“垃圾”到“宝藏”的认知革命如果你在几年前问我,细胞内那些结构松散、像一团乱麻的蛋白质片段有什么用,我大概率会耸耸肩,把它们归为“分子垃圾”或“进化残留物”。这几乎是当时整个领域的共识。然而&#xff0c…

2026/8/2 10:53:51 阅读更多 →

最新新闻

WorkBuddy 模型与思考模式详解:Hy3 快慢思考融合 + 多模型切换 + 积分透明

WorkBuddy 模型与思考模式详解:Hy3 快慢思考融合 + 多模型切换 + 积分透明

WorkBuddy 模型与思考模式详解:Hy3 快慢思考融合 多模型切换 积分透明摘要:本文拆解 WorkBuddy 的模型与思考体系。以腾讯 2026 年 7 月发布的旗舰模型 Hy3(MoE 架构、256K 上下文、快慢思考融合)为核心,讲清思考模式…

2026/8/2 11:49:10 阅读更多 →
WorkBuddy 人机双写详解:AI 住进文档,框选即改的协同编辑新范式

WorkBuddy 人机双写详解:AI 住进文档,框选即改的协同编辑新范式

WorkBuddy 人机双写详解:AI 住进文档,框选即改的协同编辑新范式摘要:本文拆解 WorkBuddy 在 2026 年 7 月下旬 V5.3.5 版本联合腾讯文档推出的「人机双写」能力。它把 AI 从聊天窗口的「外挂」变成同一份文档里的协作方,支持在原文…

2026/8/2 11:49:10 阅读更多 →
基于YOLO26智能小车地标‌检测系统1:智能小车地标‌检测数据集说明(含下载链接)

基于YOLO26智能小车地标‌检测系统1:智能小车地标‌检测数据集说明(含下载链接)

一. 前言 本篇博客是《基于YOLO26智能小车地标实时检测系统》系列文章之《智能小车地标检测数据集说明(含下载链接)》,网上有很多智能小车地标检测数据集的数据,百度一下,一搜一大堆,但质量参差不齐,很多不能用&#…

2026/8/2 11:49:10 阅读更多 →
英雄联盟智能助手终极指南:5步安装League Akari,免费提升你的游戏体验

英雄联盟智能助手终极指南:5步安装League Akari,免费提升你的游戏体验

英雄联盟智能助手终极指南:5步安装League Akari,免费提升你的游戏体验 【免费下载链接】League-Toolkit An all-in-one toolkit for LeagueClient. Gathering power 🚀. 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit …

2026/8/2 11:49:10 阅读更多 →
115、LLC谐振变换器的损耗分析模型

115、LLC谐振变换器的损耗分析模型

开关电源设计实战:从反激到LLC(115)——LLC谐振变换器的损耗分析模型 一、从一块烫手的板子说起 去年冬天,我调试一块300W的LLC电源,满载运行十分钟,谐振电感热得能煎鸡蛋。红外测温枪一打——105℃,磁芯温度倒还好,绕组却先扛不住了。客户那边催得紧,说再不改方案就…

2026/8/2 11:49:10 阅读更多 →
Grove-Bee Socket:无线模块即插即用,快速构建Arduino无线通信系统

Grove-Bee Socket:无线模块即插即用,快速构建Arduino无线通信系统

1. 项目概述:Grove - Bee Socket是什么?如果你玩过Arduino,肯定对Grove生态系统不陌生,它让硬件连接变得像搭积木一样简单。但当你需要把传感器数据从A点传到B点,中间隔着几堵墙甚至几十米距离时,普通的杜邦…

2026/8/2 11:48:10 阅读更多 →

日新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/2 0:00:38 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/2 0:00:38 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/2 0:00:38 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/2 0:00:38 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/2 0:00:38 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/2 0:00:38 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/2 6:34:16 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/2 2:47:48 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/2 0:23:22 阅读更多 →