简介:面向企业人事与财务场景的工资管理系统源码,采用ASP服务端脚本与SQL数据库构建,适合Web开发初学者、课程设计者及需要研究企业级薪资业务逻辑的开发者参考。压缩包共97个文件,约689KB,以67个asp动态页面和数据库文件为核心,辅以css样式、gif/jpg/bmp等界面图像及xml配置,完整呈现登录、员工信息、考勤、工资核算、报表与权限控制等模块的组织方式。已有423人学习下载,可在本地搭建后阅读源码,理解ASP与SQL的交互、表单处理、工资项设置和薪酬计算流程,并观察数据库表结构与页面跳转关系。资源体积较小、模块相对齐全,便于二次开发练习、教学演示和课程项目改造,也能为薪资管理系统的定制与优化提供参考。
1. 从一张工资条说起:企业工资管理系统到底解决什么
月底最后两天,财务把 Excel 工资表在群里来回传三遍,公式引用错一行,全员工资条就得重发——这种场景在几十人的小公司里太常见。一套企业工资管理系统源码用 ASP + SQL 的方式,把员工档案、部门、考勤、社保基数、个税和补贴拆成可配置的工资项,按月批量核算,出错能逐项倒查。它不追求高并发,胜在部署轻:Windows 上开个 IIS,挂个 SQL Server,把文件拷进去改一行连接串就能跑。适合内网办公、没有专职 IT 的公司,也适合想完整走一遍 ASP WEB SQL 这套经典组合的开发者拿来练手和二次开发。
2. 技术栈与目录结构:一个经典 ASP 站点的骨架长什么样
2.1 技术选型与运行环境
这套系统是典型的经典 ASP 组合:ASP(Active Server Pages,服务端 VBScript 脚本)+ ADO 访问数据库 + SQL Server 存数据 + IIS 做 Web 服务器。它最大的特点是解释执行、无需编译,改完 .asp 文件刷新页面就生效,对没有构建工具链的内网环境特别友好。代价是语言表达力弱、没有强类型、调试全靠 Response.Write 和 IIS 日志,所以代码组织是否规整,直接决定了这套源码能不能改得动。
运行环境上,我一般这样准备:
- 操作系统:Windows Server 或 Win10/11 均可,IIS 里勾选「ASP」「ISAPI 扩展」「默认文档」三项,ASP 那项里还要把「启用父路径」打开,否则
<!--#include file="../inc/conn.asp"-->会直接报 500。 - 数据库:SQL Server 2000 之后的版本基本都能跑,建库时排序规则选
Chinese_PRC_CI_AS,能省掉后面一大半中文乱码的麻烦。 - 驱动:老代码连库用
SQLOLEDB,新系统上如果报「未找到提供程序」,换成SQLNCLI11或MSOLEDBSQL即可,连接串结构不变。 - 权限:站点目录给
IIS_IUSRS读权限;如果系统带附件上传,再单独给 upload 目录写权限,别整个站点给写。
数据库初始化一般随源码带一个 .sql 脚本,在 SSMS 里新建一个空库,执行脚本就行。如果没带脚本,就得按下面的表结构自己建,这是二次开发时最先要确认的东西。
2.2 目录结构与文件职责
经典 ASP 项目没有框架约束,全靠目录约定,所以先看结构再读代码,效率高很多。这类工资系统常见的组织方式如下:
| 目录/文件 | 职责 | 注意点 |
|---|---|---|
default.asp | 入口页,判断登录态后跳转 | 通常就是跳 login 或后台首页 |
login.asp/logout.asp | 登录、退出 | 登录成功后写 Session |
conn.asp | 数据库连接与公共对象 | 全站唯一入口,改库只改这里 |
/inc/ | 公共函数、鉴权、页头页脚 | 建议配置禁止直接浏览器访问 |
/admin/ | 后台管理页面,员工、工资项、核算、报表 | 每页顶部 include 鉴权文件 |
/db/ | 建库脚本、备份文件 | 部署后应移出站点目录 |
/css//js//images/ | 静态资源 | 老项目多用 table 布局 |
/export/ | 导出的 Excel / 临时文件 | 定期清理,避免磁盘堆满 |
这个结构里最关键的是conn.asp和/inc/下的鉴权文件。前者是所有页面访问数据库的通道,后者决定了一个未登录用户能不能直接敲 URL 进后台。很多下载来的源码把鉴权写在每个页面开头,复制粘贴时漏掉一页就是个口子,二次开发时优先把它收敛成一个 include。
2.3 数据库表结构:工资系统的七张核心表
工资系统的数据模型围绕「人、钱、时间」三个维度展开,核心表不多,但字段口径要想清楚。下面是我拆这类系统时最先看的几张表:
-- 员工主表:工资核算的对象 CREATE TABLE Employee ( EmpID INT IDENTITY(1,1) PRIMARY KEY, EmpNo VARCHAR(20) NOT NULL, -- 工号,业务唯一键 EmpName NVARCHAR(30) NOT NULL, DeptID INT NOT NULL, BaseSalary DECIMAL(12,2) DEFAULT 0, -- 基本工资 BankAccount VARCHAR(40), -- 工资卡号 Status TINYINT DEFAULT 1, -- 1 在职 0 离职 EntryDate DATETIME ); -- 工资项目定义表:决定工资条上有哪些列、怎么算 CREATE TABLE SalaryItem ( ItemID INT IDENTITY(1,1) PRIMARY KEY, ItemName NVARCHAR(50) NOT NULL, -- 基本工资、绩效、社保、个税 ItemType TINYINT NOT NULL DEFAULT 0, -- 0 固定项 1 公式项 2 扣款项 Formula NVARCHAR(200), -- 公式项才用,如 BaseSalary*0.08 SortOrder INT DEFAULT 0 ); -- 月度工资单主表:一个员工一个月一条 CREATE TABLE SalaryRecord ( RecordID INT IDENTITY(1,1) PRIMARY KEY, EmpID INT NOT NULL, SalaryYM CHAR(6) NOT NULL, -- 202406 这种格式,方便按月筛选 GrossPay DECIMAL(12,2) DEFAULT 0, -- 应发 Deduction DECIMAL(12,2) DEFAULT 0, -- 扣款合计 NetPay DECIMAL(12,2) DEFAULT 0, -- 实发 Status TINYINT DEFAULT 0, -- 0 草稿 1 已审核 2 已发放 CreateTime DATETIME DEFAULT GETDATE() );除此之外还有部门表Department、工资明细表SalaryDetail(一条 Record 对应多条明细,存每个工资项的金额)、考勤表Attendance和系统用户表SysUser。SalaryYM用CHAR(6)存年月是个很实用的老做法,比存日期再格式化查询省事;NetPay冗余存一份实发金额,是为了报表和工资条查询时不用每次把明细加一遍,核算完成时一次算好写进去。
金额字段一律用DECIMAL(12,2),不要用FLOAT。这是血泪经验,浮点在做个税和社保累加时会冒出0.30000000000000004这种值,工资条上多一分钱都解释不清。
3. 数据库连接与公共模块:conn.asp、鉴权和那几个绕不开的函数
3.1 conn.asp:全站唯一的数据库入口
所有页面都靠 include 拿到连接对象,所以这个文件必须写得足够稳:连接失败要给出可读提示,而不是把原始错误抛给浏览器;连接串集中一处,换服务器只改一行。
<% ' conn.asp —— 全站数据库连接,部署时只改这一段 Dim DBConn, ConnStr Set DBConn = Server.CreateObject("ADODB.Connection") ' Data Source 可写 IP、机器名\实例名;Initial Catalog 是库名 ConnStr = "Provider=SQLOLEDB;Data Source=127.0.0.1;Initial Catalog=PayRollDB;" & _ "User ID=sa;Password=改成你的密码;" On Error Resume Next DBConn.Open ConnStr If Err.Number <> 0 Then ' 生产环境只记日志,不把连接串和错误细节输出到页面 Response.Write "数据库连接失败,请联系管理员" Response.End End If On Error GoTo 0 %>参数说明:Provider是驱动,老系统用SQLOLEDB,新环境可换MSOLEDBSQL;Data Source写127.0.0.1或机器名\SQLEXPRESS这种实例名;Initial Catalog对应建好的库名;账号建议不要用sa,单独建一个只对业务库有db_datareader、db_datawriter权限的登录名。On Error Resume Next包住Open是必须的,否则连不上库时页面直接 500,什么线索都没有。
页面里用的时候,顶部一行 include 就够:
<!--#include file="../inc/conn.asp"-->注意路径是相对当前文件的,/admin/下的页面要写../inc/conn.asp。父路径没在 IIS 里打开时,这一行会直接让整页报 500,这是新手第一个坎。
3.2 登录会话与目录级鉴权
登录页做的事很朴素:查SysUser表比对账号密码,成功后把 UserID、UserName 写进 Session。密码字段在库里一般存 MD5,登录时把输入值算一遍再比对。
<% ' login.asp 核心校验片段 Dim rs, sql, userName, userPwd userName = Trim(Request.Form("username")) userPwd = Trim(Request.Form("password")) ' 这里必须做单引号转义,否则 ' or '1'='1 直接进后台 userName = Replace(userName, "'", "''") Set rs = Server.CreateObject("ADODB.Recordset") sql = "SELECT UserID, UserName, UserRole FROM SysUser " & _ "WHERE UserName='" & userName & "' AND UserPwd='" & MD5(userPwd) & "'" rs.Open sql, DBConn, 1, 1 If Not rs.EOF Then Session("UserID") = rs("UserID") Session("UserName") = rs("UserName") Session("UserRole") = rs("UserRole") Response.Redirect "admin/index.asp" Else Response.Write "账号或密码错误" End If rs.Close : Set rs = Nothing %>鉴权则单独写一个/inc/check.asp,后台每个页面第一行 include 它:
<% ' check.asp —— 未登录一律打回登录页,并带上回跳地址 If IsEmpty(Session("UserID")) Or Session("UserID") = "" Then Response.Redirect "../login.asp?backurl=" & Server.URLEncode(Request.ServerVariables("URL")) Response.End End If %>IsEmpty和=""两个判断都要写。Session 超时后取出来可能是 Empty 而不是空串,只判空串会漏。Response.End也必须加,否则 redirect 之后代码还会继续往下跑,遇到写库操作就是事故。
3.3 公共函数库:防注入、分页、金额格式化
老 ASP 项目里这三个函数使用频率最高,放在/inc/fun.asp里统一 include:
<% ' 1) 字符串安全处理:转义单引号,防止最基本的注入 Function SafeStr(s) If IsNull(s) Then SafeStr = "" : Exit Function SafeStr = Replace(Trim(s & ""), "'", "''") End Function ' 2) 金额格式化:页面输出统一两位小数,空值当 0 Function Money(v) If IsNull(v) Or v = "" Then Money = "0.00" Else Money = FormatNumber(v, 2, -1, 0, 0) End Function ' 3) 通用分页条:pageSize 每页条数,total 总条数,cur 当前页 Function PageBar(pageSize, total, cur, url) Dim pages, i, html pages = Int((total + pageSize - 1) / pageSize) If pages <= 1 Then PageBar = "" : Exit Function html = "<div class='page'>" For i = 1 To pages If i = cur Then html = html & "<span class='cur'>" & i & "</span>" Else html = html & "<a href='" & url & "page=" & i & "'>" & i & "</a>" End If Next PageBar = html & "</div>" End Function %>SafeStr只能挡住最粗暴的单引号注入,挡不住数字型参数拼接,真正的做法见最后一章。FormatNumber(v, 2, -1, 0, 0)五个参数依次是小数位、是否补零、是否用括号表示负数、是否显示千分位,工资条上要千分位就把倒数第二个改成 -1 或 0,看习惯。PageBar这种一次拼完返回字符串的写法,比在页面里 For 循环混着 HTML 输出好维护得多。
4. 工资核算核心链路:从工资项配置到实发金额落库
4.1 工资项目与计算规则怎么建模
工资系统里最容易把代码写死的地方,就是「基本工资 + 绩效 - 社保 - 个税」这条公式。一旦公司加个饭补、改个社保比例,就得改代码重发。所以合格的实现会把工资项做成数据:SalaryItem表一行一个项目,ItemType区分固定项、公式项和扣款项,Formula存表达式,核算时按SortOrder从小到大逐项算,前一项的结果可以给后一项引用。
配置的常见做法是在后台做一个工资项管理页,新增项目时选类型:
- 固定项:金额直接取自员工档案,比如基本工资取
Employee.BaseSalary。 - 公式项:形如
BaseSalary*0.08或GrossPay*0.03,字段名对应前面算出来的变量。 - 扣款项:社保、公积金、个税这类,核算时从应发里减掉。
这里要提醒一句,公式不要做成真正的表达式引擎。老项目里普遍用Eval或者干脆在代码里Select Case判断项目名,前者有注入风险,后者改一个项目就得动代码。折中的做法是限制公式只允许「变量名 + 四则运算 + 数字」,核算前用正则做个白名单校验,超出范围直接拒绝保存。
4.2 批量核算与事务控制
核算是一条链:先按SalaryYM清掉当月草稿数据,再循环在职员工,算应发、算扣款、算实发,写主表和明细表。只要中途有一条失败,整月数据就不能是半截的,所以必须包事务。
<% ' calc_salary.asp —— 按月批量核算,整月一个事务 Dim ym, rsEmp, gross, deduct, net, itemRs ym = Request.Form("salary_ym") ' 形如 202406 DBConn.BeginTrans On Error Resume Next ' 1) 清掉当月草稿(已审核、已发放的不动) DBConn.Execute "DELETE FROM SalaryDetail WHERE RecordID IN " & _ "(SELECT RecordID FROM SalaryRecord WHERE SalaryYM='" & ym & "' AND Status=0)" DBConn.Execute "DELETE FROM SalaryRecord WHERE SalaryYM='" & ym & "' AND Status=0" ' 2) 遍历在职员工 Set rsEmp = DBConn.Execute("SELECT EmpID, BaseSalary FROM Employee WHERE Status=1") Do While Not rsEmp.EOF gross = CDbl(rsEmp("BaseSalary")) ' 固定项先落,公式项在此基础上叠加 deduct = 0 ' 3) 按 SortOrder 逐项计算,公式项这里做简化处理 Set itemRs = DBConn.Execute("SELECT * FROM SalaryItem ORDER BY SortOrder") Do While Not itemRs.EOF Select Case itemRs("ItemType") Case 0 : gross = gross + CDbl(rsEmp("BaseSalary")) Case 1 : gross = gross + EvalFormula(itemRs("Formula"), rsEmp("BaseSalary")) Case 2 : deduct = deduct + EvalFormula(itemRs("Formula"), rsEmp("BaseSalary")) End Select itemRs.MoveNext Loop itemRs.Close net = gross - deduct ' 4) 主表 + 明细一次写入 DBConn.Execute "INSERT INTO SalaryRecord(EmpID,SalaryYM,GrossPay,Deduction,NetPay,Status) " & _ "VALUES(" & rsEmp("EmpID") & ",'" & ym & "'," & gross & "," & deduct & "," & net & ",0)" rsEmp.MoveNext Loop rsEmp.Close If Err.Number <> 0 Then DBConn.RollbackTrans Response.Write "核算失败:" & Err.Description Else DBConn.CommitTrans Response.Write "核算完成,共处理 " & DBConn.Execute("SELECT COUNT(*) FROM SalaryRecord WHERE SalaryYM='" & ym & "'")(0) & " 人" End If On Error GoTo 0 %>逻辑说明:BeginTrans到CommitTrans之间任何一步出错都Rollback,保证一个月的工资不会出现「前 30 人算完、第 31 人报错」的半成品。Status=0的过滤条件很关键,已经审核发放的月份不能被重算覆盖,这是财务口径的底线。EvalFormula是自己写的简单求值函数,只接受白名单变量,不做任意表达式执行。
参数和坑点:SalaryYM从表单来,拼进 SQL 前务必校验格式(6 位数字),否则就是注入点;CDbl转换前要判断字段是否为空,空值直接转会抛类型不匹配;人数一多,逐条Execute会慢,常见优化是把明细先在内存拼成批量 INSERT,几百人规模其实感知不明显。
4.3 工资条查询与导出 Excel
核算完,员工要能查自己的工资条,财务要能导出一张总表。查询按EmpID + SalaryYM走索引即可,导出则用 Response 直接吐 HTML 表格,改 ContentType 让浏览器认成 Excel:
<% ' export_salary.asp —— 导出当月工资总表 Dim ym, rs ym = Request.QueryString("ym") Response.Clear Response.ContentType = "application/vnd.ms-excel" Response.AddHeader "Content-Disposition", "attachment;filename=salary_" & ym & ".xls" Response.Charset = "GB2312" ' 不写这句,Excel 打开中文会乱码 Response.Write "<table border='1'><tr>" Response.Write "<td>工号</td><td>姓名</td><td>应发</td><td>扣款</td><td>实发</td></tr>" Set rs = DBConn.Execute("SELECT e.EmpNo,e.EmpName,s.GrossPay,s.Deduction,s.NetPay " & _ "FROM SalaryRecord s INNER JOIN Employee e ON s.EmpID=e.EmpID " & _ "WHERE s.SalaryYM='" & ym & "' ORDER BY e.EmpNo") Do While Not rs.EOF Response.Write "<tr><td>" & rs("EmpNo") & "</td><td>" & rs("EmpName") & "</td>" & _ "<td>" & Money(rs("GrossPay")) & "</td><td>" & Money(rs("Deduction")) & "</td>" & _ "<td>" & Money(rs("NetPay")) & "</td></tr>" rs.MoveNext Loop Response.Write "</table>" rs.Close Response.End %>这套导出方式在老 ASP 里几乎是标配,优点是零依赖,缺点是导出的本质是 HTML 伪装的 xls,Excel 打开可能弹「格式与扩展名不一致」的提示,工号列如果全是数字还会被显示成科学计数法。想避开就两招:工号在单元格里前面加个单引号,或者把 ContentType 换成application/vnd.ms-excel的同时在表头加<meta charset="gb2312">。真要生成规范的 xlsx,就得引第三方组件,老服务器上装组件又是一堆事,多数项目就忍了。
5. 部署避坑:IIS + SQL Server 环境下最容易翻车的几处
5.1 环境与页面报错类
现象:所有 .asp 页面打开都是 500 - Internal server error,没有任何细节。原因:IIS 默认关闭了 ASP 的详细错误,同时「启用父路径」没开,include 相对路径直接失败。 解决:IIS 管理器里进「ASP」→「编译」→「调试属性」把「将错误发送到浏览器」设为 True,先看到真实错误;再到「行为」→「启用父路径」设为 True。上线前记得把详细错误关回去,不然报错信息会把物理路径和 SQL 语句全暴露出来。
现象:页面能打开,但中文全是问号或方块。原因:三处编码不一致——数据库排序规则、页面<%@ Language="VBScript" CodePage="936"%>、Response.CharSet。老 ASP 默认按 936(GB2312/GBK)走,数据库却建成了SQL_Latin1_General,存取就对不上。 解决:建库时选Chinese_PRC_CI_AS,页面顶部声明 CodePage=936,输出前Response.CharSet="GB2312",三处统一。已经建错的库改排序规则很麻烦,宁可重建。
5.2 数据库连接与权限类
现象:报「SQL Server 不存在或访问被拒绝」或「未找到或无法访问服务器」。原因:SQL Server 的 TCP/IP 协议没启用、SQL Browser 服务没起(命名实例必须)、或者连接串里的实例名写错。 解决:SQL Server 配置管理器里把 TCP/IP 启用并重启服务;命名实例确认 SQL Browser 在跑;连接串的Data Source先试127.0.0.1,再试机器名\实例名;防火墙放行 1433(或对应实例端口)。
现象:本机能跑,部署到服务器上连库超时。原因:服务器上的 SQL Server 只允许 Windows 身份验证,而连接串用的是 SQL 账号。 解决:实例属性里改成「SQL Server 和 Windows 身份验证模式」,重启服务,然后在安全性里建一个只对业务库有读写权限的登录名,别图省事用 sa。
5.3 数据口径与金额精度类
现象:工资条上实发金额和财务手工算的差几分钱。原因:金额字段建成了FLOAT,或者中途用CSng/CDbl转换后累加,浮点误差被放大;也有的是四舍五入时机不对,每项都四舍五入再相加,和先加后舍结果不同。 解决:字段统一DECIMAL(12,2);核算过程用CDbl保留中间精度,只在写入主表前统一Round(net, 2);个税、社保这类有明确规则的项,舍入规则写死在代码注释里,别让每个人各算各的。
现象:导出 Excel 后,工号列变成 1.23E+08,日期列变成 1900-01-01。原因:HTML 表格导出时 Excel 会按内容自动推断类型,纯数字和日期格式字符串会被误判。 解决:工号、身份证号这类「看起来是数字但不是数」的字段,输出时前面拼一个单引号或 ;日期统一格式化成yyyy-mm-dd字符串再输出。
现象:重算某个月工资后,已发放的记录也被改了。原因:核算语句没带Status条件,或者带了但状态值定义混乱。 解决:核算、删除、导出三个动作全部按Status过滤,草稿才允许重算;审核和发放做成两个独立按钮,状态只能单向流转,别给「一键改回草稿」的口子。
6. 二次开发:把这套老系统改成能长期维护的样子
拿到这套源码,最忌讳的是上来就加功能。先做三件收敛的事,后面改起来会顺很多。
第一件是把字符串拼接的 SQL 逐步换成参数化。经典 ASP 通过 ADO 的Command对象支持参数,改造不需要动数据库:
<% ' 用参数化查询替代拼接,防注入也免了转义 Dim cmd, prm Set cmd = Server.CreateObject("ADODB.Command") cmd.ActiveConnection = DBConn cmd.CommandText = "SELECT EmpID, EmpName FROM Employee WHERE DeptID=? AND Status=1" cmd.Parameters.Append cmd.CreateParameter("dept", 3, 1, 4, deptId) ' 3=Integer,1=Input Set rs = cmd.Execute %>CreateParameter五个参数依次是名称、类型、方向、长度、值;adInteger对应 3,字符串用 200(adVarWChar)并把长度给足。改造顺序建议从登录、查询、删除这三类入口开始,它们最容易被拿来试注入。
第二件是把工资公式从代码里挪出来,做一张配置表加一个受控求值函数,变量名白名单固定为BaseSalary、GrossPay、AttendanceDays这几个,公式里出现其它字符一律拒绝保存。这样以后加补贴、调比例,运营自己在后台配,不用每次找你改代码。
第三件是给数据库加一个按月备份的作业,工资数据这种东西,没有后悔药。备份文件放站点目录之外,IIS 用户组无权访问。
改造完怎么验证?我一般跑一张清单:
| 验证项 | 方法 | 通过标准 |
|---|---|---|
| 注入防护 | 登录框输入' or '1'='1 | 提示密码错误,不进后台 |
| 未登录访问 | 直接访问/admin/emp_list.asp | 跳回登录页 |
| 金额精度 | 造 10 个带小数的工资项核算 | 实发与手工计算结果一分不差 |
| 事务完整性 | 核算中途手动改错一个字段类型 | 整月数据回滚,无残留 |
| 权限边界 | 普通账号访问用户管理页 | 拒绝并记录 |
最后说个习惯。这套系统我第一次部署时,图省事没开详细错误,页面 500 查了整整一下午,最后发现只是父路径没勾。从那以后我每次拿到这种 ASP 老项目,都强制先把 IIS 详细错误打开、conn.asp 单独跑一遍、登录和鉴权各点一遍,再谈改代码。希望帮到你。
本文还有配套的精品资源,点击获取