ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于SSM框架的医院招聘考试管理系统开发实践

基于SSM框架的医院招聘考试管理系统开发实践 1. 项目概述作为一名有多年Java全栈开发经验的工程师最近完成了一个基于SSM框架的医院招聘考试管理系统。这个系统旨在解决医院招聘过程中考试报名、信息管理、权限控制等一系列痛点问题。在实际开发过程中我采用了Spring BootMyBatisVue的技术栈实现了前后端分离的架构设计。这个系统特别适合计算机相关专业的同学作为毕业设计选题因为它涵盖了企业级应用开发的完整流程包括需求分析、架构设计、数据库建模、前后端开发、系统测试等环节。对于想学习Java企业级开发的同学来说这个项目具有很强的参考价值。2. 系统架构设计2.1 技术选型分析在项目启动阶段我经过多方比较最终确定了以下技术栈后端技术Spring Boot 2.7.x简化了Spring应用的初始搭建和开发过程MyBatis-Plus 3.5.x强大的ORM框架简化了数据库操作Shiro 1.10.x负责系统的认证和授权Lombok通过注解简化POJO类的编写前端技术Vue 3.x渐进式JavaScript框架Element Plus基于Vue 3的UI组件库Axios处理HTTP请求Vue Router实现前端路由数据库MySQL 8.0关系型数据库Redis缓存高频访问数据选择这些技术主要基于以下考虑Spring Boot的自动配置和起步依赖大大简化了项目配置MyBatis-Plus提供了强大的CRUD操作和代码生成器Vue 3的Composition API使代码组织更清晰MySQL作为成熟的关系型数据库社区支持完善2.2 系统架构设计系统采用典型的三层架构表示层Web层 ↓ 业务逻辑层Service层 ↓ 数据访问层DAO层具体实现上我采用了前后端分离的架构前端架构基于Vue CLI搭建项目骨架使用Vuex进行状态管理按功能模块划分组件采用RESTful API与后端交互后端架构Spring Boot作为应用容器MyBatis-Plus处理数据持久化自定义统一异常处理全局响应封装多环境配置支持这种架构的优势在于前后端职责分离开发效率高接口定义清晰便于团队协作前端可以独立部署灵活性高后端服务无状态易于扩展3. 核心功能实现3.1 用户认证模块用户认证是系统的核心功能之一我采用了Shiro框架来实现// Shiro配置类 Configuration public class ShiroConfig { Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean new ShiroFilterFactoryBean(); factoryBean.setSecurityManager(securityManager); // 设置登录页面 factoryBean.setLoginUrl(/login); // 设置未授权页面 factoryBean.setUnauthorizedUrl(/unauthorized); MapString, String filterChainMap new LinkedHashMap(); // 配置不会被拦截的链接 filterChainMap.put(/static/**, anon); filterChainMap.put(/login, anon); // 其他请求需要认证 filterChainMap.put(/**, authc); factoryBean.setFilterChainDefinitionMap(filterChainMap); return factoryBean; } // 配置自定义Realm Bean public UserRealm userRealm() { return new UserRealm(); } }认证流程说明用户提交用户名和密码系统调用Realm进行认证认证通过后生成Token并返回给客户端后续请求携带Token进行鉴权3.2 考试报名模块考试报名是系统的核心业务功能主要涉及以下表设计CREATE TABLE exam ( id bigint NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 考试名称, start_time datetime NOT NULL COMMENT 开始时间, end_time datetime NOT NULL COMMENT 结束时间, max_candidates int DEFAULT NULL COMMENT 最大考生数, description text COMMENT 考试描述, status tinyint DEFAULT 0 COMMENT 状态0-未开始 1-进行中 2-已结束, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE registration ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 用户ID, exam_id bigint NOT NULL COMMENT 考试ID, register_time datetime NOT NULL COMMENT 报名时间, status tinyint DEFAULT 0 COMMENT 状态0-待审核 1-已通过 2-已拒绝, PRIMARY KEY (id), UNIQUE KEY uk_user_exam (user_id,exam_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;报名业务逻辑实现Service Transactional public class RegistrationServiceImpl implements RegistrationService { Autowired private RegistrationMapper registrationMapper; Autowired private ExamMapper examMapper; Override public Result register(Long userId, Long examId) { // 检查考试是否存在 Exam exam examMapper.selectById(examId); if(exam null) { return Result.fail(考试不存在); } // 检查是否已报名 LambdaQueryWrapperRegistration queryWrapper new LambdaQueryWrapper(); queryWrapper.eq(Registration::getUserId, userId) .eq(Registration::getExamId, examId); if(registrationMapper.selectCount(queryWrapper) 0) { return Result.fail(已报名该考试); } // 检查报名时间 LocalDateTime now LocalDateTime.now(); if(now.isBefore(exam.getStartTime()) || now.isAfter(exam.getEndTime())) { return Result.fail(不在报名时间内); } // 检查名额 if(exam.getMaxCandidates() ! null) { int registeredCount registrationMapper.selectCount( new LambdaQueryWrapperRegistration() .eq(Registration::getExamId, examId) ); if(registeredCount exam.getMaxCandidates()) { return Result.fail(报名人数已满); } } // 创建报名记录 Registration registration new Registration(); registration.setUserId(userId); registration.setExamId(examId); registration.setRegisterTime(now); registration.setStatus(0); // 待审核 if(registrationMapper.insert(registration) 0) { return Result.success(报名成功); } return Result.fail(报名失败); } }3.3 权限管理模块系统采用RBAC基于角色的访问控制模型主要表结构如下CREATE TABLE role ( id bigint NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 角色名称, code varchar(50) NOT NULL COMMENT 角色编码, description varchar(200) DEFAULT NULL COMMENT 描述, PRIMARY KEY (id), UNIQUE KEY uk_code (code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE permission ( id bigint NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 权限名称, code varchar(50) NOT NULL COMMENT 权限编码, url varchar(200) DEFAULT NULL COMMENT 权限URL, type tinyint NOT NULL COMMENT 类型1-菜单 2-按钮 3-API, parent_id bigint DEFAULT NULL COMMENT 父权限ID, PRIMARY KEY (id), UNIQUE KEY uk_code (code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE user_role ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, role_id bigint NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_user_role (user_id,role_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE role_permission ( id bigint NOT NULL AUTO_INCREMENT, role_id bigint NOT NULL, permission_id bigint NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_role_permission (role_id,permission_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;权限验证通过自定义Shiro Realm实现public class UserRealm extends AuthorizingRealm { Autowired private UserService userService; Autowired private RoleService roleService; Autowired private PermissionService permissionService; // 认证 Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken upToken (UsernamePasswordToken) token; String username upToken.getUsername(); User user userService.findByUsername(username); if(user null) { throw new UnknownAccountException(用户不存在); } if(user.getStatus() 0) { throw new LockedAccountException(账号已被禁用); } return new SimpleAuthenticationInfo( user, // principal user.getPassword(), // hashedCredentials ByteSource.Util.bytes(user.getSalt()), // credentialsSalt getName() // realmName ); } // 授权 Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { User user (User) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info new SimpleAuthorizationInfo(); // 获取用户角色 ListRole roles roleService.findByUserId(user.getId()); SetString roleCodes roles.stream() .map(Role::getCode) .collect(Collectors.toSet()); info.setRoles(roleCodes); // 获取用户权限 ListPermission permissions permissionService.findByUserId(user.getId()); SetString permissionCodes permissions.stream() .map(Permission::getCode) .collect(Collectors.toSet()); info.setStringPermissions(permissionCodes); return info; } }4. 系统特色与优化4.1 性能优化实践在实际开发中我针对系统性能做了以下优化缓存策略使用Redis缓存高频访问数据如菜单权限、系统配置实现二级缓存MyBatis二级缓存 Redis分布式缓存缓存雪崩防护设置随机过期时间Service public class ExamServiceImpl implements ExamService { Autowired private ExamMapper examMapper; Autowired private RedisTemplateString, Object redisTemplate; private static final String EXAM_CACHE_KEY exam:; private static final long CACHE_EXPIRE 30 new Random().nextInt(30); // 30-60分钟随机过期 Override Cacheable(value exam, key #id) public Exam getById(Long id) { // 先查Redis String key EXAM_CACHE_KEY id; Exam exam (Exam) redisTemplate.opsForValue().get(key); if(exam ! null) { return exam; } // Redis没有则查数据库 exam examMapper.selectById(id); if(exam ! null) { redisTemplate.opsForValue().set( key, exam, CACHE_EXPIRE, TimeUnit.MINUTES ); } return exam; } }SQL优化合理设计索引避免SELECT *只查询需要的字段复杂查询使用JOIN优化大数据量查询使用分页前端性能优化组件懒加载路由懒加载使用keep-alive缓存组件图片懒加载4.2 安全防护措施系统安全是重中之重我实现了以下安全措施XSS防护前端使用vue-xss过滤用户输入后端对特殊字符进行转义CSRF防护使用Spring Security的CSRF防护重要操作使用POST请求SQL注入防护使用MyBatis预编译语句禁止拼接SQL密码安全使用Shiro的盐值加密密码强度校验public class PasswordUtils { private static final String ALGORITHM_NAME SHA-256; private static final int HASH_ITERATIONS 3; public static String encrypt(String password, String salt) { return new SimpleHash( ALGORITHM_NAME, password, ByteSource.Util.bytes(salt), HASH_ITERATIONS ).toHex(); } public static boolean match(String plainPassword, String salt, String encryptedPassword) { String newEncryptedPassword encrypt(plainPassword, salt); return newEncryptedPassword.equals(encryptedPassword); } public static String generateSalt() { return UUID.randomUUID().toString().replaceAll(-, ).substring(0, 20); } }5. 开发经验分享5.1 项目开发流程在开发这个系统时我遵循了标准的软件开发流程需求分析阶段与医院人事部门沟通明确业务流程绘制用例图和流程图编写详细的需求文档设计阶段数据库设计ER图接口设计Swagger文档页面原型设计开发阶段搭建开发环境按模块分工开发每日代码审查测试阶段单元测试JUnit集成测试Postman系统测试Selenium部署阶段CI/CD流水线搭建多环境配置监控系统集成5.2 常见问题解决在开发过程中遇到的一些典型问题及解决方案问题1MyBatis动态SQL复杂查询解决方案使用MyBatis-Plus的Wrapper构建动态查询条件public PageExam search(ExamQuery query, Pageable pageable) { LambdaQueryWrapperExam wrapper new LambdaQueryWrapper(); if(StringUtils.isNotBlank(query.getName())) { wrapper.like(Exam::getName, query.getName()); } if(query.getStatus() ! null) { wrapper.eq(Exam::getStatus, query.getStatus()); } if(query.getStartTime() ! null) { wrapper.ge(Exam::getStartTime, query.getStartTime()); } if(query.getEndTime() ! null) { wrapper.le(Exam::getEndTime, query.getEndTime()); } return examMapper.selectPage(new Page(pageable.getPageNumber(), pageable.getPageSize()), wrapper); }问题2Vue组件间通信复杂解决方案父子组件使用props/$emit兄弟组件使用事件总线或Vuex深层嵌套组件使用provide/inject问题3跨域问题解决方案配置Spring Boot跨域支持Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .maxAge(3600); } }6. 项目部署指南6.1 环境准备服务器要求CPU2核以上内存4GB以上磁盘50GB以上操作系统Linux推荐CentOS 7软件依赖JDK 1.8MySQL 5.7Redis 5.0Nginx 1.186.2 后端部署步骤打包项目mvn clean package -DskipTests上传jar包到服务器创建启动脚本start.sh#!/bin/bash nohup java -jar hospital-recruitment.jar --spring.profiles.activeprod app.log 21 启动应用chmod x start.sh ./start.sh6.3 前端部署步骤构建生产环境代码npm run build配置Nginxserver { listen 80; server_name yourdomain.com; location / { root /path/to/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }重启Nginxnginx -s reload7. 项目扩展方向这个系统还有很大的扩展空间以下是一些可能的扩展方向考试监控功能实时监控考生答题情况异常行为检测如切屏次数在线考试功能题库管理随机组卷自动阅卷数据分析功能考生成绩分析考试通过率统计岗位竞争分析移动端适配开发微信小程序响应式设计优化微服务改造按功能拆分为微服务引入Spring Cloud生态服务注册与发现这个医院招聘考试管理系统作为我的毕业设计项目不仅帮助我巩固了Java全栈开发技能也让我对软件开发的完整流程有了更深入的理解。在开发过程中遇到的每一个问题都是宝贵的学习机会希望这个项目也能为其他同学提供参考和启发。
返回列表