ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SpringBoot+Vue实习生管理系统架构与实现

SpringBoot+Vue实习生管理系统架构与实现 1. 实习生管理系统架构解析这个基于SpringBootVue的实习生管理系统采用了典型的前后端分离架构后端通过RESTful API提供服务前端通过Axios消费这些API。这种架构的最大优势在于前后端可以并行开发且前端可以灵活适配多种终端Web、移动端等。后端技术栈选择SpringBoot 2.7.x版本这是一个经过充分验证的稳定版本。我们特别使用了MyBatis-Plus而非原生MyBatis因为它提供了更多开箱即用的功能比如自动分页、逻辑删除、性能分析等可以显著减少样板代码量。例如对于实习岗位的CRUD操作MyBatis-Plus的BaseMapper已经提供了基础实现public interface PostMapper extends BaseMapperPost { // 无需编写基础CRUD方法 // 自定义复杂查询 Select(SELECT * FROM intern_post WHERE post_status #{status}) ListPost selectByStatus(Param(status) Integer status); }前端选用Vue3Element Plus的组合Vue3的Composition API相比Options API更适合大型应用的状态管理。我们通过Pinia进行全局状态管理比Vuex更轻量且对TypeScript支持更好。动态路由的实现基于后端返回的权限树这样不同角色管理员、企业导师、学生登录后会看到完全不同的菜单结构。2. 核心数据模型设计2.1 实习岗位模型post_id采用Snowflake算法生成分布式ID避免自增ID的安全问题。post_status使用TINYINT而非字符串枚举存储效率更高CREATE TABLE intern_post ( post_id bigint NOT NULL COMMENT 雪花算法ID, post_title varchar(50) COLLATE utf8mb4_bin NOT NULL, company_id bigint NOT NULL, skill_require text COLLATE utf8mb4_bin, post_status tinyint DEFAULT 0 COMMENT 0-草稿 1-已发布 2-已下线, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (post_id), KEY idx_company (company_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_bin;注意技能要求字段使用TEXT类型而非VARCHAR因为不同岗位的技能描述长度差异很大。同时建立了company_id的索引加速企业维度的查询。2.2 实习申请模型审核流程状态机设计是关键我们使用状态模式来实现public interface ApplyState { void handle(InternApply apply); } Component public class PendingState implements ApplyState { Override public void handle(InternApply apply) { // 待审核状态下的业务逻辑 } } Component public class ApprovedState implements ApplyState { Override public void handle(InternApply apply) { // 审核通过后的业务逻辑 // 如生成实习记录、发送通知等 } }2.3 周报评价模型导师评分采用DECIMAL(3,1)类型支持一位小数点的0.0-10.0评分。为避免频繁查询周报表冗余存储了学生姓名和企业名称ALTER TABLE weekly_report ADD COLUMN student_name VARCHAR(20) GENERATED ALWAYS AS ( SELECT student_name FROM student_info WHERE student_id weekly_report.student_id ) STORED;3. 权限控制系统实现3.1 RBAC模型设计系统采用标准RBAC基于角色的访问控制模型包含五个核心表sys_user用户基础表sys_role角色表sys_menu菜单权限表sys_user_role用户角色关联sys_role_menu角色权限关联权限验证通过Spring Security的注解实现PreAuthorize(ss.hasPermi(intern:apply:audit)) PostMapping(/audit) public Result auditApply(RequestBody AuditDTO dto) { // 审核逻辑 }3.2 JWT安全方案采用JJWT库实现Token签发和验证。特别注意处理了刷新Token的机制public class TokenService { // 访问Token有效期2小时 private static final long ACCESS_EXPIRE 2 * 60 * 60; // 刷新Token有效期7天 private static final long REFRESH_EXPIRE 7 * 24 * 60 * 60; public String createAccessToken(LoginUser user) { return Jwts.builder() .setSubject(user.getUsername()) .claim(roles, user.getRoles()) .setExpiration(new Date(System.currentTimeMillis() ACCESS_EXPIRE * 1000)) .signWith(Keys.hmacShaKeyFor(secret.getBytes())) .compact(); } }4. 关键业务逻辑实现4.1 实习分配算法采用基于标签匹配的推荐算法将学生技能与岗位要求进行相似度计算public ListPostVO recommendPosts(Long studentId) { Student student studentMapper.selectById(studentId); ListPost posts postMapper.selectList(new QueryWrapperPost() .eq(post_status, 1)); return posts.stream() .map(post - { double score calculateSimilarity( student.getSkills(), post.getSkillRequire()); return new PostVO(post, score); }) .sorted(Comparator.comparing(PostVO::getScore).reversed()) .limit(10) .collect(Collectors.toList()); }4.2 考勤异常检测使用滑动窗口算法检测连续异常考勤def detect_abnormal_attendance(records, window_size5, threshold3): abnormalities [] for i in range(len(records) - window_size 1): window records[i:i window_size] if sum(1 for r in window if not r.is_normal) threshold: abnormalities.append({ start: window[0].date, end: window[-1].date }) return abnormalities5. 性能优化实践5.1 缓存策略采用多级缓存方案本地Caffeine缓存高频访问的字典数据Redis缓存热点业务数据数据库查询结果缓存Cacheable(value post, key #postId, unless #result null) public Post getPostById(Long postId) { return postMapper.selectById(postId); } CacheEvict(value post, key #postId) public void updatePost(Post post) { postMapper.updateById(post); }5.2 SQL优化案例实习列表查询的优化前后对比-- 优化前全表扫描 SELECT * FROM intern_post WHERE post_title LIKE %开发%; -- 优化后索引覆盖 ALTER TABLE intern_post ADD FULLTEXT INDEX ft_idx_title (post_title); SELECT * FROM intern_post WHERE MATCH(post_title) AGAINST(开发 IN BOOLEAN MODE);6. 典型问题排查记录6.1 N1查询问题在获取实习列表时原始代码会导致N1查询// 错误示例 ListPost posts postMapper.selectList(null); posts.forEach(post - { Company company companyMapper.selectById(post.getCompanyId()); post.setCompany(company); });解决方案1使用MyBatis-Plus的TableField注解Data public class Post { TableField(exist false) private Company company; } // Mapper中自定义查询 Select(SELECT p.*, c.* FROM intern_post p LEFT JOIN company c ON p.company_id c.company_id) ListPost selectPostsWithCompany();解决方案2使用ResultMap实现结果集映射resultMap idpostWithCompany typePost id propertypostId columnpost_id/ result propertypostTitle columnpost_title/ association propertycompany javaTypeCompany id propertycompanyId columncompany_id/ result propertycompanyName columncompany_name/ /association /resultMap6.2 事务失效场景以下情况会导致Transactional失效public class ApplyService { // 自调用导致事务失效 public void auditApply(Long applyId) { updateApplyStatus(applyId); this.sendNotification(); // 内部调用不走代理 } Transactional public void sendNotification() { // 通知逻辑 } }修正方案1将方法拆分到不同Service 修正方案2通过AopContext获取代理对象((ApplyService) AopContext.currentProxy()).sendNotification();7. 部署与监控方案7.1 Docker部署配置后端服务的Dockerfile示例FROM openjdk:11-jre WORKDIR /app COPY target/intern-system.jar . EXPOSE 8080 ENTRYPOINT [java, -jar, intern-system.jar, --spring.profiles.activeprod]使用docker-compose编排version: 3 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} volumes: - mysql_data:/var/lib/mysql redis: image: redis:6-alpine ports: - 6379:6379 backend: build: ./backend ports: - 8080:8080 depends_on: - mysql - redis volumes: mysql_data:7.2 监控指标采集通过Micrometer暴露SpringBoot指标Bean MeterRegistryCustomizerPrometheusMeterRegistry metricsCommonTags() { return registry - registry.config().commonTags( application, intern-system, region, ${spring.cloud.azure.region} ); }Grafana监控看板配置关键指标JVM内存/线程数接口QPS/耗时数据库连接池使用率Redis命中率8. 前端工程化实践8.1 组件化设计按功能划分的组件结构src/ ├── components/ │ ├── layout/ # 布局组件 │ ├── post/ # 岗位相关 │ ├── apply/ # 申请管理 │ └── report/ # 周报管理 ├── composables/ # 组合式函数 ├── stores/ # Pinia状态管理 └── views/ # 路由页面动态组件加载示例const loadComponent (name) defineAsyncComponent(() import(./components/${name}.vue) )8.2 权限指令实现自定义v-permission指令app.directive(permission, { mounted(el, binding) { const { value } binding const roles store.getters.roles if (value !roles.some(role value.includes(role))) { el.parentNode?.removeChild(el) } } }) // 使用方式 button v-permission[admin,mentor]审核/button9. 项目扩展方向9.1 工作流集成使用Activiti集成实习审批流程process idintern_approval name实习审批流程 startEvent idstart/ userTask idmentor_review name导师初审/ userTask idhr_confirm nameHR确认/ exclusiveGateway iddecision/ endEvent idend/ sequenceFlow sourceRefstart targetRefmentor_review/ sequenceFlow sourceRefmentor_review targetRefhr_confirm/ sequenceFlow sourceRefhr_confirm targetRefdecision/ sequenceFlow sourceRefdecision targetRefend conditionExpression xsi:typetFormalExpression ${approved} /conditionExpression /sequenceFlow /process9.2 大数据分析使用Flink实时处理考勤数据StreamExecutionEnvironment env StreamExecutionEnvironment.getExecutionEnvironment(); DataStreamAttendanceEvent events env .addSource(new KafkaSource()) .keyBy(AttendanceEvent::getStudentId) .process(new AttendanceAnalyzer()); public static class AttendanceAnalyzer extends KeyedProcessFunctionLong, AttendanceEvent, Alert { private ValueStateAttendancePattern state; Override public void processElement(AttendanceEvent event, Context ctx, CollectorAlert out) { // 异常考勤模式检测逻辑 } }10. 开发经验总结MyBatis-Plus字段映射实体类中使用TableField(typeHandler JacksonTypeHandler.class)处理JSON字段时必须同时在mapper.xml中配置jdbcTypeVARCHAR否则更新操作会报类型转换错误。Vue3响应式陷阱使用reactive包装的对象在解构后会失去响应性推荐使用ref配合.value访问或者使用toRefs保持响应式// 错误示例 const state reactive({ count: 0 }) let { count } state // 失去响应性 // 正确做法 const count ref(0) // 或 const { count } toRefs(state)Spring事务传播批量操作时默认的REQUIRED传播行为可能导致长事务。对于非核心业务如操作日志记录建议使用REQUIRES_NEWTransactional(propagation Propagation.REQUIRES_NEW) public void saveOperationLog(OperationLog log) { // 日志记录逻辑 }前端性能优化对于大型数据表格使用虚拟滚动技术。Element Plus的Table组件通过设置height属性和use-virtual可以轻松实现el-table :datatableData height500px row-keyid :virtual-scrolltrue !-- 列定义 -- /el-table
返回列表