1. 项目背景与技术选型

每到毕业季,高校都会面临一个共同的难题:如何高效管理数百名学生的毕业设计选题工作。传统的人工登记方式不仅效率低下,还容易出现信息错漏。这正是我们选择开发毕业设计选题管理系统的初衷——用技术手段解决实际教学管理痛点。

在技术选型上,我们采用了当前主流的全栈开发方案:

  • 后端:Spring Boot 2.7 + MyBatis-Plus
  • 前端:Vue 3 + Element Plus
  • 数据库:MySQL 8.0

这个组合的优势非常明显:Spring Boot的自动配置让我们能快速搭建后端服务,Vue的响应式特性让前端开发事半功倍,而MyBatis-Plus则大大简化了数据库操作。记得第一次用MyBatis-Plus的Lambda查询时,我被它的简洁惊艳到了——原本需要写十几行的SQL查询,现在只需要几行Java代码就能搞定。

2. 系统架构设计

整个系统采用经典的三层架构,但我们在传统架构上做了一些优化:

前端层(Vue3)
│
├─ 展示组件
├─ 路由管理
└─ 状态管理(Pinia)

后端层(Spring Boot)
│
├─ 控制层(Controller)
├─ 服务层(Service)
└─ 数据访问层(Mapper)

数据层
│
├─ MySQL(主数据库)
└─ Redis(缓存)

权限控制是这类系统的核心难点。我们采用RBAC(基于角色的访问控制)模型,定义了三种角色:

  • 管理员:拥有系统全部权限
  • 教师:可以发布和管理课题
  • 学生:只能申请和查看课题

在实现上,我们用了Spring Security结合JWT(JSON Web Token)的方案。这里有个小技巧:在生成Token时,我们把用户角色信息直接编码进去,这样前端就能根据Token动态渲染菜单权限。

3. 数据库设计要点

数据库设计往往决定了系统的扩展性和性能。我们的ER图包含以下几个核心表:

