简介:这套asp+ajax+json医生预约系统源码与数据库,是一份面向Web开发入门者及有经验程序员的预约管理完整示例,重点演示传统ASP后端如何通过AJAX异步请求与JSON数据格式配合,实现医生排班查询、号源预约等典型业务。资源压缩包仅321KB,共37个文件,包含9个asp页面、5个js脚本、3个css样式表、若干png/gif图标以及mdb和db两种数据库文件,覆盖从数据库连接到前端展示的完整链路。目录划分清晰,include下提供数据库连接、MD5加密等公共配置脚本,前端借助jQuery UI组件实现日历选择,预约查询、时间筛选、科室联动等接口清晰分离,便于新手对照学习异步交互流程。目前已有272人学习下载,虽小而精,却能直观看到AJAX+JSON在真实预约场景中的运用,尤其适合需要快速搭建原型或理解经典Web交互模式的读者,作为学习和改造起点非常合适。
1. 经典 ASP 配 Ajax 的医生预约系统:为什么这个老栈还值得跑一遍
看到“asp+ajax+json医生预约系统源码和数据库”这个标题,我猜你已经不是第一次在检索框里敲它了。要么是某高校的课程设计选题,要么是手里接了一个十年陈旧的诊所管理项目,需要在一周内把它从“能打开”推进到“能预约”。这套技术栈听起来像古董——经典 ASP、手写 Ajax、JSON 字符串拼拼拆拆——但它仍然活跃在一批中小型医疗信息系统的存量代码里,甚至在一些不愿意升级的老服务器上稳定跑了七八年。搞清楚它的数据流和踩坑点,比追新框架更实用。
这套系统的核心业务不复杂:患者选科室、选医生、选时段,填信息提交预约记录;后台管理端查看、确认、取消。但难点在细节——经典 ASP 里怎么接收 Ajax 发来的 JSON、怎么处理中文乱码、IIS 上怎么配才能让 POST 请求不丢数据、Access 数据库和 SQL Server 的写法差异在哪儿。这些才是让新手翻车最多的地方。这篇笔记我按“环境跑通 → 数据流拆解 → 数据库落地 → 避坑 → 进阶验证”的顺序写,目标是让你拿到源码后,能在两小时内让预约流程完整走通,而不是卡在第一句 SQL 报错上。
2. 把 ASP 环境先跑起来:IIS 配置与经典模式的两个硬前提
2.1 IIS 安装与经典 ASP 模式的启用步骤
经典 ASP 跑在 Windows 的 IIS 上,这一点和 PHP 或 Node 项目差别很大。很多人第一步就栽在 IIS 没装 ASP 模块上,页面打开后看到的是源码文本而不是执行结果。这里我按最常见做法写一份最小启动清单。
打开“控制面板 → 程序 → 启用或关闭 Windows 功能”,勾选以下节点:
Internet Information Services ├── 万维网服务 │ ├── 应用程序开发功能 │ │ └── ASP │ └── 常见 HTTP 功能 │ ├── 静态内容 │ └── 默认文档
勾选完成后,IIS 管理器里才会出现“ASP”图标。接下来在站点根目录下建一个最简单的测试文件test.asp:
<% Response.ContentType = "text/html" Response.Charset = "utf-8" Response.Write "ASP 运行正常" %>浏览器访问http://localhost/test.asp,如果页面显示中文和“ASP 运行正常”,说明脚本引擎已经在工作。如果看到的是<%开头的一整段源码,不用怀疑别的,就是 ASP 模块没装好。这里补充一个关键参数:在 IIS 的 ASP 设置里,把“启用父路径”设为 True,很多旧源码会用到../这种相对路径写法,默认 False 会导致 500 错误。
2.2 应用程序池的托管管道模式:经典模式与集成模式差别
这是熟练工和新人拉开差距的地方。IIS 从 7.0 开始默认用集成模式运行应用程序池,但经典 ASP 的脚本引擎对这两种模式的支持非常骨感。如果你在集成模式下访问 ASP 页面时遇到500.23错误——它明确告诉你“ASP 页面注册了不受支持的 HTTP 模块”——解决办法就一个:把当前站点的应用程序池切换成“经典”托管管道模式。
在 IIS 管理器左侧选中站点,右侧点“基本设置”,把应用程序池切换到一个管道模式为“Classic”的池;或者直接在应用程序池列表里右键编辑“托管管道模式”。这一步做完,很多“本地能跑、一部署就 500”的诡异问题直接消失。顺带把 32 位应用程序启用设为 True,如果你的数据库驱动是旧版 Access OLEDB 或者某公司分发的 32 位组件,这一步能省掉一个极其难查的“未在本地计算机上注册”报错。
另外提一个容易被忽略的点:ASP 临时文件目录。IIS 会为每个站点生成编译缓存,如果源码改了不生效,先去C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files和 IIS 的%systemdrive%\inetpub\temp下清空旧缓存,再回收一次应用程序池。这个问题在改数据库连接字符串后格外常见——页面还在用旧配置跑,不是因为代码错,是因为缓存没刷新。
3. Ajax 请求进 ASP:从 POST 到 JSON 字符串的完整链路
3.1 浏览器端发请求:表单序列化与请求头设置
前端发 Ajax 请求这段,核心是两个容易出错的地方:请求头的Content-Type,以及数据序列化格式。大多数旧源码用的是 jQuery,写法一般是$.ajax或$.post。我先给一个基于原生 XMLHttpRequest 的版本,方便你看清底层发生了什么:
function submitReservation() { var formData = { patientName: document.getElementById("patientName").value, departmentId: document.getElementById("department").value, doctorId: document.getElementById("doctor").value, visitDate: document.getElementById("visitDate").value, timeSlot: document.getElementById("timeSlot").value, phone: document.getElementById("phone").value }; var xhr = new XMLHttpRequest(); xhr.open("POST", "api/saveReservation.asp", true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { var response = JSON.parse(xhr.responseText); if (response.success) { alert("预约成功,编号:" + response.code); } else { alert("预约失败:" + response.msg); } } }; xhr.send("data=" + encodeURIComponent(JSON.stringify(formData))); }这里有两个关键逻辑要说明。第一,Content-Type用的是表单编码而不是application/json,这是老 ASP 接收方便的选择,后面服务端直接用Request.Form("data")就能拿到完整字符串。第二,encodeURIComponent是必须的,因为 JSON 字符串里包含中文姓名、手机号、日期等,如果不编码,POST 过程中可能因为特殊字符导致数据截断或乱码。如果你用 jQuery,等效写法是$.post("api/saveReservation.asp", { data: JSON.stringify(formData) }, ...),效果一样,但看原生代码更容易理解参数传递的本质。
3.2 服务端接收与解析:Request.Form 与 JSON 拆分
服务端这一半,经典 ASP 没有内置 JSON 解析器,必须自己写拆分函数或者引入一个 JSON 解析类文件。多数源码会带一个json.asp或者JSON2.asp的公共文件,里面封装了ParseJson和ToJson两个函数。我见过最简洁的做法是服务端先取到字符串,然后做简单的键值拆分,因为预约表单字段是固定的一套,逐字段提取比解析嵌套对象更稳:
<% Response.ContentType = "application/json" Response.Charset = "utf-8" Dim rawData rawData = Request.Form("data") If rawData = "" Then rawData = Request.QueryString("data") End If If rawData = "" Then Response.Write "{""success"":false,""msg"":""参数为空""}" Response.End End If Dim jsonObj Set jsonObj = ParseJson(rawData) Dim patientName, departmentId, doctorId, visitDate, timeSlot, phone patientName = jsonObj("patientName") departmentId = jsonObj("departmentId") doctorId = jsonObj("doctorId") visitDate = jsonObj("visitDate") timeSlot = jsonObj("timeSlot") phone = jsonObj("phone") If patientName = "" Or doctorId = "" Or visitDate = "" Then Response.Write "{""success"":false,""msg"":""必填字段缺失""}" Response.End End If ' 插入预约记录(SQL 部分见第 4 章) %>逻辑说明:先兼容 POST 和 GET 两种传参方式,这是为了调试方便,浏览器地址栏直接拼 URL 也能测;然后用ParseJson把字符串拆成字典对象,字段名和前端保持一致。注意Response.ContentType设置成application/json,这样前端JSON.parse才能正确处理,如果漏了这行,返回的文本会被当成 HTML 解析,前端会报 “Unexpected token <” 之类的错。还有一个小细节:JSON 字符串里中文被encodeURIComponent转成了%开头的编码,但其实 ASP 端拿到的已经是解码后的值,不需要再额外处理,如果你手动再解一次码反而会把中文变成乱码。
4. 数据库设计与预约核心 SQL:Access 还是 SQL Server 的选型差别
4.1 表结构设计:医生科室时段三张主表加一张预约记录表
标题里明确带了“数据库”,那这里就得写透。这套系统最实用的表结构是四张:科室表、医生表、排班时段表、预约记录表。字段设计直接影响后面 SQL 的复杂度,我按 Access 和 SQL Server 通用写法给出一份建表参考:
-- 科室表 CREATE TABLE [departments] ( [id] INT PRIMARY KEY IDENTITY(1,1), [deptName] NVARCHAR(50) NOT NULL ); -- 医生表 CREATE TABLE [doctors] ( [id] INT PRIMARY KEY IDENTITY(1,1), [deptId] INT NOT NULL, [doctorName] NVARCHAR(30) NOT NULL, [title] NVARCHAR(20), [intro] NVARCHAR(200) ); -- 排班时段表 CREATE TABLE [schedules] ( [id] INT PRIMARY KEY IDENTITY(1,1), [doctorId] INT NOT NULL, [scheduleDate] DATETIME NOT NULL, [timeSlot] NVARCHAR(20) NOT NULL, [maxCount] INT DEFAULT 20, [bookedCount] INT DEFAULT 0 ); -- 预约记录表 CREATE TABLE [appointments] ( [id] INT PRIMARY KEY IDENTITY(1,1), [scheduleId] INT NOT NULL, [patientName] NVARCHAR(30) NOT NULL, [phone] NVARCHAR(20) NOT NULL, [status] INT DEFAULT 0, [createTime] DATETIME DEFAULT GETDATE() );字段说明:预约记录表里存scheduleId而不是直接存医生和日期,这样设计的好处是改排班时预约记录不用动;status用 0/1/2 表示待确认、已确认、已取消。bookedCount是冗余字段,用来快速判断号源还有没有余量,避免每次预约都要COUNT一次预约表。代价是需要确保预约和计数在同一事务里完成,老系统里容易忽略这一点造成超约。
4.2 三个核心 SQL:查号源、插入预约、剩余号数更新
预约系统最容易被写坏的就是“插入预约”这条链路。新手常犯的错是只往预约表 INSERT,忘了更新bookedCount;或者反过来先改计数再插入,结果插入失败计数却被扣了。我一般这样处理:
-- 1. 查询号源是否可约 SELECT scheduleId, bookedCount, maxCount FROM schedules WHERE doctorId = 3 AND scheduleDate = '2025-03-10' AND timeSlot = '上午'; -- 2. 插入预约记录 INSERT INTO appointments (scheduleId, patientName, phone, status, createTime) VALUES (12, N'张三', N'13800000000', 0, GETDATE()); -- 3. 更新已约数量 UPDATE schedules SET bookedCount = bookedCount + 1 WHERE id = 12 AND bookedCount < maxCount;逻辑说明:先查bookedCount < maxCount再插入,能挡住绝大多数并发预约请求;第三步的AND bookedCount < maxCount是兜底——两个用户同时查到余量为 1,同时执行插入时,第二个更新会因为条件不满足而影响 0 行,此时回过去查一下就知道该提示“号源已满”了。这是我理解的最适合老 ASP 场景的防超约方案:不引入事务,靠条件更新做乐观锁。如果你后来把预约做成独立事务,记得在 ASP 里用BeginTrans/CommitTrans包住插入和更新两条语句。
4.3 Access 与 SQL Server 的写法差异提醒
源码里如果用的是 Access(.mdb或.accdb文件),上面的建表 SQL 就不能直接跑。Access 不支持IDENTITY(1,1)这种自增写法,需要把字段类型设为“自动编号”;GETDATE()也要换成Now()。另外 Access 对中文表名和字段名更宽容,但对保留字敏感,status在 Access 里不是保留字但在 SQL Server 里是,稳妥起见可以叫bookStatus。
还有连接字符串的差别也值得记一笔。Access 常用的是:
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Server.MapPath("data.mdb")SQL Server 则是:
"Driver={SQL Server};Server=localhost;Database=hospital;Uid=sa;Pwd=123456;"如果你拿到的源码默认是 Access 但你想迁移到 SQL Server,除了连接字符串,还要检查所有 SQL 里的Now()改为GETDATE()、字符串拼接的&改为+、布尔值True/False改为1/0。老系统翻车最多的不是复杂查询,而是这些细微差异在数十个 ASP 文件里反复出现,改不完也查不尽。
5. 避坑排查:老 ASP 预约系统最常见的 7 个翻车点
5.1 中文乱码问题,从页面到数据库的三层设置
现象:前端提交的预约人姓名存进数据库后显示“张???”或者直接变成“寮犱笁”之类的乱码。
原因有三层,缺一不可:页面编码没声明、请求头编码和页面不一致、数据库字段排序规则不是中文支持的代码页。最常见的是第一层和第三层同时出问题。解决步骤我按顺序写:第一,每个 ASP 文件顶部加<%@ Language="VBScript" CodePage="65001" %>和<meta charset="utf-8">;第二,前端 Ajax 的请求头里Content-Type带charset=utf-8;第三,SQL Server 数据库的排序规则选Chinese_PRC_CI_AS,Access 的话建库时选“中文(简体)-拼音”排序。三层都理顺后,乱码现象基本绝迹。如果仍然出现,检查一下Response.Charset是否被某个公共文件覆盖了——这种情况调试起来非常像玄学,实际上是某个 include 文件里写了Response.Charset = "gb2312"把前面的设置冲掉了。
5.2 修改不生效,ASP 缓存与浏览器缓存的双重干扰
现象:改了 ASP 文件里的 SQL 语句,刷新浏览器看到的还是旧页面,甚至报错信息都是旧的。
原因:IIS 对 ASP 有编译缓存,浏览器端对.asp动态页面默认不缓存,但如果你把预约页面写成了.html静态页,浏览器会按静态资源缓存处理,导致改了 JS 里的请求参数后页面还是旧逻辑。另外,Response.Write输出片段被 AJAX 拿到后如果做了innerHTML赋值,页面 DOM 没有刷新也会给人“没改”的错觉。
解决:IIS 应用程序池右键“回收”,然后删除%systemdrive%\inetpub\temp\ASP Compiled Templates下的内容;浏览器按 F12 打开开发者工具,Network 标签页勾选“Disable cache”,再硬刷新一次。血的教训:我曾经花两小时排查预约接口始终返回旧 JSON 结构,最后发现是集成模式下 ASP 临时目录里有旧编译产物,IIS 一直没清理。
5.3 SQL 注入与防注入过滤:老系统的安全底裤
现象:预约接口被人传入'或1=1导致查询异常,或者整个数据库被拖走。
原因:老 ASP 源码里大量使用字符串拼接 SQL,几乎没有参数化查询,这是当时的历史现状——Request.Form的每个字段都直接拼进 SQL 字符串里。
解决:我建议一个立竿见影的补丁方案,在公共文件(如conn.asp)里加一段过滤函数,对每个入参做转义:
Function SafeStr(str) If IsNull(str) Then SafeStr = "" Else SafeStr = Replace(str, "'", "''") SafeStr = Replace(SafeStr, """", """""") End If End Function然后在每个 SQL 拼接处套一层:sql = "SELECT * FROM appointments WHERE phone='" & SafeStr(phone) & "'"。注意 Replace 单引号为双单引号,这是 SQL 字符串转义的标准做法。硬编码的 SQL 参数化在老 ASP 里写起来费劲,逐页替换工程量不小,但保命要紧。如果项目还要对外运行,至少做到这一步,再考虑把用户输入的白名单做起来——比如patientName只允许中英文和空格,phone只允许数字和+,这种校验在 ASP 端做一次,数据库端就是第二道防线。
5.4 预约超时与并发更新出错,号源显示不准确
现象:两个患者同时预约最后一个号位,两个都提示成功,但数据库中只多了一条记录,页面显示剩余 0 却还能约。
原因:前面提过,插入预约表与更新bookedCount两步没有放在同一事务里,也没有条件更新兜底。经典 ASP 配合 Access 时事务支持有限,很多人干脆不做并发控制。
解决:在 SQL Server 下用一条UPDATE语句做原子判断,影响行数大于 0 才继续插入预约记录:
UPDATE schedules SET bookedCount = bookedCount + 1 WHERE id = 12 AND bookedCount < maxCount;执行后检查Recordset的RecordsAffected,如果为 0 直接返回“号源已满”,不再执行插入。Access 下没有RecordsAffected的可靠返回值,就改成先SELECT再UPDATE的短事务,配合页面提交时的一次Application.Lock把处理串行化。虽然粗放,但在一个诊所日均几十条预约的负载下完全够用。
5.5 环境变量不一致:32 位驱动与 64 位进程的恩怨
现象:数据库连接字符串写着Provider=Microsoft.Jet.OLEDB.4.0,访问时提示“未在本地计算机上注册”或“找不到 Microsoft.Jet.OLEDB.4.0 提供程序”。
原因:服务器是 64 位 Windows,IIS 应用程序池默认以 64 位进程运行,而 Jet OLEDB 驱动是 32 位的。很多机器上装了 32 位 Office 或 Access 驱动,64 进程找不到。
解决:在 IIS 应用程序池“高级设置”里将“启用 32 位应用程序”设为 True,然后回收应用程序池。这一步对老项目的价值极大——我见过有人绕了一整天去装各种 Access Database Engine 补丁,最后发现只是 IIS 进程位数的设置问题。如果你换用 ACE 驱动(Provider=Microsoft.ACE.OLEDB.12.0),也建议保持 32 位开启,因为历史源码很多时候只装了 32 位 ACE。
5.6 JSON 解析报错,特殊字符被截断或转义
现象:前端填写的手机号或地址里含&、+、#等字符,提交后服务端收到的 JSON 少了一段,ParseJson直接报错。
原因:JSON.stringify之后的字符串拼进xhr.send(),如果没用encodeURIComponent,浏览器会按application/x-www-form-urlencoded的规则把&当作参数分隔符截断,把+解码为空格,#直接丢弃后续内容。服务端拿到的rawData只剩半个 JSON。
解决:发送前一定包一层encodeURIComponent,服务端Request.Form("data")取到的已经是 URL 解码后的完整字符串,不需要二次解码。如果你在服务端还要对字符串做Server.URLDecode,这就会把中文变成乱码,因为 ASP 在取Request.Form时已经解过一次了。这个认知偏差是新手最常踩的,翻车后再来问往往已经被乱码坑了一整晚。
5.7 接口地址写死绝对路径,换机器就 404
现象:本地跑得好好的,换到服务器上预约接口全部 404,但首页能打开。
原因:源码里$.ajax的 URL 写的是http://localhost/hospital/api/saveReservation.asp或带机器名的绝对路径,换机器后路径不对,浏览器仍然请求旧地址。
解决:把请求地址改为相对路径,api/saveReservation.asp或./api/saveReservation.asp。有些旧源码用action=""的 form 提交配window.location.href跳转,同样存在此问题。我建议统一改用相对路径,然后本地和服务器上都能通。如果站点放在二级目录,可以定义一个var APP_ROOT = "/hospital/"放在公共 JS 里,所有 Ajax 请求用它拼 URL,以后改目录名只动一处。
6. 验证预约流程与进一步改造:从跑通到能上线的关键收尾
预约流程跑通后,不要急着交给客户验收,先做一件低成本高收益的事:把所有关键操作写入一个日志表。老 ASP 没有现成的中间件,我见过不少项目用文本日志凑合,但文本并发容易丢内容,所以尽量用数据库表记。建一张operation_logs,字段包含操作时间、操作类型、请求参数、返回结果,每次预约提交后写一条。后续如果出现“患者说提交成功了但没收到确认”的纠纷,翻日志就能还原现场,对于医疗场景这几乎是必修课。
然后做两层验证。第一层是接口层的功能验证:
curl -X POST http://localhost/hospital/api/saveReservation.asp ^ -H "Content-Type: application/x-www-form-urlencoded" ^ -d "data=%7B%22patientName%22%3A%22%E6%B5%8B%E8%AF%95%22%2C%22doctorId%22%3A1%2C%22visitDate%22%3A%222025-03-10%22%7D"这个请求里%7B是{的 URL 编码,%22是双引号,%E6%B5%8B%E8%AF%95是“测试”两个字的 UTF-8 编码。预期返回 JSON 包含success: true和预约编号。如果返回失败,按失败信息检查是字段校验、还是 SQL 执行错误、还是号源已满,逐层往前排查。第二层验证是并发预约,开两个浏览器窗口对同一个scheduleId同时提交,看是否只有一个成功——这一步能直接检验第 5.4 节的条件更新是否真的生效。
再进一步改造的方向,我会优先做两个:一是把预约时间段的冲突检测做细,比如同一个医生在一个时段只能被预约一次,这需要在appointments表加唯一索引(scheduleId)或者在插入前做一次存在性检查;二是给管理端加一个简单的日预约统计页面,用GROUP BY scheduleDate汇总每天各科室预约量,输出到 JSON 给前端图表用。这些都不改现有架构,是在老代码上叠加价值,也正好能把你在第 3 章学到的 Ajax 数据流复用一遍。
最后说一个我的习惯:任何老 ASP 项目改完,我都会先跑一次全站 HTTP 状态码扫描,用脚本请求每个页面和接口,把 500 和 404 全揪出来,而不是只测几条主路径。因为预约系统这种业务,页面之间互相跳转很多,漏测一条路径就可能漏掉一个运行时错误。希望这篇笔记能帮你把整套预约链路从“能打开”推进到“能稳定预约”。
本文还有配套的精品资源,点击获取