1. 项目概述
最近在帮一家中型互联网公司搭建实习生管理系统,发现市面上很多现成方案要么功能臃肿,要么扩展性差。于是基于SpringBoot+Vue技术栈重新设计了一套轻量级解决方案,目前已在生产环境稳定运行半年多。这个系统最核心的价值在于:用最精简的代码实现了实习生全生命周期管理,从入职到离职的所有关键环节都能高效处理。
系统采用经典的三层架构设计:
- 前端:Vue 3 + Element Plus构建响应式界面
- 后端:Spring Boot 2.7 + MyBatis Plus实现业务逻辑
- 数据库:MySQL 8.0提供数据存储
特别在权限控制方面做了深度优化,RBAC模型结合JWT认证,确保不同角色(HR、部门主管、实习生)只能访问对应功能模块。系统日均处理300+考勤记录,性能测试QPS达到1200+,完全能满足200人规模企业的管理需求。
2. 核心功能设计
2.1 多维度实习生档案
实习生信息表设计时特别考虑了扩展性:
CREATE TABLE `trainee` ( `id` varchar(20) NOT NULL COMMENT '学号+入职年份生成', `name` varchar(50) NOT NULL, `gender` char(1) DEFAULT NULL, `id_card` varchar(18) DEFAULT NULL COMMENT '加密存储', `school` varchar(100) NOT NULL, `major` varchar(50) NOT NULL, `education` tinyint NOT NULL COMMENT '1本科 2硕士 3博士', `mentor_id` int DEFAULT NULL COMMENT '导师员工ID', `department_id` int NOT NULL, `entry_date` date NOT NULL, `status` tinyint NOT NULL DEFAULT '1' COMMENT '0离职 1在职', PRIMARY KEY (`id`), KEY `idx_department` (`department_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4关键设计点:
- 主键采用"学校学号+入职年份"组合生成(如ZJU_20231234_2023),避免单纯自增ID的业务耦合
- 身份证号等敏感信息使用AES加密存储
- 建立部门索引加速查询,实测数据量10万时查询速度提升8倍
2.2 智能考勤模块
考勤表设计支持多种打卡方式:
public class Attendance { private Long id; private String traineeId; private LocalDateTime checkIn; // 支持自动定位打卡 private LocalDateTime checkOut; private Integer lateMinutes; // 自动计算迟到分钟 private Integer earlyMinutes; // 早退分钟 private Integer status; // 0正常 1迟到 2早退 3缺勤 private String location; // GPS坐标/WiFi定位 private String deviceId; // 防代打卡 }实现细节:
- 采用Redis GEO处理位置校验,误差范围50米内视为有效
- 考勤异常自动触发邮件通知(使用Spring Mail)
- 支持批量导入导出Excel(EasyExcel实现)
3. 技术实现关键点
3.1 前后端分离架构
后端API设计遵循RESTful规范:
@RestController @RequestMapping("/api/attendance") public class AttendanceController { @GetMapping("/{id}") public Result<AttendanceVO> getDetail(@PathVariable String id) { // 参数校验逻辑 } @PostMapping public Result<String> create(@Valid @RequestBody AttendanceDTO dto) { // 业务处理 } @GetMapping("/stats") public Result<AttendanceStatsVO> getStats( @RequestParam String department, @RequestParam @DateTimeFormat(pattern="yyyy-MM") String month) { // 统计逻辑 } }前端采用Axios封装请求:
const api = axios.create({ baseURL: import.meta.env.VITE_API_URL, timeout: 10000, headers: { 'Authorization': `Bearer ${getToken()}` } }) // 请求拦截器 api.interceptors.request.use(config => { if (!config.headers['Authorization']) { config.headers['Authorization'] = `Bearer ${getToken()}` } return config })3.2 性能优化实践
- 缓存策略:
@Cacheable(value = "trainee", key = "#id") public Trainee getById(String id) { return traineeMapper.selectById(id); } @CacheEvict(value = "trainee", key = "#trainee.id") public void updateTrainee(Trainee trainee) { traineeMapper.updateById(trainee); }- 数据库查询优化:
<select id="selectWithDepartment" resultMap="TraineeResultMap"> SELECT t.*, d.name as department_name FROM trainee t LEFT JOIN department d ON t.department_id = d.id WHERE t.status = 1 <if test="departmentId != null"> AND t.department_id = #{departmentId} </if> ORDER BY t.entry_date DESC LIMIT #{pageSize} OFFSET #{offset} </select>4. 部署与运维方案
4.1 生产环境配置
Nginx反向代理配置示例:
upstream backend { server 127.0.0.1:8080; keepalive 32; } server { listen 80; server_name hr.example.com; location /api { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; } location / { root /var/www/frontend; try_files $uri $uri/ /index.html; } }4.2 监控方案
- Spring Boot Actuator配置:
management: endpoints: web: exposure: include: health,info,metrics endpoint: health: show-details: always metrics: enabled: true- Prometheus监控指标采集:
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() { return registry -> registry.config().commonTags( "application", "intern-system" ); }5. 踩坑经验总结
- 跨域问题解决方案:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("*") .maxAge(3600); } }- MyBatis批量插入优化:
@Transactional public void batchInsert(List<Trainee> list) { SqlSession session = sqlSessionTemplate.getSqlSessionFactory() .openSession(ExecutorType.BATCH, false); try { TraineeMapper mapper = session.getMapper(TraineeMapper.class); for (Trainee trainee : list) { mapper.insert(trainee); } session.commit(); } finally { session.close(); } }- 前端性能优化技巧:
// 使用虚拟滚动处理大数据列表 <el-table :data="tableData" style="width: 100%" height="500" row-key="id" :row-height="60" :virtual-scroll="true"> <!-- 列定义 --> </el-table>这套系统从设计到上线共迭代了3个版本,最大的体会是:合理的领域建模比技术选型更重要。比如最初将考勤和绩效强耦合,导致后期扩展困难,重构为独立模块后才实现灵活配置。建议在开发初期就做好领域划分,避免后期大规模返工。