1. 项目概述
这个导师选择管理系统是我去年为某高校计算机学院开发的一个实际项目,主要解决学生在导师双选过程中的信息不对称问题。系统基于SpringBoot+Vue前后端分离架构开发,包含了学生端、导师端和管理员端三个角色模块。最核心的功能是实现了双向选择机制,学生可以查看导师的研究方向和剩余名额,导师也能浏览学生的基本情况和研究兴趣,双方通过系统完成匹配。
提示:这类学术管理系统在开发时特别要注意数据权限的控制,比如学生只能看到自己所在学院的导师信息,导师也只能操作自己名下的学生数据。
2. 系统架构设计
2.1 技术选型分析
后端采用SpringBoot 2.7 + MyBatis Plus组合,主要考虑了以下几个因素:
- 快速开发:SpringBoot的自动配置和起步依赖可以大幅减少XML配置
- 数据安全:整合Spring Security实现基于角色的访问控制
- 接口规范:使用Swagger生成API文档,前后端协作更高效
- 性能考虑:MyBatis Plus的AR模式简化了单表操作,复杂查询则用XML映射
前端选用Vue 3 + Element Plus,主要优势在于:
- 组件化开发方便复用UI元素
- 响应式设计适配不同终端
- Axios拦截器统一处理HTTP请求
- Vue Router实现前端路由权限控制
2.2 数据库设计关键点
系统核心表包括:
sql复制CREATE TABLE `t_teacher` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL COMMENT '导师姓名',
`title` varchar(20) DEFAULT NULL COMMENT '职称',
`max_stu` int DEFAULT '5' COMMENT '最大指导学生数',
`current_stu` int DEFAULT '0' COMMENT '当前学生数',
`research_area` varchar(255) DEFAULT NULL COMMENT '研究方向',
`profile` text COMMENT '个人简介',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `selection_relation` (
`id` bigint NOT NULL AUTO_INCREMENT,
`stu_id` bigint NOT NULL,
`teacher_id` bigint NOT NULL,
`status` tinyint DEFAULT '0' COMMENT '0-待确认 1-已接受 2-已拒绝',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_stu_teacher` (`stu_id`,`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别注意的点:
- 导师表设置了最大指导学生数和当前学生数两个字段,便于名额控制
- 选择关系表建立了学生和导师的唯一索引,避免重复选择
- 状态字段使用枚举值表示不同的选择阶段
3. 核心功能实现
3.1 双向选择流程实现
选择流程的状态机设计:
java复制public enum SelectionStatus {
PENDING(0, "待确认"),
ACCEPTED(1, "已接受"),
REJECTED(2, "已拒绝");
// 省略构造方法和getter
}
@Service
public class SelectionServiceImpl implements SelectionService {
@Transactional
public Result applySelection(Long stuId, Long teacherId) {
// 检查是否已存在申请记录
if (selectionMapper.exists(
new QueryWrapper<SelectionRelation>()
.eq("stu_id", stuId)
.eq("teacher_id", teacherId))) {
return Result.fail("请勿重复申请");
}
// 检查导师名额是否已满
Teacher teacher = teacherMapper.selectById(teacherId);
if (teacher.getCurrentStu() >= teacher.getMaxStu()) {
return Result.fail("该导师名额已满");
}
// 创建申请记录
SelectionRelation relation = new SelectionRelation();
relation.setStuId(stuId);
relation.setTeacherId(teacherId);
relation.setStatus(SelectionStatus.PENDING.getCode());
selectionMapper.insert(relation);
return Result.success();
}
}
3.2 即时通讯模块
系统集成了WebSocket实现站内信功能,关键实现步骤:
- 配置WebSocket端点
java复制@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("*")
.withSockJS();
}
}
- 消息处理控制器
java复制@Controller
public class MessageController {
@MessageMapping("/chat")
@SendToUser("/queue/messages")
public MessageResponse handleMessage(
@Payload MessageRequest request,
Principal principal) {
// 保存消息到数据库
messageService.saveMessage(
request.getContent(),
principal.getName(),
request.getReceiverId());
return new MessageResponse("消息发送成功");
}
}
- 前端订阅实现
javascript复制const connectWebSocket = () => {
const socket = new SockJS('/ws');
const stompClient = Stomp.over(socket);
stompClient.connect({}, () => {
stompClient.subscribe('/user/queue/messages', (message) => {
const msg = JSON.parse(message.body);
// 处理接收到的消息
});
});
};
4. 系统安全设计
4.1 权限控制实现
使用Spring Security进行角色权限管理:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/teacher/**").hasRole("TEACHER")
.antMatchers("/student/**").hasRole("STUDENT")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
4.2 数据权限过滤
通过MyBatis拦截器实现数据行级权限:
java复制@Intercepts({
@Signature(type= Executor.class, method="query",
args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class DataPermissionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取当前用户角色
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String role = auth.getAuthorities().iterator().next().getAuthority();
// 修改查询参数
Object parameter = invocation.getArgs()[1];
if (parameter instanceof Map) {
Map<?,?> map = (Map<?,?>) parameter;
if ("STUDENT".equals(role)) {
map.put("studentId", auth.getName());
} else if ("TEACHER".equals(role)) {
map.put("teacherId", auth.getName());
}
}
return invocation.proceed();
}
}
5. 部署与性能优化
5.1 多环境配置
使用Spring Profile管理不同环境配置:
yaml复制# application-dev.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/teacher_selection_dev
username: devuser
password: devpass
# application-prod.yml
server:
port: 80
spring:
datasource:
url: jdbc:mysql://prod-db:3306/teacher_selection
username: produser
password: prodpass
hikari:
maximum-pool-size: 20
5.2 缓存策略
使用Redis缓存热门导师数据:
java复制@Service
public class TeacherServiceImpl implements TeacherService {
@Cacheable(value = "teachers", key = "#deptId")
public List<TeacherVO> getByDepartment(Long deptId) {
// 数据库查询逻辑
}
@CacheEvict(value = "teachers", key = "#teacher.deptId")
public void updateTeacher(Teacher teacher) {
// 更新逻辑
}
}
6. 常见问题解决方案
6.1 并发选择问题
使用数据库乐观锁解决并发选择:
java复制public Result acceptSelection(Long relationId) {
SelectionRelation relation = selectionMapper.selectById(relationId);
Teacher teacher = teacherMapper.selectById(relation.getTeacherId());
if (teacher.getCurrentStu() >= teacher.getMaxStu()) {
return Result.fail("名额已满");
}
// 乐观锁更新
int updated = teacherMapper.updateCurrentStu(
teacher.getId(),
teacher.getCurrentStu() + 1,
teacher.getVersion());
if (updated == 0) {
throw new OptimisticLockingFailureException("导师数据已被修改,请刷新重试");
}
// 更新选择状态
relation.setStatus(SelectionStatus.ACCEPTED.getCode());
selectionMapper.updateById(relation);
return Result.success();
}
6.2 大文件上传优化
采用分片上传解决大文件(如导师上传资料)问题:
java复制@PostMapping("/upload")
public Result upload(@RequestParam("file") MultipartFile file,
@RequestParam("chunkNumber") int chunkNumber,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("identifier") String identifier) {
// 创建临时目录
String tempDir = "/tmp/upload/" + identifier;
new File(tempDir).mkdirs();
// 保存分片
String chunkFilename = chunkNumber + ".part";
file.transferTo(new File(tempDir, chunkFilename));
// 如果是最后一个分片则合并文件
if (chunkNumber == totalChunks) {
mergeFiles(tempDir, originalFilename);
}
return Result.success();
}
7. 项目扩展方向
在实际使用过程中,我们发现系统还可以在以下方面进行增强:
-
智能推荐功能:基于学生的研究兴趣和导师的研究方向,使用协同过滤算法推荐匹配度高的导师
-
多维度评价体系:增加学生对导师的评价功能,包括指导频率、学术水平等指标
-
移动端适配:开发微信小程序版本,方便师生随时查看和操作
-
数据分析看板:为管理员提供可视化数据分析,展示各学院导师选择分布情况
这个项目从需求分析到最终上线历时3个月,最大的收获是深入理解了教育领域信息系统的特殊需求,比如学术数据的敏感性、选择流程的公平性要求等。如果重新设计,我会在初期就引入更完善的日志系统和操作审计功能,这在后期排查问题时非常重要。