尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

SpringBoot+Vue实习管理系统架构设计与实现 1. 实习管理系统架构解析与实现作为一名长期从事教育信息化系统开发的工程师我深知传统实习管理模式的痛点Excel表格满天飞、邮件来回确认、进度难以追踪。今天分享的这套基于SpringBootVue的实习管理系统正是为了解决这些实际问题而设计的全栈解决方案。系统采用经典的三层架构设计前端Vue.js ElementUI实现响应式管理界面后端SpringBoot 2.7 MyBatis Plus构建RESTful API数据库MySQL 8.0提供稳定数据存储这种技术选型组合在2023年StackOverflow开发者调查中分别位列各自领域使用率前三名其稳定性和社区支持度已经过充分验证。下面我将从数据库设计开始逐步拆解这个系统的实现细节。1.1 核心数据模型设计数据库设计遵循第三范式3NF主要包含6个核心表这里重点解析三个最具业务特色的表结构实习岗位表(job_post)的优化设计CREATE TABLE job_post ( job_id INT NOT NULL AUTO_INCREMENT, job_title VARCHAR(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 岗位名称, company_id INT NOT NULL COMMENT 关联企业表, publish_time DATETIME DEFAULT CURRENT_TIMESTAMP, job_location VARCHAR(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 支持多地点格式: 城市|区县|详细地址, job_requirement TEXT COLLATE utf8mb4_unicode_ci NOT NULL, status TINYINT(1) DEFAULT 0 COMMENT 0-草稿 1-已发布 2-已下线, max_apply INT DEFAULT NULL COMMENT 最大申请人数限制, PRIMARY KEY (job_id), KEY idx_company (company_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;关键设计要点使用utf8mb4字符集支持完整Unicode包括emoji状态字段采用TINYINT而非ENUM便于后续扩展添加复合索引提升查询效率地点信息采用结构化存储便于后续GIS扩展实习申请表(internship_application)的业务约束Entity Table(name internship_application) public class InternshipApplication { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long applyId; ManyToOne JoinColumn(name student_id, nullable false) private Student student; ManyToOne JoinColumn(name job_id, nullable false) private JobPost jobPost; Column(columnDefinition ENUM(PENDING,APPROVED,REJECTED)) private String status; Column(updatable false) private LocalDateTime applyTime LocalDateTime.now(); // 唯一约束防止重复申请 TableConstraint( name uk_student_job, columns {student_id, job_id}, type ConstraintType.UNIQUE ) private void validate() {} }这个设计通过JPA实体注解实现了自动化的时间戳管理多表关联关系映射业务级的唯一约束枚举类型的状态控制1.2 权限控制系统实现系统采用RBAC基于角色的访问控制模型通过Spring Security JWT实现安全的API访问控制。核心配置如下安全配置类Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers(/api/auth/**).permitAll() .antMatchers(/api/student/**).hasRole(STUDENT) .antMatchers(/api/teacher/**).hasRole(TEACHER) .antMatchers(/api/company/**).hasRole(COMPANY) .antMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated() .and() .addFilter(new JwtAuthenticationFilter(authenticationManager())) .addFilter(new JwtAuthorizationFilter(authenticationManager())) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); } }JWT令牌生成逻辑public String generateToken(UserDetails userDetails) { MapString, Object claims new HashMap(); claims.put(roles, userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() JWT_EXPIRATION)) .signWith(SignatureAlgorithm.HS512, JWT_SECRET) .compact(); }安全实践建议令牌有效期设置为2-4小时生产环境使用HTTPS传输令牌实现令牌黑名单机制用于即时注销敏感操作需二次认证2. 前后端交互关键实现2.1 Vuex状态管理设计前端采用Vuex进行复杂状态管理下面是核心store模块划分const store new Vuex.Store({ modules: { auth: { state: () ({ user: null, token: localStorage.getItem(token) || null }), mutations: { SET_TOKEN(state, token) { state.token token localStorage.setItem(token, token) axios.defaults.headers.common[Authorization] Bearer ${token} } } }, internship: { namespaced: true, state: () ({ jobs: [], applications: [] }), actions: { async fetchJobs({ commit }, filters) { const res await api.getJobs(filters) commit(SET_JOBS, res.data) } } } } })2.2 Axios请求拦截器统一处理HTTP请求和响应axios.interceptors.request.use(config { const token store.state.auth.token if (token) { config.headers.Authorization Bearer ${token} } return config }) axios.interceptors.response.use( response response, error { if (error.response.status 401) { store.commit(auth/LOGOUT) router.push(/login) } return Promise.reject(error) } )2.3 文件上传组件实现实习报告上传功能采用分块上传方案template el-upload action/api/upload :before-uploadhandleBeforeUpload :on-progresshandleProgress :on-successhandleSuccess :chunk-size5 * 1024 * 1024 :datauploadData multiple drag i classel-icon-upload/i div classel-upload__text拖拽文件到此处或em点击上传/em/div /el-upload /template script export default { methods: { handleBeforeUpload(file) { const isPDF file.type application/pdf const isLt50M file.size / 1024 / 1024 50 if (!isPDF) { this.$message.error(仅支持PDF格式!) } if (!isLt50M) { this.$message.error(文件大小不能超过50MB!) } return isPDF isLt50M } } } /script3. 典型业务场景实现3.1 实习申请审批工作流采用状态机模式实现申请审批流程public enum ApplicationState { DRAFT { Override public ApplicationState nextState() { return SUBMITTED; } }, SUBMITTED { Override public ApplicationState nextState() { return UNDER_REVIEW; } }, UNDER_REVIEW { Override public ApplicationState nextState() { return APPROVED; // or REJECTED } }, APPROVED { Override public ApplicationState nextState() { return COMPLETED; } }; public abstract ApplicationState nextState(); }对应的审批服务实现Service Transactional public class ApprovalService { Autowired private ApplicationRepository applicationRepo; public void processApproval(Long applicationId, ApprovalCommand command) { Application app applicationRepo.findById(applicationId) .orElseThrow(() - new ResourceNotFoundException(Application not found)); if (command.isApproved()) { app.approve(command.getComment()); sendApprovalNotification(app); } else { app.reject(command.getComment()); sendRejectionNotification(app); } applicationRepo.save(app); } private void sendApprovalNotification(Application app) { // 实现邮件/站内信通知逻辑 } }3.2 实习评价多维统计使用JPA Criteria API实现动态查询public PageEvaluationStats getEvaluationStats(StatsQuery query, Pageable pageable) { CriteriaBuilder cb entityManager.getCriteriaBuilder(); CriteriaQueryEvaluationStats cq cb.createQuery(EvaluationStats.class); RootEvaluation root cq.from(Evaluation.class); ListPredicate predicates new ArrayList(); if (query.getCompanyId() ! null) { predicates.add(cb.equal(root.get(company).get(id), query.getCompanyId())); } if (query.getStartDate() ! null) { predicates.add(cb.greaterThanOrEqualTo(root.get(evaluationTime), query.getStartDate())); } cq.multiselect( root.get(student).get(department), cb.avg(root.get(score)), cb.count(root.get(id)) ).where(predicates.toArray(new Predicate[0])) .groupBy(root.get(student).get(department)); TypedQueryEvaluationStats typedQuery entityManager.createQuery(cq); typedQuery.setFirstResult((int) pageable.getOffset()); typedQuery.setMaxResults(pageable.getPageSize()); ListEvaluationStats result typedQuery.getResultList(); return new PageImpl(result, pageable, result.size()); }4. 部署与性能优化4.1 生产环境部署方案推荐使用Docker Compose进行容器化部署version: 3.8 services: db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: internship MYSQL_USER: ${DB_USER} MYSQL_PASSWORD: ${DB_PASSWORD} volumes: - mysql_data:/var/lib/mysql ports: - 3306:3306 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 5s timeout: 5s retries: 5 backend: build: ./backend depends_on: db: condition: service_healthy environment: SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/internship SPRING_DATASOURCE_USERNAME: ${DB_USER} SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD} ports: - 8080:8080 frontend: build: ./frontend ports: - 80:80 depends_on: - backend volumes: mysql_data:4.2 性能优化实践缓存策略Cacheable(value jobs, key #companyId - #status) public ListJobPost getJobsByCompanyAndStatus(Long companyId, JobStatus status) { return jobRepository.findByCompanyIdAndStatus(companyId, status); } CacheEvict(value jobs, allEntries true) public JobPost updateJob(JobPost job) { return jobRepository.save(job); }数据库查询优化Repository public interface JobRepository extends JpaRepositoryJobPost, Long { EntityGraph(attributePaths {company}) Query(SELECT j FROM JobPost j WHERE j.status :status) ListJobPost findActiveJobsWithCompany(Param(status) JobStatus status); Query(value SELECT j.* FROM job_post j WHERE MATCH(j.job_title, j.job_requirement) AGAINST(:keywords IN BOOLEAN MODE), nativeQuery true) PageJobPost fullTextSearch(Param(keywords) String keywords, Pageable pageable); }5. 常见问题排查问题1前端打包后API请求404检查vue.config.js中的devServer.proxy配置确保Nginx配置了正确的反向代理规则location /api { proxy_pass http://backend:8080; proxy_set_header Host $host; }问题2SpringBoot应用启动时报数据库连接失败确认application.yml中的连接参数正确检查MySQL用户权限GRANT ALL ON internship.* TO user%验证网络连通性telnet db_host 3306问题3Vue页面刷新后路由丢失配置Nginx处理前端路由的fallbacklocation / { try_files $uri $uri/ /index.html; }这套系统在实际部署时建议根据具体业务需求进行以下扩展增加Elasticsearch实现更强大的搜索功能集成WebSocket实现实时通知添加Prometheus监控指标实现Excel导入导出功能我在实际部署过程中发现合理配置MySQL的连接池参数可以显著提升并发性能。以下是我的生产环境配置建议spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000
返回列表