最近在做一个高校的毕业设计管理系统,从零开始搭建确实挺有挑战的。传统的开发模式,需求一变就得改一堆代码,返工是家常便饭。这次我尝试引入了一些 AI 辅助开发工具,比如 GitHub Copilot 和通义灵码,整个过程顺畅了不少。今天就来聊聊,如何用 Java(主要是 Spring Boot + MyBatis-Plus)结合 AI 工具,高效地搞定这样一个系统。

1. 高校毕设管理的那些“坑”

在动手之前,得先搞清楚我们要解决什么问题。毕业设计管理,听起来简单,但实际流程挺复杂:

  • 师生“双向盲选”的冲突:学生选导师,导师选学生,两边同时操作,很容易出现一个学生被多个导师选中,或者一个导师被太多学生“围攻”的情况。这本质是一个高并发的资源竞争问题。
  • 流程状态像一团乱麻:从课题申报、学生选题、开题报告、中期检查、论文提交到最终答辩,状态多,流转条件复杂。手动管理或者用简单的状态字段,很容易出现状态机混乱,比如“未开题”的学生直接跳到了“答辩通过”。
  • 文档版本管理噩梦:开题报告、论文初稿、终稿,学生反复修改提交,如何确保老师和学生看到的是最新版本?历史版本是否需要保留?
  • 权限控制细碎:学生、导师、系主任、教务管理员,不同角色能看到和操作的数据天差地别。普通的 RBAC 模型可能不够用,常常需要数据级权限控制。

毕业设计管理流程示意图

2. 技术栈选型:为什么是 Spring Boot + MyBatis-Plus?

选择技术栈就像选装备,合适最重要。这里简单对比一下:

  • Spring Boot vs. 其他框架(如纯 Servlet、Play Framework):Spring Boot 的“约定大于配置”和丰富的 Starter 生态是快速开发的不二之选。它内置了 Web 服务器、安全框架、数据访问等组件,能让我们聚焦业务逻辑。AI 工具对 Spring Boot 的支持也最全面,代码提示和生成准确率很高。
  • MyBatis-Plus vs. JPA (Hibernate):这是一个持久层的有趣选择。
    • MyBatis-Plus:它保留了 MyBatis 的灵活 SQL 能力,同时提供了强大的 CRUD 封装(QueryWrapper, LambdaQueryWrapper)。对于复杂查询、报表统计这类需要精细控制 SQL 的场景,它非常顺手。它的代码生成器功能强大,结合 AI 提示,能快速生成实体类、Mapper、Service 甚至 Controller 层的基础代码。
    • JPA:更面向对象,通过实体关系映射,写起来更“Java”。但对于复杂动态查询,有时会觉得不如直接写 SQL 直观。在需要处理“选题并发”这种需要直接操作更新语句(update ... set version = version + 1 where ...)的场景下,JPA 的乐观锁实现虽然优雅,但可能不如 MyBatis-Plus 直接写 SQL 或使用 UpdateWrapper 来得清晰和高效。

综合考虑开发效率、灵活性和社区支持,我选择了 Spring Boot + MyBatis-Plus + MySQL 的组合。前端可以用 Vue 或 React,这里主要聊后端。

3. 核心模块设计与 AI 辅助编码

系统主要分为用户中心、选题管理、过程监控(文档/进度)、评审答辩等模块。AI 工具在这里大显身手,尤其是在编写重复性高的模板代码和复杂业务逻辑的初稿时。

3.1 用户与角色权限模块

我们设计了四类角色:学生(STUDENT)、导师(TUTOR)、专业负责人(MAJOR_HEAD)、教务管理员(ADMIN)。权限使用 Spring Security + 注解方式控制。

AI 辅助点:当你输入 @PreAuthorize(“hasRole(‘TUTOR’)”) 时,AI 会自动补全方法签名,并提示常见的权限表达式。在定义权限常量类时,输入 public static final String,AI 能快速生成一连串的权限字符串。

3.2 选题管理模块(含关键代码示例)

