【人力资源管理系统】C#实现
软件项目实训报告2025版
- 第1部分 实训的目标与主要内容
- 1.1 实训目标
- 1.2 实训主要内容
- 第2部分 最终作品的完成情况
- 2.1 课堂上要求的功能完成情况
- 2.1.1 完成的功能及完成情况分析
- 1. 建立数据库
- 2. 登录界面
- 3. MDI主窗体显示
- 4. 登录日志功能
- 5.员工列表查询功能
- 2.2 作业中要求的功能完成情况
- 1. 新用户注册
- 2. 密码修改
- 3. 部门管理
- 4.操作员管理
- 5. 查询工资单
- 6.员工管理模块
- 6.1测试添加员工模块
- 6.2删除员工功能
- 6.3测试入职时间搜索功能
- 6.4导出员工功能
- 7. 生成工资单
- 8.查询工资单
- 9. 操作员管理
- 10.薪资单报表打印
- 第3部分 实训的主要收获
- 3.1 简单程序设计与软件系统开发区别
- 3.2 系统开发中的分层架构体会
- 3.3 代码的规范重要性
- 3.4 其他主要收获
- 第4部分 实训中遇到的困难及其解决方法
- 第5部分 通过实训发现自己的不足及其对策
- 第6部分 对实训课的建议
- 源码
第1部分 实训的目标与主要内容
1.1 实训目标
- 目标1:熟练掌握一种集成开发环境,并能够运用各类技术、资源解决软件开发中的问题。
- 目标2:在明确软件功能需求下,能够根据两层/三层架构设计特定的软件模块。
- 目标3:理解复杂软件系统的分层架构思想,能够运用两层/三层架构设计软件。
- 目标4:了解复杂软件系统,通过设计实现不同的组件、模块及其关系,完成整个软件的开发,并体现创新意识。
- 目标5:能够撰写报告,清晰表达软件开发中的问题和方案。
1.2 实训主要内容
完成HR人力资源管理系统的主要功能,包括:
- 登录模块
- 用户注册模块
- 员工信息管理模块
- 薪资管理模块
- 工资单生成模块
- 员工数据导出模块
该系统的设计目标是通过实践掌握如何运用分层架构,开发出具有基本管理功能的系统,同时加强对软件开发中常用工具和技术的掌握,如数据库连接和管理、窗体设计、业务逻辑层与数据访问层的交互等。
第2部分 最终作品的完成情况
2.1 课堂上要求的功能完成情况
2.1.1 完成的功能及完成情况分析
1. 建立数据库
- 在自己的电脑本地新建数据库:HRMDB,运行老师提供的脚本(提供在文末),将本项目所需的相关数据库表及数据导入到本地HRMDB数据库中,新建一个查询,并输入以下SQL语句。
SELECT * FROM Operator
SELECT COUNT(*) FROM Operator WHERE UserName = 'admin' AND Password = '202cb962ac59075b964b07152d234b70'
也可以在Operator表
中插入数据
INSERT INTO Operator(Id, UserName, Password, IsDeleted, RealName, IsLocked, IsAdmin) VALUES(NEWID(),
'rhye', '698d51a19d8a121ce581499d7b701668', 0, N'叶荣华', 0, 0)
- 默认情况下,MS SQL使用Windows身份登录数据库,这样权限较高,不利于数据库管理员管理,因此创建了数据库用户并设置了登录名和密码,赋予了相应的操作数据库权限。
CREATE LOGIN [hrmtest] WITH PASSWORD = 'test'
- 在数据库HRMDB中为该登录名(hrmtest)创建一个用户(hrmtest),并使用exec指令授权该用户连接数据库(HRMDB)。
CREATE USER [hrmtest]WITH DEFAULT_SCHEMA = dbo
GO
GRANT CONNECT TO [hrmtest]
//赋予用户操作数据库权限
exec sp_addrolemember 'db_owner', 'hrmtest'
- 断开Windows身份验证登录数据库,改为使用新建的用户名及密码通过SQL Server身份验证进行登录。
2. 登录界面
提高登录安全性:为了提高用户输入密码时的安全性,防止密码泄露,我们可以更改密码输入框的属性,找到PasswordChar,将默认值改为*。
-
登录窗体: 启动软件后,首先进入“系统登录”界面,等待用户输入用户名和密码。
-
如果用户名与密码与数据库HRMDB中的Operator表中的数据匹配,则显示“登录成功”的文本框,并进入系统主窗体界面。
-
如果用户名与密码与数据库HRMDB中的Operator表中的数据没有匹配项,则显示“登录失败”的文本框,提示错误信息,直接终止该进程。
实现思路:
- 数据合法性验证模块:
- 从
textBoxUserName
和textBoxPassword
文本框控件中读取用户输入的用户名和密码,使用Trim
方法去除首尾空格,使用un
和pwd
两个字符串类型的变量保存。 - 使用
IsNullOrEmpty()
方法检查用户名或密码是否为空,如果为空,显示“登录失败”的文本框,并通过return
语句结束当前按钮点击事件,终止后续操作。
- MD5加密模块:
- 在设计数据库时,为了提升数据库整体安全性,数据库表中的密码字段需要使用MD5算法加密。在登录窗体使用用户名与密码进行登录认证时,需要先将用户在文本框控件中输入的明文密码使用
CommonHelper.GetMD5()
方法转换为密文后,再进行数据库查询。
- 构造SQL查询语句:
- 采用参数传入的方式,防止SQL注入攻击。
完成情况分析:
- 完成情况良好,成功实现了基于用户输入的用户名和密码验证的登录功能,使用了MD5加密算法提升了数据库存储数据的安全性。同时,避免了SQL注入攻击,保证了系统的安全性。
3. MDI主窗体显示
- 窗体速览:
- 当用户在登录窗体中正确输入用户名和密码后,系统会跳转到主窗体。主窗体显示底部的状态栏(显示登录用户真实姓名、登录时间等信息)和顶部的标签栏(提供各种功能:薪资管理、员工管理、管理员功能等选项卡)。
- MDI窗体的好处在于,在一个父窗体中可以同时打开多个子窗体。
实现思路:
- 子窗体与父窗体绑定:
- 用户点击标签栏中的某个按钮后,系统会加载子窗体,并实例化子窗体对象。为了让子窗体能够在父窗体中正确显示,需要设置其
MdiParent
为父窗体对象(用this
指代),然后调用子窗体的Show
方法来显示子窗体。
private void tsmiModifyPassword_Click(object sender, EventArgs e){FormModifyPassword fmp = new FormModifyPassword();fmp.MdiParent = this;fmp.Show();}
- 设置单例类
LoginUserInfo
,用于在不同的窗体之间传递信息:
-
户通过用户名和密码成功登录系统后,会实例化一个单例类
LoginUserInfo
,用于后续在不同窗体中访问和获取登录用户的相关信息,如RealName
、IsAdmin
等。 -
通过该单例类,确保系统中只有一个
LoginUserInfo
实例,可以避免不同窗体之间的数据不一致问题。
// 1. 在模型层添加单例类LoginUserInfo
private LoginUserInfo(){ }
单例类LoginUserInfo的构造函数设置为private私有属性,防止在别处通过使用new关键字实例化该单例类。在该单例类内部的代码可以调用这个构造函数,确保只创建⼀个实例。
// 2. 静态实例变量
private static LoginUserInfo? inst = null;
- 静态变量
inst
用于存储单例类LoginUserInfo
的唯一实例。其在整个生命周期只会存在一个变量,初始时没有创建实例,给初始值为null
。
// 3. 静态实例获取⽅法:
public static LoginUserInfo GetInstance()
{if(inst == null){inst = new LoginUserInfo();}return inst;
}
完成情况分析:
- 完成情况良好,成功实现了MDI窗体作为父容器,与子窗体之间的绑定关系。通过
LoginUserInfo
单例类,确保了不同窗体间共享相同的用户信息,避免数据不一致问题。
4. 登录日志功能
- 窗体速览:
-
需要实现:
添加日志、查询全部日志等功能,通过设置代码逻辑实现了跳转“上一页”、“下一页”、“首页”、“尾页”等分页功能。
private void FormLogQuery_Load(object sender, EventArgs e)
{dataGridViewLoglist.DataSource = logServ.GetOperationLogsList(1, 10);dataGridViewLoglist.ReadOnly = true;dataGridViewLoglist.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;comboBoxNum.Items.Add("10");comboBoxNum.Items.Add("20");comboBoxNum.Items.Add("50");comboBoxNum.SelectedIndex = 0;totalPages = logServ.GetPageNum(10);labelTot.Text = $"共{totalPages}页";labelCur.Text = "第 1 页";}private void comboBoxNum_SelectedIndexChanged(object sender, EventArgs e)
{pageSize = int.Parse(comboBoxNum.SelectedItem.ToString());dataGridViewLoglist.DataSource = logServ.GetOperationLogsList(1, pageSize);labelCur.Text = "第 1 页";totalPages = logServ.GetPageNum(pageSize);labelTot.Text = $"共{totalPages}页";linkLabelPrevious.Enabled = false;
}private void linkLabelFirst_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{pageNo = 1;linkLabelPrevious.Enabled = false;linkLabelNext.Enabled = true;labelCur.Text = $"第{pageNo} 页";dataGridViewLoglist.DataSource = logServ.GetOperationLogsList(pageNo, pageSize);
}private void linkLabelPrevious_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{pageNo--;if (pageNo == 1){pageNo = 1;linkLabelPrevious.Enabled = false;}linkLabelNext.Enabled = true;labelCur.Text = $"第{pageNo} 页";dataGridViewLoglist.DataSource = logServ.GetOperationLogsList(pageNo, pageSize);
}private void linkLabelNext_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{pageNo++;if (pageNo == totalPages){linkLabelNext.Enabled = false;pageNo = totalPages;}linkLabelPrevious.Enabled = true;labelCur.Text = $"第{pageNo} 页";dataGridViewLoglist.DataSource = logServ.GetOperationLogsList(pageNo, pageSize);
}private void linkLabelLast_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{pageNo = logServ.GetPageNum(pageSize);linkLabelNext.Enabled = false;linkLabelPrevious.Enabled= true;labelCur.Text = $"第{pageNo} 页";dataGridViewLoglist.DataSource = logServ.GetOperationLogsList(pageNo, pageSize);
}private void buttonJump_Click(object sender, EventArgs e)
{if(int.Parse(textBoxJump.Text) <= totalPages && int.Parse(textBoxJump.Text) > 0){pageNo = int.Parse(textBoxJump.Text);linkLabelPrevious.Enabled = true;linkLabelNext.Enabled = true;if (pageNo == 1){linkLabelPrevious.Enabled = false;}if(pageNo == totalPages){linkLabelNext.Enabled = false;}labelCur.Text = $"第{pageNo} 页";dataGridViewLoglist.DataSource = logServ.GetOperationLogsList(pageNo, pageSize);}else{CommonHelper.ShowErrorMessage("输入页数不合法!!");textBoxJump.Text = "";}
}
- 如何实现同一功能的两层架构
该功能可以拆分为两个独立的模块:数据层和页面层。数据层负责访问数据库,获取所需的数据;页面层则根据数据库返回的结果展示操作记录列表,并处理交互逻辑。业务逻辑可以适当地整合到界面层,使其能够高效地调用数据层,实现功能的完整性。 - 如何实现按钮的合法性控制
除了通过将 enable 设为 false 使按钮不可用外,还需特别关注 Last(最后一页) 和 First(第一页) 按钮的状态。当用户位于第一页时,应禁用“上一页(Prev)”按钮,而在最后一页时,则应禁用“下一页(Next)”按钮。在跳转页面时,需先将所有按钮设置为可用(true),然后根据当前页码的情况动态调整相应控件的可用性。这样可以确保翻页功能的逻辑清晰,并避免用户操作不当导致错误。
实现思路:
- 模型层
OperationLog
类:
定义了操作日志的基本属性,与数据库HRMDB中的OperationLog
表中的字段相对应。
public class Operator{public Guid Id { get; set; }public string? UserName { get; set; }public string? Password { get; set; }public bool IsDeleted {get; set;}public string? RealName { get; set; }public bool IsLocked { get; set; }public bool IsAdmin { get; set; }}
-
数据层
OperationLogService
类: -
核心方法
AddOperationLog(OperationLog log) :插入单条日志记录到数据库。
GetOperationLogList(int pageIndex, int pageSize):分页查询日志数据,返回DataTable
GetLogCount():获取日志总条数,用于分页计算
public class OperationLogService{private string connStr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;public bool AddOperationLog(OperationLog log){string sql = "INSERT INTO OperationLog VALUES(@Id, @OperatorId, @ActionDate, @ActionDesc)";SqlParameter[] parameters = new SqlParameter[]{new SqlParameter("@Id",log.Id),new SqlParameter("@OperatorId",log.OperatorId),new SqlParameter("@ActionDate",log.ActionDate),new SqlParameter("@ActionDesc",log.ActionDesc)};return SqlHelper.ExecuteNonQuery(sql,parameters) > 0;}//无参构造public DataTable GetOperationLogsList(){string sql = "SELECT OperationLog.Id AS Id, RealName AS 操作员, ActionDate AS 操作时间, ActionDesc AS 操作描述 FROM OperationLog, Operator WHERE OperationLog.OperatorId = Operator.Id";return SqlHelper.GetDataTable(sql);}//重载含参构造public DataTable GetOperationLogsList(int pageNo,int pageSizes){ string sql = "SELECT ROW_NUMBER() OVER (ORDER BY ActionDate DESC) AS No, OperationLog.Id AS Id, Operator.RealName AS 操作员, ActionDate AS 操作时间, ActionDesc AS 操作描述 from OperationLog, Operator WHERE OperationLog.OperatorId=Operator.Id ORDER BY No OFFSET @OffsetValue ROWS FETCH NEXT @PageSizes ROWS ONLY";SqlParameter[] parameters = new SqlParameter[] {new SqlParameter("@OffsetValue",(pageNo - 1)* pageSizes),new SqlParameter("@PageSizes",pageSizes)}; return SqlHelper.GetDataTable(sql,parameters);}public int GetPageNum(int pageSizes){string sql = "SELECT COUNT(*) FROM OperationLog";int ans = (int)SqlHelper.ExecuteScalar(sql);if (ans % pageSizes == 1){return ans / pageSizes + 1;}else{return ans / pageSizes;}}}
完成情况分析:
- 完成情况良好,成功实现了登录日志记录功能,能够按照分页显示数据库中的操作日志,并支持日志记录的增删查改。
5.员工列表查询功能
- 窗体速览:
- 初始加载时显示所有员工信息,并初始化部门下拉框。用户可以根据设置的搜索条件(如姓名、部门、入职时间)进行查询。
- 查询结果会显示在下方的
DataGridView
控件中,点击“重置查询”按钮会清空所有查询条件并显示全部员工列表。
实现思路:
- 窗体层
FormEmpList
:- 在窗体加载时,初始化界面信息,调用
StartSearch()
方法,将查询相关的控件状态初始化,并清空输入框内容,禁用查询按钮。 - 查询员工列表,调用
DisplayEmpList()
方法,从数据库获取所有员工列表数据并显示在DataGridView
中。
- 在窗体加载时,初始化界面信息,调用
完成情况分析:
- 完成情况良好,成功实现了根据不同查询条件动态查询员工列表功能,并支持结果显示和重置查询。
2.2 作业中要求的功能完成情况
1. 新用户注册
-
窗体速览:
-
在系统的登录窗体中,用户可以通过单击“新用户注册”超链接,正确输入待创建的用户名、密码、密码确认和真实姓名,进行账户的注册。
-
只有当用户输入了4个必填项,且密码与密码确认一致,才能执行DAL(数据访问层)提供的相应服务,连接数据库并将相应记录添加到
Operator
表中,最后提示用户“注册成功”。
private void buttonLogin_Click(object sender, EventArgs e){if (textBoxCheckPassword.Text == textBoxPassword.Text){string un = textBoxUserName.Text;string pwd = CommonHelper.GetMD5(textBoxPassword.Text);string cpwd = CommonHelper.GetMD5(textBoxCheckPassword.Text);string rn = textBoxRealName.Text;string connstr = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;if (un == "" || rn == "" || pwd == ""){CommonHelper.ShowErrorMessage("用户名或密码或真实姓名不能为空!");}if ( pwd != cpwd){CommonHelper.ShowErrorMessage("两次密码必须输入一致!");}// 核心操作:将前端输入的信息利用sql语句插入Operator表项中!string insertQuery = "INSERT INTO Operator(Id, UserName, Password, IsDeleted, RealName, IsLocked, IsAdmin) " +"VALUES(NEWID(), @UserName, @Password, 0, @RealName, 1, 0)";SqlConnection conn = new SqlConnection(connstr);SqlParameter sp1 = new SqlParameter("@UserName", un);SqlParameter sp2 = new SqlParameter("@Password", pwd);SqlParameter sp3 = new SqlParameter("@RealName", rn);SqlCommand cmd = new SqlCommand(insertQuery, conn);cmd.Parameters.Add(sp1);cmd.Parameters.Add(sp2);cmd.Parameters.Add(sp3);conn.Open();int n = (int)cmd.ExecuteNonQuery();if (n > 0){MessageBox.Show("注册成功", "正确提示", MessageBoxButtons.OK, MessageBoxIcon.Information);}else{MessageBox.Show("注册失败", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);}conn.Close();}else{MessageBox.Show("密码输入错误", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);}}
-
数据合法性验证模块:
- 根据业务逻辑,当前界面的4个文本框控件均为必填项,不能有空值。如果其中任何一个为空,或者密码与确认密码不一致,就不允许继续执行数据库操作,提高软件的效率。
-
MD5加密模块:
- 为了便于后续向数据库
Operator
表中插入数据,需要将用户输入的明文密码通过MD5算法转换为密文。通过调用CommonHelper
静态类中的静态方法GetMD5()
,传入用户输入的明文密码作为参数,该方法返回加密后的密文,存储在变量pwdSafe
中。
- 为了便于后续向数据库
2. 密码修改
-
通过三层架构实现了密码修改的主要功能。为了确保安全性,虽然用户已经成功登录进入系统,但仍需要用户输入一次当前密码,再输入两次相同的需要新设置的密码,然后点击“修改密码”按钮完成相应的业务逻辑。
-
窗体速览:
- 只有当用户正确输入旧密码(此次登录的密码)后,才可以执行相应的密码修改操作。如果用户输入错误的密码,则会给出相应的提示信息。
-
实现思路:
- 获取用户信息:
- 通过
LoginUserInfo
单例类中的GetInstance()
方法获取当前登录用户的相关信息。
- 通过
- 输入验证与原密码验证:
- 当用户点击“修改密码”按钮后,系统会判断当前密码是否正确,以及两次输入的新密码是否一致。如果不一致则显示提示信息“两次输入密码不一致”,并终止后续操作。如果一致,则可以继续执行密码修改操作。
- 密码修改:
- 通过数据访问层
OperatorService
类提供的ModifyPwd
方法执行密码修改。需要正确构造SQL更新语句(UPDATE Operator SET Password=@Password WHERE UserName=@UserName
)并传入相关参数,调用封装好的SqlHelper
类中的ExecuteNonQuery
方法进行数据库操作。
- 通过数据访问层
- 获取用户信息:
完成情况分析:
- 完成情况良好,成功实现了修改密码功能,确保用户密码安全性,系统也正确提示了错误信息,避免无效操作。
3. 部门管理
-
管理员可以通过模糊关键词查询
Department
表中的数据,当需要对选中的部门进行删除时,系统会进行提醒,以避免误操作。同时,部门管理的相关操作也设置了相应的日志记录,便于回溯。 -
窗体速览:
-
- 系统会自动显示当前数据库
Operator
表中的所有部门信息,并提供“增加部门”、“删除部门”、“查找全部部门”的功能区域,管理员可以对部门信息进行维护。
- 系统会自动显示当前数据库
实现思路:
- 数据访问层
DepartmentService
类:- 封装了获取所有部门信息的
GetDepartmentList()
方法,用于获取并绑定部门数据到窗体控件中。还提供了添加、删除部门的功能。
- 封装了获取所有部门信息的
完成情况分析:
- 完成情况良好,成功实现了部门的查询、删除和添加功能,且每次操作都记录在操作日志表中,便于后期回溯。
4.操作员管理
-
该功能允许管理员对当前系统数据库中保存的操作员权限进行管理,包括新建、删除操作员、重置密码为123、更改账号状态(设置为锁定、删除、管理员权限),并可查看操作员的详细信息。
-
窗体速览:
- 在界面加载时,系统通过数据访问层
OperatorService
类的reFreshList()
方法获取所有操作员信息,并显示在界面中。
- 在界面加载时,系统通过数据访问层
完成情况分析:
- 完成情况良好,成功实现了操作员权限的管理,管理员可以方便地管理系统中的操作员,包括分配权限、删除账号等。
5. 查询工资单
-
窗体速览
-
在该窗体加载时,系统默认显示所有工资单列表,并允许用户点击指定工资单记录,查看选中工资单的详情状况。
实现思路:
- 在窗体加载时,通过调用数据访问层
SalarySheetService
类的GetSalarySheetList()
方法获取数据库中所有工资单记录,并显示在DataGridView
控件中。
完成情况分析:
- 完成情况良好,达成了工资单查询功能,可以查看每个部门的工资单信息,并根据筛选条件查看特定记录。
6.员工管理模块
界面速览
6.1测试添加员工模块
6.2删除员工功能
6.3测试入职时间搜索功能
- 界面上有三个可选搜索条件:
员工姓名(textBoxName)
所属部门(comboBoxDepartment)
员工入职的日期范围(dateTimePickerBegin 和 dateTimePickerLast)。 - 当用户点击“搜索”按钮时:
如果勾选了姓名,代码会获取姓名文本框的值;
如果勾选了部门,代码会获取部门下拉框所选部门的ID;
如果勾选了入职时间,代码会获取所选择的起止日期范围。 - 根据用户勾选和填写的条件,程序构建一个serchEmployee对象,将其作为参数传入EmployeeService.GetEmployeeList方法中查询数据库;
- 查询结果会绑定到界面的DataGridView控件中,显示符合条件的员工信息。
第一个方法 GetDepartmentId(string departmentName)
根据给定的部门名称查询数据库,返回对应部门的唯一标识符(GUID)。
第二个方法 GetDepartmentList()
查询数据库中的所有部门,创建一个部门对象列表。它用SQL语句从数据库中选取部门的所有记录,并遍历数据读取器(reader),为每一行创建一个 Department 对象,将部门的ID和名称赋值后加入到列表中,最后返回整个部门列表。
无条件查询GetEmployeeList() 无参方法
:执行简单SQL查询:SELECT Employee.Id as ID, 工号, Employee.Name as 姓名, Department.Name as 部门, 入职时间, 国籍, NativePlace,并返回包含所有员工信息的数据表。条件查询GetEmployeeList(Employee) 有参方法
:根据用户选择的条件动态构建SQL查询条件:- 如果指定了员工姓名,则添加姓名查询条件;
- 如果指定部门,则通过部门ID进行筛选;
- 如果指定了入职时间,则加入时间段条件(起始日期和结束日期)。
动态条件构造
根据传入的员工实体对象(searchEmployee),动态拼接SQL条件,并使用参数化查询,以防止SQL注入攻击,提升安全性。
结果返回通过封装好的SqlHelper类,将查询结果转换成DataTable对象,并返回给界面上的DataGridView显示。
6.4导出员工功能
界面速览
7. 生成工资单
- 界面情况
8.查询工资单
9. 操作员管理
该窗体可以对当前系统数据库中保存的用户,可以添加和修改系统内用户使用数据库的权限。
10.薪资单报表打印
1.在HRMDB数据库中新建报表视图。
2.打印报表界面
第3部分 实训的主要收获
3.1 简单程序设计与软件系统开发区别
在学习的C语言与数据结构之中,主要通过控制台进行数据的输入和输出。它们主要聚焦于实现一些特定的功能或解决某个问题的算法
。这类编程通常是为了培养我们的代码能力,锻炼编程思维所用,但实际开发过程中可能并没有复杂的算法,但是有庞大的业务逻辑需要我们分析
,无论是多层结构的设计,类的封装和不同模块的数据传递,都和简单的程序设计有很多的区别。项目文件明显庞大。而且实际开发更加注重我们的代码规范程度,而在一些算法竞赛中,很多命名都是非常随意的,我们需要转变自己的工程性思维,培养自己代码的规范程度,写出可读性强的代码。
以实训课中的HR人力资源管理系统开发为例,系统包括了多个功能模块,如登录
、注册
、员工信息管理
、工资单生成
、员工数据导出
等。因此,需要对不同层中的多个项目文件进行合理的管理,比如专门设置界面项目、业务逻辑层项目、数据访问层项目和模型层项目的文件夹。
此外,软件系统开发还需要考虑可扩展性。随着项目的不断更新迭代,许多新功能会被引入,因此在设计软件架构和功能模块时,要预留相关接口,以便后续的扩展和修改。软件架构的设计在软件系统开发中至关重要,尤其是在开发HR人力资源管理系统时,准确设计各个功能层次并把握应用需求显得尤为重要。
3.2 系统开发中的分层架构体会
实训课开发的HR人力资源管理系统采用了三层架构,分别是界面层
、业务逻辑层(BLL)
和数据访问层(DAL)
。
-
界面层(UI层)
- 窗体的相关代码建立了用户与业务逻辑层之间的交互。窗体中的各个控件接收用户的输入并输出相应的显示信息。UI界面在软件系统中至关重要,直接影响用户体验。通过窗体中的事件(如窗体加载、按钮点击等),业务逻辑层封装的相应方法得以触发,数据通过预留接口传递给业务逻辑层进行处理。
-
业务逻辑层(BLL)
- 业务逻辑层处理界面层传来的请求,通过使用数据访问层提供的接口与数据库进行交互。业务逻辑层是软件系统各个功能实现的核心,负责处理具体的业务逻辑。例如,在登录模块中,业务逻辑层验证用户输入的数据是否合法,将明文密码通过
MD5算法
转化为密文,并通过调用数据访问层与数据库中的数据进行匹配。
- 业务逻辑层处理界面层传来的请求,通过使用数据访问层提供的接口与数据库进行交互。业务逻辑层是软件系统各个功能实现的核心,负责处理具体的业务逻辑。例如,在登录模块中,业务逻辑层验证用户输入的数据是否合法,将明文密码通过
-
数据访问层(DAL)
- 数据访问层负责与数据库进行交互,业务逻辑层通过数据访问层提供的接口进行数据访问。数据访问层执行增、删、查、改操作,并将查询结果返回给业务逻辑层。数据访问层与数据库的交互通过封装的接口实现,业务逻辑层只需要传递相应的参数,无需关心数据库的具体实现细节。
-
三者逻辑关系
- 界面层 调用 业务逻辑层: 界面层调用业务逻辑层提供的方法处理用户请求。
- 业务逻辑层 调用 数据访问层: 业务逻辑层通过调用数据访问层提供的方法获取数据,实现核心业务。
- 数据访问层: 负责与数据库交互,执行相应的数据操作。
这种架构使得整个系统结构清晰,各层之间分工明确。当某一层的功能代码需要修改时,只需要确保相应接口不变,不会对其他层的功能产生影响,从而提高了系统的可维护性和可扩展性。
3.3 代码的规范重要性
在开发过程中,代码的规范性非常重要。为了提高开发效率和后期的可维护性,在开发复杂功能和相关方法时,需要充分利用VS2022自带的注释功能,解释每个方法的主要作用和实现思路。规范的变量和函数命名规则至关重要,例如在定义变量时使用 userName
、password
等具有明确意义的命名,而不是随意使用无意义的字符。
为了减少代码冗余,在系统中多次会被不同模块调用的方法可以进行统一封装,从而提高代码的复用性。在本系统开发过程中,将常用功能(如MD5加密函数、数据库连接函数、执行相关SQL语句的函数)统一封装在静态类 CommonHelper
中,便于集中管理、修改和维护。这种做法使得不同模块之间能够复用相同的代码,减少了冗余并提高了开发效率。
在团队开发过程中,确保代码风格一致是非常重要的。如果团队中的每个成员的代码风格不同,其他成员在阅读和理解代码时将变得困难,这大大提高了开发和维护的难度。因此,在团队开发中,必须遵循相应的代码风格约定,以确保代码的规范性,保证团队成员之间的协作顺畅。
3.4 其他主要收获
通过实训课程,我了解了项目开发的基本流程,包括需求分析、代码设计和测试等,它模拟了大型软件系统的开发,帮助我为未来的毕业设计和就业准备了坚实的基础。我切实地提高了自己在问题解决方面的能力。我完成了HR人力资源系统的基本开发。在这个过程中,我不仅巩固了上学期所学的C#基本语法、Windows窗体应用开发的基本思路和数据库操作技能,还接触了新的业务流程,例如表单创建和将数据库数据导出为Excel文件等。课程以项目为驱动,促使我在实践中不断学习,通过实际操作进一步提升了我的技术能力。
第4部分 实训中遇到的困难及其解决方法
在与数据库交互、数据库数据导出为Excel文件等操作中,这些功能我是第一次编写,难度较大。通过查阅CSDN博客、微软.Net开发指南,询问deepseek大模型工具,最终我成功完成了相关功能。还有一些困难比如如何拿到数据视图中选中的员工的具体信息用dataGridView.Rows[dataGridView.CurrentRow.Index].Cells[0].Value.ToString()
方法拿到具体信息即可。
在开发过程中,经常会遇到各种报错,如程序报错、数据库连接失败、数据错误显示等。通过运用调试工具,如VS2022中的断点调试功能,我尝试逐步排查代码错误,快速定位错误并找到解决方法。
第5部分 通过实训发现自己的不足及其对策
还需要积累很多开发知识,培养自己对于业务逻辑地敏感程度,提升产品的稳定性,例如本次课程中第一次接触到Sql注入带来的软件安全性隐患提醒到我,很多东西开发者第一次接触很难考虑周全,确实是需要大量经验支撑,不断地在项目中体会才可以磨练代码开发能力。
第6部分 对实训课的建议
-
引入AI与大数据技术:
随着人工智能和深度学习等前沿技术的不断发展,课程内容可以进一步扩展,涵盖这些领域的知识。例如,在HR人力资源管理系统中加入员工绩效预测、员工数据分析等功能,或者新增考勤功能模块,借助OpenCVSharp
库实现人脸识别签到,体验传统编程与计算机视觉领域的结合。尽管由于课时有限,可能无法完全实现这些功能,但可以作为未来课程的拓展内容。 -
版本管理:
在现代软件开发中,Git版本控制系统已经广泛应用于项目管理,能够有效解决多人协作中的代码冲突和版本管理问题。因此,建议课程中增加版本管理相关内容,帮助学生掌握Git的基本使用技巧,理解版本控制在团队协作中的重要性。具体来说,可以学习如何进行代码提交、分支管理、回溯代码等操作,从而提升代码管理的效率,特别是在处理大型项目时。
源码
源码已经上传至gitee
gitee项目链接:C#实现人事管理系统