简介这是一份面向计算机相关专业在校生、教师及企业员工的超市管理系统毕业设计/课程设计源码包提供了一套完整的前后端实现方案。代码已通过多轮测试并成功运行答辩评审平均分达96分可直接用于毕设、课设或大作业也适合基础扎实的读者在此基础上扩展会员管理、库存预警、销售统计等模块。压缩包共2001个文件整体约52.19MB涵盖Java源码、JSP页面、ASP处理脚本、HTML/CSS/JavaScript前端页面、配置文件、数据库文件以及大量图片和图标素材目录结构与项目层次安排较为清晰便于按功能模块学习和查阅。目前已有48人学习/下载适合需要快速落地一整套超市进销存流程的在校学生和开发者。下载后建议先阅读README.md了解项目结构、部署方式及测试账号再结合源码理解用户登录、商品管理、订单处理等核心功能。1. 一份超市管理系统藏着一套经典 ASP 的进阶结构解压这份毕业设计/课程设计用的超市管理系统第一眼看到文件清单我就知道它和那种“单文件凑合型作业”不是一路货controller.ashx与controller.asp并存负责分发action_list.asp、action_upload.asp是具体动作的处理ASPJson.class.asp、Uploader.Class.asp、MultiformProcessor.class.asp、PathFormatter.class.asp则把 JSON 编解码、multipart 解析、路径格式化分别封装成类。这种结构对正在做课程设计或大作业的人很有参考价值超市商品、供应商、库存、收银后台这些业务没那么复杂但请求分发、数据序列化、文件上传三个环节是所有管理系统都绕不开的。下面我会按照这套代码实际跑起来时的调用顺序把每一层怎么设计、参数怎么定、哪些地方容易写错讲清楚。2. 请求分发怎么做controller.asp 里的白名单路由与动作映射2.1 为什么同一个项目里既有 .asp 又有 .ashxIIS 允许同一站点同时挂载经典 ASP 与 ASP.NET 管道.asp结尾的请求交给经典 ASP 引擎.ashx、.aspx进入 .NET 运行时。这个项目之所以双入口是为了让两类请求各取所长前台页面和后台管理表单用经典 ASP 直接输出 HTML逻辑写在controller.asp和action_*.asp里改动即生效不需要编译富文本编辑器里的图片上传、文件浏览这类接口要返回固定结构的 JSON适合用upload_json.ashx、file_manager_json.ashx这类 .NET 轻量处理程序响应体小、行为可控。如果你只在做课程设计完全可以把后台所有请求都塞进controller.asp一个入口如果想把商品列表这类接口对接到前端做异步刷新用controller.ashx统一返回 JSON 是更省事的选择。两种控制器在同一套系统里分工是经典 ASP 项目迭代到后期最常见的状态也意味着排错时要分清“哪个请求走哪个管道”。2.2 白名单分发代码是怎么写的controller.asp的核心逻辑是“读取 action 参数映射到具体动作文件”。我会写成白名单校验加Server.Execute的组合% controller.asp —— 后台动作统一入口 Dim action, allowed action Request(action) allowed list,upload,delete,edit,getinfo 白名单校验防止 action../../somefile 这类路径穿越 If action And InStr(, allowed ,, , action ,) 0 Then Server.Execute(action_ action .asp) Else Response.Write {code:0,msg:invalid action} End If %这段代码有三个值得留意的点。第一Request(action)会同时读取 QueryString 和 Form 里同名参数所以前端无论 GET 还是 POST 提交后端都可以用同一套路由代价是参数来源被隐藏调试时不容易看出是哪一端传的。第二白名单用“逗号包裹字符串 InStr 判断”是经典 ASP 里的惯用写法比数组遍历更紧凑但它只能做包含判断所以白名单两侧都要拼上逗号避免listtom这种子串被误放行。第三我没有用!-- #include fileaction_list.asp --做分发因为 include 在页面编译阶段就展开所有动作代码会一起执行而Server.Execute是运行时跳转执行完被调用页面后再返回两个页面之间变量不共享天然隔离。动作与页面文件的映射关系在这类系统里通常长这样action 值对应文件主要职责listaction_list.asp商品/订单列表支持分页和关键字搜索uploadaction_upload.asp商品图片上传内部调用 Uploader.Class.aspdeleteaction_delete.asp标记删除或物理删除editaction_edit.asp回显表单、保存修改getinfoaction_getinfo.asp返回单条记录供编辑页回填Server.Execute有一个容易忽略的行为被调用页面里如果出现Response.End整个请求都会终止而不是只结束被调用的动作页面。所以动作文件里别放调试用的Response.End否则后续输出会被硬生生截断前端拿到半截 JSON 直接报 parse 错误。2.3 controller.ashx 端点的轻量写法如果controller.ashx需要提供商品分类、商品列表这类 AJAX 数据常见做法是把它写成 .NET 的 IHttpHandlerC# 代码大致长这样public class controller : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType application/json; context.Response.Charset utf-8; string action context.Request[action] ?? string.Empty; switch (action) { case product_list: context.Response.Write(ProductService.List(context.Request)); break; case category_tree: context.Response.Write(CategoryService.Tree()); break; default: context.Response.Write({\code\:0,\msg\:\unknown\}); break; } } public bool IsReusable { get { return true; } } }这里的ContentType必须显式指定为application/json否则前端拿到响应会按 text/html 处理$.getJSON在严格模式下直接报 parseerror。另一个细节是IsReusable返回 true减少处理程序被重复创建的开销——虽然这个项目规模下影响不大但这是和经典 ASP 页面“每次请求都新建整个页面对象”最大的性能差别。如果你把controller.ashx当作商品接口用所有响应逻辑集中在ProductService、CategoryService这类静态类里入口文件只做路由转发维护起来会轻松很多。3. ASPJson.class.asp用 VBScript 手写 JSON 编解码器3.1 为什么不能在经典 ASP 里直接 JSON.parse经典 ASP 只有 VBScript 或 JScript 两种脚本引擎既不提供JSON.stringify也没有System.Text.Json。很多早年的管理系统直接在页面里用字符串拼 JSON遇到字段里有引号、换行、中文时就会拼出不合法的 JSON。ASPJson.class.asp这类文件就是为了解决“服务器生成 JSON、客户端又往服务器传 JSON”的读写问题它的核心工作有两件把 VBScript 变量转成 JSON 文本以及把 JSON 文本解析回 VBScript 变量。3.2 序列化时按 VarType 分发处理用 VBScript 写序列化最常见的做法是基于VarType函数做类型分发因为 VBScript 没有函数重载只能靠运行时判断变量类型 ASPJson.class.asp —— 序列化核心 Class ASPJson Public Function Encode(ByVal obj) Select Case VarType(obj) Case vbString Encode Replace(obj, , \) Case vbInteger, vbLong, vbSingle, vbDouble Encode CStr(obj) Case vbBoolean If obj Then Encode true Else Encode false Case vbNull, vbEmpty Encode null Case vbArray Encode EncodeArray(obj) Case Else If IsObject(obj) And Not obj Is Nothing Then Encode EncodeObject(obj) Else Encode CStr(obj) End If End Select End FunctionvbString分支必须做Replace(obj, , \)不处理转义时商品名里出现一个英文双引号就能把整段 JSON 炸掉。vbBoolean要输出true/false而不是1/0这是前后端对接时反复踩的坑CStr(True)得到的是TrueJSON.parse 直接拒绝。空值和未初始化变量统一归到null可以避免序列化结果里出现空白字段。对象类型进入EncodeObject数组类型进入EncodeArray两条路径都靠递归完成嵌套转换。对象和数组两个分支的遍历方式不同代码如下Private Function EncodeObject(ByVal obj) Dim sb, key sb { For Each key In obj sb sb key : Encode(obj.Item(key)) , Next If Right(sb, 1) , Then sb Left(sb, Len(sb) - 1) End If EncodeObject sb } End Function Private Function EncodeArray(ByVal arr) Dim i, sb sb [ For i LBound(arr) To UBound(arr) If i LBound(arr) Then sb sb , sb sb Encode(arr(i)) Next EncodeArray sb ] End Function End Class写EncodeObject容易忽略的是Dictionary的For Each遍历出来的是键名obj.Item(key)取出的是一个 Variant再次调用Encode时会递归处理嵌套对象。这比字符串拼接的优势在于结构清晰一个包含订单和商品明细的字典能被完整地序列化成两层 JSON。效率上不用抱太大期望几百行记录没问题到上万行时明显比成熟 JSON 库慢但课程设计场景完全够用。VarType到 JSON 类型的对应关系可以整理成下面这张表排查序列化异常时很有用VBScript VarType值输出 JSON 类型说明vbEmpty0null未初始化变量vbNull1null数据库 Null 值vbLong3number32 位整数vbString8string必须对双引号做转义vbBoolean11true/false不能输出 1/0vbArray8192array遍历 LBound 到 UBoundvbObject/Dictionary9/1object键值对递归序列化3.3 从 Recordset 到 JSON 数组的完整示例action_list.asp里最常见的配合方式是把查询结果逐行塞进Scripting.Dictionary再把字典数组交给ASPJson.Encode输出 action_list.asp —— 商品列表 JSON 输出 % Dim conn, rs, arrRows, i, dict, jsoner Set conn Server.CreateObject(ADODB.Connection) conn.Open ConnStr Set rs Server.CreateObject(ADODB.Recordset) rs.Open SELECT ProductID, ProductName, Price, Stock FROM Products ORDER BY ProductID DESC, conn, 1, 1 ReDim arrRows(rs.RecordCount - 1) i 0 Do While Not rs.EOF Set dict Server.CreateObject(Scripting.Dictionary) dict.Add id, rs(ProductID).Value dict.Add name, rs(ProductName).Value dict.Add price, rs(Price).Value dict.Add stock, rs(Stock).Value Set arrRows(i) dict i i 1 rs.MoveNext Loop rs.Close Set rs Nothing Set jsoner New ASPJson Response.Write {code:1,rows: jsoner.Encode(arrRows) } %这里的ConnStr需要在页面顶部或单独的conn.asp里定义连接串就是普通 SQLOLEDB 格式ProviderSQLOLEDB;Data Sourcelocalhost;Initial CatalogSuperMarket;User IDsa;Password********。rs.Open的第三个参数是游标类型取1adOpenKeyset才能让RecordCount返回准确行数如果省略成默认的 forward-only 游标RecordCount很可能是 -1ReDim arrRows(-2)会直接报下标越界。提示Response.Write后面的 JSON 字符串里出现了双引号VBScript 里要写成转义这是初学者最容易写错的地方。3.4 前端如何消费这份 JSON前端拿到{code:1,rows:[...]}后常见的处理方式是把 rows 渲染成表格。如果项目引用了 jQuery可以直接这样写$.getJSON(/controller.ashx?actionproduct_list, function (res) { if (res.code ! 1) { alert(res.msg); return; } let html ; res.rows.forEach(function (item) { html trtd item.id /tdtd item.name /td td item.price /tdtd item.stock /td/tr; }); document.getElementById(productTbody).innerHTML html; });要留意的是文件保存编码必须和Response.Charset一致。项目文件可以整体按 UTF-8 保存同时页面顶部写% LanguageVBScript CodePage65001 %并设置Response.Charsetutf-8两边都一致时才不会出现“JSON 里中文变成乱码”的现象。如果前端发现中文显示成问号或菱形优先检查这一处而不是去改数据库。4. 上传链路拆解MultiformProcessor 与 PathFormatter4.1 multipart/form-data 到达服务器后的真实样子商品图片上传是管理系统必有的功能。表单只要加了enctypemultipart/form-data浏览器就不再按application/x-www-form-urlencoded编码而是把整个请求体分成若干部分每一段用同一个 boundary 字符串分隔。抓取一个请求看报文大致如下POST /action_upload.asp HTTP/1.1 Content-Type: multipart/form-data; boundary----WebKitFormBoundary7MA4YWxkTrZu0gW ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; nameproduct_id 1024 ------WebKitFormBoundary7MA4YWxkTrZu0gW Content-Disposition: form-data; nameimage; filenamegoods.jpg Content-Type: image/jpeg 二进制图片数据 ------WebKitFormBoundary7MA4YWxkTrZu0gW--关键点在两部分nameproduct_id这一段是普通表单字段nameimage; filenamegoods.jpg这一段是文件字段名后面多了filename并且带独立的Content-Type。文件内容从两个\r\n\r\n之后开始一直延续到下一个 boundary 之前。经典 ASP 的Request.Form只能拿普通字段拿不到文件字节流所以必须自己解析Request.BinaryRead读出来的原始数据。4.2 MultiformProcessor.class.asp 的拆分逻辑拆解multipart的流程可以分成四步取 boundary、读二进制、按 boundary 分割、识别 name/filename。实际部署时还要处理文本转码和二进制保留所以我把核心逻辑放在MultiformProcessor.Parse里 MultiformProcessor.class.asp —— multipart 核心解析 Class MultiformProcessor Private m_Fields Private m_Files Private m_FileNames Private Sub Class_Initialize() Set m_Fields Server.CreateObject(Scripting.Dictionary) Set m_Files Server.CreateObject(Scripting.Dictionary) Set m_FileNames Server.CreateObject(Scripting.Dictionary) End Sub Public Property Get Fields() Set Fields m_Fields End Property Public Property Get Files() Set Files m_Files End Property Public Property Get FileNames() Set FileNames m_FileNames End Property Public Sub Parse() Dim boundary, raw, text, parts, part, headerEnd, header, content Dim reg, m, fieldName, fileName boundary Mid(Request.ServerVariables(CONTENT_TYPE), _ InStr(Request.ServerVariables(CONTENT_TYPE), boundary) 9) raw Request.BinaryRead(Request.TotalBytes) text BytesToUtf8(raw) parts Split(text, -- boundary) Set reg New RegExp For Each part In parts headerEnd InStr(part, vbCrLf vbCrLf) If headerEnd 0 Then header Left(part, headerEnd - 1) content Mid(part, headerEnd 4) content Left(content, Len(content) - 2) 去掉结尾的 CRLF reg.Pattern name([^]*) Set m reg.Execute(header) If m.Count 0 Then fieldName m(0).SubMatches(0) reg.Pattern filename([^]*) Set m reg.Execute(header) If m.Count 0 Then fileName m(0).SubMatches(0) m_FileNames.Add fieldName, fileName m_Files.Add fieldName, content Else m_Fields.Add fieldName, content End If End If Next End Sub End Classboundary取自Content-Type头浏览器每次生成的 boundary 是随机的写死无法解析成功。BytesToUtf8是把二进制流转成文本再拆分的辅助函数普通字符串字段没问题但二进制图片内容一旦被转成文本就回不去了所以生产级写法应该在字节流里直接定位\r\n\r\n用ADODB.Stream按长度取出文件内容而不是过一遍字符串。这里把原始文件名保存在m_FileNames文件内容保存在m_Files二者必须分开不能混用。4.3 Uploader.Class.asp 的保存与参数校验解析完成后文件内容要交给Uploader执行校验和落盘。保存前至少要检查三样扩展名白名单、文件大小上限、目标目录是否存在 Uploader.Class.asp —— 文件保存封装 Class Uploader Public MaxSize Public AllowExts Public Function Save(ByVal savePath, ByVal originalFileName, ByVal fileContent) Dim ext, newName, fso ext LCase(Mid(originalFileName, InStrRev(originalFileName, .) 1)) If InStr(| LCase(AllowExts) |, | ext |) 0 Then Save Exit Function End If newName PathFormatter.Format(originalFileName, timestamp) Set fso Server.CreateObject(Scripting.FileSystemObject) If Not fso.FolderExists(Server.MapPath(savePath)) Then fso.CreateFolder Server.MapPath(savePath) End If Dim stream Set stream Server.CreateObject(ADODB.Stream) stream.Type 1 stream.Open stream.Write fileContent stream.SaveToFile Server.MapPath(savePath newName), 2 stream.Close Save newName End Function End ClassAllowExts建议写成jpg|png|gif|webp这种竖线分隔串内部用竖线包裹后再做包含判断两边夹上竖线可以避免gif被jpeg包含的误判。MaxSize在这里只做占位真正的字节数判断应该在MultiformProcessor解析完成时用LenB做因为拿到 content 后判断长度成本最低。目录不存在时FolderExists CreateFolder会一路建出来避免首次上传时因为少建了 uploads 目录直接抛 500。4.4 PathFormatter 决定文件被存成什么名字PathFormatter.class.asp解决的是“文件名怎么生成”。直接保留原始文件名不可取中文名在老旧 IIS 上可能编码出错同名文件又会互相覆盖。常见策略有三种策略示例输出优点缺点保留原始文件名goods.jpg直观前端拼 URL 方便重名覆盖、中文名有编码风险时间戳 随机数20250603_143055_12.jpg几乎不冲突文件不可读缺日期维度日期目录 随机名/2025/06/03/xxxx.jpg便于按日清理和归档需要递归建目录权限校验变多PathFormatter的timestamp模式本质就是把Year(Date) Month(Date) Day(Date)拼出来再接上Hour(Time) Minute(Time) Second(Time)和一个随机数。Rnd在不调用Randomize的情况下同一进程里每次都会走同一套伪随机序列连续上传两张图可能拿到相同随机数所以上传动作里应该先执行一次Randomize再生成文件名。这个细节很容易被忽略但它直接决定两个用户同时传图时会不会互相覆盖。4.5 action_upload.asp 的最终组装当controller.asp分发到action_upload.asp时动作页做的工作非常少把 Processor 解析出来的文件内容交给 Uploader再返回 JSON 给调用方 action_upload.asp —— 上传动作 % Dim processor, uploader, newName, fieldName Set processor New MultiformProcessor processor.Parse() fieldName image If processor.Files.Exists(fieldName) Then Set uploader New Uploader uploader.MaxSize 2048000 uploader.AllowExts jpg|png|gif|webp newName uploader.Save(/uploads/, processor.FileNames(fieldName), processor.Files(fieldName)) If newName Then Response.Write {code:1,url:/uploads/ newName } Else Response.Write {code:0,msg:upload failed} End If Else Response.Write {code:0,msg:no file} End If %这里体现的设计是动作页只做参数编排不写具体实现校验和落盘逻辑全部收敛到Uploader类里。当你要支持 base64 图片上传、生成缩略图或限制图片宽高时只需要改Uploader.Save一个方法前端调用方式完全不用变。MultiformProcessor.Files里存的是文件内容FileNames里存的是原始文件名二者一旦传反Uploader会拿文件字节当文件名去取扩展名结果要么扩展名非法要么存出一个没有后缀的文件。5. 部署排错Windows 上跑经典 ASP 的配置与验证清单5.1 三步把环境搭起来在 Windows Server 或 Windows 10/11 上经典 ASP 需要手动启用。服务端推荐用 DISM 一次性装齐dism /online /enable-feature /featurename:IIS-ASP /featurename:IIS-ManagementConsole装完 IIS 后进入“IIS 管理器 → 应用程序开发功能 → ASP”把“启用父路径”设为 True页面里用到../uploads/这类相对路径时父路径不打开会报 Active Server Pages error 0126。默认脚本语言保持 VBScript请求限制里的“最大请求实体体”默认只有 200000 字节图片上传功能不调大根本跑不通。5.2 用 curl 验证接口是否正常服务起来后别急着开浏览器先用两条 curl 打一下能快速定位问题在路由还是在上传curl -i http://localhost/controller.asp?actionlistpage1rows20 curl -i -F imageC:\test\goods.jpg http://localhost/action_upload.asp第一条应返回商品列表 JSON如果得到invalid action先看 URL 里的 action 是否和白名单一致。第二条用-F构造 multipart 请求不能丢丢了就变成传字符串而不是文件。返回 JSON 里code:1且带url说明上传链路已通。5.3 三个高频坑的位置中文乱码多数是文件保存编码与Response.Charset不一致统一为 UTF-8 后顺手在页面顶部加% LanguageVBScript CodePage65001 %。500 错误先看 IIS 的 ASP 详细错误是否开启未开启时浏览器只给状态码不开详细日志等于盲调。上传大文件如果直接返回 413那是 IIS 请求筛选在拦需要在站点的web.config里放开maxAllowedContentLength例如设置成 10MBsystem.webServer security requestFiltering requestLimits maxAllowedContentLength10485760 / /requestFiltering /security /system.webServer把 IIS 日志和 ASP 详细错误开关先打开再用第 5.2 节的两条 curl 重新跑一遍绝大多数问题的真实报错会立刻出现在响应体里。本文还有配套的精品资源点击获取