简介:这是一套基于C# .NET Web API与Vue.js实现的前后端分离式仓库管理系统源码,面向需要快速落地企业级库存管理模块的开发者、毕业设计学生及求职项目储备者,解决传统单体架构复用性差、前后端耦合度高、权限与跨域处理不规范等实际开发痛点。资源共243个文件,涵盖112个C#后端逻辑文件(含JWT鉴权过滤器、Swagger接口文档配置)、19个Vue组件页面、20个CSS样式与17个JS交互脚本,以及Web.config跨域配置、Global.asax启动项、.sln解决方案等核心工程文件,压缩包仅9.48MB,轻量易部署。已有767人学习下载,代码结构清晰,完整呈现Bearer Token登录流程(含Token生成、响应封装、控制器级Options预检处理)、Swagger自动生成API文档、Web API跨域集成等关键实践细节,可直接用于二次开发或作为.NET+Vue全栈学习范例。 做仓库管理系统这几年,前后端分离已经成为标配,但很多朋友在真正动手时还是会被一些细节卡住。比如接口怎么设计才算合理、权限怎么控制才够安全、Vue端和.NET后端到底怎么联调最顺畅。这套基于C# .NET + Vue的仓库管理系统,我把PC端和server端完整走了一遍,从数据库设计到接口开发再到前端页面,踩了不少坑,也沉淀了一套可以直接抄作业的方案。无论你是刚接触前后端分离的新手,还是想找一套完整参考的开发者,这篇内容都能给你实在的帮助。
1. 项目背景与整体架构设计
1.1 为什么选择C# .NET + Vue这套组合
先聊点实在的。仓库管理系统这种业务型项目,选型最怕两头不到岸。纯前端方案撑不起复杂业务逻辑,纯后端又没法做出好的交互体验。C# .NET做服务端,稳定性高、并发处理能力强,尤其在企业内部系统里有天然优势——大部分公司IT环境都是Windows,部署维护成本低。Vue作为前端框架,上手曲线平缓、生态成熟,配合Element UI这类组件库,做后台管理系统的效率非常高。
前后端分离架构下,后端只需要提供标准化的RESTful API,前端完全通过HTTP请求进行数据交互,互不干扰。这套系统里我用了.NET 8 + Vue 3,整体表现非常稳。接口响应时间普遍在50ms以内,即便在万级数据量的库存表上做分页查询,也没有明显卡顿。
适合参考这套方案的读者,我建议是这几类:已经在做.NET后端想补前端能力的、做Vue前端想理解后端接口设计的、以及需要一套完整前后端分离实战案例来做毕设或公司项目的。技术栈其实不算新,但前后端分离的完整链路——从环境搭建到联调部署——是很多教学资源覆盖不到的。
1.2 仓库管理系统的核心业务模块拆解
仓库管理系统听起来简单,真正拆解起来业务模块不少。我把它分成六大核心模块:用户权限、基础档案、入库管理、出库管理、库存管理和报表统计。每个模块不是独立存在的,它们之间通过单据和库存表互相联动。
用户权限模块负责登录认证、角色分配、菜单权限控制;基础档案管理物料信息、供应商、客户、仓库和货位;入库管理处理采购入库、退货入库、生产入库;出库管理对应销售出库、领料出库、退货出库;库存管理则是核心中的核心,实时更新库存快照、锁定库存、库存预警;报表统计对进出库数据进行汇总分析,给管理层做决策参考。
模块划分的核心思路是"单据驱动、库存联动"。所有库存变化必须通过单据触发,不允许直接修改库存表。这一点非常关键,因为仓库系统最怕数据不透明,库存一旦可以随便改,后面整个报表都是废的。我在设计数据库时就定了规矩:库存流水只增不改,库存当前值只是流水的最新快照。
1.3 前后端分离的数据流设计思路
前后端分离后,最大的变化是数据流从"页面刷新获取"变成了"接口异步请求"。前端Vue通过axios发起HTTP请求,后端Web API接收请求并处理业务逻辑,从数据库读取数据后返回JSON,前端再渲染到页面上。
这套系统的数据流向我梳理成一条主线:前端页面 → 路由守卫校验登录态 → Axios请求拦截器附带Token → 后端JWT认证中间件验证身份 → 控制器接收参数 → 业务逻辑层处理 → EF Core访问数据库 → 返回统一JSON格式 → 前端响应拦截器解析数据 → 页面组件更新视图。
为了规范这套流程,我在后端定义了一个统一响应格式:成功时返回{ code: 200, data: ..., message: "操作成功" },失败时返回对应错误码和提示信息。这样前端拦截器只要判断code是否为200,统一处理成功和异常,不用每个页面单独写错误判断。
2. 后端:C# .NET Web API核心实现
2.1 项目结构与分层设计
后端我用的是经典三层架构基础上演进的方案,按职责拆成四个项目:Api层(控制器和启动配置)、Service层(业务逻辑)、Repository层(数据访问)、Models层(实体类和DTO)。这样做的好处是各层职责清晰,后期维护不用到处找代码。
实际结构是这样的:
Solution/ ├── Warehouse.Api/ # Web API层 │ ├── Controllers/ # 控制器 │ ├── Middleware/ # 中间件(异常处理、JWT) │ └── Program.cs ├── Warehouse.Service/ # 业务逻辑层 │ ├── IInventoryService.cs │ └── InventoryService.cs ├── Warehouse.Repository/ # 数据访问层 │ ├── InventoryRepository.cs │ └── DbContext.cs ├── Warehouse.Models/ # 实体模型与DTO │ ├── Entities/ │ └── DTO/分层设计的核心价值是"可替换性"。比如你前期用EF Core,后期觉得性能不够想换Dapper,只需要改Repository层的实现,Service层完全不用动。我实际做项目时经常遇到这种需求变更,分层带来的便利是实实在在的。
2.2 数据库设计与EF Core映射
数据库是仓库管理系统的根基。我的表设计遵循三个原则:主键统一用雪花ID(不用自增ID,方便分库分表和数据迁移)、所有业务表都带创建时间和操作人字段、关键表设计唯一约束防止重复数据。
核心表结构我整理了一张,工作量最大的就是这几张:
| 表名 | 核心字段 | 说明 |
|---|---|---|
| Users | Id, UserName, PasswordHash, RoleId | 用户表,密码存哈希不存明文 |
| Roles | Id, RoleName, Permissions | 角色表,权限用JSON格式存储 |
| Materials | Id, MaterialCode, MaterialName, Spec, UnitId | 物料档案表 |
| Warehouses | Id, WarehouseName, Location | 仓库表 |
| Stock | Id, MaterialId, WarehouseId, Quantity, LockedQuantity | 库存快照表 |
| StockFlow | Id, MaterialId, WarehouseId, ChangeType, ChangeQuantity, BeforeQuantity, AfterQuantity | 库存流水表 |
| InboundOrder | Id, OrderNo, SupplierId, Status, TotalAmount | 入库单主表 |
| InboundItem | Id, OrderId, MaterialId, Quantity, Price | 入库单明细表 |
EF Core映射我用的是Fluent API写实体配置,没用Data Annotation,因为Fluent API可以把所有配置集中管理,表结构变更时不用满世界找特性标记。下面这段是库存表的配置示例:
public class StockConfiguration : IEntityTypeConfiguration<Stock> { public void Configure(EntityTypeBuilder<Stock> builder) { builder.ToTable("Stock"); builder.HasKey(x => x.Id); builder.Property(x => x.Quantity).HasPrecision(18, 2); builder.HasIndex(x => new { x.MaterialId, x.WarehouseId }).IsUnique(); } }注意那个联合唯一索引,这是防止相同物料在相同仓库出现两条库存记录的兜底措施。我在实际运维中确实遇到过因为并发导致重复数据的情况,这个约束能保证即使代码出bug也不会污染数据。
2.3 JWT认证与权限控制详解
仓库管理系统几乎没有不需要登录就能访问的页面,认证和权限是安全底线。我选用JWT(JSON Web Token)而不是传统的Session方案,原因在于前后端分离场景下,JWT天然无状态,服务器不需要保存会话信息,非常适合分布式部署。
JWT生成逻辑在登录接口中,验证用户名密码后生成Token,包含用户ID、用户名、角色ID和过期时间,用密钥签名后返回给前端。前端在后续请求的Header中携带Authorization: Bearer {token},后端中间件自动解析验证。
var claims = new[] { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.Role, user.RoleId.ToString()) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"])); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _config["Jwt:Issuer"], audience: _config["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddHours(8), signingCredentials: credentials); return new JwtSecurityTokenHandler().WriteToken(token);过期时间我设置了8小时,工厂上班时间正好覆盖。这里有个容易被忽略的点:密钥长度必须满足HmacSha256的最低要求,至少16个字符,否则运行时会直接抛异常。不少新手在这踩坑。
权限控制上用访问令牌的方式实现。在需要权限的接口上标注[Authorize],在需要特定角色的接口上标注[Authorize(Roles = "Admin")]。但我的实际经验是不要过度依赖角色硬编码,因为业务里经常出现"角色一样但能看的菜单不同"的需求,所以我把前端菜单权限和后端接口权限分开控制:后端管接口访问权,前端管菜单显示权,两者通过角色下的权限列表关联。
2.4 核心业务接口实现:入库、出库、库存
入库接口是整个系统的典型业务,涉及主表、明细表、库存表、流水表四张表的操作,必须放在一个数据库事务里。我用的是EF Core的IDbContextTransaction,任何一步失败就整体回滚。
public async Task<ResultDto> CreateInboundOrder(InboundOrderCreateDto dto) { using var transaction = await _context.Database.BeginTransactionAsync(); try { var order = new InboundOrder { OrderNo = GenerateOrderNo("RK"), SupplierId = dto.SupplierId, Status = "待审核", CreatedAt = DateTime.Now }; await _context.InboundOrders.AddAsync(order); await _context.SaveChangesAsync(); foreach (var item in dto.Items) { // 写入明细 var orderItem = new InboundItem { OrderId = order.Id, ... }; await _context.InboundItems.AddAsync(orderItem); // 更新库存(存在则累加,不存在则新增) var stock = await _context.Stocks .FirstOrDefaultAsync(s => s.MaterialId == item.MaterialId && s.WarehouseId == dto.WarehouseId); if (stock != null) { stock.Quantity += item.Quantity; } else { await _context.Stocks.AddAsync(new Stock { ... }); } // 写流水 await _context.StockFlows.AddAsync(new StockFlow { MaterialId = item.MaterialId, ChangeType = "入库", ChangeQuantity = item.Quantity, BeforeQuantity = stock?.Quantity ?? 0, AfterQuantity = (stock?.Quantity ?? 0) + item.Quantity }); } await _context.SaveChangesAsync(); await transaction.CommitAsync(); return ResultDto.Success(order.Id); } catch { await transaction.RollbackAsync(); throw; } }注意流水表里我记录了BeforeQuantity和AfterQuantity,这不仅是可追溯的需要,后期做数据对账时能直接对比每笔单据对库存的影响,排查问题效率翻倍。
出库接口逻辑类似,但有个额外动作:检查库存是否充足,而且要用"锁定库存"的概念来防止超卖。我的做法是先在库存表里扣减LockedQuantity(锁定数量),实际出库确认时再扣减Quantity并释放锁定。这样可以防止订单提交后、出库确认前,别的订单把货抢走了。
3. 前端:Vue 3 + Element Plus实现
3.1 前端工程化架构
前端我选的是Vue 3 + Vite + Element Plus + Pinia + Vue Router的组合。Vite取代Webpack后,开发环境的冷启动速度提升非常明显,不再有漫长等待的痛苦。用npm create vite@latest创建项目后,再安装对应依赖模块。
目录结构我延续了实际项目规范化的做法:
src/ ├── api/ # 接口统一管理 │ ├── auth.ts │ ├── inventory.ts │ └── stock.ts ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── layout/ # 布局组件 ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── utils/ # 工具函数(axios封装、格式化等) └── views/ # 页面视图每个模块的接口调用统一放在api目录下,页面组件不直接写axios地址。这样后端接口地址变动时只需要改一个文件,不用全局搜索替换。路径别名用@指向src目录,避免写一长串相对路径。
3.2 登录状态管理与动态路由
前端登录流程是整套系统的入口环节,处理不好后续全乱。用户在登录页输入账号密码,调用后端登录接口获取Token和用户信息,然后把Token存储到localStorage,用户信息存到Pinia中。路由守卫每次跳转前检查是否存在Token,没有Token就直接重定向到登录页。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.path !== '/login' && !token) { next('/login'); } else if (to.path === '/login' && token) { next('/'); } else { next(); } });动态路由是权限控制的关键实现。用户登录后,后端返回该用户拥有权限的菜单和按钮编码列表,前端遍历这个列表动态生成路由表,用router.addRoute注册到路由器中。这样用户没权限的模块,连路由都不存在,直接在入口拦截掉。
一个重要的细节:动态路由需要持久化。页面刷新后Vue实例重建,需要重新从后端拉取路由配置。所以我把用户权限信息在Pinia中做了缓存,刷新时先检查缓存,没有就从后端拉取,再走动态路由注册流程。
3.3 Axios封装与请求拦截器
Axios封装是前端项目的核心基建。我封装的主要目的有三个:统一附加Token、统一处理错误码、统一格式化响应数据。这里展示封装的核心逻辑:
const service = axios.create({ baseURL: '/api', timeout: 10000 }); service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); service.interceptors.response.use( response => { const res = response.data; if (res.code === 200) { return res; } if (res.code === 401) { localStorage.removeItem('token'); router.push('/login'); return Promise.reject(new Error('登录已过期')); } ElMessage.error(res.message || '请求失败'); return Promise.reject(new Error(res.message)); }, error => { ElMessage.error('网络异常,请稍后重试'); return Promise.reject(error); } );超时时间设置10秒,仓库系统在弱网环境下的操作频率不高,这个时间足够。响应拦截器中,碰到401就直接清Token跳登录页,这是很多项目都会忽略但极其重要的设计。如果没有这一步,用户Token过期后,每个接口都会报错,页面一堆红条提示,体验很差。
3.4 仓库作业页面的关键交互实现
仓库作业页面最核心的交互是单据录入。以入库单为例,用户需要先选择供应商和仓库,然后再明细区域动态增加物料行,填写数量、单价,还有弹窗搜索物料的功能。
物料选择弹窗是这类页面的重灾区,数据量大了以后,一次性拉全部物料会导致首屏卡死。我采用的是远程搜索方案:输入关键字后调用后端接口模糊查询,接口限制条数最多返回20条。这样既保证了用户体验,又不给服务器造成压力。
<el-select v-model="materialId" filterable remote :remote-method="searchMaterial" placeholder="请输入物料编码或名称"> <el-option v-for="item in materialOptions" :key="item.id" :label="item.materialCode + ' - ' + item.materialName" :value="item.id" /> </el-select>库存盘点页面用到了高亮和编辑状态切换,用户在表格中直接输入实盘数量,系统自动对比账面数量和实盘数量,差异部分高亮显示。这一步前端用computed属性做实时计算就够,不用每个格子都请求后端,保存时再一次性提交差异数据,性能体验都好很多。
4. 前后端联调与生产环境部署
4.1 本地开发跨域与代理配置
前后端分离开发阶段,最大的拦路虎就是跨域。前端开发服务器跑在5173端口,后端API跑在5000端口,浏览器直接请求会触发CORS策略。我用的方案是前端代理,不是后端开启CORS。虽然开发时后端开启CORS也能解决,但生产环境部署后反而容易埋下安全隐患。
Vite配置代理非常方便,修改vite.config.ts:
export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:5000', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } } });这样前端代码里请求地址直接写/api/inbound/create,开发环境下Vite自动代理到http://localhost:5000/inbound/create。前端代码不用写完整域名,生产环境环境通过Nginx做类似代理,前端代码一行都不用改。
代码里有个容易踩的坑:changeOrigin必须设为true。不设置时后端接受到的请求头Host还是localhost:5173,某些后端框架对域名校验严格的话就会拒请求。我第一次联调时这就卡了半小时。
4.2 生产环境部署:不同方案的优缺点
生产部署是我这次最想重点分享的部分。前后端分离的部署,本质上是"静态资源交给Web服务器,API请求转发给后端进程"。我先后尝试了两种方案,最后混合使用。
第一种方案是前端构建后交给IIS托管,同时用URL Rewrite模块做反向代理。核心是web.config里的配置:
<configuration> <system.webServer> <rewrite> <rules> <rule name="vue-history-route" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> </conditions> <action type="Rewrite" url="/index.html" /> </rule> <rule name="api-proxy" stopProcessing="true"> <match url="^api/(.*)" /> <action type="Rewrite" url="http://localhost:5000/{R:1}" /> </rule> </rules> </rewrite> </system.webServer> </configuration>这个配置解决两个问题:Vue Router的history模式刷新页面时会404,所以把所有非文件请求一律重写到index.html;和后端接口通信,需要把/api/开头的请求转发到后端。
第二种方案是纯Nginx部署,前端构建后放在/usr/share/nginx/html,Nginx配置中做同样的history回退和API代理。Nginx的部署方式更轻量,对服务器资源占用小,适合Linux服务器上部署的场景。
建议网络条件复杂的公司内网,优先用IIS方案,和Windows域环境集成比较方便;独立部署在公网服务器就用Nginx,性能和灵活性更优。
5. 常见问题与部署避坑记录
5.1 前端Token过期后的体验优化
这个坑我印象特别深。最初Token过期后,用户正在填表,突然点击保存时弹出"登录已过期",然后被强制踢到登录页,填了一半的数据全丢了。这在仓库现场使用中几乎是灾难级的体验。
我后来的优化思路是:Token设置8小时过期,在有效期内用户在页面操作用不到重新登录,同时记录Token获取的时间戳。在路由守卫中,每隔一段时间检查Token剩余有效期,如果低于30分钟,就静默调用刷新Token接口,替换本地存储的旧Token。用户全程无感知,不用中断操作。
如果刷新Token接口也失败,说明用户确实登录太久了。此时不直接跳转,而是弹出一个可以"保存草稿并重新登录"的提示框,用户确认后把当前表单数据存到localStorage再跳登录页。这个改动后来在客户现场反馈特别好。
5.2 数据库并发导致库存异常的解决办法
多用户同时操作时,库存扣减经常发生超卖问题。我遇到的具体场景是:用户A和用户B同时看到库存剩10件,A提交出库8件,B也提交出库8件,两个请求同时通过了"库存充足"的判断,结果库存被扣成了负数。
这个问题光靠事务的默认隔离级别解决不了,我用的是乐观锁方案:库存表中加一个Version字段,更新时不仅检查库存数量,还要检查版本号是否和查询时一致。
var stock = await _context.Stocks .FirstOrDefaultAsync(s => s.MaterialId == materialId && s.WarehouseId == warehouseId); // 检查库存是否足够 if (stock.Quantity < quantity) return ResultDto.Fail("库存不足"); var rows = await _context.Database.ExecuteSqlRawAsync( "UPDATE Stock SET Quantity = Quantity - {0}, Version = Version + 1 " + "WHERE Id = {1} AND Version = {2}", quantity, stock.Id, stock.Version); if (rows == 0) return ResultDto.Fail("库存已被其他操作修改,请刷新后重试");利用EF Core的ExecuteSqlRaw执行原生更新,用版本号做冲突检测,影响行数为0说明数据被其他操作修改过。这条语句能保证原子性,同时利用数据库行锁防止并发问题。类似的方案后来也应用在锁定库存和释放库存上。
5.3 EF Core性能优化:避免N+1查询
后端开发里最容易被忽视的性能问题是EF Core的N+1查询。典型场景是查询入库单列表时,主表一条记录,代码里循环读取明细和物料名称,数据量到几千条时接口直接卡死。
我后来统一用Include或ProjectTo来优化:
var orders = await _context.InboundOrders .Include(o => o.Items) .ThenInclude(i => i.Material) .Where(o => o.CreatedAt >= startDate && o.CreatedAt <= endDate) .OrderByDescending(o => o.CreatedAt) .Select(o => new InboundOrderListDto { Id = o.Id, OrderNo = o.OrderNo, SupplierName = o.Supplier.SupplierName, ItemCount = o.Items.Count, TotalAmount = o.Items.Sum(i => i.Quantity * i.Price) }) .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToListAsync();EF Core的延迟加载是个陷阱,框架看起来"好用",但业务复杂后每条子查询都拖垮性能。我建议对列表查询一律用即时加载或投影,确保生成一条SQL语句。也可以用SQL日志记录功能,把EF生成的SQL打印出来,逐条分析是否有多余查询。
5.4 前端构建部署后的静态资源缓存问题
部署Vue项目后出现过一个状况:界面更新了,但用户浏览器还是显示旧版本。这是因为服务器对静态文件设置了强缓存,浏览器直接用了本地缓存,完全没发请求。
解决方案有两步:第一步,Vite构建时开启文件指纹,默认已经开了,文件名带哈希值;第二步,服务器设置缓存策略时,对带哈希的文件设置缓存永久有效,对index.html设置no-cache。
Nginx下的配置写法:
location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; } location / { add_header Cache-Control "no-cache"; try_files $uri $uri/ /index.html; }这样带哈希的文件每次更新后文件名变更,浏览器自然请求新文件;index.html每次都重新验证,保证能拿到最新的入口文件。实际部署时还有个小技巧:发布前清一下Nginx缓存目录,避免旧文件残留。
5.5 部署常见错误速查表
结合自己经历和帮朋友排查的case,汇总几条高频错误:
| 错误现象 | 原因 | 解决方法 |
|---|---|---|
| 前端页面刷新后404 | Vue Router history模式未配置回退 | 配置IIS URL Rewrite或Nginx try_files |
| 接口返回404 | 前端代理路径与后端路由不一致 | 检查代理rewrite规则是否正确 |
| Token验证一直失败 | 签发和验证的密钥不一致 | 检查配置文件Jwt:Key是否一致,密钥长度需≥16字符 |
| 跨域报CORS错误 | 前端代理未生效或生产环境少了代理层 | 确认代理配置,生产环境用Nginx反代 |
| 数据库字段精度丢失 | EF Core默认decimal精度18,2 | Framework配置HasPrecision指定精度 |
这些问题的共同点是:解决方案不复杂,但排查看不到底层逻辑就会浪费很长时间。我通常建议部署前后先画一张部署架构图,明确每个请求的完整路径,再逐段排查,效率会高很多。
6. 项目扩展与二次开发建议
6.1 系统功能的横向扩展方向
这套仓库管理系统的核心功能已经跑通,但实际落地到企业环境时,通常还需要做一些扩展。我建议优先考虑这几个方向:多仓库多货位的精细化管理,在现有Warehouse表基础上增加货位表,库存记录精确到货位级别,这对仓库拣货和盘点很有价值;条码扫描支持,配合PDA或者手机摄像头,入出库时直接扫条码录入,效率提升非常明显;对接上游ERP系统,通过Web API或者消息队列同步物料档案和单据数据。
扩展时要注意保持现有的单据驱动、流水可追溯的设计理念。很多系统改着改着就开始直接改库存、跳过单据,短期看省事,长期看后患无穷。我做过的一次二次开发就是客户要求"做一个库存调整功能",我坚持把这个功能也做成标准单据流程,后续对账查数据时省了不少力气。
6.2 性能优化和监控配置
仓库系统在数据量增长后,性能和监控是关键。我在这里做了三件事:第一,给核心查询表加上复合索引,比如库存流水表上的(MaterialId, CreatedAt)索引,列表中高频筛选字段都建了索引;第二,后端启用响应压缩中间件,JSON响应体量直接减少60%以上,对弱网环境尤其友好;第三,接入Sentry做前端错误监控,以及Serilog做后端日志记录,出了问题能快速定位到具体请求和堆栈。
监控这块的建议是:物料和库存相关接口的日志必须包括操作人、操作时间和变更前后数据,因为一旦业务数据出问题,要能倒查是谁什么时候改的。这一步在仓库系统里不只是技术支持,还是财务审计层面的要求。
6.3 移动端适配与PDA手持终端
仓库作业中,操作员经常要推着车、搬着货,在电脑上操作很不方便。移动端适配是仓库管理系统落地时的加分项。我的做法是把核心作业页面(入库、出库、盘点)做成响应式布局,用Vant组件库做移动端适配。由于前后端分离,接口层完全不用改,只是前端多了一套移动端页面入口。
如果现场有条件用手持PDA,可以用WebView套壳方案,把移动端页面包装成PDA应用。条码枪这类设备一般都支持扫描后模拟键盘输入,聚焦到输入框后扫码,内容自动输入,然后用回车或Tab键触发查询,这套交互在Vue里实现成本很低,但现场体验提升非常大。
7. 写在最后的实操心得
做这套仓库管理系统前后花了将近两个月,最大的体会是:前后端分离架构的价值不在于技术新,而在于让团队协作和系统扩展变得干净。后端只操心业务逻辑和数据处理,前端只关心交互和展示,接口对清楚了,两边基本可以并行开发不互相卡脖子。
我在实际开发中踩到最深的坑,一个是JWT密钥长度导致部署后认证失败,另一个是EF Core延迟加载在数据量上来后的性能灾难。这两个问题如果能在设计阶段就规避,后面能省下大量排查时间。
最后再分享一个小技巧:前后端联调时,强烈建议把Postman或者Apifox的接口环境配置好,把所有接口按模块分好目录。我用的流程是后端写完一个控制器就更新接口文档和测试用例,前端联调时直接导入测试集合,效率比一边开发一边联调高很多。这套项目本身就是一个完整的起点,里面的设计模式、目录结构、错误处理方式都可以直接复用,希望你能在这个基础上做出更适合自己业务的版本。
本文还有配套的精品资源,点击获取