基于Spring Boot与Vue的校企实习管理系统全栈开发实战
2026/7/22 12:23:29 网站建设 项目流程

最近不少计算机专业的同学都在为毕业设计发愁,选题、需求、技术栈、实现……每一步都让人头大。特别是“校企实习管理系统”这类经典选题,看似简单,但要做到功能完整、逻辑清晰、代码规范,还得有实际应用价值,确实需要一套清晰的指导。

本文将为你完整拆解一个“校企实习管理系统”从零到一的开发全过程。你不用再自己绞尽脑汁构思需求,我们将基于一个成熟、通用的业务模型,带你一步步完成环境搭建、数据库设计、核心功能实现、前后端联调,直到最终部署。文章包含完整的代码示例、清晰的配置说明和避坑指南,无论是 Java + Spring Boot 新手,还是希望系统学习项目开发流程的同学,都能直接上手,快速搭建出一个可用于毕设或课程设计的完整项目。

1. 项目背景与核心需求分析

在开始编码之前,我们必须明确系统要解决什么问题,以及为谁服务。一个典型的校企实习管理系统,核心目标是搭建一个连接学校、企业和学生的数字化平台,实现实习流程的线上化、规范化管理。

1.1 系统核心角色与痛点

系统主要涉及三类用户,每类用户都有其核心诉求:

  • 学生:痛点在于寻找实习机会困难、投递简历流程繁琐、实习过程缺乏有效跟踪与指导、实习报告提交与考核不透明。
  • 企业:痛点在于招聘实习生渠道有限、筛选简历工作量大、难以系统化管理实习生入职、在岗及考核全流程。
  • 学校/导师:痛点在于难以掌握学生实习的真实情况(地点、岗位、安全等)、实习过程管理分散、成绩评定缺乏数据支撑、与企业沟通效率低。

1.2 系统核心功能模块

基于以上痛点,我们可以将系统拆解为以下几个核心功能模块:

  1. 权限与用户管理:实现学生、企业、导师、管理员等多角色登录、注册及权限控制。
  2. 实习岗位管理:企业发布、修改、下架实习岗位;学生浏览、搜索、收藏、投递岗位。
  3. 简历与投递管理:学生创建并维护个人简历;查看投递状态(已投递、已查看、通过初筛、面试邀请、已录用、已拒绝)。
  4. 实习过程管理:学生提交周报/月报;企业或导师进行批阅与评分;关键事件记录。
  5. 考核与评价管理:实习结束后,企业对学生进行评价,学生对企业/岗位进行反馈,导师综合评定最终实习成绩。
  6. 公告与消息系统:系统通知、实习相关公告、站内信等。

1.3 技术栈选型说明

为了高效、稳定地实现上述功能,我们选择一套当前企业级开发中主流、生态成熟的技术栈:

  • 后端:Spring Boot 2.7.x。它极大地简化了Spring应用的初始搭建和开发过程,内嵌Tomcat,约定优于配置。
  • 数据库:MySQL 8.0。关系型数据库,适合存储结构化的用户、岗位、投递记录等数据。
  • 持久层框架:MyBatis-Plus。在MyBatis基础上增强,提供了通用的CRUD操作,减少大量SQL编写。
  • 权限控制:Spring Security + JWT (JSON Web Token)。用于接口级别的认证与授权,保障系统安全。
  • 前端:Vue 3 + Element Plus。渐进式JavaScript框架,配合成熟的UI组件库,快速构建美观的管理界面。
  • 项目管理:Maven。用于项目构建和依赖管理。

2. 开发环境准备与项目初始化

“工欲善其事,必先利其器”。在开始写代码前,请确保你的本地开发环境已就绪。

2.1 基础环境安装

