ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot+Vue3实习生管理系统开发实践

SpringBoot+Vue3实习生管理系统开发实践 1. 项目概述最近在帮朋友公司开发实习生管理系统时基于SpringBootVue3MyBatis技术栈实现了一套前后端分离的解决方案。这个系统从需求分析到最终上线历时3个月期间踩了不少坑也积累了一些值得分享的经验。传统实习生管理通常依赖Excel表格和邮件往来不仅效率低下还容易出现数据不一致的问题。我们设计的系统实现了实习生全生命周期管理包括信息录入、考勤记录、任务分配、绩效评估等核心功能。系统上线后人力资源部门的工作效率提升了60%以上实习生满意度调查显示90%的用户对系统体验表示满意。2. 技术架构设计2.1 整体架构方案系统采用经典的三层架构设计前端Vue3 Element Plus Axios后端SpringBoot 2.7 MyBatis Plus数据库MySQL 8.0部署Docker Nginx选择前后端分离架构主要基于以下考虑开发效率前后端可以并行开发接口定义好后互不干扰性能优化静态资源单独部署减轻服务器压力技术栈灵活性前端可以随时升级框架而不影响后端2.2 关键技术选型后端技术栈SpringBoot简化配置快速启动MyBatis Plus强大的CRUD操作和条件构造器HutoolJava工具包处理各种常见操作JWT无状态认证方案Redis缓存热点数据和会话管理前端技术栈Vue3组合式API开发体验更好Pinia状态管理替代VuexElement PlusUI组件库ECharts数据可视化AxiosHTTP请求库3. 数据库设计详解3.1 核心表结构设计实习生信息表(trainee_info)CREATE TABLE trainee_info ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, name varchar(50) NOT NULL COMMENT 姓名, gender char(1) DEFAULT NULL COMMENT 性别, phone varchar(20) NOT NULL COMMENT 手机号, email varchar(50) DEFAULT NULL COMMENT 邮箱, school varchar(100) DEFAULT NULL COMMENT 学校, major varchar(50) DEFAULT NULL COMMENT 专业, department_id int DEFAULT NULL COMMENT 部门ID, position varchar(50) DEFAULT NULL COMMENT 实习岗位, status tinyint DEFAULT 0 COMMENT 状态(0-待入职,1-实习中,2-已离职), entry_date date DEFAULT NULL COMMENT 入职日期, leave_date date DEFAULT NULL COMMENT 离职日期, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone), KEY idx_department (department_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT实习生信息表;考勤记录表(attendance_record)CREATE TABLE attendance_record ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, trainee_id bigint NOT NULL COMMENT 实习生ID, check_in_time datetime DEFAULT NULL COMMENT 签到时间, check_out_time datetime DEFAULT NULL COMMENT 签退时间, status tinyint DEFAULT 0 COMMENT 状态(0-正常,1-迟到,2-早退,3-缺勤), remark varchar(200) DEFAULT NULL COMMENT 备注, record_date date NOT NULL COMMENT 记录日期, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_trainee_date (trainee_id,record_date), KEY idx_date (record_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT考勤记录表;3.2 索引设计原则所有表必须有主键优先使用自增ID频繁查询的条件字段建立普通索引唯一性约束建立唯一索引联合索引遵循最左前缀原则避免在更新频繁的字段上建索引4. 后端核心实现4.1 SpringBoot应用配置application.yml关键配置server: port: 8080 servlet: context-path: /api spring: datasource: url: jdbc:mysql://localhost:3306/trainee_manage?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 minimum-idle: 5 redis: host: localhost port: 6379 password: database: 0 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted # 逻辑删除字段 logic-delete-value: 1 # 逻辑已删除值 logic-not-delete-value: 0 # 逻辑未删除值4.2 权限控制实现基于RBAC模型设计权限系统核心类结构// 用户实体 Data public class User { private Long id; private String username; private String password; private String salt; private Integer status; private ListRole roles; } // 角色实体 Data public class Role { private Long id; private String name; private String code; private ListPermission permissions; } // 权限实体 Data public class Permission { private Long id; private String name; private String code; private String url; private String method; } // 自定义注解 Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface RequiresPermissions { String[] value(); Logical logical() default Logical.AND; }权限校验拦截器实现public class PermissionInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 获取请求路径和方法 String requestURI request.getRequestURI(); String method request.getMethod(); // 获取用户权限列表 User user (User) request.getSession().getAttribute(user); SetString permissions user.getPermissions(); // 校验权限 if (!permissions.contains(method : requestURI)) { response.sendError(HttpStatus.FORBIDDEN.value(), 无权限访问); return false; } return true; } }5. 前端关键实现5.1 Vue3项目结构src/ ├── api/ # API请求 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── composables/ # 组合式函数 ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── styles/ # 全局样式 ├── utils/ # 工具函数 ├── views/ # 页面组件 ├── App.vue # 根组件 └── main.js # 入口文件5.2 动态路由实现根据用户权限动态生成路由// 路由配置 const routes [ { path: /, component: Layout, children: [ { path: , name: Dashboard, component: () import(/views/dashboard.vue), meta: { title: 首页, icon: home, requiresAuth: true } }, // 其他路由... ] } ] // 动态添加路由 function addDynamicRoutes() { const userStore useUserStore() const permissionRoutes userStore.permissions.map(permission { return { path: permission.path, name: permission.name, component: () import(/views/${permission.component}.vue), meta: { title: permission.title, icon: permission.icon, requiresAuth: true } } }) router.addRoute({ path: /, component: Layout, children: permissionRoutes }) }5.3 考勤打卡组件template div classattendance-card el-card template #header div classcard-header span考勤打卡/span el-tag :typestatusTag.type{{ statusTag.text }}/el-tag /div /template div classcard-body el-button typeprimary :disabled!canCheckIn clickhandleCheckIn 上班打卡 /el-button el-button typesuccess :disabled!canCheckOut clickhandleCheckOut 下班打卡 /el-button div classtime-display p v-ifcheckInTime上班时间: {{ formatTime(checkInTime) }}/p p v-ifcheckOutTime下班时间: {{ formatTime(checkOutTime) }}/p /div /div /el-card /div /template script setup import { ref, computed, onMounted } from vue import { ElMessage } from element-plus import { checkIn, checkOut, getTodayRecord } from /api/attendance const checkInTime ref(null) const checkOutTime ref(null) const attendanceStatus ref(0) const statusMap { 0: { text: 未打卡, type: info }, 1: { text: 已签到, type: success }, 2: { text: 已签退, type: warning }, 3: { text: 迟到, type: danger }, 4: { text: 早退, type: danger } } const statusTag computed(() { return statusMap[attendanceStatus.value] }) const canCheckIn computed(() { return !checkInTime.value }) const canCheckOut computed(() { return checkInTime.value !checkOutTime.value }) onMounted(async () { await loadTodayRecord() }) async function loadTodayRecord() { try { const res await getTodayRecord() checkInTime.value res.data.checkInTime checkOutTime.value res.data.checkOutTime attendanceStatus.value res.data.status } catch (error) { console.error(error) } } async function handleCheckIn() { try { const res await checkIn() checkInTime.value res.data.checkInTime attendanceStatus.value res.data.status ElMessage.success(打卡成功) } catch (error) { ElMessage.error(error.message) } } async function handleCheckOut() { try { const res await checkOut() checkOutTime.value res.data.checkOutTime attendanceStatus.value res.data.status ElMessage.success(签退成功) } catch (error) { ElMessage.error(error.message) } } function formatTime(time) { return new Date(time).toLocaleTimeString() } /script6. 系统部署方案6.1 生产环境部署架构----------------- | Nginx | | (负载均衡/反向代理) | ---------------- | -------------------------------- | | ------------------ ------------------ | SpringBoot App | | SpringBoot App | | (Docker) | | (Docker) | ------------------- ------------------- | | ------------------ ------------------ | MySQL | | Redis | | (主从复制) | | (集群) | ------------------- -------------------6.2 Docker部署配置后端DockerfileFROM openjdk:11-jre-slim WORKDIR /app COPY target/trainee-manage.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]前端DockerfileFROM nginx:alpine WORKDIR /usr/share/nginx/html COPY dist . COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80docker-compose.ymlversion: 3.8 services: mysql: image: mysql:8.0 container_name: mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: trainee_manage ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql restart: always redis: image: redis:6.2 container_name: redis ports: - 6379:6379 volumes: - redis_data:/data restart: always backend: build: ./backend container_name: backend ports: - 8080:8080 environment: - SPRING_DATASOURCE_URLjdbc:mysql://mysql:3306/trainee_manage - SPRING_DATASOURCE_USERNAMEroot - SPRING_DATASOURCE_PASSWORDroot123 - SPRING_REDIS_HOSTredis depends_on: - mysql - redis restart: always frontend: build: ./frontend container_name: frontend ports: - 80:80 depends_on: - backend restart: always volumes: mysql_data: redis_data:7. 开发经验与优化建议7.1 性能优化实践接口响应优化启用MyBatis二级缓存使用Redis缓存热点数据批量操作代替循环单条处理合理使用Transactional注解前端加载优化路由懒加载组件异步加载图片压缩和CDN加速启用Gzip压缩数据库优化建立合适的索引避免SELECT * 查询使用连接池控制连接数定期执行ANALYZE TABLE7.2 常见问题解决方案问题1MyBatis查询结果映射失败解决方案检查实体类字段名与数据库列名是否一致使用TableField注解指定映射关系确认是否开启了驼峰命名转换检查SQL语句中的AS别名问题2Vue3响应式数据更新但视图不更新解决方案确保使用ref或reactive声明响应式数据对于数组操作使用push等变更方法或重新赋值对于对象新增属性使用Vue.set或重新赋值检查是否在异步回调中正确更新数据问题3跨域问题后端解决方案Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .maxAge(3600); } }前端解决方案// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } } })8. 项目扩展方向移动端适配开发微信小程序版本使用uniapp跨端方案实现扫码签到功能智能分析实习生能力雷达图考勤异常自动检测任务完成预测分析流程自动化自动生成实习证明邮件通知自动化考核报告自动生成多租户支持SAAS化改造租户数据隔离自定义字段支持在实际开发过程中最大的挑战是权限系统的设计和性能优化。特别是在处理复杂的数据关联查询时需要特别注意N1查询问题。通过合理使用MyBatis的关联查询和缓存机制最终将关键接口的响应时间控制在200ms以内。
返回列表