Spring Boot 2.x + Vue 2 教务系统课设:37个接口与12张表的设计与实现
·
Spring Boot 2.x + Vue 2 教务系统全栈开发实战:从37个接口到12张表的技术精要
教务管理系统作为高校信息化建设的核心组件,其技术实现涉及前后端协同、数据模型设计、业务逻辑处理等多个维度。本文将从一个真实的课程设计项目出发,深度剖析基于Spring Boot 2.x和Vue 2的全栈开发实践,重点解读12张表的关系模型设计和37个核心接口的实现策略。
1. 技术架构设计与选型考量
全栈开发的技术选型需要平衡学习曲线、社区支持和项目需求。这套技术组合的选择背后有着明确的工程考量:
-
后端技术栈 :
- Spring Boot 2.4.5:提供自动配置、起步依赖等开箱即用特性
- MyBatis-Plus 3.4.2:简化CRUD操作,内置代码生成器
- Spring Security + JWT:实现基于令牌的身份认证
- Hutool 5.7.3:Java工具包,简化各种操作
-
前端技术栈 :
- Vue 2.6.12:渐进式框架,组件化开发
- Element UI 2.15.1:提供丰富的PC端组件
- Axios 0.21.1:处理HTTP请求
- Vue Router 3.5.1:前端路由管理
// Spring Boot主启动类示例
@SpringBootApplication
@MapperScan("com.edusystem.mapper")
public class EduApplication {
public static void main(String[] args) {
SpringApplication.run(EduApplication.class, args);
}
}
技术选型提示:对于学生课程设计项目,建议选择文档丰富、社区活跃的稳定版本,避免使用过新或过时的技术版本。
2. 数据库设计的艺术:12张表的精妙构思
教务系统的数据模型需要准确反映业务实体及其关系。经过多次迭代,最终确定的12张表形成了完整的业务闭环:
| 表类型 | 表名称 | 核心功能描述 |
|---|---|---|
| 基础信息表 | administrator | 存储管理员账号和基本信息 |
| teacher | 教师信息及所属院系 | |
| student | 学生基本信息及班级归属 | |
| 课程业务表 | course_application | 记录教师发起的课程申请 |
| course_examination | 存储审批状态类型 | |
| course | 课程基本信息及当前状态 | |
| course_status | 定义课程生命周期状态 | |
| 系统配置表 | course_switch | 控制选课系统的开关状态 |
| department | 院系组织结构信息 | |
| place | 教室等教学场地信息 | |
| 关系映射表 | courses_students | 学生选课关系及成绩记录 |
| 日志记录表 | operation | 记录关键操作日志 |
核心关系模型设计要点 :
- 教师与课程:一对多关系(一个教师可申请多门课程)
- 学生与课程:多对多关系(通过courses_students关联表实现)
- 课程状态机:通过course_status_id外键实现状态流转
- 数据一致性:使用外键约束保证关联数据的完整性
-- 课程申请表DDL示例
CREATE TABLE `course_application` (
`course_application_id` int NOT NULL AUTO_INCREMENT,
`teacher_id` int DEFAULT NULL COMMENT '外键教师id',
`course_id` int DEFAULT NULL COMMENT '外键课程id',
`course_name` varchar(45) DEFAULT NULL COMMENT '课程名称',
`course_credit` varchar(5) DEFAULT NULL COMMENT '课程学分',
`course_hour` varchar(5) DEFAULT NULL COMMENT '课程学时',
`course_time` varchar(45) DEFAULT NULL COMMENT '上课时间',
`course_place_id` int DEFAULT NULL COMMENT '外键上课地点id',
`course_description` text COMMENT '课程描述',
`course_examination_id` int NOT NULL COMMENT '外键课程审批id',
`operation_id` int NOT NULL COMMENT '外键操作id',
`date_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间',
PRIMARY KEY (`course_application_id`),
KEY `fk_teacher_idx` (`teacher_id`),
CONSTRAINT `fk_teacher` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 接口设计的工程实践:37个API的智慧
基于RESTful规范设计的37个接口覆盖了系统全部业务场景,主要分为以下几类:
3.1 认证与权限接口组
// JWT认证过滤器核心代码
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
try {
String username = Jwts.parser()
.setSigningKey(jwtSecret)
.parseClaimsJws(token)
.getBody()
.getSubject();
if (username != null && SecurityContextHolder.getContext()
.getAuthentication() == null) {
UserDetails userDetails = userDetailsService
.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null,
userDetails.getAuthorities());
authentication.setDetails(
new WebAuthenticationDetailsSource()
.buildDetails(request));
SecurityContextHolder.getContext()
.setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("JWT令牌验证失败: {}", e.getMessage());
}
}
chain.doFilter(request, response);
}
}
3.2 核心业务接口设计
课程审批状态机实现 :
@RestController
@RequestMapping("/api/course-approval")
public class CourseApprovalController {
@Autowired
private CourseService courseService;
@PostMapping("/approve/{applicationId}")
@PreAuthorize("hasRole('ADMIN')")
public Result approveApplication(
@PathVariable Integer applicationId,
@RequestParam(required = false) String comment) {
CourseApplication application = courseService
.getApplicationById(applicationId);
if (application == null) {
return Result.error("申请记录不存在");
}
// 状态流转:待审批 -> 已通过
if (courseService.approveApplication(applicationId)) {
// 自动创建课程记录
Course newCourse = convertToCourse(application);
courseService.createCourse(newCourse);
return Result.success("审批通过,课程已创建");
}
return Result.error("审批操作失败");
}
private Course convertToCourse(CourseApplication application) {
Course course = new Course();
BeanUtils.copyProperties(application, course);
course.setCourseStatusId(COURSE_STATUS_PENDING);
return course;
}
}
典型接口清单 :
-
用户认证接口
- POST /api/auth/login - 用户登录
- POST /api/auth/logout - 用户登出
- PUT /api/auth/password - 修改密码
-
人员管理接口
- GET /api/students - 分页查询学生
- POST /api/students - 新增学生
- PUT /api/students/{id} - 更新学生信息
- DELETE /api/students - 批量删除学生
-
课程管理接口
- POST /api/courses/apply - 教师申请课程
- GET /api/courses/applications - 审批列表
- PUT /api/courses/arrange - 排课操作
- POST /api/courses/select - 学生选课
4. 关键业务逻辑的实现策略
4.1 选课系统的并发控制
选课高峰期可能出现多个学生同时抢课的情况,需要特别处理并发问题:
@Service
public class CourseSelectionServiceImpl implements CourseSelectionService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Transactional
@Override
public Result selectCourse(Integer studentId, Integer courseId) {
// 使用Redis分布式锁防止超选
String lockKey = "course:lock:" + courseId;
boolean locked = false;
try {
locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!locked) {
return Result.error("操作太频繁,请稍后重试");
}
Course course = courseMapper.selectById(courseId);
if (course == null || course.getIsDelete() == 1) {
return Result.error("课程不存在或已被删除");
}
// 检查选课开关状态
if (!courseSwitchService.isSelectionOpen()) {
return Result.error("当前不是选课时间");
}
// 检查是否已选
if (courseSelectionMapper.existsSelection(studentId, courseId)) {
return Result.error("您已选择该课程");
}
// 执行选课操作
CourseSelection selection = new CourseSelection();
selection.setStudentId(studentId);
selection.setCourseId(courseId);
selection.setSelectionTime(new Date());
courseSelectionMapper.insert(selection);
return Result.success("选课成功");
} finally {
if (locked) {
redisTemplate.delete(lockKey);
}
}
}
}
4.2 事务管理的最佳实践
课程审批涉及多表操作,必须保证事务一致性:
@Service
public class CourseApprovalServiceImpl implements CourseApprovalService {
@Autowired
private CourseApplicationMapper applicationMapper;
@Autowired
private CourseMapper courseMapper;
@Autowired
private OperationLogMapper logMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public boolean approveApplication(Integer applicationId,
Integer adminId, String comment) {
// 1. 更新申请状态
CourseApplication application = applicationMapper
.selectById(applicationId);
if (application == null) {
throw new BusinessException("申请记录不存在");
}
application.setCourseExaminationId(APPROVED_STATUS);
applicationMapper.updateById(application);
// 2. 创建课程记录
Course course = new Course();
BeanUtils.copyProperties(application, course);
course.setCourseStatusId(COURSE_STATUS_PENDING_ARRANGEMENT);
courseMapper.insert(course);
// 3. 记录操作日志
OperationLog log = new OperationLog();
log.setOperationId(APPROVE_OPERATION);
log.setAdminId(adminId);
log.setTargetId(applicationId);
log.setComment(comment);
logMapper.insert(log);
return true;
}
}
5. 前端工程化与组件设计
Vue前端项目采用模块化组织方式,核心功能组件包括:
src/
├── api/ # 接口封装
│ ├── auth.js # 认证相关API
│ ├── course.js # 课程管理API
│ └── student.js # 学生管理API
├── components/ # 公共组件
│ ├── Pagination.vue # 分页组件
│ └── TableToolbar.vue # 表格操作栏
├── router/ # 路由配置
├── store/ # Vuex状态管理
├── utils/ # 工具函数
└── views/
├── admin/ # 管理员视图
├── teacher/ # 教师视图
└── student/ # 学生视图
典型组件实现 - 课程表格分页查询 :
<template>
<div class="course-table">
<el-table :data="tableData" v-loading="loading">
<el-table-column prop="courseName" label="课程名称"></el-table-column>
<el-table-column prop="teacherName" label="授课教师"></el-table-column>
<el-table-column prop="credit" label="学分"></el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button
size="small"
@click="handleSelect(scope.row)"
:disabled="!selectionOpen">
选课
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</template>
<script>
import { getAvailableCourses } from '@/api/course';
import { mapGetters } from 'vuex';
export default {
data() {
return {
tableData: [],
loading: false,
currentPage: 1,
pageSize: 10,
total: 0
};
},
computed: {
...mapGetters(['selectionOpen'])
},
created() {
this.fetchData();
},
methods: {
async fetchData() {
this.loading = true;
try {
const res = await getAvailableCourses({
page: this.currentPage,
size: this.pageSize
});
this.tableData = res.data.list;
this.total = res.data.total;
} finally {
this.loading = false;
}
},
handleSizeChange(val) {
this.pageSize = val;
this.fetchData();
},
handleCurrentChange(val) {
this.currentPage = val;
this.fetchData();
},
handleSelect(row) {
this.$emit('select', row);
}
}
};
</script>
6. 项目部署与性能优化
6.1 后端优化策略
- 缓存应用 :对课程列表等高频查询使用Redis缓存
- SQL优化 :为常用查询字段添加索引
- 连接池配置 :合理设置Druid连接池参数
# application.yml部分配置
spring:
datasource:
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
6.2 前端优化方案
- 路由懒加载 :拆分代码块,按需加载
- API请求节流 :防止重复提交
- 组件异步加载 :提升首屏渲染速度
// 路由懒加载配置
const routes = [
{
path: '/courses',
component: () => import('@/views/CourseList.vue')
},
{
path: '/courses/:id',
component: () => import('@/views/CourseDetail.vue')
}
];
7. 项目演进与扩展思考
已完成的基础版本可考虑以下扩展方向:
-
微服务化改造 :
- 将用户服务、课程服务、选课服务拆分为独立微服务
- 引入Spring Cloud Alibaba生态
-
数据分析模块 :
- 集成ECharts实现选课数据可视化
- 添加课程评价统计分析
-
移动端适配 :
- 开发基于Vant的H5版本
- 实现微信小程序版本
-
自动化测试 :
- 后端集成JUnit5 + Mockito
- 前端引入Jest单元测试
// 示例:使用JUnit5进行服务层测试
@ExtendWith(MockitoExtension.class)
class CourseServiceTest {
@Mock
private CourseMapper courseMapper;
@InjectMocks
private CourseServiceImpl courseService;
@Test
@DisplayName("测试课程创建-成功场景")
void testCreateCourseSuccess() {
Course course = new Course();
course.setCourseName("软件工程");
when(courseMapper.insert(any())).thenReturn(1);
boolean result = courseService.createCourse(course);
assertTrue(result);
verify(courseMapper).insert(course);
}
}
在真实项目开发中,我们发现MyBatis-Plus的Lambda查询方式能显著提高代码可读性,而前端采用集中式状态管理(Vuex)对于中等复杂度的教务系统已经足够。当遇到选课高峰期时,Redis的分布式锁方案比数据库乐观锁表现更为稳定。
更多推荐



所有评论(0)