简介:这是一套基于ASP与MSSQL开发的Web进销存与仓库管理系统多用户版源码,面向中小企业、门店及需要搭建内部进销存平台的技术人员,可用于学习或二次开发一套完整的采购、销售、库存与财务一体化流程。压缩包为rar格式,大小约3.43MB,初始账号密码均为admin,恢复或附加数据库后需修改web目录中conn.asp的数据库连接内容。系统功能覆盖基本资料(往来单位、商品、仓库、员工、地区、会计科目、生产工序与工价)、期初建帐(库存、应收应付、现金银行、启用帐套)、采购与销售管理(出入库、收付款、退货、库存状况、毛利查询)、库存管理(调拨、报损报溢、拆装、盘点)、财务管理(费用单、收入单、转帐、工资单、记帐凭证、财务报表)以及系统设置(权限分配、数据库备份还原、月结存年结存)。目前已有726人学习下载,适合希望研究多用户进销存架构、权限控制与账套结存逻辑的开发者参考借鉴。
1. 从一张Excel管三个仓库说起:这套ASP+MSSQL进销存源码到底能扛什么活
如果你管过仓库,大概率经历过这种场面:采购入库用一张表,销售出库用另一张表,月底对账时发现库存数量对不上,翻遍聊天记录也找不到是哪一笔出了问题。更麻烦的是,多个仓管同时操作同一份文件,改着改着就冲突了。这套基于ASP+MSSQL的多用户进销存管理系统源码,解决的正是这个场景——它把采购、销售、库存、往来账目收进一个Web系统,多人通过浏览器同时操作,数据统一落在MSSQL里,不用再靠Excel传来传去。
这套源码的技术栈是经典ASP(不是ASP.NET)配SQL Server,前端是传统的HTML+CSS+少量JavaScript,服务端用VBScript处理业务逻辑。它适合两类人:一是中小型商贸公司或批发档口的IT负责人,想快速搭一套内部用的进销存系统,预算有限、不需要花哨界面;二是正在学ASP或维护老系统的开发者,需要一份结构完整、业务闭环的代码来参考或二次开发。不适合追求前后端分离、高并发、移动端适配的团队——这套东西的定位就是“够用、能跑、好改”。
我拿到这份源码后,第一件事不是急着部署,而是先理清它的模块边界和数据流向。下面按“能干什么→怎么跑起来→怎么改→坑在哪”的顺序拆开说。
2. 模块拆解与数据库设计:先看懂表结构再动手
2.1 核心功能模块与文件分布
这套系统的功能模块划分比较清晰,根目录下通常能看到几个关键文件夹和入口文件。常见结构是:/admin放后台管理,/inc放数据库连接和公共函数,/images放界面素材,根目录下default.asp是登录入口,main.asp是登录后的主框架页。
业务模块大致分五块:
- 基础资料:商品档案、供应商、客户、仓库、员工。商品表里会存编码、名称、规格、单位、默认进价、默认售价、库存上下限。
- 采购管理:采购订单、采购入库、采购退货。入库时更新库存和供应商往来。
- 销售管理:销售订单、销售出库、销售退货。出库时扣减库存并记录客户应收。
- 库存管理:库存查询、库存调拨、盘点单。调拨涉及两个仓库的同时增减。
- 报表与账目:进销存汇总、库存流水、应收应付明细。
每个模块通常对应一个ASP页面加一个动作参数,比如purchase_in.asp?action=save处理入库保存。这种“页面即接口”的写法在老ASP系统里很常见,改的时候要顺着action分支走。
2.2 数据库表结构与关键字段
MSSQL这边,核心表大概十来张。我一般先看这几张:
| 表名(常见命名) | 用途 | 关键字段 |
|---|---|---|
tb_Product | 商品档案 | ProductID, ProductCode, ProductName, Unit, StockQty |
tb_Stock | 分仓库存 | StockID, ProductID, WarehouseID, Qty |
tb_Purchase | 采购单主表 | PurchaseID, SupplierID, PurchaseDate, TotalAmount |
tb_PurchaseDetail | 采购单明细 | DetailID, PurchaseID, ProductID, Qty, Price |
tb_Sale | 销售单主表 | SaleID, CustomerID, SaleDate, TotalAmount |
tb_SaleDetail | 销售单明细 | DetailID, SaleID, ProductID, Qty, Price |
tb_StockLog | 库存流水 | LogID, ProductID, WarehouseID, ChangeQty, LogType, LogTime |
tb_User | 用户表 | UserID, UserName, Password, RoleID |
库存数量不是只存一个总数,而是按仓库分存在tb_Stock里。每次入库、出库、调拨,除了改tb_Stock.Qty,还要往tb_StockLog插一条流水。这个设计的好处是能追溯每一笔变动,坏处是如果代码里漏了写流水,对账时就会出现“库存对但流水缺”的玄学问题。
提示:拿到源码后先别改代码,用SQL Server Management Studio把建表脚本跑一遍,然后手动插几条测试数据,把
tb_Stock和tb_StockLog的关系走通,后面调试会快很多。
2.3 多用户并发下的库存扣减逻辑
多用户版最关键的点在于:两个人同时卖同一件商品,库存怎么保证不超卖。这套源码常见的做法是在出库保存时先查当前库存,再执行更新。伪代码逻辑如下:
-- 出库前检查库存(简化示意) SELECT Qty FROM tb_Stock WHERE ProductID = @ProductID AND WarehouseID = @WarehouseID; -- 如果 Qty >= 出库数量,则执行扣减 UPDATE tb_Stock SET Qty = Qty - @OutQty WHERE ProductID = @ProductID AND WarehouseID = @WarehouseID;问题在于,这两步之间如果有另一个请求进来,就可能出现两个请求都查到库存足够、然后都扣减的情况。老ASP系统里常见的补救方式是在UPDATE时加条件:
UPDATE tb_Stock SET Qty = Qty - @OutQty WHERE ProductID = @ProductID AND WarehouseID = @WarehouseID AND Qty >= @OutQty;然后判断@@ROWCOUNT是否为1,为0就回滚并提示库存不足。这个写法不能完全替代数据库事务隔离,但在中小规模并发下够用。如果你要上生产,建议把出库逻辑包在事务里,并对tb_Stock的ProductID+WarehouseID建唯一索引。
参数说明:@ProductID和@WarehouseID来自页面表单,@OutQty是本次出库数量。@@ROWCOUNT是MSSQL内置变量,返回上一条语句影响的行数。这个细节在源码里不一定写得规范,我见过不少版本直接UPDATE完就跳转,没判断行数,结果库存扣成负数——这是后面避坑章要展开的。
3. 部署实操:IIS配ASP+MSSQL的完整走一遍
3.1 环境准备与IIS配置
这套系统跑在Windows Server或Windows 10/11的IIS上。先确认几件事:IIS里要启用“ASP”功能(默认不开),MSSQL要允许SQL Server身份验证,并且TCP/IP协议已启用。
配置步骤:
- 打开“启用或关闭Windows功能”,勾选Internet Information Services,展开后勾选“ASP”和“ISAPI扩展”。
- 在IIS管理器中新建网站,物理路径指向源码根目录,绑定端口比如8080。
- 点进网站,双击“ASP”,把“启用父路径”设为True(老ASP常用
../包含文件,不开启会报错)。 - 确认应用程序池的.NET CLR版本设为“无托管代码”,因为ASP不需要.NET运行时。
MSSQL这边,用SSMS连接后新建数据库,比如命名JXC_DB,然后执行源码里附带的.sql建表脚本。如果源码没有建表脚本,只有.mdf文件,就用“附加数据库”的方式挂上去。
3.2 数据库连接字符串配置
源码里通常有一个conn.asp或dbconn.asp,里面写着连接字符串。典型内容:
<% Dim Conn, ConnStr ConnStr = "Provider=SQLOLEDB;Data Source=127.0.0.1;Initial Catalog=JXC_DB;User ID=sa;Password=你的密码;" Set Conn = Server.CreateObject("ADODB.Connection") Conn.Open ConnStr %>逻辑说明:Provider=SQLOLEDB是MSSQL的OLE DB驱动,老ASP基本都用这个。Data Source填服务器地址,本机可以用127.0.0.1或localhost。Initial Catalog是数据库名。如果用的是SQL Server 2008以上,也可以换成SQLNCLI11,但兼容性上SQLOLEDB更稳。
参数怎么改:把User ID和Password换成你MSSQL里实际能登录的账号。如果报“用户登录失败”,先确认SQL Server是否开了混合验证模式,以及该账号是否有JXC_DB的访问权限。
注意:有些源码把连接字符串写死在多个文件里,改的时候用编辑器的“在文件中查找”搜
ConnStr或sa,确保全部替换,不然会出现部分页面能开、部分页面报错的翻车现场。
3.3 登录与权限初始化
数据库跑通后,访问http://localhost:8080/default.asp,应该能看到登录页。默认账号通常在tb_User表里,常见的是admin配一个简单密码。如果登录报错,按这个顺序查:
- 看
tb_User里有没有数据,没有就手动插一条。 - 看登录页的SQL查询是明文比对还是MD5。老系统明文居多,如果是MD5,你得知道原始密码或者自己生成一个MD5值替换。
- 看Session是否正常。ASP的Session依赖IIS的ASP配置,如果登录后跳转又回到登录页,多半是Session没存住,检查应用程序池是否回收太频繁。
权限方面,多用户版一般分管理员和普通操作员。管理员能改基础资料和看报表,操作员只能录入单据。这个控制通常写在每个页面开头的if Session("RoleID") <> 1 then Response.Redirect "login.asp"之类的判断里。二次开发时如果要加角色,就在tb_User的RoleID上做文章,然后在页面入口加判断。
4. 二次开发与功能扩展:改哪里、怎么改
4.1 新增一个“客户应收明细”报表
假设老板要一张按客户汇总的应收报表,显示每个客户当前欠款总额和最近一笔交易日期。原系统可能只有简单的应收列表,没有汇总。改法是在/report目录下新建customer_ar.asp,核心SQL:
SELECT c.CustomerName, ISNULL(SUM(s.TotalAmount), 0) AS TotalSale, ISNULL(SUM(p.PaidAmount), 0) AS TotalPaid, ISNULL(SUM(s.TotalAmount), 0) - ISNULL(SUM(p.PaidAmount), 0) AS Balance, MAX(s.SaleDate) AS LastSaleDate FROM tb_Customer c LEFT JOIN tb_Sale s ON c.CustomerID = s.CustomerID LEFT JOIN tb_Payment p ON c.CustomerID = p.CustomerID GROUP BY c.CustomerName ORDER BY Balance DESC;逻辑说明:LEFT JOIN保证没有交易的客户也显示。ISNULL把空值转成0,避免ASP里做加法时报类型错误。Balance是应收减已收。LastSaleDate用MAX取最近一次销售日期。
参数方面,如果要做日期筛选,就在WHERE里加s.SaleDate BETWEEN @StartDate AND @EndDate,然后在ASP页面用Request.Form接收日期输入。注意ASP里拼SQL要用参数化或者至少做单引号转义,不然客户名里带单引号就会报错——这也是老ASP系统的常见坑。
4.2 库存调拨的事务处理
调拨功能如果原系统写得粗糙,可能只做了两个仓库的UPDATE,没写流水也没做事务。我一般会把它改成:
<% Conn.BeginTrans On Error Resume Next Conn.Execute "UPDATE tb_Stock SET Qty = Qty - " & OutQty & " WHERE ProductID=" & PID & " AND WarehouseID=" & FromWH If Err.Number <> 0 Then Conn.RollbackTrans : Response.Write "调出失败" : Response.End Conn.Execute "UPDATE tb_Stock SET Qty = Qty + " & OutQty & " WHERE ProductID=" & PID & " AND WarehouseID=" & ToWH If Err.Number <> 0 Then Conn.RollbackTrans : Response.Write "调入失败" : Response.End Conn.Execute "INSERT INTO tb_StockLog (ProductID, WarehouseID, ChangeQty, LogType, LogTime) VALUES (" & PID & ", " & FromWH & ", -" & OutQty & ", '调拨出', GETDATE())" Conn.Execute "INSERT INTO tb_StockLog (ProductID, WarehouseID, ChangeQty, LogType, LogTime) VALUES (" & PID & ", " & ToWH & ", " & OutQty & ", '调拨入', GETDATE())" If Err.Number <> 0 Then Conn.RollbackTrans Response.Write "流水写入失败" Else Conn.CommitTrans Response.Write "调拨成功" End If %>逻辑说明:BeginTrans开启事务,任何一步出错就RollbackTrans回滚。On Error Resume Next让ASP不中断,靠Err.Number判断。流水表记录调出为负、调入为正,方便后续对账。
参数怎么改:OutQty、PID、FromWH、ToWH都来自表单,实际写的时候要做类型转换(CLng或CInt),防止非数字输入导致SQL报错。这个写法比原系统直接两条UPDATE要稳,但要注意ASP的事务是连接级别的,必须保证所有操作走同一个Conn对象。
4.3 界面层的小改与兼容性
老ASP系统的界面一般是表格布局,改起来不难但烦。如果要加一个字段到列表页,步骤是:先在SQL查询里把字段查出来,然后在HTML表格的<td>里输出<%=rs("FieldName")%>,最后在表头加一列。注意rs是记录集,字段名要和SQL里的别名一致。
兼容性方面,这套系统在IE下基本没问题,但现代浏览器可能对某些老JS报错。常见的是document.all这种IE专有写法,在Chrome里会失效。如果遇到按钮点了没反应,先按F12看控制台报什么错,把document.all.xxx改成document.getElementById("xxx")通常能解决。
5. 避坑与排查:这几处翻车点我替你踩过了
5.1 库存扣成负数,但流水里查不到异常
现象:月底盘点发现某个商品库存是负的,但查tb_StockLog没有对应的出库记录,或者记录数量对不上。
原因:出库代码只更新了tb_Stock,没有写tb_StockLog,或者写流水的语句在UPDATE之后、但没判断UPDATE是否真的影响了行。更隐蔽的情况是,出库时没加Qty >= @OutQty条件,并发下两个请求都扣成功。
解决:在出库逻辑里强制加AND Qty >= @OutQty,判断@@ROWCOUNT,为0就回滚。同时把流水写入和库存更新放在同一个事务里,确保要么都成功要么都失败。已经出现负库存的,手动盘盈调整,并在流水里补一条“盘盈”记录。
5.2 中文乱码,商品名变成问号
现象:录入商品时中文正常,但查询出来显示成???或者乱码。
原因:ASP页面的编码和数据库的排序规则不一致。常见情况是页面用了<%@ CodePage=65001 %>(UTF-8),但数据库字段是varchar且排序规则是Chinese_PRC_CI_AS,或者反过来。
解决:统一编码。要么页面用GB2312(<%@ CodePage=936 %>),数据库用varchar;要么页面用UTF-8,数据库字段用nvarchar。改的时候注意conn.asp里如果有Session.CodePage也要一起改。最稳的办法是数据库字段全用nvarchar,页面用UTF-8,连接字符串加CharSet=UTF-8。
5.3 登录后操作几分钟就掉线
现象:登录成功,用了一会儿点任何按钮都跳回登录页。
原因:ASP的Session默认超时是20分钟,但如果应用程序池设置了较短的回收时间,或者IIS里“ASP”的“会话属性”超时设得太短,就会提前掉线。另一个可能是web.config或global.asa里有Session清理逻辑。
解决:在IIS的ASP配置里把“会话超时”改成60或120分钟。应用程序池的“回收”里把“固定时间间隔”设长或关掉。如果是global.asa里写了Session.Timeout = 5,改成Session.Timeout = 60。
5.4 报表查询超时,数据量一大就卡死
现象:进销存汇总报表在测试数据下正常,正式数据几万条后点查询要等很久甚至超时。
原因:SQL没有走索引,或者用了SELECT *加多层嵌套子查询。老ASP系统常见的是在tb_StockLog上按LogTime范围查,但LogTime没建索引。
解决:在tb_StockLog的LogTime、ProductID、WarehouseID上建非聚集索引。报表SQL尽量用JOIN代替子查询,避免SELECT *。如果还慢,考虑加汇总表,每天定时把流水汇总成日结存,报表查汇总表而不是查原始流水。
5.5 上传的源码里conn.asp被改过,连不上数据库
现象:从不同渠道拿到的源码,conn.asp里的连接字符串指向一个不存在的服务器或数据库名。
原因:源码在传播过程中被人改过配置,或者原本就是按作者本机环境写的。
解决:不要信任源码里的连接字符串,自己按第3章的方法重写一个。同时检查inc目录下有没有其他文件也写了连接字符串,用“在文件中查找”搜Provider=或Data Source=,全部统一。
6. 进阶技巧:用视图和存储过程把报表从ASP里解放出来
老ASP系统里,复杂报表的SQL往往直接写在ASP页面里,几百行SQL混着HTML,改起来痛苦。我的习惯是把报表逻辑下沉到MSSQL的视图和存储过程里,ASP只负责调用和展示。这样做的好处是:SQL可以在SSMS里单独调试,ASP页面变薄,以后换前端也不用重写业务逻辑。
以“进销存汇总”为例,先建一个视图:
CREATE VIEW v_StockSummary AS SELECT p.ProductCode, p.ProductName, w.WarehouseName, ISNULL(s.Qty, 0) AS CurrentQty, ISNULL(inv.InQty, 0) AS TotalIn, ISNULL(outv.OutQty, 0) AS TotalOut FROM tb_Product p CROSS JOIN tb_Warehouse w LEFT JOIN tb_Stock s ON p.ProductID = s.ProductID AND w.WarehouseID = s.WarehouseID LEFT JOIN ( SELECT ProductID, WarehouseID, SUM(Qty) AS InQty FROM tb_StockLog WHERE ChangeQty > 0 GROUP BY ProductID, WarehouseID ) inv ON p.ProductID = inv.ProductID AND w.WarehouseID = inv.WarehouseID LEFT JOIN ( SELECT ProductID, WarehouseID, SUM(ABS(ChangeQty)) AS OutQty FROM tb_StockLog WHERE ChangeQty < 0 GROUP BY ProductID, WarehouseID ) outv ON p.ProductID = outv.ProductID AND w.WarehouseID = outv.WarehouseID;逻辑说明:CROSS JOIN让每个商品和每个仓库组合,保证没有库存记录的商品也能显示。两个子查询分别算总入库和总出库,ABS把负数转正。ISNULL处理空值。
然后在ASP里这样调用:
<% Dim rs Set rs = Conn.Execute("SELECT * FROM v_StockSummary ORDER BY ProductCode, WarehouseName") Do While Not rs.EOF Response.Write "<tr>" Response.Write "<td>" & rs("ProductCode") & "</td>" Response.Write "<td>" & rs("ProductName") & "</td>" Response.Write "<td>" & rs("WarehouseName") & "</td>" Response.Write "<td>" & rs("CurrentQty") & "</td>" Response.Write "<td>" & rs("TotalIn") & "</td>" Response.Write "<td>" & rs("TotalOut") & "</td>" Response.Write "</tr>" rs.MoveNext Loop rs.Close Set rs = Nothing %>参数方面,如果要做日期过滤,就把视图改成存储过程,传入@StartDate和@EndDate,在子查询里加WHERE LogTime BETWEEN @StartDate AND @EndDate。存储过程的好处是执行计划可以缓存,比每次拼SQL快。
验证方法:在SSMS里先单独跑视图,确认数据对得上,再放到ASP里。如果ASP里显示的数据和SSMS里不一致,检查连接字符串用的账号是不是同一个,以及有没有WHERE条件在ASP里被漏掉。
从那以后我每次拿到老ASP源码,都先把核心报表的SQL抽到视图或存储过程里,再动ASP页面。这样即使页面改崩了,数据逻辑还在,恢复起来快。希望帮到你。
本文还有配套的精品资源,点击获取