这是核心中的核心。主要实体包括 Topic(课题)、SelectionRecord(选题记录)。关键流程是导师发布课题,学生选择,导师确认。

这里重点展示如何用 AI 辅助编写一个具有幂等性和事务性的“学生选题”服务方法。幂等性是为了防止学生重复点击提交造成多次选题。

/**
 * 学生选题服务
 */
@Service
@Transactional(rollbackFor = Exception.class)
@Slf4j
public class TopicSelectionService {

    @Autowired
    private TopicMapper topicMapper;
    @Autowired
    private SelectionRecordMapper selectionRecordMapper;

    /**
     * 学生选择课题(幂等操作)
     * @param studentId 学生ID
     * @param topicId 课题ID
     * @return 操作结果
     */
    public ApiResult selectTopic(Long studentId, Long topicId) {
        // 1. 幂等性检查:查询该学生是否已经选择了该课题
        LambdaQueryWrapper<SelectionRecord> checkWrapper = new LambdaQueryWrapper<>();
        checkWrapper.eq(SelectionRecord::getStudentId, studentId)
                   .eq(SelectionRecord::getTopicId, topicId);
        SelectionRecord existingRecord = selectionRecordMapper.selectOne(checkWrapper);
        if (existingRecord != null) {
            log.warn(“学生[{}]重复选择课题[{}],直接返回成功”, studentId, topicId);
            return ApiResult.success(“您已成功选择该课题”);
        }

        // 2. 查询课题信息,并检查状态(使用悲观锁或乐观锁,此处演示乐观锁)
        Topic topic = topicMapper.selectById(topicId);
        if (topic == null) {
            return ApiResult.fail(“课题不存在”);
        }
        if (!TopicStatus.AVAILABLE.equals(topic.getStatus())) {
            return ApiResult.fail(“该课题不可选”);
        }
        if (topic.getSelectedCount() >= topic.getMaxSelection()) {
            return ApiResult.fail(“该课题已选满”);
        }

        // 3. 使用乐观锁更新课题已选人数
        UpdateWrapper<Topic> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq(“id”, topicId)
                    .eq(“version”, topic.getVersion()) // 乐观锁条件
                    .setSql(“selected_count = selected_count + 1, version = version + 1”);
        int updateRows = topicMapper.update(null, updateWrapper);
        if (updateRows == 0) {
            // 更新失败,说明数据已被其他线程修改,通常意味着并发冲突
            log.error(“学生[{}]选择课题[{}]时发生乐观锁冲突”, studentId, topicId);
            throw new ConcurrentSelectionException(“选题人数已满或课题状态已变更,请重试”);
        }

        // 4. 创建选题记录(事务边界内)
        SelectionRecord record = new SelectionRecord();
        record.setStudentId(studentId);
        record.setTopicId(topicId);
        record.setSelectionTime(new Date());
        record.setStatus(SelectionStatus.PENDING); // 待导师确认
        selectionRecordMapper.insert(record);

        log.info(“学生[{}]成功选择课题[{}]”, studentId, topicId);
        return ApiResult.success(“选题成功,等待导师确认”);
    }
}

代码说明:@Transactional 确保步骤3和4在一个事务里。幂等性检查放在事务最开始,避免不必要的锁竞争。乐观锁通过 version 字段实现,确保在高并发下数据的一致性。

AI 辅助点:在编写这个方法时,我只需要打出方法名 selectTopic 和参数,AI 就能自动补全整个方法的骨架,包括注解、日志声明和基本的参数校验。在写 UpdateWrapper 的链式调用时,AI 也能非常准确地提示 .eq().setSql() 等方法。

4. 高并发选题:乐观锁与缓存策略

上面代码已经演示了乐观锁。但仅仅这样还不够,如果选题入口的 QPS 很高,频繁的数据库更新和失败重试对 DB 压力大。