CREATE TABLE `user` (
  `id` varchar(32) NOT NULL COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码',
  `role` enum('ADMIN','TEACHER','STUDENT') NOT NULL COMMENT '角色',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `topic` (
  `id` varchar(32) NOT NULL COMMENT '课题ID',
  `title` varchar(200) NOT NULL COMMENT '课题名称',
  `description` text COMMENT '课题描述',
  `teacher_id` varchar(32) NOT NULL COMMENT '指导教师ID',
  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态:0-待审核 1-已发布 2-已选定',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_teacher` (`teacher_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

特别注意这几个设计决策:

  1. 所有表都使用varchar(32)作为主键,而不是自增ID,这样更利于分布式部署
  2. 建立合适的索引(如用户名的唯一索引)
  3. 使用枚举类型明确角色取值范围

4. 核心功能实现

4.1 课题发布流程

教师发布课题的接口实现很有代表性:

@PostMapping("/topics")
@PreAuthorize("hasRole('TEACHER')")
public Result publishTopic(@RequestBody TopicDTO dto, 
                         @RequestHeader("Authorization") String token) {
    // 从Token中解析教师ID
    String teacherId = JwtUtil.getUserId(token);
    
    Topic topic = new Topic();
    BeanUtils.copyProperties(dto, topic);
    topic.setTeacherId(teacherId);
    topic.setStatus(TopicStatus.PENDING_REVIEW);
    
    if (topicService.save(topic)) {
        return Result.success("课题提交成功,等待审核");
    }
    return Result.error("课题提交失败");
}

前端对应的Vue组件需要注意表单验证:

<template>
  <el-form :model="form" :rules="rules" ref="formRef">
    <el-form-item label="课题名称" prop="title">
      <el-input v-model="form.title" />
    </el-form-item>
    <el-form-item label="课题描述" prop="description">
      <el-input type="textarea" v-model="form.description" />
    </el-form-item>
    <el-button type="primary" @click="submit">提交</el-button>
  </el-form>
</template>

<script setup>
const form = reactive({
  title: '',
  description: ''
});

const rules = {
  title: [{ required: true, message: '请输入课题名称', trigger: 'blur' }],
  description: [{ required: true, message: '请输入课题描述', trigger: 'blur' }]
};

const submit = () => {
  // 调用API接口...
};
</script>

4.2 选题申请功能

学生申请课题时需要考虑并发问题——热门课题可能被多人同时申请。我们采用乐观锁来解决:

@Transactional
public boolean applyTopic(String studentId, String topicId) {
    // 检查学生是否已有课题
    if (applicationMapper.existsByStudentId(studentId)) {
        throw new BusinessException("每位学生只能选择一个课题");
    }
    
    // 使用乐观锁更新课题状态
    int updated = topicMapper.updateStatusIfNotSelected(topicId);
    if (updated == 0) {
        throw new BusinessException("该课题已被其他同学选择");
    }
    
    // 记录申请
    Application app = new Application();
    app.setStudentId(studentId);
    app.setTopicId(topicId);
    app.setApplyTime(LocalDateTime.now());
    return applicationMapper.insert(app) > 0;
}

对应的SQL语句:

UPDATE topic 
SET status = 2 
WHERE id = #{topicId} AND status = 1

5. 踩坑与优化经验

在开发过程中,我们遇到过几个典型问题:

  1. 跨域问题:开发阶段前端访问后端API时出现CORS错误。解决方案是在Spring Boot中添加配置类:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("*")
                .allowedHeaders("*");
    }
}
  1. 性能优化:课题列表页在数据量大的时候加载缓慢。我们通过以下措施优化:

    • 添加Redis缓存热门查询
    • 实现分页查询
    • 使用MyBatis-Plus的QueryWrapper构建动态查询
  2. 文件导入功能:批量导入学生信息时,最初的做法是逐条插入,速度极慢。后来改为批量插入:

@Transactional
public void batchImportStudents(MultipartFile file) {
    List<Student> students = parseExcel(file); // 解析Excel
    if (!students.isEmpty()) {
        // 使用MyBatis-Plus的saveBatch方法
        studentService.saveBatch(students, 100); // 每批100条
    }
}

6. 部署与运维

项目最终采用Docker Compose部署,docker-compose.yml配置如下:

version: '3'
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: thesis
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql

  redis:
    image: redis:6
    ports:
      - "6379:6379"

  backend:
    build: ./backend
    ports:
      - "8080:8080"
    depends_on:
      - mysql
      - redis

  frontend:
    build: ./frontend
    ports:
      - "80:80"
      
volumes:
  mysql_data:

部署时需要注意:

  1. 确保MySQL和Redis容器先启动
  2. 后端应用需要等待数据库就绪后再启动(可以使用wait-for-it.sh脚本)
  3. 前端构建时需要配置正确的API基础地址

7. 项目扩展方向

这个基础系统还可以进一步扩展:

  1. 微信小程序端:方便学生随时查看选题状态
  2. 智能推荐:根据学生专业和兴趣推荐合适课题
  3. 过程管理:增加开题报告、中期检查等环节
  4. 查重集成:对接论文查重服务

我在实际开发中发现,使用Elasticsearch实现课题搜索功能能显著提升用户体验。只需要在Spring Boot中集成Spring Data Elasticsearch,就能轻松实现全文检索:

public interface TopicRepository extends ElasticsearchRepository<Topic, String> {
    List<Topic> findByTitleOrDescription(String title, String description);
}

这个毕业设计选题平台从需求分析到最终上线历时三个月,期间遇到了各种技术挑战,但也收获满满。最让我自豪的是,系统上线后真正帮助学院简化了选题流程,老师们再也不用熬夜整理Excel表格了。如果你正在学习全栈开发,这类实战项目绝对是提升技能的绝佳选择。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