简介:这份资源是一套面向企业薪资管理场景的完整Web系统源码,采用ASP结合SQL数据库开发,适合Web开发学习者、数据库初学者以及需要定制薪资管理模块的开发者研究参考。压缩包共97个文件,约689KB,以asp动态页面为主体,配合css样式表、gif与jpg图片素材、xml配置及mdf、ldf数据库文件,构成可直接部署运行的完整项目结构。系统覆盖员工信息管理、工资结构设置、薪酬自动计算、个税处理、工资发放记录、报表统计与多级权限控制等模块,代码中可清晰看到ASP与数据库交互的查询与写入逻辑。已有423人学习下载,读者可通过研读源码掌握动态页面与SQL查询的配合方式,理解企业级应用的业务分层与目录组织,并借鉴防止SQL注入等安全实践,对薪资系统的定制优化与教学演示均有较高参考价值。
1. 接手一套老 ASP 工资系统,先别急着改代码
朋友公司行政离职,丢过来一个压缩包,里面是一套 ASP + SQL Server 的企业工资管理系统源码,让我帮忙看看还能不能用。打开一看,.asp文件配conn.asp,数据库是.mdf备份,典型的 WebForm 之前那一代技术栈。这类系统在中小企业里存量极大,功能不花哨但业务逻辑扎实:员工档案、考勤录入、工资项配置、月度核算、工资条打印,一条链路走完。适合谁?一是需要低成本维护内部薪酬流程的行政或财务团队,二是想拿一套完整 CRUD 项目练手 ASP 与 SQL 配合的开发者。它不现代,但结构清晰,改起来比想象中顺手。
2. 系统架构与目录结构:先摸清这套 ASP 工程的骨架
2.1 技术栈定位与运行环境
这套系统是典型的 ASP 经典栈:服务端用 VBScript 写业务逻辑,页面以.asp为后缀,数据库走 SQL Server,连接方式通过 ADO(ActiveX Data Objects)的ADODB.Connection对象。没有 MVC,没有路由,每个页面自己处理请求、查库、拼 HTML。听起来原始,但好处是调试直观——出问题直接看对应.asp文件,不用追框架源码。
运行环境我一般这样配:Windows Server 或 Win10 自带 IIS,开启 ASP 功能(控制面板 → 启用或关闭 Windows 功能 → Internet Information Services → 万维网服务 → 应用程序开发功能 → 勾选 ASP),数据库装 SQL Server Express 就够,把.mdf附加进去。IIS 里新建站点指向源码目录,应用程序池的「启用 32 位应用程序」按数据库驱动位数决定,老项目多数是 32 位驱动,建议先设 True 试。
提示:IIS 默认关闭详细错误,调试阶段在站点「ASP」配置里把「将错误发送到浏览器」设为 True,否则 500 错误只给一句「An error occurred」,排查全靠猜。
2.2 目录结构与模块划分
源码目录一般长这样,我按实际拆包看到的典型布局整理:
salary-system/ ├── conn.asp # 数据库连接串,全局引用 ├── inc/ │ ├── check.asp # 登录态校验,每个业务页头部 include │ ├── func.asp # 公共函数:金额格式化、日期处理 │ └── menu.asp # 左侧导航 ├── login.asp # 登录入口 ├── emp/ # 员工档案模块 │ ├── emp_list.asp │ └── emp_edit.asp ├── attend/ # 考勤模块 │ └── attend_input.asp ├── salary/ # 工资核算模块 │ ├── item_config.asp # 工资项配置 │ ├── calc.asp # 月度核算 │ └── slip_print.asp # 工资条打印 ├── db/ │ └── salary.mdf # SQL Server 数据库文件 └── images/ # 静态资源模块边界很清楚:emp管人,attend管出勤,salary管钱。conn.asp和inc/check.asp是所有页面的公共依赖,改配置和权限只动这两处。这种「按业务分目录 + 公共 include」的组织方式,是那个年代 ASP 项目的标准做法,理解了这个约定,后面读任何页面都不会迷路。
2.3 数据库连接与公共层
conn.asp是整个系统的命门,所有页面第一行几乎都是<!--#include file="conn.asp"-->。典型内容如下:
<% ' conn.asp - 全局数据库连接 Dim conn, connStr Set conn = Server.CreateObject("ADODB.Connection") ' 连接串:服务器地址、库名、账号密码按实际环境改 connStr = "Provider=SQLOLEDB;Data Source=127.0.0.1;Initial Catalog=salary_db;User ID=sa;Password=your_password;" conn.Open connStr ' 页面结束统一关闭,避免连接泄漏 Sub CloseConn() If IsObject(conn) Then If conn.State = 1 Then conn.Close Set conn = Nothing End If End Sub %>逻辑说明:Server.CreateObject("ADODB.Connection")创建连接对象,connStr里Data Source是 SQL Server 实例地址,Initial Catalog是数据库名,User ID/Password是登录凭据。CloseConn子过程负责释放,业务页在末尾调用。参数上最容易翻车的是Data Source——本机 Express 版通常要写成127.0.0.1\SQLEXPRESS,只写127.0.0.1会连不上默认实例。
inc/check.asp负责登录态,核心是读 Session:
<% ' check.asp - 登录校验,业务页头部 include If Trim(Session("admin_id") & "") = "" Then Response.Write "<script>alert('登录已失效,请重新登录');location.href='../login.asp';</script>" Response.End End If %>Session("admin_id")在login.asp验证账号密码成功后写入,这里做空值判断,为空就跳回登录页。Response.End会立即终止后续脚本执行,这是 ASP 里中断请求的标准手段。注意Trim(... & "")的写法,是为了把 Null 安全转成空串再比较,老代码里这种防御性写法很常见,别嫌啰嗦,删了容易在 Session 过期时抛类型错误。
3. 核心业务链路:从员工档案到工资条打印
3.1 员工档案的增删改查
emp/emp_edit.asp是典型的「一个页面兼顾新增和编辑」写法,靠 URL 参数id区分。核心片段:
<% Dim id, rs, sql id = Trim(Request.QueryString("id") & "") Set rs = Server.CreateObject("ADODB.Recordset") If id = "" Then ' 新增:打开空记录集准备插入 sql = "SELECT * FROM employee WHERE 1=0" Else ' 编辑:按主键取记录 sql = "SELECT * FROM employee WHERE emp_id=" & CLng(id) End If rs.Open sql, conn, 1, 3 ' 1=adOpenKeyset, 3=adLockOptimistic If Request.ServerVariables("REQUEST_METHOD") = "POST" Then If id = "" Then rs.AddNew rs("emp_name") = Trim(Request.Form("emp_name")) rs("dept_id") = CLng(Request.Form("dept_id")) rs("base_salary")= CDbl(Request.Form("base_salary")) rs.Update Response.Redirect "emp_list.asp" End If %>逻辑说明:rs.Open sql, conn, 1, 3里第三个参数是游标类型(1 表示键集游标,可前后移动),第四个是锁定类型(3 表示乐观锁,允许更新)。新增时用WHERE 1=0打开一个空结构记录集,再AddNew填字段;编辑时按emp_id定位。CLng、CDbl做类型转换,防止字符串拼进数值字段导致 SQL 报错。
参数上要盯住Request.Form的字段名必须和表单name完全一致,大小写不敏感但拼写不能错,错了就是空值入库。另外CLng(id)这步很关键——直接把 URL 里的id拼进 SQL 是注入高危点,CLng强制转长整型,非数字直接报错,等于顺手堵了注入。
3.2 考勤数据录入与校验
attend/attend_input.asp按月批量录入,表单是表格形式,每行一个员工。提交后循环处理:
<% Dim i, empId, workDays, leaveDays For i = 1 To Request.Form("row_count") empId = CLng(Request.Form("emp_id_" & i)) workDays = CDbl(Request.Form("work_days_" & i)) leaveDays= CDbl(Request.Form("leave_days_" & i)) ' 校验:出勤天数不能为负,且不超过当月应出勤 If workDays < 0 Or workDays > 31 Then Response.Write "<script>alert('第" & i & "行出勤天数异常');history.back();</script>" Response.End End If conn.Execute "INSERT INTO attendance(emp_id, att_month, work_days, leave_days) " & _ "VALUES(" & empId & ",'" & Request.Form("att_month") & "'," & workDays & "," & leaveDays & ")" Next %>逻辑说明:表单里藏一个row_count表示行数,字段名用emp_id_1、emp_id_2这种带序号的命名,循环时按序号取值。conn.Execute直接执行 INSERT,适合不需要返回记录集的写操作。校验放在入库前,天数越界就history.back()退回,避免脏数据。
这里有个血泪经验:att_month是字符串字段,拼 SQL 时两侧的单引号不能少,但值本身如果带单引号就会破坏语句。常见做法是用Replace(str, "'", "''")转义,或者干脆改成参数化查询。老项目多数没做,二次开发时建议补上。
3.3 工资核算的计算逻辑
salary/calc.asp是整套系统的大脑,把员工基本工资、考勤扣款、各项补贴汇总成实发工资。核心计算:
<% ' 按月份核算:遍历在职员工,逐人计算 Dim rsEmp, gross, deduction, netPay Set rsEmp = conn.Execute("SELECT e.emp_id, e.base_salary, ISNULL(a.work_days,0) AS wd, " & _ "ISNULL(a.leave_days,0) AS ld FROM employee e " & _ "LEFT JOIN attendance a ON e.emp_id=a.emp_id AND a.att_month='" & attMonth & "' " & _ "WHERE e.status=1") Do While Not rsEmp.EOF gross = CDbl(rsEmp("base_salary")) ' 缺勤扣款:日薪 = 基本工资/21.75,按缺勤天数扣 deduction = (gross / 21.75) * CDbl(rsEmp("ld")) netPay = gross - deduction conn.Execute "INSERT INTO salary_detail(emp_id, sal_month, gross, deduction, net_pay) " & _ "VALUES(" & rsEmp("emp_id") & ",'" & attMonth & "'," & gross & "," & deduction & "," & netPay & ")" rsEmp.MoveNext Loop rsEmp.Close %>逻辑说明:LEFT JOIN attendance保证没录考勤的员工也能算(缺勤按 0 处理),ISNULL兜底空值。日薪按21.75天折算,这是国内薪酬核算的常用月计薪天数。Do While Not rsEmp.EOF ... MoveNext是遍历记录集的标准循环。核算结果写入salary_detail表,供工资条打印读取。
参数上,attMonth来自表单,格式2024-06,拼进 SQL 前要确认格式,否则 JOIN 条件匹配不上,全员缺勤为 0,算出来的工资全是满额——这种「静默错误」最坑,数字看着正常,其实全错。我一般核算完先抽查两三个有请假记录的员工,对得上再批量确认。
3.4 工资条打印与导出
salary/slip_print.asp按员工逐条输出 HTML 表格,配合浏览器打印。核心是循环salary_detail并套模板:
<% Set rsSlip = conn.Execute("SELECT s.*, e.emp_name FROM salary_detail s " & _ "INNER JOIN employee e ON s.emp_id=e.emp_id " & _ "WHERE s.sal_month='" & salMonth & "' ORDER BY s.emp_id") Do While Not rsSlip.EOF %> <table class="slip"> <tr><td>姓名</td><td><%=rsSlip("emp_name")%></td></tr> <tr><td>应发</td><td><%=FormatNumber(rsSlip("gross"),2)%></td></tr> <tr><td>扣款</td><td><%=FormatNumber(rsSlip("deduction"),2)%></td></tr> <tr><td>实发</td><td><%=FormatNumber(rsSlip("net_pay"),2)%></td></tr> </table> <% rsSlip.MoveNext : Loop %>逻辑说明:<%= %>是 ASP 的输出简写,等价于Response.Write。FormatNumber(x, 2)保留两位小数,金额展示必须用,否则会出现1234.5这种不整齐的数字。HTML 表格配合 CSS 的page-break-after可以实现一人一页打印。
参数上,salMonth同样来自请求,打印前建议先判断该月是否已核算,没核算就提示,避免打出空表。导出 Excel 的话,常见做法是设置Response.ContentType = "application/vnd.ms-excel"并把表格直接输出,老项目里很常见,简单粗暴但能用。
4. 避坑与常见问题:这套老系统最容易翻车的几个点
4.1 连接串写错导致全站 500
现象:所有页面打开都是 500,IIS 日志里是ADODB.Connection error '800a0e7a'。原因:conn.asp里的Data Source或账号密码和实际环境不符,Express 版实例名没带\SQLEXPRESS。解决:先用 SSMS 用同一套凭据连一次,确认能连上再回填连接串;IIS 应用程序池位数和驱动位数对齐,32 位驱动就把「启用 32 位应用程序」设为 True。
4.2 中文乱码
现象:页面显示问号或乱码,入库的中文也乱。原因:ASP 默认按系统代码页解析,文件保存编码和Response.Charset不一致。解决:每个页面顶部加<%@ Language=VBScript CodePage=936%>,conn.asp里加Session.CodePage = 936,文件本身用 ANSI 或 GB2312 保存。数据库字段用nvarchar更稳,但老库多是varchar,那就把代码页统一到 936。
4.3 工资核算结果全员满额
现象:核算完发现所有人扣款都是 0。原因:att_month格式和考勤表里存的不一致(比如一个存2024-6一个存2024-06),JOIN 匹配不上,ISNULL全兜成 0。解决:统一月份格式,录入和核算都走同一个格式化函数,核算前先SELECT COUNT(*) FROM attendance WHERE att_month='xxx'确认有数据。
4.4 Session 过期后操作报类型错误
现象:放着不动一段时间,点任何按钮都报Type mismatch。原因:Session 超时后Session("admin_id")为 Empty,某些页面没走check.asp直接用了它。解决:确认每个业务页头部都 include 了check.asp,比较时统一用Trim(Session("admin_id") & "")的写法,别直接拿 Session 值做数值运算。
4.5 打印工资条串页
现象:打印出来两个员工的工资条挤在一页,或者表格被截断。原因:CSS 没控制分页,表格高度不固定。解决:给每个.slip表格加page-break-after: always;,打印样式里设@media print { .slip { page-break-inside: avoid; } },再用浏览器打印预览逐页确认。
5. 二次开发改造:让这套老系统多撑几年
拿到源码后,我的改造顺序一般是「先保命、再提效、后扩展」。保命指安全和环境适配:把所有拼 SQL 的地方过一遍,数值参数用CLng/CDbl强转,字符串参数至少做单引号转义,条件允许就换成ADODB.Command参数化查询,这是投入产出比最高的一步。环境上把连接串抽到独立配置文件,别硬编码在conn.asp里,换服务器时只改一处。
提效指业务逻辑收敛。这套系统里工资计算公式散落在calc.asp里,改个扣款规则要动源码。我的做法是新建一张salary_rule表,把日薪天数、扣款比例、补贴项做成可配置,calc.asp改成读配置再算:
<% ' 从规则表读参数,替代硬编码的 21.75 Dim ruleRs, dayFactor Set ruleRs = conn.Execute("SELECT rule_value FROM salary_rule WHERE rule_key='day_factor'") dayFactor = CDbl(ruleRs("rule_value")) ruleRs.Close ' 后续计算用 dayFactor 替代 21.75 %>这样行政自己就能调参数,不用每次找我改代码。扩展方向看需求:加个 Excel 导入员工档案、把工资条导出 PDF、给核算加个操作日志表记录谁在什么时候算的——都是小改动,但能明显减少扯皮。
改造时有个习惯我一直保持:每改一个页面,先在测试库上跑一遍完整链路(登录 → 录考勤 → 核算 → 打印),确认数字对得上再上生产。老系统没有单元测试,全靠这条手工链路兜底。从那以后我每次动这类薪酬系统,都强制走一遍「录一个带请假的测试员工 → 核算 → 核对扣款金额」的流程,宁可多花十分钟,也不让错账流到工资条上。希望帮到你。
本文还有配套的精品资源,点击获取