优化策略:

  1. 前端限流与防重:提交按钮做防重复点击(禁用),并可以设置一个用户级的短暂冷却时间(如 3 秒内只能提交一次)。
  2. 缓存热点课题信息:使用 Redis。在课题发布或状态变更时,将热门课题的 id, selectedCount, maxSelection, version 等信息缓存起来,并设置较短的过期时间(如 5 秒)。
    • 学生在选题前,先快速查询缓存,如果缓存显示已选满,则直接前端提示,无需访问数据库。
    • 更新数据库成功后,立即失效或更新该课题的缓存。
  3. 异步处理与队列削峰:极端情况下,可以将选题请求放入消息队列(如 RabbitMQ/Kafka),后端服务异步消费处理,确保数据库平稳。但这样会牺牲一定的实时性,需要根据业务权衡。

5. 安全性考量

  • 防越权访问:这是重中之重。除了角色注解,必须在业务逻辑层做数据归属校验。例如,导师确认选题的接口,不仅要校验角色是导师,还要校验传入的 selectionRecordId 是否属于当前登录导师名下的课题。AI 工具可以帮忙生成这类校验代码的模板
    // 在服务方法内
    SelectionRecord record = selectionRecordMapper.selectById(recordId);
    if (record == null || !record.getTopic().getTutorId().equals(currentUserId)) {
        throw new UnauthorizedException(“无权操作此记录”);
    }
    
  • 敏感操作日志:所有关键操作,如课题发布、选题、成绩录入、状态修改,必须记录详细的操作日志(谁、何时、做了什么、操作前/后的数据快照)。可以使用 Spring AOP 统一实现。AI 可以辅助编写切面(Aspect)代码。
  • 数据脱敏:在查询学生列表、论文信息等接口,对手机号、邮箱等敏感信息进行脱敏处理。

6. 生产环境避坑指南

这些是血泪教训,AI 不一定能告诉你,但很重要:

  • 定时任务冷启动延迟:系统可能有“自动关闭过期选题”、“定时生成统计报表”等任务。使用 Spring @Scheduled 时要注意,如果应用启动时间较长,可能错过第一次执行。可以在配置中设置 fixedDelay 或使用 initialDelay 给应用留出启动时间。
  • 数据库连接池配置误区:不要盲目设置超大连接数。连接数 max-active 应该根据数据库和服务器的实际处理能力来定,一般公式是 (核心服务线程数) * (实例数)。设置过大反而会导致数据库性能下降。监控连接池活跃连接数、等待线程数等指标至关重要
  • 事务失效场景@Transactional 在同类方法内调用、方法不是 public、异常被捕获未抛出等情况会失效。写代码时要留意,AI 生成的代码有时也会忽略这一点。
  • MyBatis-Plus 分页插件内存溢出:在进行大数据量、无 WHERE 条件的分页查询时,Page 对象会先执行 COUNT(*),可能非常慢。对于确实不需要总数的情况,可以使用 Page 的不查询总数重载方法,或者干脆用 List 配合 limit

系统架构与数据流示意图

结尾思考:AI 辅助的边界与未来

这次用 AI 辅助开发,最大的感受是它像一个不知疲倦的结对编程伙伴,能极大提升编写模板代码、常见算法和 API 接口的效率。但它目前更擅长的是“根据已有模式生成代码”,而不是“创造性的架构设计”或“复杂的业务逻辑梳理”。

那么,我们能否将 AI 的能力进一步前置或后延呢?

  • 需求分析阶段:能否将模糊的自然语言需求(如“学生和导师可以双向选择,但一个课题最多被3人选”),通过提示工程,让 AI 帮助我们初步梳理出用例图、领域实体和关键业务流程?这可能是下一个提效点。
  • 测试用例生成:AI 在单元测试和集成测试的代码生成上潜力巨大。我们可以将业务方法抛给它,让它生成覆盖正常路径、边界条件和异常情况的测试用例骨架,我们再补充具体的断言逻辑。这能极大提升测试的完备性和编写效率。

AI 辅助开发不是要取代开发者,而是将我们从重复劳动中解放出来,让我们能更专注于架构设计、核心算法和解决那些真正复杂、模糊的业务难题。用好它,我们就能跑得更快。

Logo

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

更多推荐