尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ASP.NET库存管理系统实战:从数据库设计到并发扣库存

ASP.NET库存管理系统实战:从数据库设计到并发扣库存 简介一套基于ASP.NET的库存管理系统完整源码适合ASP.NET初学者、中级开发者以及需要快速搭建库存管理功能的企业技术人员参考使用。压缩包内含90个文件总大小约942KB以.cs后端逻辑文件和.aspx页面文件为主另有SQL Server数据库文件、Web.config配置、CSS样式及GIF图标等涵盖系统运行所需代码、数据与界面资源。系统实现了入库、出库、库存查询、预警、盘点、报表、权限管理等常用功能并集成了OA办公流程代码结构清晰包含登录、商品管理、仓库管理、客户/供应商管理等完整页面便于理解ASP.NET Web Forms开发模式和数据库交互。已有267人浏览学习对于希望从实际项目中学习ASP.NET业务系统开发、数据库设计及权限控制的读者具有较好的参考价值。 搞库存管理系统用 ASP.NET 这套技术栈的人是真不少。我之前帮几个做商贸和中小型工厂的朋友搭过类似系统从最早的 ASP.NET Web Forms 到后来的 MVC再到现在的 ASP.NET Core踩过的坑攒了一堆。这篇就把整个系统的设计思路、数据库结构、核心业务代码出入库、权限、并发扣库存和部署时的常见问题一次性讲透全部是基于实际跑过的项目总结的不是那种纯概念堆砌的教程。先说清楚这套系统能干什么商品信息维护、入库单/出库单录入、实时库存查询、库存流水追溯、低库存预警、操作员登录与权限控制再加上简单的报表统计。适合三类人参考一是刚接触 ASP.NET 想找个完整项目练手的开发者二是公司里需要自己搭内部工具的 IT 人员三是想把现有 Excel 台账升级成 Web 系统的业务负责人。1. 项目背景与整体设计思路1.1 为什么选 ASP.NET 而不是其他技术栈很多人纠结库存管理系统到底用 Java、PHP 还是 ASP.NET 写。我的观点很直接如果团队本身熟悉 C#或者服务器环境是 Windows Server IIS那 ASP.NET 是最省事的选择。它的优点是开发效率高Visual Studio 一套工具链从建项目到调试部署全包了类型安全编译期就能抓到一堆低级错误生态成熟NuGet 上找第三方组件很方便。缺点是跨平台部署相对麻烦——虽然 ASP.NET Core 已经支持 Linux但大部分传统企业还是跑在 Windows 服务器上。我见过不少团队用 ASP.NET 写库存系统后来维护不下去的原因仈九成不是因为技术选型错了而是数据库设计一开始就没做好业务逻辑全堆在页面代码里。所以下面我花大篇幅讲数据库和业务层这部分想清楚了系统就成功了一半。1.2 系统功能模块划分一套完整的库存管理系统核心模块就六个别贪多。我见过有人上来就做采购审批流、多级审核、供应商对账结果做了三个月上线遥遥无期。先把基础功能跑起来后续再迭代比什么都强。模块核心功能优先级商品管理新增/编辑/停用商品维护SKU、条码、规格、单位P0入库管理采购入库单、退货入库、入库审核P0出库管理销售出库、领料出库、出库审核P0库存查询实时库存、批次库存、可用库存P0库存流水记录所有出入库操作支持追溯P0系统管理用户登录、角色权限、操作日志P1P0 是系统上线必须有的P1 是一周内补上的。很多初学者上来先写登录注册页面把自己的兴趣磨没了。正确姿势是先搭数据库、再做库存核心业务、最后补权限和日志。2. 数据库设计与核心表结构2.1 商品信息表设计商品表是库存系统的基础设计得不好后面处处难受。我常用的设计是两张表Product存商品主数据ProductStock存库存数量。为什么拆开因为商品信息是基本档案库存数量是变动数据混在一张表里每次 UPDATE 都要锁行并发高的时候容易出问题。商品主表字段设计有讲究我这里给一个经过多个项目验证的版本CREATE TABLE Product ( Id INT IDENTITY(1,1) PRIMARY KEY, ProductCode VARCHAR(50) NOT NULL UNIQUE, -- 商品编码业务上唯一 ProductName NVARCHAR(200) NOT NULL, BarCode VARCHAR(50) NULL, -- 条码扫码枪录入用 CategoryId INT NULL, -- 分类 Spec NVARCHAR(100) NULL, -- 规格型号 Unit NVARCHAR(20) NOT NULL DEFAULT 件, -- 计量单位 SafetyStock DECIMAL(18, 2) DEFAULT 0, -- 安全库存低于此值预警 Status TINYINT DEFAULT 1, -- 1启用 0停用 CreatedTime DATETIME DEFAULT GETDATE() );商品编码必须唯一这是硬性要求。很多系统用自增 Id 当业务编号打印单据、Excel 导入导出时特别不方便建议单独设一个 ProductCode 或 SKU 字段。计量单位用 NVARCHAR 而不用关联表是因为大部分中小企业的单位也就件、箱、公斤十来个没必要为这个建表增加联查复杂度。2.2 库存流水表设计要点库存流水表StockLog是整个系统的账本所有出入库操作都要在这里留下一行记录。为什么库存流水如此重要因为库存表里的数字可以被 UPDATE 覆盖但流水是不可篡改的审计痕迹。一旦账面库存和实物对不上全靠流水倒查。所以流水表的设计原则是只追加、不修改、不删除。CREATE TABLE StockLog ( Id BIGINT IDENTITY(1,1) PRIMARY KEY, ProductId INT NOT NULL, ChangeType TINYINT NOT NULL, -- 10入库 20出库 30盘盈 40盘亏 50调整 ChangeQty DECIMAL(18, 2) NOT NULL, BeforeQty DECIMAL(18, 2) NOT NULL, AfterQty DECIMAL(18, 2) NOT NULL, RefNo NVARCHAR(50) NULL, -- 关联单号如入库单号IN20250101 Remark NVARCHAR(200) NULL, CreatedBy NVARCHAR(50) NOT NULL, CreatedTime DATETIME DEFAULT GETDATE() );注意BeforeQty和AfterQty这两个字段很多新手会忽略。有了它们库存对账的时候可以直接看出来某一笔操作发生前后的库存变化不用临时反推。加上索引(ProductId, CreatedTime DESC)查某个商品的流水历史非常快。我第一版做的时候没设计流水表结果商品数量错了只能手工改数据库运营同事每天拿着 Excel 来对账苦不堪言。后来重构加了 StockLog所有数据变动都有迹可循问题定位效率至少提升了一倍。这个过程让我印象深刻所以强调一下——流水表不是可选项是必须项。3. 登录认证与权限控制的实现3.1 登录注册控件的使用示例用 ASP.NET 做登录认证不同版本做法差异很大。如果是老项目用的 Web FormsVisual Studio 工具栏里拖一个Login控件配置一下 Membership Provider几分钟就能跑起来。但说实话用 Membership 那套东西后续定制很痛苦密码哈希算法固定、数据库表结构锁死想扩展用户字段比如部门、岗位都费劲。我的建议是用 ASP.NET Core Identity 或自己做简单的 FormsAuthentication。这里分享一个我最常用的轻量级登录实现密码用 PBKDF2 哈希存储不存明文// 密码哈希PBKDF2迭代10万次 public static string HashPassword(string password) { using var rng RandomNumberGenerator.Create(); byte[] salt new byte[16]; rng.GetBytes(salt); using var pbkdf2 new Rfc2898DeriveBytes(password, salt, 100000, HashAlgorithmName.SHA256); byte[] hash pbkdf2.GetBytes(32); byte[] combined new byte[49]; Array.Copy(salt, 0, combined, 0, 16); Array.Copy(hash, 0, combined, 16, 32); return Convert.ToBase64String(combined); }登录成功后用 Session 存用户信息配合一个[Authorize]属性拦截未登录请求。如果你项目中正好用了登录注册控件的使用示例相关搜索大概率是在找这套东西可以按这个思路实现比控件灵活得多。3.2 基于 Session 的权限拦截中小企业内部系统权限做到角色-功能这一层就够了别上来就搞数据权限、行级权限那是自找麻烦。我常用的做法是用户表里存一个 Role 字段Admin/Operator/Viewer然后在控制器或页面上判断。public class AuthorizeFilter : IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { var user context.HttpContext.Session.GetString(UserName); if (string.IsNullOrEmpty(user)) { context.Result new RedirectResult(/Account/Login); return; } // 可选校验角色决定是否返回403 } }这里有个经典坑IIS 应用池默认 20 分钟回收Session 说丢就丢。解决方案是改用 Cookie 保存登录票据或者用IDistributedCache存 Session 到 Redis。如果用户量不大几十人最简单的是适当调长应用池的Idle Timeout同时让 Session 的过期时间和 Cookie 一致。我在一个客户那边遇到过登录半小时就掉线的问题最后查下来就是应用池回收导致 Session 丢失把回收时间改掉就解决了。4. 核心业务逻辑入库、出库与库存扣减4.1 出入库事务处理出入库是最核心的业务是库存系统的心脏。入库和出库操作本身不复杂复杂的是要保证多个表的数据一致性操作库存表、插入流水表、更新关联单据这三步必须在一个数据库事务里完成。只要漏掉任何一步库存数据就悬了。以出库为例完整的逻辑是校验库存足够扣减库存表数量插入流水记录更新出库单状态为已完成。用 C# 实现核心就是TransactionScope或 SQL 里的BEGIN TRAN。我习惯在数据库存储过程里做事务CREATE PROCEDURE sp_OutStock ProductId INT, Qty DECIMAL(18,2), RefNo NVARCHAR(50), Operator NVARCHAR(50) AS BEGIN SET NOCOUNT ON; BEGIN TRAN; DECLARE CurrentQty DECIMAL(18,2); SELECT CurrentQty StockQty FROM ProductStock WITH (UPDLOCK, ROWLOCK) WHERE ProductId ProductId; IF CurrentQty IS NULL OR CurrentQty Qty BEGIN ROLLBACK; THROW 50001, 可用库存不足, 1; END UPDATE ProductStock SET StockQty StockQty - Qty, LastUpdateTime GETDATE() WHERE ProductId ProductId; INSERT INTO StockLog(ProductId, ChangeType, ChangeQty, BeforeQty, AfterQty, RefNo, CreatedBy) VALUES(ProductId, 20, -Qty, CurrentQty, CurrentQty - Qty, RefNo, Operator); COMMIT; END注意WITH (UPDLOCK, ROWLOCK)这句。UPDLOCK是更新锁防止多个请求同时读到同一个库存值ROWLOCK是行级锁避免锁住整个表影响其他商品操作。这两个锁组合是库存扣减场景下的黄金搭档。4.2 并发扣减库存的解决方案多个用户同时下单库存只剩 10 件结果 5 个人都提交成功了——这种情况叫超卖是库存系统最要命的并发问题。解决办法有好几层第一层数据库层面加锁。就是上面存储过程的做法UPDLOCK保证同一时间只有一个请求能读改同一个商品库存。这种方式简单可靠也是我推荐优先用的。第二层乐观锁。在 ProductStock 表加一个Version字段UPDATE 的时候带上WHERE Version oldVersion受影响行数为 0 说明数据已被别人改过直接提示库存已变化请刷新重试。这种方式适合读多写少的场景但业务代码里要做重试机制。第三层Redis 分布式锁。并发量非常大比如秒杀才需要考虑普通进销存系统用不上别过度设计。实测下来对于中小企业的库存系统用带UPDLOCK的存储过程就能解决 90% 的并发问题。上线前一晚我用 JMeter 压了 200 并发扣同一个商品库存数据一模一样没有超卖。4.3 读取请求体的坑StreamReader 的正确用法有朋友在热搜里搜asp.net c# streamreader(httpcontext.request.body)这个场景我太熟了。ASP.NET 里想读取请求的原生 Body 数据最常见的是做 Web API 对接、接收外部系统推送的 JSON或者读取自定义格式的报文。直接new StreamReader(HttpContext.Request.Body)读出来是空字符串百分之百会出现。原因很简单Request.Body 是一个只能向前读的流模型绑定Model Binding已经把它读完了指针停在流的末尾。你再用 StreamReader 去读当然读不到东西。正确做法是先让 Position 归零再读取记住要加LeaveOpen或提前保存 Body 内容public async TaskIActionResult ReceiveData() { // 重要先让Body流回到开始位置否则读取结果为空 Request.Body.Position 0; using var reader new StreamReader(Request.Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true); string rawBody await reader.ReadToEndAsync(); // 读完后再重置避免影响后续中间件读取 Request.Body.Position 0; // 解析JSON或XML var data JsonSerializer.DeserializeDictionarystring, object(rawBody); return Ok(new { success true }); }注意三点第一leaveOpen: true一定不能漏否则读完 Body 后整个请求流被关闭第二某些情况下 Request.Body 不支持直接设置 Position需要先启用Request.EnableBuffering()在 ASP.NET Core 3.0 中推荐在控制器或中间件里显式调用第三如果你的接口同时被模型绑定再用 StreamReader 读顺序一定是先读、再走模型绑定否则同样拿不到数据。还有一种场景是把库存系统的对外接口比如接受 ERP 推送的发货单写成自定义 Handler直接用HttpContext.Current.Request.InputStream读取老版本 Web Forms 项目用的是这种方式。但总而言之现代 ASP.NET Core 项目里记住先 Reset Position再 Read这个核心就可以了。5. 部署与常见问题排查5.1 IIS 部署要点ASP.NET 项目开发时一切正常一部署到 IIS 就各种问题这是家常便饭。我把踩过的坑和对应的解法写成一个速查表现象原因解决方案403.14 Forbidden默认文档未配置IIS 里添加Default.aspx或Home/Index路由HTTP 500.19请求头大小超限/配置文件错误检查 web.config 格式确认 ASP.NET Core 模块已安装HTTP 500.30应用启动失败查看 Windows 事件查看器日志多半是数据库连接串错误中文乱码页面编码与数据库编码不一致统一使用 UTF-8连接串加CharSetutf8登录后 Session 丢失应用池回收应用池设置回收时间改为 0不回收或大幅调长5.2 数据库连接与数据初始化教训另外一个容易忽略的是数据库账号权限。生产环境我见过有人用sa账号直连密码写在 web.config 里还提交到了 Git 仓库这非常危险。建议创建专用账号kucun_user只授予该系统需要的最小权限——SELECT、INSERT、UPDATE、DELETE、EXECUTE不授予 DDL 权限。再用连接串加密或使用环境变量注入避免明文密码到处飘。上线前的数据初始化也值得注意商品编码的规则要想清楚。是自己编的有规律的编码比如类别流水号还是直接用条码或者是供应商给的物料号这块如果没定好后期导入数据、跟外部系统对账会非常痛苦。5.3 免费下载项目的选择思路最后聊下资产管理系统 asp.net mvc web 免费 下载这类搜索背后的需求。很多人上来想找现成的免费系统改改就能用这个思路可以理解但我建议拿到开源项目之后别急着用先做三件事第一检查数据库脚本是否能直接跑通很多免费版数据库表不全跑起来报错一堆。第二看代码里有没有隐藏的后门或加密的逻辑免费项目不一定安全重要的业务数据别直接放在里面。第三自己重新改一遍用户表和管理员初始密码务必确认默认密码已被替换。我甚至遇到过某个开源项目里留了一个万能密码的账号可以直接登录这种隐患不排查干净系统根本不敢上线。如果只是练手学习我强烈建议自己动手写一个精简版从商品表建库、登录页、入库出库、列表查询一路写下来比下载别人的项目看十遍都管用。自己踩过的坑才会留下深刻记忆。6. 一些实操心得根据我多次从头搭建的经验库存管理系统看着简单实际做起来细节特别多。再补充几点书里不会写的体会。第一编码规范从第一天就要立好。商品编码、单据编号的规则必须在项目启动时确定并且做成系统参数配置不要硬编码在代码里。我吃过亏的教训是第一版把单据号规则写死在代码里后来业务方要求改成日期部门流水号改代码加存量数据迁移搞了一整周。第二权限控制宁可先紧后松。做系统的时候控制得严一点后面发现问题再放开容易反过来一开始全部放开后面要收紧就会吵翻天——每个业务员都觉得我为什么不能看这个按钮。建议上线前把每个角色的权限清单打出来做一次评审再动工写权限逻辑。第三日志和操作记录的粒度一定要细。谁在什么时候做了什么操作、改了哪个商品的库存、从多少改成多少这些记录除了用来排查问题还有一个重要作用业务上出现争议时系统能给出证据。有这套记录在业务人员对系统的信任度高一大截。第四界面别花哨流程要顺手。库存系统的使用者往往是仓库阿姨、车间文员她们不会喜欢五颜六色的仪表盘真正要的是录入单据快、查询结果清楚、按钮位置固定。做开发之前在用户旁边至少待半天看看她们是怎么用现有 Excel 表格的然后照着那个习惯来设计页面布局。我在几个项目里试过效果特别好。本文还有配套的精品资源点击获取
返回列表