【人力资源管理系统】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#实现人事管理系统
相关文章:
【人力资源管理系统】C#实现
软件项目实训报告2025版 第1部分 实训的目标与主要内容1.1 实训目标1.2 实训主要内容 第2部分 最终作品的完成情况2.1 课堂上要求的功能完成情况2.1.1 完成的功能及完成情况分析1. 建立数据库2. 登录界面3. MDI主窗体显示4. 登录日志功能5.员工列表查询功能 2.2 作业中要求的功…...
C#插件与可扩展性
外接程序为主机应用程序提供了扩展功能或服务。.net framework提供了一个编程模型,开发人员可以使用该模型来开发加载项并在其主机应用程序中激活它们。该模型通过在主机和外接程序之间构建通信管道来实现此目的。该模型是使用: System.AddIn, System.AddIn.Hosting, System.…...
【工具】gtest
在写代码的时候大家是怎么判断自己所写的程序运行有没有问题呢?可能是在程序中穿插打印数据,看比对数据是否有问题,这是传统的肉眼观察法。如果代码量并不大,数据量比较少的话是比较好用的。那在数据量庞大时,打印信息…...
力扣DAY56-59 | 热100 | 回溯:子集、电话号码的字母组合、组合总和、括号生成
前言 中等 √ 怒刷回溯,逐渐有了手感,重点就在于设计树复原状态sometimes剪枝。 子集 我的题解 全排列的基础上修改:1)每个状态(而不是size等于数组长度)都加入答案数组中。2)设置指针&…...
ChatUI vs Ant Design X 技术选型对比
引言 本文从核心功能、架构设计、易用性等维度对比分析阿里巴巴的 ChatUI 和 Ant Design 的 Ant Design X,帮助开发者选择适合的对话式 UI 开发方案。 核心功能对比 维度ChatUIAnt Design X定位聚焦对话界面(Chatbot)的轻量级解决方案全面的…...
第24周:Resnet结合DenseNet
目录 前言 一、 前期准备 1. 设置GPU 2.查看数据 二、构建模型 1.划分数据集 2.划分标签 3.编译及训练模型 三、结果可视化 四、总结 前言 🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊 一、 前期准备 1.…...
【论文阅读20】-CNN-Attention-BiGRU-滑坡预测
这篇论文主要探讨了基于深度学习的滑坡位移预测模型,结合了MT-InSAR(多时相合成孔径雷达干涉测量)观测数据,提出了一种具有可解释性的滑坡位移预测方法。 [1] Zhou C, Ye M, Xia Z, et al. An interpretable attention-based deep…...
Linux根据 PID 进行性能分析
根据 PID 进行性能分析 当需要深入分析某个特定进程的性能问题时,Linux 提供了多种强大的工具来监控和分析进程行为。以下是针对 PID 进行性能分析的完整方法: 1. 基础监控命令 1.1 top - 实时监控指定进程 top -p PID1,PID2,PID3 # 监控多个PID交互…...
Spring MVC 初体验~~
Java EE三层架构 在Java EE开发中,系统经典的三层架构包括表现层、业务层和持久层。三层架构中,每一层各司其职。 表现层(Web层)负责接收客户端请求,并向客户端响应结果 业务层(Service层)负责…...
2025年03月中国电子学会青少年软件编程(Python)等级考试试卷(五级)真题
青少年软件编程(Python)等级考试试卷(五级) 分数:100 题数:38 答案解析:https://blog.csdn.net/qq_33897084/article/details/147341437 一、单选题(共25题,共50分) 1. 以下哪个选…...
警惕阿里云中的yum update操作不当导致:/sbin/init被清空导致Linux无法正常启动
由于使用阿里云进行部署测试,因而会对yum update进行操作,这两天更新了systemd-239-82.0.3.4.al8.2.x86_64,但存在报错,然后进行yum history undo和清空yum cache,但出现操作Linux命令行无效。具体来说,几个…...
使用Selenium和Python实现Web抓取指南
1. 环境准备 安装Selenium库 bash 复制 pip install selenium 下载浏览器驱动(以Chrome为例) 下载对应浏览器版本的驱动: ChromeDriver: https://chromedriver.chromium.org/downloads 将驱动文件(如chromedriver.exe&…...
避免IP地址关联,多个手机设备的完美公网IP问题
在现代工作室中,手机设备的数量常常多于一个,为了方便管理和使用,通常会将这些设备连接到同一个Wi-Fi网络。这样的做法看似无害,却隐藏着一个普遍而被忽视的问题:多个手机共用同一个公网IP。这个看似技术性的细节&…...
深入浅出 MVCC:MySQL 并发背后的多版本世界
📌 一句话理解 MVCC(Multi-Version Concurrency Control): MVCC 指的是多版本并发控制,MVCC 通过为每个事务提供数据的快照版本,让读取操作无需加锁,从而实现高并发的同时,又能维持…...
鸿蒙-跨设备互通,设备互通提供跨设备的相机、扫描、图库访问能力,平板或2in1设备可以调用手机的相机、扫描、图库等功能。
跨设备互通 跨设备互通提供跨设备的相机、扫描、图库访问能力,平板或2in1设备可以调用手机的相机、扫描、图库等功能。 约束与限制 需同时满足以下条件,才能使用该功能: 设备限制 本端设备:HarmonyOS版本为HarmonyOS NEXT及以上…...
C#进阶学习(六)单向链表和双向链表,循环链表(下)循环链表
目录 📊 链表三剑客:特性全景对比表 一、循环链表节点类 二、循环链表的整体设计框架 三、循环列表中的重要方法: (1)头插法,在头结点前面插入新的节点 (2)尾插法实现插入元素…...
Spring Boot配置文件优先级全解析:如何优雅覆盖默认配置?
📚 一、为什么需要了解配置文件优先级? 想象一下,你正在玩一个游戏🎮,游戏里有默认设置,但你可以通过不同的方式修改这些设置: 游戏内置的默认设置(就像Spring Boot的默认配置&…...
【多目标进化算法】NSGA-II 算法(结合例子)
目录 一、NSGA-II 是干什么的? 二、通过一个简单例子来解释 例子:挑选手机 三、NSGA-II 解决步骤 1. 初始化种群 2. 非支配排序(Fast Non-dominated Sorting) 3. 拥挤度距离(Crowding Distance) 4. 选择 + 交叉 + 变异 5. 合并种群、排序、更新 四、最后结果(…...
【Spring Boot】把jar包导入本地系统
【Java】把jar包导入本地maven仓库 一、方法一:将 JAR 添加到项目本地的 libs/ 目录二、方法二:把 JAR 安装到本地 Maven 仓库(推荐)三、查看是否安装成功(1)直接用文件管理器/终端查看(2&#…...
钧瓷收藏防坑指南:如何科学评估与理性收藏
关注大禹智库及时接收干货报告和视频 大禹智库 第 8期〔总第462期〕2025-4-17 一、价格敏感背后的收藏心理 每次钧瓷估价速算表的更新都会引发收藏圈的热议,这反映出藏家最核心的关切:“买得值不值?” 即便对部分藏家而言价格并非首要因素…...
CrewAI Community Version(一)——初步了解以及QuickStart样例
目录 1. CrewAI简介1.1 CrewAI Crews1.2 CrewAI Flows1.3 Crews和Flows的使用情景 2. CrewAI安装2.1 安装uv2.2 安装CrewAI CLI 3. 官网QuickStart样例3.1 创建CrewAI Crews项目3.2 项目结构3.3 .env3.4 智能体角色及其任务3.4.1 agents.yaml3.4.2 tasks.yaml 3.5 crew.py3.6 m…...
Vue 3.0 Composition API 与 Vue 2.x Options API 的区别
引言 Vue 作为一款流行的 JavaScript 框架,经历了多个版本的迭代。Vue 2.x 时期,Options API 是主要的开发方式;而到了 Vue 3.0,引入了 Composition API。这两种 API 风格各有特点,理解它们的区别对于开发者来说至关重…...
江苏广电HC2910-创维代工-Hi3798cv200-2+8G-海美迪安卓7.0-强刷包
江苏广电HC2910-创维代工-Hi3798cv200-28G-海美迪安卓7.0-强刷包 说明 1、由于原机的融合网关路由不能设置,原网口无法使用,需要用usb2.0的RJ45usb网卡接入。 通过usb接口网卡联网可以实现百兆网口连接。原机usb3.0的接口可以以接入硬盘,播放…...
clickhouse数据导出导入
clickhouse数据导出导入 CSV格式导出为csv格式导入为csv格式 JSON格式导出为json格式导入为json格式 SQL格式导出为SQL CSV格式 导出为csv格式 # 不带表头 clickhouse-client -h 127.0.0.1 --database"db" --query"select * from db.test_table FORMAT CSV&qu…...
GPU 在机器学习中的应用优势:从技术特性到云端赋能
一、引言:当机器学习遇见算力革命 在人工智能浪潮席卷全球的今天,机器学习已从实验室走向商业落地的核心战场。随着深度神经网络模型复杂度呈指数级增长(如 GPT-4 参数量突破万亿级),以及数据规模迈向 ZB 级别&…...
C++: 类和对象(中)
📔个人主页📚:秋邱-CSDN博客 ☀️专属专栏✨:C 🏅往期回顾🏆:C: 类和对象(上) 🌟其他专栏🌟:C语言_秋邱 类的默认成员函数 构造…...
基于slimBOXtv 9.16 V2-晶晨S905L3A/ S905L3AB-Mod ATV-Android9.0-线刷通刷固件包
基于slimBOXtv 9.16 V2-晶晨S905L3A/ S905L3AB-Mod ATV-Android9.0-线刷通刷固件包,基于SlimBOXtv 9 修改而来,贴近于原生ATV,仅支持晶晨S905L3A/ S905L3AB芯片刷机。 适用型号:M401A、CM311-1a、CM311-1s…...
Rocky8 升级 Python 3.9.20 并部署 Airflow 2.10.5
Rocky8 升级 Python 3.9.20 并部署 Airflow 2.10.5 1.系统环境配置1.1Python安装1.2 Airflow 配置1.2.1 基础配置1.2.2 新建数据库1.2.3 配置文件 airflow.cfg 1.3 安装 Airflow 2.Airflow 测试1.启动定时器2.登录系统3.自定义流3.1测试流 1.系统环境配置 # 系统版本查看 cat …...
基础智能体的进展与挑战第 3 章【记忆】
目录 第三章记忆3.1 人类记忆概述3.1.1 人类记忆的类型3.1.2 人类记忆模型 3.2 从人类记忆到智能体记忆3.3 智能体记忆的表示3.3.1 感知记忆3.3.2 短期记忆3.3.3 长期记忆 3.4 记忆生命周期3.4.1 记忆获取3.4.2 记忆编码3.4.3 记忆衍生3.4.4 记忆检索与匹配3.4.5 神经记忆网络3…...
Docker 容器与镜像核心操作命令大全(实战指南)
Docker 容器与镜像核心操作命令大全(实战指南) 摘要:本文全面整理 Docker 容器与镜像管理的高频操作命令,涵盖容器生命周期管理、镜像构建技巧、网络配置、文件挂载等场景,并附赠企业级高级用法。适用于开发、测试及生…...
见多识广3:帕累托最优解与帕累托前沿
目录 前言定义特点应用场景求解算法总结 前言 这里的知识都是kimi告诉我的,我主要记录一下。 定义 帕累托最优解:在多目标优化问题中,如果一个解在某个目标上优于另一个解,而在其他目标上至少不比另一个解差,那么这…...
遥感技术赋能电力设施监控:应用案例篇
目前主流的电力巡检手段利用无人机能够通过设定灵活航线进行低空飞行、搭载不同的采集设备,能够从不同角度对输电线进行贴近拍摄,但缺陷是偏远山区无人机飞行技术要求高,成本高,且飞行的无人机也可能会对输电线产生破坏。 星图云开…...
Docker容器虚拟化存储架构
本文主要描述Docker容器引擎中运行的应用如何持久化地存储数据。 如上所示,Docker容器引擎的总体应用架构图,包括Docker客户端应用、Docker Host服务端应用以及Docker Registry镜像仓库端应用。其中,Docker Host服务端应用包括Docker daemon容…...
Silverlight发展历程(微软2021年已经停止支持Silverlight 5)
Microsoft Silverlight 发展历程 引言 Microsoft Silverlight 是微软在 Web 多媒体和富互联网应用 (RIA) 领域的一次重要尝试,它从诞生到消亡的过程折射出了 Web 技术发展的变迁和行业格局的演变。本文将详细回顾 Silverlight 的完整发展历程,探讨其技…...
“星睿O6” AI PC开发套件评测 - 部署PVE搭建All in One NAS服务器
Radxa O6平台上部署PVE搭建All in One NAS服务器 Radxa O6是一款性能卓越的单板计算机,其强劲的硬件配置和多样化的接口设计,使其成为家庭和小型企业理想的All in One服务器解决方案。值得一提的是,O6原生配备了两个5G网口,便于直…...
【路由交换方向IE认证】BGP选路原则之AS-Path属性
文章目录 一、路由器BGP路由的处理过程控制平面和转发平面选路工具 二、BGP的选路顺序选路的前提选路顺序 三、AS-Path属性选路原则AS-Path属性特性AS-Path管进还是管出呢?使用AS-Path对进本AS的路由进行选路验证AS-Path不接收带本AS号的路由 四、BGP邻居建立配置 一…...
《软件设计师》复习笔记(14.3)——设计模式
目录 一、设计模式分类 1. 创建型模式(Creational Patterns) 2. 结构型模式(Structural Patterns) 3. 行为型模式(Behavioral Patterns) 真题示例: 一、设计模式分类 架构模式 高层设计决…...
Windows10,11账户管理,修改密码,创建帐户...
在这里,我们使用微软操作系统的一款工具:netplwiz 它可以非常便捷的管理用户账户. 一:修改密码(无需现在密码) 01修改注册表 运行命令:regedit 在地址栏输入: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Passwor…...
数据类型相关问题导致的索引失效 | OceanBase SQL 优化实践
背景 针对在OceanBase 论坛中遇到的一些典型SQL调优问题,进行记录与总结,分享给大家。本文介绍的事3个场景:数据类型不匹配、字符集相关属性不匹配,和过滤/联接条件上包含系统函数。 场景一:数据类型不匹配 类型不匹…...
银行卡风险画像在社交行业网络安全的应用
据中国支付清算协会统计,2023年银行卡欺诈案件造成的经济损失同比增长21%,而社交平台中超过35%的诈骗行为涉及金融账户盗用。本文将讲述如何使用风险画像技术助力社交网络安全。 银行卡风险画像的核心逻辑 银行卡风险画像是通过多维度数据分析构建的用…...
C++程序设计基础实验:C++对C的扩展特性与应用
C程序设计基础实验:C对C的扩展特性与应用 🔥 本文详细讲解C基础实验,包含C对C语言的扩充与增强特性,从零开始掌握函数重载、引用、指针等核心概念,附详细代码分析与运行结果。适合C初学者和有C语言基础想学习C的同学&a…...
极狐GitLab 外部授权控制机制是怎样的?
极狐GitLab 是 GitLab 在中国的发行版,关于中文参考文档和资料有: 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 外部授权控制 (BASIC SELF) 在高度控制的环境中,访问策略可能需要由外部服务控制,该服务允许基于项目…...
告别Feign:基于Spring 6.1 RestClient构建高可用声明式HTTP客户端
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...
极狐GitLab 项目和群组的导入导出速率限制如何设置?
极狐GitLab 是 GitLab 在中国的发行版,关于中文参考文档和资料有: 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 项目和群组的导入导出速率限制 (BASIC SELF) 您可以为项目和群组的导入和导出配置速率限制: 更改速率限制&#…...
中华传承-医山命相卜-铁板神数
铁板神数 子平法 子平法 徐子平 倪海夏 一月(公历2025年1月29日-2025年2月27日) 运势:事业开局不利,难以快速适应工作节奏,可能面临上级的质疑或竞争压力。财富方面容易财来财去,需留意理财陷阱。 原因&…...
C++学习:六个月从基础到就业——面向对象编程:接口设计
C学习:六个月从基础到就业——面向对象编程:接口设计 本文是我C学习之旅系列的第十五篇技术文章,重点讨论在C中进行接口设计的原则、技术和最佳实践。查看完整系列目录了解更多内容。 引言 在面向对象的软件开发中,良好的接口设计…...
工作总结(十二)——迁移svn单项目到gitlab上,保留历史提交记录
文章目录 前言一、目的二、操作步骤1.创建项目库2.复制历史提交者账号3.复制待迁移项目以及历史记录4.push到gitlab远程仓库 总结 前言 本系列文章主要记录工作中一些需要记录的内容 一、目的 因为一些原因,我需要将svn库上的某个项目迁移到公司的gitlab库管理平台…...
PS中制作一张扣洞贴图
要在PS制作如下一张贴图,如下图所示 步骤: 1.首先复制一张图层 2.将最底层图层的透明度调整为0 3.选择画笔的模式为清除 4.设置画笔大小 5.选中需要清除的图层,然后就可以将图层的像素点清除了 6.导出成PNG文件即可 注࿱…...
STM32 HAL库 Freertos创建多任务
1. 引言 STM32F407 是 ST 公司推出的一款高性能微控制器,具有丰富的外设资源和强大的处理能力。HAL(Hardware Abstraction Layer)库是 ST 为其微控制器提供的硬件抽象层,它简化了硬件操作,提高了开发效率。FreeRTOS 是…...
android测试硬件工具 安卓硬件测试命令
Android开发常用ADB命令大全 在Android开发过程中,ADB(Android Debug Bridge)是一个非常重要的调试工具。掌握这些命令可以大大提高开发效率。如果你正在使用克魔开发助手(Keymob)这样的开发工具,你会发现它已经集成了很多ADB功能,让调试变得…...