ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MFC+Access考勤管理系统:从课程设计到工程实践

2026/9/18 8:52:46 拓冰建站 浏览量
MFC+Access考勤管理系统:从课程设计到工程实践 简介这是一份软件工程课程设计报告主题为员工考勤管理系统的分析与设计适合软件工程、管理信息系统方向的学生作为课程设计或毕业设计的参考资料。报告基于Visual C的MFC与Microsoft Access 2000内容涵盖可行性分析、需求分析、总体设计、数据库结构设计等环节详细描述了员工信息管理、考勤记录查询与出勤分析等功能模块并给出了员工信息表、职工考勤表等表结构及系统功能模块图有助于理解考勤系统的完整开发流程。资源包内仅有1个PDF文件大小约581KB内容为报告全文便于阅读与打印。该资源已有94人学习对于正在完成类似课设任务或希望快速掌握MFCAccess开发思路的学习者具有较好的参考价值。1. 一份考勤管理系统的课程设计PDF藏着MFCAccess的完整套路前阵子整理旧资料时翻出一份《员工考勤管理系统分析与设计.pdf》页脚写着课程设计、作者周勇、指导老师卢曼莎。单看题目像是CS专业里再普通不过的软件工程课设但顺着正文读下去会发现它其实串起了一条很完整的桌面应用开发链路可行性分析、需求分析、Access 2000 数据表设计、VC 6.0 的 MFC AppWizard 生成 SDI 工程、ODBC 绑定数据库、以及签到/签离/迟到早退这些具体业务逻辑的实现。现在做考勤系统大家第一反应是 Web 或 SaaS几乎不会有人再碰 MFC 和 Access但把这样一个“老”项目拆开看反而能把数据表设计、界面框架、业务逻辑和数据库访问之间的边界看得更清楚。这篇就按课程设计文档的顺序把它从需求到实现完整复现一遍并补上当年文档里没写清楚的建表语句、关键代码和现在重做时最容易踩的坑。2. 需求分析与可行性研究考勤系统先划边界再写代码课设文档把可行性分析放在需求分析之前这和软件工程过程的基本顺序一致先判断有没有必要做、能不能做再去细化功能边界。这里直接拆成三部分看功能需求、性能与可靠性需求、可行性三视角分析。2.1 功能需求编号、姓名、部门、签到时间、签离时间是主数据线从文档的“系统综合要求”和两张核心表可以看出系统必须完成的功能并不复杂登录验证、员工信息维护、考勤登记、考勤记录查询、迟到早退标记。员工信息表需要的字段是职工编号、职工性别、职工姓名、职工年龄、职业爱好、所在部门职工考勤表需要的字段是职工编号、职工姓名、职工部门、签到时间、签离时间、迟到否、早退否。用表格归纳如下功能模块涉及字段说明登录验证账号/密码对话框文档中只有一个登录界面对话框没有细说账号表员工信息维护职工编号、姓名、性别、年龄、爱好、部门增删改查均围绕员工信息表考勤登记签到时间、签离时间、迟到否、早退否写入职工考勤表考勤查询职工姓名、签到时间、签离时间等文档给出的是按姓名顺序查找迟到/早退标记迟到否、早退否用文本“是/否”标记在需求阶段可以把验收场景写成类似 Gherkin 的描述方便后续对照测试# attendance.feature Feature: 员工签到登记 Scenario: 员工正常签到 Given 系统已通过 ODBC 连接“考勤管理系统数据库” When 考勤人员触发签到操作 Then 系统读取当前时间写入签到时间字段 And 若当前时间晚于规定上班时间迟到标记置为“是”这里用 Gherkin 只是为了对齐语义并不是说 MFC 工程要跑 BDD。需求阶段最重要的产出是字段级约定比如“迟到否”到底是布尔型还是文本型、签到时间用 DATETIME 还是字符串这两点不敲定后面表结构和代码都会返工。2.2 性能、可靠性与出错处理需求单机版也要关心数据一致课设文档写的是“Windows XP、VC、Microsoft Access 2000”说明这是一个单机或局域网共享文件模式的桌面系统。性能需求方面课程设计级别不需要压测指标但从表结构可以推断出数据规模员工几百人、考勤记录按天累积。容量约束主要是 Access 2000 单文件上限 2GB对课设来说完全够用。可靠性需求主要体现在数据库文件的备份和恢复上。Access 是文件型数据库直接复制.mdb文件就能备份但代价是并发写入能力弱。如果多个考勤人员同时操作Jet 引擎会出现锁冲突所以文档里没有把“多用户同时写入”作为目标。出错处理方面原文档的流程图里已经出现了“已经登记了”的判断说明系统需要考虑重复签到的情况此外还要处理“对不起无此人”这类查询失败提示。更严谨的做法是给职工考勤表增加“职工编号 日期”复合唯一索引防止同一个人同一天被插入多条签到记录但原文档在表结构里没有体现这一点。2.3 可行性三视角为什么是 VC MFC Access 2000从经济可行性看当时 Windows 和 Office 是企业标配VC 6.0 和 Access 2000 是现成工具开发与部署成本都很低。从技术可行性看MFC 的 CRecordset 封装了 ODBC开发者不需要手写大量数据库访问代码Access 2000 的 Jet 引擎支持 SQL 和索引足够支撑考勤这种轻量级业务。从法律可行性看项目使用的是有授权的开发工具链即可避免把未经授权的软件带入交付物。这个选型放到今天看并不先进但它回答了一个关键问题为什么是“SDI 而不是 Dialog”因为 SDI 自带 Document/View 架构文档类适合持有 CRecordset视图类负责表单展示。考勤系统既有员工信息对话框又有出勤状况对话框在 SDI 里切换和扩展都比把全部控件堆在单个对话框里更清晰。理解了这一层后面看 MFC AppWizard 的配置步骤就不会觉得只是机械点下一步。3. Access 2000 考勤库设计两张表的字段、索引与 DDL考勤管理系统的核心是表结构。原文档用表格手工画了“员工信息表”和“职工考勤表”但没有给出完整的 CREATE TABLE 语句。为了能在一台干净的 Windows 环境里复现这里按 Access 2000 / Jet SQL 把两张表补全并说明字段类型和约束的取舍。3.1 员工信息表长整型主键与必填文本字段原文档中“员工信息数据表”的结构如下字段名称数据类型字段大小索引/约束必填职工编号数字长整型主键/唯一是职工性别文本2-是职工姓名文本20-是职工年龄数字长整型-是职业爱好文本30-否所在部门文本30-是职工编号用长整型数字做主键是因为员工编号是自然编号而且 Access 自增字段在 ODBC 写入时有时需要特殊处理用外部传入的长整型更直接。字符串字段按文档给的大小就行但注意 Access 2000 的TEXT(20)在 Jet 里表示 20 个字符宽度中文不会按 2 个字节截断。实际通过 ODBC 读写时字符集和字段长度在极端字符环境下可能产生空格补齐的问题所以姓名给 20 个字符宽度是合理的部门建议给到 30。3.2 职工考勤表DATETIME 字段与文本“是/否”的取舍职工考勤表重点看“迟到否”和“早退否”两个字段。文档把它们定义为文本类型而不是 Access 的“是/否”布尔类型。原因是 VC 6.0 的 CRecordset 通过 ODBC 读取 Jet 布尔字段时返回的是BOOL在某些 ODBC 驱动下会得到 -1 或 0和 MFC 的BOOL混用容易出问题。写成TEXT(10)字段后MFC 侧直接映射为CString判断时用 是即可代码直观且不容易踩类型坑。这是一种“牺牲规范换稳定”的做法对于课程设计这种规模完全值得。缺点是统计迟到次数时要对字符串做比较不能直接用 SQL 的SUM。3.3 用 Jet SQL 在 Access 中执行建表在 Access 2000 的查询窗口里可以直接执行以下 DDLCREATE TABLE [员工信息] ( [职工编号] LONG NOT NULL PRIMARY KEY, [职工姓名] TEXT(20) NOT NULL, [职工性别] TEXT(2) NOT NULL, [职工年龄] LONG NULL, [职业爱好] TEXT(50) NULL, [所在部门] TEXT(30) NOT NULL ); CREATE TABLE [职工考勤表] ( [自动编号] COUNTER PRIMARY KEY, [职工编号] LONG NOT NULL, [职工姓名] TEXT(20) NOT NULL, [职工部门] TEXT(30) NOT NULL, [签到时间] DATETIME NULL, [签离时间] DATETIME NULL, [迟到否] TEXT(10) NULL, [早退否] TEXT(10) NULL );这里比原文档多增加了一个[自动编号]字段因为同一职工会有多条考勤记录单独用职工编号做主键会造成主键冲突。COUNTER是 Jet SQL 的自增类型适合作为考勤表的物理主键。如果想让“同一员工同一天只能有一条签到记录”可以再创建唯一索引CREATE UNIQUE INDEX IX_Attend_Unique ON [职工考勤表] ([职工编号], [签到时间]);不过原文档没有做这一步实际实现时靠代码先判断“是否已经登记”来控制二选一即可。注意 Jet SQL 中中文表名和字段名最好用方括号括起来避免被当成保留字。4. MFC SDI ODBC搭建单文档考勤程序框架建好数据库后下一步是在 VC 6.0 里通过 MFC AppWizard 生成 SDI 工程。原文档的“程序概要设计”已经写得很清楚单文档界面SDI封面资源是IDD_KQGLSYS_FORMODBC 数据源名为“考勤管理系统数据库”绑定表为“职工考勤表”。这一章把配置步骤和关键生成文件说明完整。4.1 为什么选 SDI 而不是基于对话框很多人做课设时第一步就会选“基于对话框”因为界面布局所见即所得代码量少。但对于考勤系统SDI 的 Document/View 架构更合适。文档类CKQGLSYSDoc持有CRecordset派生对象视图类CKQGLSYSView从CRecordView继承直接把数据库字段映射到对话框控件。这样员工信息对话框和出勤状况对话框可以共存于同一个框架配合菜单和工具栏切换。MFC AppWizard 生成 SDI 工程时的默认类名如下类文件名基类CKQGLSYSAppKQGLSYS.h / KQGLSYS.cppCWinAppCMainFrameMainFrm.h / MainFrm.cppCFrameWndCKQGLSYSDocKQGLSYSDoc.h / KQGLSYSDoc.cppCDocumentCKQGLSYSViewKQGLSYSView.h / KQGLSYSView.cppCRecordViewCKQGLSYSSetKQGLSYSSet.h / KQGLSYSSet.cppCRecordset这套结构对后来维护者来说非常清晰看到CKQGLSYSSet就知道它对应数据库表看到CKQGLSYSView就知道界面在这里。4.2 MFC AppWizard 的关键配置复现步骤如下打开 VC 6.0选择“文件 / 新建 / MFC AppWizard(EXE)”工程名输入KQGLSYS。在 Step 1 选择“单个文档”资源语言选择“中文[中国]”。在 Step 2 选择“数据库查看不使用文件支持”然后点击Data Source按钮。在Database Options里选择 ODBC数据源名选择“考勤管理系统数据库”记录集类型选Snapshot。选择要绑定的表[职工考勤表]点击 OK完成向导。这里的核心是第 3 步。所谓“不使用文件支持”是指 MFC 不需要CArchive序列化因为数据全部存在 Access 数据库里。如果选错了向导会生成一堆Serialize相关代码反而干扰数据库视图的使用。4.3 CRecordset 派生类与连接字符串向导会自动生成CKQGLSYSSet但我们需要把关键成员变量补上并按原文档的命名习惯对应字段// KQGLSYSSet.h class CKQGLSYSSet : public CRecordset { public: CKQGLSYSSet(CDatabase* pDatabase NULL); virtual CString GetDefaultConnect(); virtual CString GetDefaultSQL(); long m_ZGBH; // 职工编号 CString m_ZGXM; // 职工姓名 CString m_ZGBM; // 职工部门 CString m_QDTIME; // 签到时间 CString m_QLTIME; // 签离时间 CString m_CDF; // 迟到否 CString m_ZTF; // 早退否 };// KQGLSYSSet.cpp CString CKQGLSYSSet::GetDefaultConnect() { return _T(ODBC;DSN考勤管理系统数据库;); } CString CKQGLSYSSet::GetDefaultSQL() { return _T([职工考勤表]); }GetDefaultConnect返回的是 ODBC 连接字符串DSN名称必须和控制面板里配置的用户数据源一致。如果系统是 64 位 Windows而 VC 6.0 编译出来的是 32 位程序要使用 32 位 ODBC 管理器来创建 DSN否则会报找不到数据源。GetDefaultSQL返回表名用方括号包住中文表名能避免解析歧义。在视图的OnInitialUpdate中把记录集与视图关联void CKQGLSYSView::OnInitialUpdate() { m_pSet GetDocument()-m_KQGLSYSSet; CRecordView::OnInitialUpdate(); }这样视图上的控件就自动绑定到m_pSet对应的字段MFC 的DDX_FieldText机制会把控件与记录字段关联起来。原文中提到的IDD_KQGLSYS_FORM就是这个视图使用的对话框资源模板。5. 签到、签离与姓名查询核心流程的 C 实现框架搭好后真正体现考勤逻辑的是签到时间写入、迟到早退判定和按姓名查询这三段代码。原文档用流程图描述了过程这里直接转成可编译的 MFC 代码并说明边界条件。5.1 用 CTime::GetCurrentTime 生成签到时间签到操作的核心是获取当前系统时间写入记录集并和上班时间比较void CKQGLSYSView::OnSignIn() { CTime now CTime::GetCurrentTime(); CString strNow now.Format(_T(%H:%M:%S)); CString strDateTime now.Format(_T(%Y-%m-%d %H:%M:%S)); m_pSet-m_QDTIME strDateTime; // 假设上班时间为 09:00:00 if (strNow _T(09:00:00)) m_pSet-m_CDF _T(是); else m_pSet-m_CDF _T(否); m_pSet-Update(); UpdateData(FALSE); }CTime::GetCurrentTime()返回的是本地系统时间不需要自己处理时区。Format把时间转成字符串%H:%M:%S是 24 小时制的时分秒。这里直接拿字符串和09:00:00比较因为固定宽度且顺序就是字典序在 24 小时制下是可靠的。如果业务涉及跨天倒班这种比较就会出错需要先解析成CTime再比较时间戳但对于课设的固定白班场景这种简化可以接受。5.2 签离时间与早退判定签离逻辑和签到对称void CKQGLSYSView::OnSignOut() { CTime now CTime::GetCurrentTime(); CString strNow now.Format(_T(%H:%M:%S)); CString strDateTime now.Format(_T(%Y-%m-%d %H:%M:%S)); m_pSet-m_QLTIME strDateTime; // 假设下班时间为 18:00:00 if (strNow _T(18:00:00)) m_pSet-m_ZTF _T(是); else m_pSet-m_ZTF _T(否); m_pSet-Update(); UpdateData(FALSE); }这里有一个容易被忽略的问题签离时可能还没有定位到正确的记录。如果用户先查出一名员工再点签离当前记录就是该员工当天的考勤记录如果用户直接新建记录那 m_pSet 的位置可能不对。常见做法是先把“职工编号 日期”作为筛选条件定位到记录再执行签离更新。原文档没有详细写这一步但实际调试中很容易出现“签离写到了别的员工名下”。5.3 按姓名顺序查找记录与 EOF 处理原文档流程图里最经典的就是这段顺序查找逻辑输入姓名从首记录开始移动游标逐条比较直到 EOF 结束。void CKQGLSYSView::OnQuery() { CString strName; m_editName.GetWindowText(strName); m_pSet-MoveFirst(); BOOL bFound FALSE; while (!m_pSet-IsEOF()) { if (m_pSet-m_ZGXM strName) { bFound TRUE; break; } m_pSet-MoveNext(); } if (!bFound) { AfxMessageBox(_T(对不起无此人)); return; } m_QTIME m_pSet-m_QDTIME; m_QLTIME m_pSet-m_QLTIME; m_CDF m_pSet-m_CDF; m_ZTF m_pSet-m_ZTF; UpdateData(FALSE); }MoveFirst把游标移到结果集第一条IsEOF()指示是否越过了最后一条记录。每次MoveNext后判断 EOF可以避免在最后一条记录上再取字段值导致异常。查到目标记录后把记录集字段赋值给视图成员变量UpdateData(FALSE)刷新界面。顺序查找的时间复杂度是 O(n)员工人数达到几万时会有明显卡顿所以下一章会改为参数化查询。状态字段的对应关系如下MFC 成员变量数据库字段取值m_QDTIME签到时间yyyy-MM-dd HH:mm:ssm_QLTIME签离时间yyyy-MM-dd HH:mm:ssm_CDF迟到否“是”或“否”m_ZTF早退否“是”或“否”6. 交完课设之后把旧考勤系统改造成能继续用的样子原文档的结尾做了面向过程和面向对象设计的比较这部分理论没有问题但没有给出实际优化方向。这里补三个在维护和重做这个系统时最值得做的事。6.1 32 位 DSN 和 64 位 Windows 的坑VC 6.0 编译出来的 MFC 程序是 32 位的。在 64 位 Windows 10/11 上控制面板看到的“ODBC 数据源管理器(64 位)”不会对 32 位程序生效。必须用C:\Windows\SysWOW64\odbcad32.exe打开 32 位版本创建用户 DSN“考勤管理系统数据库”。否则CRecordset打开时会报“找不到数据源名称且没有指定默认驱动程序”。6.2 用 ADO 替代 ODBC去掉 DSN 依赖如果不想每台机器都手工配 DSN可以用 ADO 直接连接 Access 文件_ConnectionPtr pConn; pConn.CreateInstance(__uuidof(Connection)); pConn-Open( _T(ProviderMicrosoft.Jet.OLEDB.4.0;Data SourceC:\\data\\考勤管理系统.mdb;), _T(), _T(), adConnectUnspecified);Jet 4.0 OLEDB 驱动可以直接打开 Access 2000 格式的.mdb文件不需要 ODBC DSN部署时只要保证目标机器有对应驱动。注意 Access 2000 文件在 64 位 Office 环境里可能打不开如果只是查看数据可以先用新版 Access 另存为 Access 2003 或.accdb格式再换用ACE.OLEDB驱动连接。6.3 参数化查询代替 MoveNext 循环姓名查询如果改成 SQL 过滤代码更简洁且性能更好m_pSet-m_strFilter _T([职工姓名] ?); m_pSet-m_paramName strName; m_pSet-Requery();m_strFilter是CRecordset提供的过滤条件问号是参数占位符m_paramName需要在记录集类里定义为与字段类型匹配的成员变量并在DoFieldExchange中通过RFX_Text绑定。Requery会重新执行查询让 Jet 引擎在数据库端完成筛选返回的结果集只包含匹配记录查询速度比 MoveNext 循环快一个量级。如果要在课设基础上增加月报统计可以在 Access 里建一个按职工编号分组的查询统计迟到次数和早退次数SELECT 职工编号, SUM(IIF(迟到否 是, 1, 0)) AS 迟到次数 FROM 职工考勤表 GROUP BY 职工编号;再把该查询作为记录集数据源绑定到CRecordset就能复用现有 MFC 视图不需要额外写列表控件。本文还有配套的精品资源点击获取