请确保你的计算机上已安装以下软件,并配置好环境变量:

  • JDK:版本 8 或 11(推荐11)。安装后通过java -version验证。
  • Maven:版本 3.6+。安装后通过mvn -v验证。
  • MySQL:版本 5.7+(推荐8.0)。安装并启动服务,记住root密码。
  • Node.js:版本 16+(推荐18 LTS)。安装后通过node -vnpm -v验证。
  • IDE:IntelliJ IDEA(后端开发) 和 Visual Studio Code(前端开发)。

2.2 创建Spring Boot后端项目

我们使用 Spring Initializr 快速初始化项目。

  1. 访问 start.spring.io 。
  2. 选择以下配置:
    • Project: Maven Project
    • Language: Java
    • Spring Boot: 2.7.x (选择一个稳定版本,如 2.7.18)
    • Project Metadata:
      • Group:com.example
      • Artifact:internship-system
      • Packaging: Jar
      • Java: 11
  3. Dependencies: 添加Spring Web,Spring Security,MyBatis Framework,MySQL Driver,Lombok
  4. 点击“Generate”下载项目压缩包,解压后用IDEA打开。

2.3 初始化数据库

打开MySQL客户端(如命令行或Navicat),执行以下SQL创建数据库和用户表(后续其他表会在项目启动后通过MyBatis-Plus自动生成)。

