资讯详情

软件工程实训:人事管理系统从设计到答辩的完整实现指南

📅 2026/10/10 12:42:54 | 华诺云谱 👁 阅读
软件工程实训:人事管理系统从设计到答辩的完整实现指南
简介这份资源是2023年湖南科技大学计算机学院综合创新实训3的完整项目报告面向软件工程专业学生及需要完成课程设计的学习者围绕人事管理系统的设计与实现展开。报告以docx文档形式呈现压缩包内仅1个文件约947KB内容涵盖需求规约、体系结构设计、接口与界面设计、数据结构与流程设计、数据建模及范式分析等完整阶段。项目采用工厂模式与外观模式结合进行总体结构设计并附有类图、状态图、顺序图等UML建模成果同时记录了小组规约与工作日志体现单人成组的开发过程。已有1188人学习该资源读者可借此获得一份结构规范、文档齐全的课程设计参考模板理解从需求分析到详细设计的完整软件工程流程适合作为实训报告撰写与项目复盘的对照材料。1. 从一份实训任务书说起人事管理系统到底要交什么每年到了综合创新实训的档期总有一批软件工程方向的同学卡在同一个地方任务书上写着“人事管理系统的实现”看起来平平无奇真动手才发现坑比想象中多。有人三天写完增删改查就交差答辩时被问“权限怎么分的”“并发请假怎么处理”直接哑火也有人一上来就堆框架光环境配置耗掉一周最后功能没跑通。这篇笔记就围绕这个典型的实训题目把一套能落地、能演示、经得起追问的实现路径拆开讲清楚。人事管理系统本质是一个带权限控制的信息管理系统核心业务对象是员工、部门、岗位、考勤和薪资这几类数据。它不追求高精尖算法考的是你对需求拆解、数据建模、分层架构和边界情况的处理能力。适合正在做同类实训的本科生也适合想拿一个完整项目练手 CRUD 到权限体系的后端初学者。下面从选型、建模、编码到排错一步步来。2. 技术选型与数据建模别让第一行代码就埋雷选型和建模是这类项目最容易翻车的地方。很多人一上来就纠结用哪个前端框架结果数据库表设计得一团糟写到一半发现字段不够用只能删库重来。我的建议是先把数据模型定死再谈技术栈。2.1 技术栈怎么选才不给自己挖坑实训项目周期通常两到四周选型的第一原则是“能快速跑通、文档齐全、出问题好搜”。下面这套组合是我带过几届学生后觉得最稳的层次推荐方案选它的理由不推荐的方案后端框架Spring Boot 2.7.x生态成熟Starter 依赖开箱即用裸 Servlet配置量大持久层MyBatis-Plus单表 CRUD 零 SQL复杂查询仍可手写纯 JPA多表关联容易失控数据库MySQL 8.0窗口函数、CTE 可用字符集默认 utf8mb4SQLite并发写有锁前端Vue 3 Element Plus表格表单组件现成管理后台模板多手写原生 DOM权限Spring Security JWT无状态前后端分离友好Session跨域麻烦构建Maven依赖冲突排查直观Gradle新手易懵这套组合的关键在于每一层都有“偷懒”的空间。MyBatis-Plus 的BaseMapper直接给你 17 个单表方法省掉大量重复代码Element Plus 的el-table和el-form把管理后台最常见的两个交互场景包了。省下来的时间应该花在业务逻辑和边界处理上而不是造轮子。提示Spring Boot 版本不要追最新2.7.x 是长期维护版和 MyBatis-Plus、Spring Security 的兼容性经过大量项目验证。用 3.x 可能遇到 Jakarta 包名迁移的连锁问题实训期间没必要冒这个险。2.2 五张核心表的设计与字段取舍人事系统的数据模型围绕“人”展开但人不是孤立存在的他属于部门、担任岗位、产生考勤和薪资记录。下面是我一般会建的五张核心表字段经过多轮精简只保留实训演示和答辩追问都覆盖得到的部分。-- 员工表系统的核心实体 CREATE TABLE employee ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, emp_no VARCHAR(20) NOT NULL COMMENT 工号业务唯一键, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT DEFAULT 0 COMMENT 0未知 1男 2女, birth_date DATE DEFAULT NULL COMMENT 出生日期, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱, dept_id BIGINT DEFAULT NULL COMMENT 所属部门, position_id BIGINT DEFAULT NULL COMMENT 岗位, hire_date DATE NOT NULL COMMENT 入职日期, status TINYINT DEFAULT 1 COMMENT 1在职 2离职 3试用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT DEFAULT 0 COMMENT 逻辑删除标记, PRIMARY KEY (id), UNIQUE KEY uk_emp_no (emp_no), KEY idx_dept (dept_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT员工表;这段建表语句里有几个设计决策值得展开说。emp_no单独做工号而不是直接用id是因为工号有业务含义比如部门前缀加序号而自增主键只负责唯一标识两者职责分开。status用 TINYINT 而不是 ENUM方便后续扩展状态值也避免 ENUM 改值时要 ALTER TABLE 的麻烦。deleted字段配合 MyBatis-Plus 的逻辑删除删员工时只改标记不物理删除答辩时被问“删错了怎么办”能答上来。部门表和岗位表结构类似核心是id、name、parent_id部门支持树形、sort_order。考勤表记录emp_id、attendance_date、check_in、check_out、status正常/迟到/早退/缺勤。薪资表记录emp_id、month、base_salary、bonus、deduction、actual_salary。这五张表之间的外键关系构成了整个系统的骨架。2.3 用 MyBatis-Plus 代码生成器省掉半天体力活五张表对应五套 Entity、Mapper、Service、Controller手写至少半天。MyBatis-Plus 的代码生成器可以一键生成改改就能用。// 代码生成器配置放在 test 目录下运行一次即可 public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create( jdbc:mysql://localhost:3306/hr_system?useUnicodetruecharacterEncodingutf8, root, your_password) .globalConfig(builder - builder .author(dev) // 作者名随意 .outputDir(System.getProperty(user.dir) /src/main/java) .disableOpenDir()) // 生成后不自动打开文件夹 .packageConfig(builder - builder .parent(com.example.hr) // 包名前缀 .entity(entity) .mapper(mapper) .service(service) .controller(controller)) .strategyConfig(builder - builder .addInclude(employee, department, position, attendance, salary) // 指定要生成的表 .entityBuilder() .enableLombok() // 用 Lombok 省 getter/setter .logicDeleteColumnName(deleted) // 逻辑删除字段 .enableTableFieldAnnotation() // 字段加 TableField .controllerBuilder() .enableRestStyle()) // 生成 RestController .execute(); } }运行前需要确认三件事数据库连接串里的库名和实际一致addInclude里的表名和数据库里完全匹配大小写敏感outputDir指向项目的 Java 源码根目录。生成之后Entity 上的TableName和TableField注解会自动填好逻辑删除字段也会配上TableLogic。这一步做完单表增删改查的接口基本就通了接下来把精力放在业务逻辑和权限上。3. 权限体系与核心业务从登录到请假审批的完整链路权限是人事系统的灵魂也是答辩时最容易被追问的点。一个只有增删改查的系统和加了角色权限控制的系统完成度差一个档次。这一章把 JWT 认证、角色拦截和请假审批流串起来讲。3.1 JWT 登录认证的四个关键步骤前后端分离下登录成功后服务端签发一个 Token后续请求带着它证明身份。整个链路分四步用户提交账号密码、服务端校验并生成 Token、客户端存储并在请求头携带、服务端拦截器解析 Token 并放行或拒绝。// JWT 工具类生成和解析 Token Component public class JwtUtil { // 签名密钥实际项目应从配置中心读取不要硬编码 private static final String SECRET hr-system-secret-key-2024; // Token 有效期2 小时 private static final long EXPIRE 2 * 60 * 60 * 1000L; // 生成 Tokensubject 存用户 idclaim 存角色 public String generate(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) // 角色放进自定义 claim .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } // 解析 Token失败会抛异常由调用方捕获 public Claims parse(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }SECRET硬编码在代码里是实训项目的常见做法但答辩时如果被问到安全性要能说出“生产环境应放在配置中心或环境变量”。EXPIRE设两小时是权衡太短用户频繁重新登录太长 Token 泄露风险大。claim(role, role)把角色写进 Token后续拦截器直接从 Token 里取角色判断不用每次查库。拦截器里做两件事从请求头Authorization取出 Token 并解析把用户 id 和角色塞进ThreadLocal供后续使用然后检查当前请求路径是否在放行名单里登录、注册、静态资源。如果 Token 过期或签名不对返回 401前端收到后跳转登录页。3.2 角色与接口权限的映射表怎么定人事系统一般分三种角色管理员全部权限、部门主管本部门员工查看、请假审批、普通员工查看自己信息、提交请假。权限控制有两种粒度接口级和按钮级。实训项目做到接口级就够按钮级前端配合隐藏即可。接口路径方法管理员部门主管普通员工/api/employee/listGET是本部门否/api/employeePOST是否否/api/employee/{id}DELETE是否否/api/attendance/myGET是是是/api/leave/approvePOST是是否/api/salary/{empId}GET是本部门仅自己这张表定下来之后拦截器里的判断逻辑就清晰了。管理员直接放行部门主管需要额外查一次“目标员工是否属于本部门”普通员工只允许访问带my或路径参数等于自己 id 的接口。这里有个容易忽略的点部门主管查本部门员工时不能只信前端传的deptId要从 Token 里的用户 id 反查数据库拿到部门 id否则改个参数就能越权。3.3 请假审批流的状态机实现请假审批是人事系统里少有的“有状态流转”的业务也是答辩加分项。一个请假单从提交到结束状态变化是待审批 → 已批准 / 已驳回 → 已销假。用状态机约束流转避免出现“已驳回的单子又被批准”这种脏数据。// 请假单状态枚举与流转校验 public enum LeaveStatus { PENDING(0, 待审批), APPROVED(1, 已批准), REJECTED(2, 已驳回), CANCELLED(3, 已销假); private final int code; private final String desc; // 构造和 getter 省略 // 定义允许的状态流转key 是当前状态value 是可达状态集合 private static final MapLeaveStatus, SetLeaveStatus TRANSITIONS Map.of( PENDING, Set.of(APPROVED, REJECTED), APPROVED, Set.of(CANCELLED), REJECTED, Set.of(), CANCELLED, Set.of() ); public static boolean canTransfer(LeaveStatus from, LeaveStatus to) { return TRANSITIONS.getOrDefault(from, Set.of()).contains(to); } }审批接口里先查当前请假单状态调用canTransfer判断是否允许变更不允许就抛业务异常。这样即使前端传了非法状态后端也能兜住。Map.of是 Java 9 的语法如果项目用 Java 8换成HashMap手动 put 即可。状态流转的校验逻辑放在 Service 层Controller 只负责参数接收和结果返回保持职责单一。4. 避坑与排查那些让实训进度归零的常见问题这一章记录的是我在带实训过程中反复见到的翻车现场每一条都按“现象 → 原因 → 解决”展开。提前知道这些能省下大量调试时间。4.1 跨域请求被浏览器拦截Postman 却正常现象前端调接口报 CORS 错误但用 Postman 直接请求同一个 URL 能通。原因浏览器同源策略拦截了跨域响应Postman 没有这个限制。解决在后端加全局跨域配置注意allowedOriginPatterns而不是allowedOrigins后者在携带凭证时会报错。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) // 允许所有来源模式 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) // 允许携带 Cookie/Authorization .maxAge(3600); // 预检请求缓存 1 小时 } }如果用了 Spring Security还要在 Security 配置里显式开启 CORS否则跨域配置不生效。这是最常见的“配了没用”的原因。4.2 逻辑删除后唯一索引冲突现象删除工号为 E001 的员工后想重新添加同工号的员工报唯一键冲突。原因逻辑删除只是把deleted改成 1数据库里那条记录还在uk_emp_no唯一索引仍然生效。解决把唯一索引改成联合索引(emp_no, deleted)但这样只能支持一次删除。更稳妥的做法是删除时把emp_no改写成E001_del_时间戳或者用单独的归档表。实训项目里我一般建议直接物理删除加操作日志简单直接。4.3 分页查询总数不对现象MyBatis-Plus 分页插件返回的total是 0 或者等于当前页条数。原因没有注册分页拦截器或者注册了但顺序不对。解决在配置类里加MybatisPlusInterceptor把PaginationInnerInterceptor加进去并指定数据库类型为 MySQL。Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 分页插件指定数据库类型否则 count 语句可能生成错误 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }4.4 日期字段前后端差 8 小时现象数据库存的入职日期是 2024-03-01前端显示成 2024-02-29 16:00。原因时区不一致数据库用 UTCJava 用 GMT8序列化时没指定时区。解决在application.yml里配spring.jackson.time-zone: GMT8同时 JDBC 连接串加serverTimezoneAsia/Shanghai。两个地方都要改只改一个还会差。4.5 事务不生效导致数据不一致现象新增员工时同时插入考勤初始化记录员工插入成功但考勤插入失败员工却留在库里了。原因方法没有加Transactional或者加了但同类内部调用导致代理失效。解决在 Service 实现类的公开方法上加Transactional(rollbackFor Exception.class)注意rollbackFor要写Exception.class默认只回滚运行时异常。另外Controller 里不要直接调 Mapper所有写操作走 Service。5. 从能跑到能演示接口自测与答辩追问的应对技巧系统跑通只是及格线能在答辩现场稳定演示、接住追问才算完成。这一章讲两个具体技巧用 Knife4j 做接口自测和文档以及提前准备高频追问的答案。5.1 用 Knife4j 生成可交互的接口文档Knife4j 是 Swagger 的增强版界面更友好支持在线调试。引入依赖后加一个配置类就能用。!-- pom.xml 中加依赖版本与 Spring Boot 2.7.x 匹配 -- dependency groupIdcom.github.xiaoymin/groupId artifactIdknife4j-openapi2-spring-boot-starter/artifactId version4.3.0/version /dependencyConfiguration EnableSwagger2WebMvc public class SwaggerConfig { Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(new ApiInfoBuilder() .title(人事管理系统接口文档) .version(1.0) .build()) .select() // 只扫描 controller 包避免把实体类也扫进去 .apis(RequestHandlerSelectors.basePackage(com.example.hr.controller)) .paths(PathSelectors.any()) .build(); } }启动后访问http://localhost:8080/doc.html就能看到所有接口点开可以直接填参数调试。答辩时如果老师要求现场演示用这个页面比 Postman 更直观。注意接口上的ApiOperation注解要写清楚用途参数用ApiParam说明含义这些细节会体现在文档里。5.2 三个高频追问的应对思路根据我观察到的答辩现场下面三个问题出现频率最高提前想清楚答案能明显加分。第一个是“你的权限是怎么控制的”。回答要分层认证用 JWT无状态授权在拦截器里根据角色和请求路径判断数据级权限比如主管只能看本部门在 Service 层用当前用户 id 反查部门再过滤。不要只说“用了 Spring Security”要讲清楚判断逻辑在哪一层。第二个是“如果两个人同时审批同一张请假单会怎样”。这是并发问题。诚实回答当前实现没有加锁可能出现重复审批。改进方案有两种乐观锁请假单加 version 字段更新时比对或悲观锁SELECT ... FOR UPDATE。实训项目里能说出改进方向就够不要求实现。第三个是“员工离职后他的考勤和薪资记录怎么处理”。回答员工状态改为离职不物理删除考勤和薪资记录保留但查询接口默认过滤离职员工如果需要彻底清理走单独的归档流程。这个回答体现了对数据生命周期管理的理解。5.3 演示前的检查清单演示前半小时按这个顺序过一遍能避免大部分现场事故。数据库确认连的是本地库不是测试库且有一条完整的演示数据一个部门、三个员工、一条待审批请假。后端服务启动无报错Knife4j 页面能打开。前端登录后能正常跳转菜单根据角色显示正确。浏览器开两个窗口一个登管理员一个登普通员工演示权限差异。最后把数据库密码、JWT 密钥这些敏感配置从代码里挪到application.yml截图时不会暴露。这套东西做下来一个能演示、能答辩、代码结构清晰的人事管理系统就成型了。我带实训这些年最大的体会是别贪多把五张表、三种角色、一条审批流做扎实比堆十个半成品功能强得多。答辩老师看的是你对自己代码的理解深度不是功能数量。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