-- 创建数据库 CREATE DATABASE IF NOT EXISTS `internship_db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE `internship_db`; -- 用户表(核心基础表) CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` varchar(50) NOT NULL COMMENT '用户名(登录账号)', `password` varchar(255) NOT NULL COMMENT '加密后的密码', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `avatar` varchar(500) DEFAULT NULL COMMENT '头像URL', `role` varchar(20) NOT NULL COMMENT '角色:STUDENT, ENTERPRISE, TEACHER, ADMIN', `status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-正常', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统用户表';

2.4 前端Vue项目初始化

打开终端(命令行),执行以下命令创建Vue项目。

# 使用Vue官方脚手架创建项目 npm create vue@latest internship-system-frontend # 创建过程中,根据提示选择以下特性(按空格选择/取消): # - TypeScript: 否 (为简化,本项目先用JavaScript) # - JSX: 否 # - Vue Router: 是 # - Pinia: 是 (状态管理) # - ESLint: 是 # - Prettier: 是 # 进入项目目录并安装Element Plus和axios cd internship-system-frontend npm install npm install element-plus axios npm install --save-dev sass

3. 后端核心模块设计与实现

后端是整个系统的大脑,负责业务逻辑、数据持久化和API提供。我们将采用分层架构:Controller -> Service -> Mapper (DAO) -> Model。

3.1 项目结构规划

在IDEA中,你的后端项目src/main/java/com/example/internshipsystem目录结构应如下:

src/main/java/com/example/internshipsystem/ ├── InternshipSystemApplication.java // 启动类 ├── config/ // 配置类(安全、跨域等) ├── controller/ // 控制层,接收请求,返回响应 ├── entity/ // 实体类,与数据库表对应 ├── dto/ // 数据传输对象,用于前后端交互 ├── vo/ // 视图对象,用于返回给前端的数据封装 ├── mapper/ // 数据访问层接口(MyBatis Mapper) ├── service/ // 业务逻辑层接口 │ └── impl/ // 业务逻辑层实现 ├── utils/ // 工具类(JWT、加密等) └── common/ // 通用类(常量、异常、结果封装)

3.2 集成MyBatis-Plus与代码生成

首先,在pom.xml中添加MyBatis-Plus依赖,替换掉之前Initializr生成的MyBatis依赖。

<!-- pom.xml --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <!-- 代码生成器依赖(可选,用于快速生成entity, mapper, service) --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.3.1</version> <scope>test</scope> </dependency>

然后,配置application.ymlapplication.properties连接数据库。这里以yml格式为例:

# src/main/resources/application.yml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/internship_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: your_password # 替换为你的MySQL密码 # MyBatis-Plus 配置 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL,开发时开启 global-config: db-config: logic-delete-field: deleted # 全局逻辑删除字段名(如果表中有此字段) logic-delete-value: 1 # 逻辑已删除值 logic-not-delete-value: 0 # 逻辑未删除值

3.3 实现JWT认证与Spring Security配置

认证与授权是管理系统的基石。我们将使用JWT实现无状态认证。

  1. 创建JWT工具类:用于生成和解析Token。
// src/main/java/com/example/internshipsystem/utils/JwtUtil.java @Component public class JwtUtil { private static final String SECRET_KEY = "your-secret-key-change-in-production"; // 生产环境务必更改并妥善保管 private static final long EXPIRATION_TIME = 86400000; // 24小时,单位毫秒 public String generateToken(String username, String role) { Map<String, Object> claims = new HashMap<>(); claims.put("role", role); return Jwts.builder() .setClaims(claims) .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } // ... 其他验证方法 }
  1. 配置Spring Security:自定义登录逻辑和权限过滤。
// src/main/java/com/example/internshipsystem/config/SecurityConfig.java @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() // 禁用CSRF,因为使用JWT无状态 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态会话 .and() .authorizeRequests() .antMatchers("/api/auth/login", "/api/auth/register").permitAll() // 登录注册公开 .antMatchers("/api/student/**").hasRole("STUDENT") // 学生接口需要学生角色 .antMatchers("/api/enterprise/**").hasRole("ENTERPRISE") // 企业接口需要企业角色 .antMatchers("/api/admin/**").hasRole("ADMIN") // 管理员接口 .anyRequest().authenticated() // 其他所有请求都需要认证 .and() .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); // 添加JWT过滤器 return http.build(); } // 注册JWT认证过滤器Bean @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } }

3.4 实现用户登录与注册接口

创建认证相关的Controller、Service。

// src/main/java/com/example/internshipsystem/controller/AuthController.java @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthService authService; @PostMapping("/login") public ResultVO<LoginVO> login(@RequestBody @Valid LoginDTO loginDTO) { LoginVO loginVO = authService.login(loginDTO); return ResultVO.success(loginVO); } @PostMapping("/register") public ResultVO<String> register(@RequestBody @Valid RegisterDTO registerDTO) { authService.register(registerDTO); return ResultVO.success("注册成功"); } }
// src/main/java/com/example/internshipsystem/service/impl/AuthServiceImpl.java @Service public class AuthServiceImpl implements AuthService { @Autowired private SysUserMapper userMapper; @Autowired private PasswordEncoder passwordEncoder; @Autowired private JwtUtil jwtUtil; @Override public LoginVO login(LoginDTO loginDTO) { SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>() .eq(SysUser::getUsername, loginDTO.getUsername())); if (user == null || !passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) { throw new BusinessException("用户名或密码错误"); } if (user.getStatus() == 0) { throw new BusinessException("账号已被禁用"); } // 生成JWT Token String token = jwtUtil.generateToken(user.getUsername(), user.getRole()); LoginVO loginVO = new LoginVO(); loginVO.setToken(token); loginVO.setUserInfo(convertToUserInfoVO(user)); return loginVO; } @Override public void register(RegisterDTO registerDTO) { // 检查用户名是否已存在 Long count = userMapper.selectCount(new LambdaQueryWrapper<SysUser>() .eq(SysUser::getUsername, registerDTO.getUsername())); if (count > 0) { throw new BusinessException("用户名已存在"); } SysUser user = new SysUser(); BeanUtils.copyProperties(registerDTO, user); // 加密密码 user.setPassword(passwordEncoder.encode(registerDTO.getPassword())); user.setRole(registerDTO.getRole()); // STUDENT, ENTERPRISE, TEACHER user.setStatus(1); userMapper.insert(user); } }

4. 核心业务功能实现:以实习岗位管理为例

我们以“实习岗位管理”这个核心业务模块为例,展示从数据库表设计到前后端联调的完整流程。

4.1 数据库表设计

-- 实习岗位表 CREATE TABLE `internship_position` ( `id` bigint NOT NULL AUTO_INCREMENT, `enterprise_id` bigint NOT NULL COMMENT '发布企业ID', `title` varchar(200) NOT NULL COMMENT '岗位标题', `description` text COMMENT '岗位描述', `requirement` text COMMENT '任职要求', `location` varchar(100) DEFAULT NULL COMMENT '工作地点', `category` varchar(50) DEFAULT NULL COMMENT '岗位类别(如技术、市场等)', `recruit_num` int DEFAULT NULL COMMENT '招聘人数', `apply_deadline` date DEFAULT NULL COMMENT '申请截止日期', `status` tinyint DEFAULT '1' COMMENT '状态:0-已下架,1-招聘中,2-已结束', `view_count` int DEFAULT '0' COMMENT '浏览量', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_enterprise` (`enterprise_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='实习岗位表'; -- 岗位投递记录表 CREATE TABLE `position_apply` ( `id` bigint NOT NULL AUTO_INCREMENT, `position_id` bigint NOT NULL, `student_id` bigint NOT NULL, `resume_id` bigint DEFAULT NULL COMMENT '关联的简历ID', `status` varchar(20) DEFAULT 'PENDING' COMMENT '状态:PENDING-待处理, VIEWED-已查看, INTERVIEW-面试, OFFER-录用, REJECT-拒绝', `apply_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_position_student` (`position_id`,`student_id`), -- 防止重复投递 KEY `idx_student` (`student_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='岗位投递记录表';

4.2 后端实体与Mapper

使用MyBatis-Plus,我们只需创建Entity和Mapper接口。

// src/main/java/com/example/internshipsystem/entity/InternshipPosition.java @Data @TableName("internship_position") public class InternshipPosition { @TableId(type = IdType.AUTO) private Long id; private Long enterpriseId; private String title; private String description; private String requirement; private String location; private String category; private Integer recruitNum; private Date applyDeadline; private Integer status; private Integer viewCount; private Date createTime; private Date updateTime; }
// src/main/java/com/example/internshipsystem/mapper/InternshipPositionMapper.java @Mapper public interface InternshipPositionMapper extends BaseMapper<InternshipPosition> { // 复杂的多表查询可以在这里定义XML或注解SQL @Select("SELECT p.*, e.company_name FROM internship_position p LEFT JOIN sys_user e ON p.enterprise_id = e.id WHERE p.status = 1 ORDER BY p.create_time DESC") List<Map<String, Object>> selectPositionWithEnterprise(); }

4.3 业务层与服务实现

创建Service层,处理业务逻辑,如状态校验、数据组装。

// src/main/java/com/example/internshipsystem/service/InternshipPositionService.java public interface InternshipPositionService extends IService<InternshipPosition> { PageVO<PositionVO> getPositionPage(PageDTO pageDTO, PositionQueryDTO queryDTO); PositionDetailVO getPositionDetail(Long id); void publishPosition(PositionPublishDTO publishDTO, Long enterpriseId); void updatePosition(PositionUpdateDTO updateDTO, Long enterpriseId); void applyPosition(Long positionId, Long studentId); }

在实现类中,需要注入Mapper,并处理具体的业务规则,比如企业只能修改自己发布的岗位。

4.4 控制层API设计

设计清晰的RESTful API。

// src/main/java/com/example/internshipsystem/controller/PositionController.java @RestController @RequestMapping("/api/position") public class PositionController { @Autowired private InternshipPositionService positionService; // 分页查询岗位列表(学生/所有人) @GetMapping("/page") public ResultVO<PageVO<PositionVO>> getPositionPage(PageDTO pageDTO, PositionQueryDTO queryDTO) { PageVO<PositionVO> page = positionService.getPositionPage(pageDTO, queryDTO); return ResultVO.success(page); } // 获取岗位详情 @GetMapping("/{id}") public ResultVO<PositionDetailVO> getPositionDetail(@PathVariable Long id) { PositionDetailVO detail = positionService.getPositionDetail(id); return ResultVO.success(detail); } // 企业发布岗位 (需要ENTERPRISE角色) @PostMapping("/publish") @PreAuthorize("hasRole('ENTERPRISE')") public ResultVO<String> publishPosition(@RequestBody @Valid PositionPublishDTO publishDTO, @RequestAttribute("userId") Long enterpriseId) { positionService.publishPosition(publishDTO, enterpriseId); return ResultVO.success("发布成功"); } // 学生投递岗位 (需要STUDENT角色) @PostMapping("/{positionId}/apply") @PreAuthorize("hasRole('STUDENT')") public ResultVO<String> applyPosition(@PathVariable Long positionId, @RequestAttribute("userId") Long studentId) { positionService.applyPosition(positionId, studentId); return ResultVO.success("投递成功"); } }

5. 前端页面开发与联调

前端使用Vue 3 + Element Plus + Axios,构建一个单页面应用(SPA)。

5.1 配置Axios与路由守卫

首先,在src/utils/request.js中创建配置好的axios实例,统一处理请求拦截(添加JWT Token)和响应拦截(处理错误)。

// src/utils/request.js import axios from 'axios'; import { ElMessage } from 'element-plus'; import router from '@/router'; import { useUserStore } from '@/stores/user'; const service = axios.create({ baseURL: 'http://localhost:8080/api', // 后端API地址 timeout: 10000, }); // 请求拦截器 service.interceptors.request.use( (config) => { const userStore = useUserStore(); if (userStore.token) { config.headers['Authorization'] = `Bearer ${userStore.token}`; } return config; }, (error) => { return Promise.reject(error); } ); // 响应拦截器 service.interceptors.response.use( (response) => { const res = response.data; if (res.code === 200) { return res.data; } else { ElMessage.error(res.message || '请求失败'); return Promise.reject(new Error(res.message || 'Error')); } }, (error) => { if (error.response?.status === 401) { ElMessage.error('登录已过期,请重新登录'); router.push('/login'); } else { ElMessage.error(error.message || '网络错误'); } return Promise.reject(error); } ); export default service;

在路由守卫中,检查用户权限。

// src/router/index.js router.beforeEach((to, from, next) => { const userStore = useUserStore(); if (to.meta.requiresAuth && !userStore.token) { next('/login'); } else if (to.meta.roles && !to.meta.roles.includes(userStore.role)) { ElMessage.warning('权限不足'); next(from.path); // 无权限则留在原地 } else { next(); } });

5.2 实习岗位列表页开发

使用Element Plus的表格和分页组件。

<!-- src/views/position/PositionList.vue --> <template> <div class="position-list"> <el-card> <template #header> <div class="card-header"> <span>实习岗位</span> <el-button v-if="userStore.role === 'ENTERPRISE'" type="primary" @click="handlePublish">发布新岗位</el-button> </div> </template> <!-- 搜索区域 --> <el-form :model="queryParams" inline> <el-form-item label="岗位名称"> <el-input v-model="queryParams.title" placeholder="请输入" clearable /> </el-form-item> <el-form-item label="地点"> <el-input v-model="queryParams.location" placeholder="请输入" clearable /> </el-form-item> <el-form-item> <el-button type="primary" @click="handleSearch">搜索</el-button> <el-button @click="handleReset">重置</el-button> </el-form-item> </el-form> <!-- 表格 --> <el-table :data="tableData" v-loading="loading"> <el-table-column prop="title" label="岗位标题" width="200" /> <el-table-column prop="companyName" label="发布企业" width="150" /> <el-table-column prop="location" label="工作地点" width="120" /> <el-table-column prop="category" label="类别" width="100" /> <el-table-column prop="recruitNum" label="招聘人数" width="100" /> <el-table-column prop="applyDeadline" label="截止日期" width="120"> <template #default="{ row }"> {{ formatDate(row.applyDeadline) }} </template> </el-table-column> <el-table-column label="操作" width="180"> <template #default="{ row }"> <el-button link type="primary" @click="handleViewDetail(row.id)">查看详情</el-button> <el-button v-if="userStore.role === 'STUDENT'" link type="success" @click="handleApply(row.id)">立即投递</el-button> </template> </el-table-column> </el-table> <!-- 分页 --> <div class="pagination"> <el-pagination v-model:current-page="pageParams.pageNum" v-model:page-size="pageParams.pageSize" :total="total" layout="total, sizes, prev, pager, next, jumper" @current-change="handlePageChange" @size-change="handleSizeChange" /> </div> </el-card> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { useRouter } from 'vue-router'; import { ElMessage } from 'element-plus'; import { getPositionPage, applyPosition } from '@/api/position'; import { useUserStore } from '@/stores/user'; import { formatDate } from '@/utils/date'; const router = useRouter(); const userStore = useUserStore(); const loading = ref(false); const tableData = ref([]); const total = ref(0); const pageParams = ref({ pageNum: 1, pageSize: 10 }); const queryParams = ref({ title: '', location: '' }); const fetchData = async () => { loading.value = true; try { const res = await getPositionPage({ ...pageParams.value, ...queryParams.value }); tableData.value = res.list; total.value = res.total; } catch (error) { console.error(error); } finally { loading.value = false; } }; const handleSearch = () => { pageParams.value.pageNum = 1; fetchData(); }; const handleReset = () => { queryParams.value = { title: '', location: '' }; handleSearch(); }; const handlePageChange = (val) => { pageParams.value.pageNum = val; fetchData(); }; const handleSizeChange = (val) => { pageParams.value.pageSize = val; pageParams.value.pageNum = 1; fetchData(); }; const handleViewDetail = (id) => { router.push(`/position/detail/${id}`); }; const handleApply = async (positionId) => { try { await applyPosition(positionId); ElMessage.success('投递成功!'); } catch (error) { ElMessage.error(error.message || '投递失败'); } }; const handlePublish = () => { router.push('/position/publish'); }; onMounted(() => { fetchData(); }); </script>

5.3 前后端联调与测试

  1. 启动后端:在IDEA中运行InternshipSystemApplication的main方法。
  2. 启动前端:在终端进入前端项目目录,运行npm run dev
  3. 测试接口:使用浏览器访问http://localhost:5173(Vue默认端口),或使用Postman等工具直接测试后端APIhttp://localhost:8080/api/...
  4. 关键检查点
    • 跨域问题:确保后端已配置CORS,允许前端域名访问。
    • Token传递:检查浏览器开发者工具Network标签,请求头中是否包含Authorization: Bearer <token>
    • 权限控制:尝试用学生账号访问企业发布接口,应返回403无权限错误。
    • 数据一致性:前端表格显示的数据是否与数据库查询结果一致。

6. 常见问题与排查思路

在开发过程中,你可能会遇到以下典型问题。

问题现象可能原因排查步骤与解决方案
前端请求后端接口报4041. 后端服务未启动。
2. 请求URL路径错误。
3. 后端Controller未添加@RestController@RequestMapping
1. 检查IDEA控制台,确保Spring Boot启动成功。
2. 核对前端request.js中的baseURL和后端接口路径。
3. 检查后端Controller类和方法上的注解。
前端请求报403 Forbidden401 Unauthorized1. 接口需要认证但未携带Token。
2. Token已过期。
3. 用户角色权限不足。
1. 检查是否已登录,Token是否被正确设置在请求头中。
2. 查看后端JWT工具类中设置的过期时间,前端可在收到401时跳转登录页。
3. 检查@PreAuthorize注解或Security配置中的角色要求。
数据库连接失败1. MySQL服务未启动。
2.application.yml中数据库配置错误(密码、端口、数据库名)。
3. 驱动版本不匹配。
1. 使用mysql -u root -p测试MySQL连接。
2. 仔细核对yml配置,注意缩进和冒号后的空格。
3. 确认pom.xml中MySQL驱动版本与安装的MySQL版本兼容。
MyBatis-Plus 插入数据失败,主键冲突实体类主键策略@TableId配置错误,或数据库表主键未设置自增。1. 实体类主键字段添加@TableId(type = IdType.AUTO)
2. 检查数据库表DDL,主键字段是否设置了AUTO_INCREMENT
前端页面样式错乱或Element Plus组件不显示1. Element Plus未正确引入。
2. Vue版本与Element Plus版本不兼容。
1. 检查main.js中是否按官方文档正确引入了Element Plus及其样式。
2. 查看package.json,确保安装的element-plus版本支持你的Vue 3版本。
修改代码后前端热更新不生效浏览器缓存或Vite开发服务器问题。1. 尝试硬刷新浏览器(Ctrl+F5)。
2. 重启前端开发服务器npm run dev

7. 项目部署与上线建议

完成开发后,若想将项目部署到服务器,供他人访问或用于毕设演示,可以参考以下步骤。

7.1 后端项目打包与部署

  1. 打包:在项目根目录下执行mvn clean package -DskipTests,会在target目录下生成internship-system-0.0.1-SNAPSHOT.jar
  2. 环境配置:将application.yml中的数据库连接等信息改为生产环境配置(如服务器IP、正式数据库账号密码)。切勿将配置文件中的密码等敏感信息提交到代码仓库,建议使用环境变量或配置中心。
  3. 上传与运行:将jar包上传至Linux服务器(如使用CentOS或Ubuntu)。
    # 在服务器上运行(后台运行并输出日志) nohup java -jar internship-system-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > app.log 2>&1 & # 查看日志 tail -f app.log

7.2 前端项目构建与部署

  1. 构建:在前端项目目录下执行npm run build,会在dist目录生成静态文件。
  2. 部署:将dist文件夹内的所有文件上传到Web服务器(如Nginx、Apache)的网站根目录。
  3. 配置Nginx:如果使用Nginx,需要配置代理,将API请求转发到后端服务。
    # /etc/nginx/conf.d/internship.conf server { listen 80; server_name your-domain.com; # 你的域名或IP # 前端静态资源 location / { root /path/to/your/dist; index index.html; try_files $uri $uri/ /index.html; # 支持Vue Router的history模式 } # 后端API代理 location /api/ { proxy_pass http://localhost:8080; # 后端服务地址 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
    重启Nginx:sudo systemctl restart nginx

7.3 生产环境注意事项

  1. 安全
    • 密码加密:务必使用强哈希算法(如BCrypt)存储用户密码,切勿明文存储。
    • SQL注入:使用MyBatis-Plus等ORM框架,或严格使用参数化查询,避免拼接SQL字符串。
    • XSS与CSRF:前端对用户输入进行转义,后端可根据需要开启CSRF保护(如果不用JWT)。
    • JWT Secret:生产环境的JWT密钥必须足够复杂且妥善保管,定期更换。
  2. 性能
    • 数据库索引:为高频查询条件(如status,enterprise_id)和关联字段建立索引。
    • 接口优化:对于列表查询,务必做好分页,避免一次性拉取大量数据。
    • 静态资源:使用CDN加速前端JS、CSS、图片等资源加载。
  3. 可维护性
    • 日志:配置完整的日志框架(如Logback),记录INFO、WARN、ERROR级别日志,便于排查问题。
    • 监控:考虑集成简单的健康检查接口 (/actuator/health),或使用Prometheus、SkyWalking等监控应用状态。
    • 文档:编写清晰的API接口文档(可使用Swagger/SpringDoc生成)和项目部署文档。

至此,一个具备核心功能的“校企实习管理系统”从需求分析到部署上线的全流程已经走通。你可以在此基础上,继续完善其他模块(如周报管理、评价系统、消息通知),增加更复杂的业务逻辑(如岗位推荐算法),或者优化UI/UX。这个项目不仅能够满足你的毕业设计需求,其采用的分层架构、主流技术栈和开发流程,也是你未来求职时一份非常不错的实战作品。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询