SpringBoot+MySQL教学网站设计与实现:课程设计到毕业设计全流程解析 简介本资源是一套面向计算机专业本科生的毕业设计级教学网站开发实战项目聚焦中学信息技术课程数字化教学场景为教育信息化系统开发提供完整落地参考。压缩包共1179个文件总计85.95MB涵盖62个Java核心业务类如XueshengControler、LaoshiControler、ZuoyetControler等、464个前端交互JS脚本、228张界面PNG资源图、114个HTML页面模板、60个CSS样式文件及2个SQL建表脚本完整支撑前后端分离架构下的课程管理、在线考试、作业提交、教学视频与资源下载等核心功能模块。已有58人学习下载资源包含可直接运行的SpringBoot源码、MySQL数据库脚本、详细设计文档及答辩PPT目录结构规范模块职责清晰便于理解MVC分层逻辑与角色权限控制实现机制特别适合毕业设计选题、Java Web课程实训及教育类系统二次开发参考。 每年到这个时间点总会收到一堆学弟学妹的消息课程设计或者毕业设计想做一个“教学网站”网上搜出来一大堆项目下载下来却跑不起来要么数据库脚本缺表要么配置文件对不上最头疼的是答辩时老师一追问“为什么这样设计”就卡壳。我当年做“中学信息技术课程教学网站”时也走过这些弯路所以这篇直接把整个项目的设计思路、数据库核心表、关键代码实现和部署细节掰开揉碎讲清楚。项目本身基于 SpringBoot MySQL是很经典的主流技术栈功能上覆盖了课程资源管理、在线作业、在线测试、成绩统计这些教学闭环场景源码、数据库脚本、设计文档和答辩PPT我都整理了适合用来做课程设计也完全扛得住毕业设计答辩的追问。这篇不是把代码贴一遍就完事而是回答三类问题为什么这样设计表结构、核心功能是怎么实现的、哪些坑是教程里不会告诉你的。无论你是刚开始学 SpringBoot 的初学者还是已经写了一些 CRUD 但想把项目做得更“像样”的同学都能在里边找到直接能用的东西。1. 做这个项目前先想清楚“教学网站”到底在解决什么问题很多同学拿到“中学信息技术课程教学网站”这个选题第一反应就是课程列表、资源下载、用户登录完事。真这么做出来项目简单是简单但答辩时老师一句“那你这和传统的文件共享有什么区别”就能把人问住。所以动手写代码之前必须先把这个教学网站的业务价值想透给它设计足够真实的业务闭环。1.1 传统信息技术课的真实痛点中学信息技术课和语数外不一样它是以操作实践为主的科目日常教学里有几个很典型的痛点。第一教学资源分散老师上课用的PPT、练习素材、操作视频散落在各个QQ群、U盘和网盘里学生课后想找一份上节课的素材得翻半天聊天记录。第二作业收交低效信息技术课的作业大多是电子文件传统收法是一个个微信传或者学生U盘拷给课代表老师批改时要按文件名一个个对到底谁交了谁没交工作量非常大。第三上机考试人工批改累操作题、客观题混在一起考试结束老师要花大量时间统计成绩。这三个痛点就是这个项目的核心需求来源。也就是说这个网站不是简单的“放课件的地方”它必须能解决资源统一管理、作业在线提交与批改、考试在线组织与自动判分这三件事。1.2 三类用户角色与核心诉求基于上面的痛点系统角色自然分成了三类学生、教师、管理员。我建议在做需求分析时就把每个角色的核心诉求写清楚后面所有功能都从这些诉求里推导出来。学生能浏览课程内容下载教学资源能查看老师布置的作业并在线提交文件能参加在线测试交卷后看到客观题得分能查看自己的历史成绩。教师能维护自己负责的课程和资源能发布作业、查看提交情况、给主观题评分能管理题库、组卷、发布考试能查看班级成绩统计。管理员负责用户管理教师账号开通、学生账号导入、课程管理、系统公告发布、基础数据维护。这里有一个设计要点不要把管理员做成一个“超级万能角色”管理员只管系统级的东西教学业务全部下沉给教师。这样划分在答辩时很加分因为体现了角色职责分离的基本设计思想。1.3 功能清单推导需求梳理清楚后功能模块就非常自然地出来了。整个系统按前端展示和后端管理两层来组织后端管理按角色差异展示不同菜单。学生端功能登录注册、查看课程列表与课程详情、资源下载、查看作业列表、提交作业支持上传文件、参加在线测试、交卷查看客观题得分、查看个人成绩与错题。教师端功能课程与资源管理资源上传、编辑、删除、作业管理发布、截止时间设置、查看学生提交情况、主观题打分、题库管理单选、多选、判断题的增删改查、考试管理组卷规则设置、发布考试、查看成绩统计。管理员端功能用户管理学生批量导入、教师账号管理、重置密码、课程审核、公告管理、系统日志查看。到这里项目的业务边界就清晰了它是一个包含“教、学、练、考、评”五个环节的完整教学管理系统而不是零散的CRUD堆砌。带着这个理解去做数据库设计后面每一步都会有据可依。2. 技术选型的真实理由SpringBoot为主前端与数据库怎么搭技术选型这部分是答辩时老师问得最频繁的你一定要能说出来“为什么选它而不是别的”而不只是“因为我只会这个”。下面是我最终确定的技术栈以及每个选择的实际考量。2.1 后端框架SpringBoot 2.7.xSpringBoot 在课程设计、毕业设计里的统治地位不是没有道理的。它最大的价值是“约定优于配置”内嵌Tomcat打包后一个jar直接跑省掉了传统SSH整合时那一大堆XML配置文件。选择2.7.x而不是3.x是因为大部分教学资源和网上教程都基于2.x问题答案好找而且JDK8就能跑不用在环境上折腾。如果你机器上装的是JDK17用3.x也行但要注意SpringBoot 3是基于Jakarta EE的包名从javax改成jakarta网上很多老代码直接粘过来会报红。2.2 前端方案服务端渲染用Thymeleaf不要硬上前后端分离这里我说句实在话毕设项目真的不建议硬搞Vue SpringBoot前后端分离。我知道市面上很多付费教程在推这种架构但对于一个教学网站这种以增删改查为主的系统前后端分离意味着你要同时维护两套工程、处理跨域、处理Token鉴权工作量直接翻倍而且如果前端写得不熟练答辩时反而露怯。我选用的是 Thymeleaf 做服务端渲染。用户在浏览器里访问页面SpringBoot 直接返回渲染好的HTML。这样整个项目就是一个工程运行逻辑清晰答辩时也容易讲清楚“请求-处理-响应”的完整链路。稍微注意一下Thymeleaf 的语法和Vue的插值表达式都是双大括号如果以后你升级成前后端分离模板渲染这块要全部重写。2.3 ORM框架MyBatis-Plus数据库操作层我选了 MyBatis-Plus。它本质上是MyBatis的增强工具最实用的就两个能力一是内置通用Mapper单表CRUD不用写SQLBaseMapper提供的方法直接调二是分页插件只需要一个配置类就能用Page对象传入当前页和每页条数即可。对于这个项目里大量单表查询的场景课程列表、作业列表、成绩列表开发效率比纯MyBatis写XML高非常多。复杂查询还是要写SQL的比如随机组卷、成绩统计报表这些在Mapper里用注解或XML实现。记住一个原则简单查询靠框架复杂查询靠SQL不要为了“省事”把所有逻辑都堆在Service层循环处理。2.4 数据库选型MySQL 8.0MySQL 8.0 是目前的主流版本性能比5.7好而且是《数据库原理》课程里教的主流数据库项目衔接最自然。字符集一定要用 utf8mb4不是 utf8。utf8 在MySQL里最多存3字节emoji 表情和特殊符号存不进去上课素材文件名里偶尔有特殊符号插入数据库时报错就很尴尬。排序规则用 utf8mb4_general_ci兼容性最好。存储引擎用InnoDB支持事务和外键后面作业提交、考试答题这类写操作密集的场景才有保障。2.5 权限方案拦截器不引入Spring Security很多教程推荐Spring Security做登录鉴权我不建议在这个项目里用。Spring Security功能强大但学习曲线陡配置复杂对于课程设计级别你要的只是“未登录用户不能访问某些页面”“教师页面学生进不去”这两个能力自己写一个 HandlerInterceptor 完全够用而且逻辑一目了然。具体做法是登录成功后把用户对象放进Session拦截器里判断Session是否存在再根据访问路径前缀判断角色。比如 /teacher/** 路径要求教师角色/student/** 路径要求学生角色/admin/** 要求管理员角色。这样设计还有一个隐藏好处答辩时你能完整讲清楚“拦截器-处理器-视图渲染”这条链路比笼统地说“用了Spring Security”要实在得多。技术选型对比表格技术点我的选择备选方案选择理由后端框架SpringBoot 2.7.xSpringBoot 3.x生态成熟资料多JDK8友好前端ThymeleafVue前后端分离单工程好维护答辩链路清晰ORMMyBatis-PlusSpring Data JPA单表CRUD快分页简单数据库MySQL 8.0 utf8mb4MySQL 5.7字符集完整主流鉴权拦截器 SessionSpring Security够用且逻辑透明项目管理MavenGradle教学场景默认选择3. 数据库设计是这类项目最值钱的“隐藏工作”很多课程设计项目“看起来简单、跑起来粗糙”问题大多出在数据库设计上。表建得随意字段命名混乱没有索引没有约束功能一写深就各种Bug。我花了整个项目将近三分之一的时间在数据库设计上这部分也是答辩时最值得展开讲的内容。下面按照业务模块把核心表拆开讲。3.1 用户权限设计user 表 role 字段用户表是整个系统的基础。我用的是一张用户表加 role 字段区分角色而不是拆成 student 表、teacher 表、admin 表三张理由是三个角色的公共字段用户名、密码、姓名、创建时间几乎完全一样拆表会让登录校验变成“先查student表再查teacher表”反而麻烦。role 字段用 tinyint 类型0表示管理员、1表示教师、2表示学生。注意这里不要直接用字符串存“teacher”“student”用数字做映射的好处是查询快也方便程序里用常量类统一管理。user 表核心字段字段名类型说明idbigint 主键自增用户IDusernamevarchar(50) 唯一索引登录账号passwordvarchar(100)加密后的密码real_namevarchar(50)姓名roletinyint0管理员 1教师 2学生class_namevarchar(50)学生所在班级教师管理员为空statustinyint1正常 0禁用create_timedatetime创建时间密码必须加密存储不要明文存。我用的 BCrypt 加密Spring Security 框架里自带这个工具类即使不引入Spring Security单独把 spring-security-crypto 依赖加进来也能用。BCrypt 每次加密同一个字符串得到的结果都不同但它内部自带盐校验时用 matches 方法比对即可。3.2 课程与资源模块course、course_resource课程表 course 记录课程基本信息课程名称、课程介绍、封面图、授课教师ID、所属学期、创建时间。这里要注意一门课程本质上属于创建它的教师所以直接加一个 teacher_id 外键关联 user 表不要去搞复杂的“教师-课程多对多”除非你要做“多个老师共同教一门课”的需求这个项目不需要那么重。资源表 course_resource 用来存教学资源文件。每个资源归属于一个课程所以加 course_id 外键。文件上传后数据库里不存文件本身存的是文件的存储路径和访问URL这个思路要贯穿整个项目。资源表字段id、course_id、resource_name、resource_type1课件 2素材 3视频、file_path、file_size、uploader_id、create_time。资源查询是典型的“按课程查列表”操作所以在 course_id 上加普通索引就够了不需要复合索引。3.3 作业与提交模块homework、homework_submission作业表 homework 存作业本身的信息所属课程、作业标题、内容描述、截止时间、创建教师。这里有个细节截止时间要用 datetime 类型并且业务上要判断当前时间大于截止时间时学生端不能再提交教师端要能看到“已截止”状态。homework_submission 是学生提交作业的记录表每次提交存一条评论记录。表结构如下字段名类型说明idbigint 主键自增提交IDhomework_idbigint作业IDstudent_idbigint学生IDsubmit_contenttext学生填写的备注file_pathvarchar(255)提交的文件路径submit_timedatetime提交时间statustinyint1已提交 2已批改scoreint教师打分未批改时为空teacher_commentvarchar(500)教师评语这个表一定要建唯一约束UNIQUE KEY uk_homework_student (homework_id, student_id)。这个约束是防止学生重复提交的关键。如果不要这个约束同一学生提交两次会出现两条记录教师看到“张三交了两份”到底以哪份为准加了唯一约束后第二次提交走更新逻辑而不是新增记录这样数据始终只有一条。这个细节在答辩时讲出来非常提分因为体现了并发场景下的数据一致性考虑。3.4 在线测试模块题库与组卷的设计在线测试是这个项目业务复杂度最高的模块表设计一定要想清楚。我的方案是四张表question题目、exam考试、exam_question考试与题目的关联表、exam_record考试记录表学生作答记录。question 题表核心字段id、question_type1单选 2多选 3判断、question_content、option_a、option_b、option_c、option_d、answer、analysis、difficulty1简单 2中等 3困难、course_id、creator_id。这里多选题的 answer 字段存储格式要注意用逗号分隔的选项字母比如“A,B,D”判分时把学生的答案字符串拆成数组再比对。判断题的选项用命题文本答案字段存“对”或“错”。exam 考试表id、exam_name、course_id、start_time、end_time、duration考试时长单位分钟、total_score、status1未开始 2进行中 3已结束。exam_question 关联表id、exam_id、question_id、question_score。为什么要有这张表因为同一道题可能在不同考试中出现而且同一道题在不同考试中的分值也可能不同。把“考试和题目的关系”以及“这道题在这次考试中的分值”单独存一张表就不会污染题目本身的属性。组卷策略有两种实现方式。一种是手动选题教师在题库中勾选题目逐题设置分值系统插入 exam_question 记录。另一种是自动随机组卷教师选择课程、题目类型、每题分值、总题数系统从题库中随机抽取符合条件的题目。自动组卷SQL核心思想是按条件随机排序取前N条MyBatis-Plus中写成LambdaQueryWrapperQuestion wrapper new LambdaQueryWrapper(); wrapper.eq(Question::getCourseId, courseId) .eq(Question::getQuestionType, type) .orderByAsc(Question::getId); // 注意真正的随机用order by rand() PageQuestion page questionMapper.selectPage(new Page(0, count), wrapper);在Mapper里用随机排序SELECT * FROM question WHERE course_id #{courseId} AND question_type #{type} ORDER BY RAND() LIMIT #{count}说实话ORDER BY RAND() 在数据量大时有性能问题但对于中学信息技术课的题库规模几百道题完全不需要优化。答辩时如果有人问就这么答数据量小随机组卷用ORDER BY RAND()逻辑简单清晰如果后续题库量增长到十万级可以改成先查Id集合再随机取Id的方案。这种回答方式比死背“我用了什么方案”要高级。exam_record 考试记录表id、exam_id、student_id、answer_content、objective_score客观题得分交卷自动判分、status1未交卷 2已交卷、submit_time。answer_content 存储的是学生每道题的作答内容用JSON格式比如 [{questionId:1,answer:A},{questionId:2,answer:B,D}]。JSON存储的优势是结构灵活不需要为每个学生每道题建一条记录查询一次就能拿到整个答卷内容。但这里我要提醒一个点如果以后你想做“每个学生每道题的正确/错误统计”JSON存储就不好查了。所以更规范的设计是再建一张 exam_answer 明细表每道题一条记录。我最初用的是JSON方案后来为了做一个“班级易错题统计”的功能业务上需要知道第5题有多少人做错被迫把数据导出来在Java里解析。如果你时间充裕建议直接上明细表后面做数据统计会舒服很多。3.5 成绩相关表成绩视图还是成绩实体表成绩数据可以实时从 exam_record 查出来没必要单独建成绩表。学生成绩列表本质上就是“查考试记录表关联考试表和用户表”。但如果老师要录入“平时成绩”这种非系统产生的分数就需要 score 表了。我建议加一张 score 表字段id、student_id、course_id、exam_id可空如果为NULL表示平时成绩、score_type1平时成绩 2考试成绩、score_value、create_time。这样成绩模块既能统计系统自动判分的考试成绩也能容纳教师手动录入的平时成绩期末总评时按比例加权计算。数据库设计到这里整个系统的“骨架”就清晰了。为了帮你看表之间的关联我用文字描述一下核心关系链用户表教师创建课程课程绑定多个资源和多个作业学生针对作业产生提交记录教师创建考试考试关联题目学生参加考试产生考试记录考试记录关联考试成绩成绩表综合考试成绩和平时成绩。所有业务模块都有清晰的主外键链条这是答辩时讲数据库设计的关键故事线。4. 核心功能实现拆解从登录到在线组卷判分的完整链路数据库设计好了开发就是按模块逐个实现。下面挑几个最有技术含量、也最可能在答辩时被追问的功能点把实现思路和关键代码讲一遍。4.1 登录鉴权与角色访问控制登录流程是这样的用户提交用户名密码后端先根据用户名查用户表查不到直接返回“用户不存在”查到了用 BCrypt 的 matches 方法比对密码比对成功判断 status 是否为1被禁用的账号不允许登录全部通过后把用户对象放进 Session并按角色跳转到不同的首页。拦截器实现核心代码public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); User user (User) session.getAttribute(loginUser); if (user null) { response.sendRedirect(/login); return false; } return true; } }角色控制我用的是路径前缀拦截。先定义三个拦截器或者一个拦截器加路径判断规则Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/login, /register, /css/**, /js/**, /images/**); registry.addInterceptor(new TeacherInterceptor()) .addPathPatterns(/teacher/**); registry.addInterceptor(new AdminInterceptor()) .addPathPatterns(/admin/**); } }这种“前缀即权限”的设计简单直观控制器里 /student/home 就是学生端页面/teacher/course/list 就是教师端功能。答辩时你可以说这是基于RBAC思想的轻量级实现访问控制规则集中在配置类中后续扩展角色只需要增加拦截器和路径前缀即可。4.2 文件上传的完整实现思路教学网站绕不开文件上传教学资源、作业提交、课程封面都是文件。前后端都在同一工程里上传接口比较好写。核心步骤就三步接收 MultipartFile、保存到服务器本地目录、把访问路径存到数据库。保存文件的路径不要写死在代码里放在 application.yml 配置中file: upload-dir: ./upload/ access-path: /upload/**编写配置类做静态资源映射Configuration public class FileConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Value(${file.access-path}) private String accessPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(accessPath) .addResourceLocations(file: uploadDir); } }这样用户访问 http://localhost:8080/upload/xxx.pdf 就能直接看到文件数据库里存的就是 /upload/xxx.pdf 这个相对路径。实际开发中我踩过一个坑直接用原始文件名保存两个不同目录下有个同名文件就互相覆盖了而且中文文件名在部分浏览器上会乱码。解决办法是生成唯一文件名String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFilename UUID.randomUUID().toString().replace(-, ) ext;用UUID重命名还能避免文件名注入风险比如原始文件名是“../../etc/passwd”这种拼接路径时可能出问题UUID生成后这个问题就不存在了。文件上传大小限制也要配置SpringBoot默认最大单文件1MB教学视频动不动就几十MB不配置就会上传失败spring: servlet: multipart: max-file-size: 200MB max-request-size: 200MB4.3 在线测试随机组卷、学生答题、自动判分在线测试的逻辑要仔细设计我把它拆成开始考试、学生答题、交卷判分三个阶段。开始考试时系统根据 exam 记录里的 exam_question 关联表查出考试包含的所有题目组装成试卷数据放到Model里渲染到页面。这里要注意正确答案绝不能返回到前端页面。如果直接把 question 表的所有字段一股脑传给页面学生在浏览器F12里就能看到答案。正确做法是只把题干、选项、题目ID传到前端answer字段在组卷查询时排除掉。我的做法是在组装试卷VO时只挑选必要字段public class ExamQuestionVO { private Long questionId; private Integer questionType; private String questionContent; private String optionA; private String optionB; private String optionC; private String optionD; private Integer questionScore; // 注意没有answer字段 }学生答题过程中前端用JavaScript收集每题选中的答案存在一个对象里。交卷时把整个答案对象转成JSON字符串和 examId 一起通过AJAX提交到后端。后端收到后解析JSON逐题比对答案计算客观题得分同时把作答明细和得分一并存入 exam_record 表和 exam_answer 明细表。自动判分逻辑大致如下public Integer calculateScore(String answerJson, ListExamQuestion questions) { JSONArray answers JSON.parseArray(answerJson); int score 0; MapLong, String answerMap new HashMap(); for (int i 0; i answers.size(); i) { JSONObject obj answers.getJSONObject(i); answerMap.put(obj.getLong(questionId), obj.getString(answer)); } for (ExamQuestion eq : questions) { Question q eq.getQuestion(); String studentAnswer answerMap.get(q.getId()); if (q.getAnswer() ! null q.getAnswer().equalsIgnoreCase(studentAnswer)) { score eq.getQuestionScore(); } } return score; }多选题判分是这里最容易出错的地方。学生答案“A,B”和标准答案“B,A”在字符串比较时不相等所以多选题判分前要把两个字符串都拆成List再排序比较。更严谨的做法是无论单选多选统一拆成List后排序再比较避免各种格式边缘问题。交卷前还要做“考试时间是否已到”的校验。只在前端倒计时归零时自动交卷是不够的后端必须再校验一次防止有人改了前端时间绕过限制。判断逻辑当前时间大于start_time duration 分钟就按超时处理强制提交。4.4 作业提交与教师评分学生提交作业和前面文件上传逻辑类似区别在于提交操作的幂等处理。因为有 homework_submission 表的唯一约束Service层要做两步操作先按 homeworkId studentId 查是否已有记录没有就insert有就update把file_path和submit_time覆盖成新值。你也可以用数据库的 ON DUPLICATE KEY UPDATE 一步完成但MyBatis-Plus默认不支持这个语法我是在Mapper里手写了SQLINSERT INTO homework_submission (homework_id, student_id, submit_content, file_path, submit_time, status) VALUES (#{homeworkId}, #{studentId}, #{submitContent}, #{filePath}, NOW(), 1) ON DUPLICATE KEY UPDATE submit_content VALUES(submit_content), file_path VALUES(file_path), submit_time NOW(), status 1;教师端作业批改列表就是按 homeworkId 查询所有提交记录关联学生表显示姓名已批改的显示分数未批改的状态是1点击“批改”填写分数和评语后更新status为2。全体批改统计页可以做一个“已交人数/总人数”的计数一条SQL用COUNT配合GROUP BY就能算出来。5. 把“能跑的代码”变成“能答辩的项目”部署、文档与PPT的关键细节代码写完了只是第一步。很多同学代码功能全对最后文档和PPT一塌糊涂答辩时被老师质疑“这是不是你自己做的”。所以项目答辩准备的环节重要性一点不比写代码低。5.1 本地部署运行步骤为了方便拿到源码的人能跑起来我习惯在项目根目录放一个 README 文件写清楚环境和部署步骤。这里直接说一下我的运行环境清单JDK 1.8SpringBoot 2.7.x 要求JDK8及以上Maven 3.6MySQL 8.0IDEA 2022部署步骤五步走第一步新建数据库school_it_course字符集选 utf8mb4第二步执行项目下的sql/school_it_course.sql脚本导入表结构和初始数据第三步修改 application.yml 里的数据库用户名密码改成自己本机的配置第四步在IDEA里以Maven项目导入源码等待依赖下载完成第五步运行主类SchoolItCourseApplication.java浏览器访问 http://localhost:8080。初始账号我预置了三类管理员 admin/admin123教师 teacher/123456学生 student/123456。文档里把每个角色的初始账号写清楚方便答辩时现场演示。我还要特别提醒一个SpringBoot版本相关的问题如果你本机的Maven仓库之前下载过其他版本的依赖可能会出现依赖冲突。真遇到了就执行mvn clean然后右键项目 Maven - Reload Project 重新加载。启动报错时优先看端口是否被占用SpringBoot默认8080你可以通过server.port8081临时改端口测试。5.2 代码结构怎么组织才显得“专业”我见过不少课程设计代码所有类都塞在一个包下面Service写了一千行这种代码就算能跑答辩也容易被怀疑。合理的包结构应该是这样的com.school.itcourse ├── controller # 控制层 │ ├── admin │ ├── teacher │ └── student ├── service # 业务层 │ └── impl ├── mapper # 数据访问层 ├── entity # 实体类 ├── vo # 视图对象 ├── common # 通用类常量、异常、返回值封装 ├── config # 配置类 └── interceptor # 拦截器分层结构在讲项目时思路特别清晰controller只负责接收参数和返回视图service写业务逻辑mapper操作数据库。答辩时老师问你“某行代码在哪一层”你能准确说出它在哪个类的哪个方法里这基本就赢了七八成。5.3 数据库文档和答辩PPT的重心数据库设计文档是这部分的重头戏。不要只贴几张表的字段清单就完事要讲清楚设计过程和设计理由。我的文档结构是这样的先写需求分析角色分析、功能需求、数据流梳理再给ER图用工具导出来的实体关系图然后是每张表的详细设计字段名、类型、是否可空、默认值、备注最后写设计规范说明比如统一主键策略自增ID、统一时间字段命名create_time/update_time、逻辑删除约定deleted字段0未删1已删。答辩PPT我建议控制在15页左右按这个逻辑走选题背景与意义1-2页、系统需求分析2页、技术选型1页、系统功能结构图1页、数据库设计3页重点是ER图和核心表、核心功能实现展示4-5页截图加关键代码、系统测试2页、总结与展望1页。演示时不要照着PPT念而是先花2分钟讲清楚“我要解决什么问题”再用系统演示来验证。6. 我在做这个项目时踩过的坑以及后续的扩展思路最后一个部分写点实在的都是我做这个项目时实际遇到过的问题有些问题反复查了好久才解决。分享出来希望你能一次性避开。6.1 踩坑一日期格式化问题前端显示时间出现“2025-05-01T10:30:00”这种格式非常难看。原因是SpringBoot返回JSON时默认使用Jackjson对LocalDateTime序列化格式不友好。解决办法是在 application.yml 里配置全局统一格式化spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8但注意这个配置对 LocalDateTime 类型有时不生效。更稳定的做法是在实体类的日期字段上加注解JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8) private LocalDateTime createTime;加完之后前后端显示就一致了这次我是两种方式都配上了彻底解决。6.2 踩坑二MyBatis-Plus分页查询不生效分页插件加上了但查询结果还是返回全部数据。这个问题90%是因为分页拦截器没有正确注册。在MyBatis-Plus 3.4版本之后用 MybatisPlusInterceptor 而不是旧版的 PaginationInnerInterceptorConfiguration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }如果是旧版项目从低版本升级上来的注意把代码里的 OptimisticLockerInnerInterceptor 等旧配置一起迁移。6.3 踩坑三逻辑删除字段导致的唯一约束冲突我在用户表加了逻辑删除字段 deleted设计本意是防止用户误删。但用户名本身有唯一索引如果用户A删除了再注册一个同名用户A数据库里就存在两条 username 相同、deleted 不同的记录唯一索引直接报错。这个问题有两种解决思路一是删除时把 username 改写成 username_del_时间戳 这种形式二是把唯一索引改成联合唯一索引 (username, deleted)。我当时图省事选了第一种但后来发现业务上修改用户名时同样可能遇坑。建议你在需求阶段就约定号这个项目里用户不能自行注销账号只有管理员能禁用禁用用 status0 而不是逻辑删除这样就天然避开了这个坑。由此可见不是所有表都适合加逻辑删除用字段状态控制往往比逻辑删除更合适。6.4 扩展思路从“能答辩”到“有亮点”如果做完基础功能还有时间我建议按以下优先级做一两个亮点功能。第一个推荐做“班级成绩统计可视化”用 ECharts 展示成绩分布柱状图和平均分趋势折线图前端引入ECharts的CDN就能用不复杂但视觉效果好答辩时很出彩。第二个可以做“编程题在线评测”中学信息技术课有Python编程内容学生提交代码后后端调用系统命令在沙箱环境中执行测试用例返回运行结果。这个功能一开始要多少做多少但注意安全问题不要在自己电脑上直接执行学生提交的任意代码比较稳妥的做法是用Docker容器隔离执行或者干脆只做代码展示不做自动运行。第三个可以接入“AI学习助手”用大模型API做答疑但要注意数据安全和隐私要求学生提问时不能传入个人信息只能传题目相关的内容。对于课程设计来说前两个足够了第三个可以作为项目答辩时的“未来展望”提一下就行。6.5 最后说点真心话做这个项目最大的收获不是把CRUD写熟练了而是你学会了从需求出发去设计系统。很多同学上来就写代码写着写着发现表结构不对推倒重来折腾几轮人就很疲惫。我的经验是花时间认真做需求分析和数据库设计后面开发其实非常快因为每个功能对应哪张表、哪几个字段心里清清楚楚。另一个经验是遇到问题先自己查日志再看控制台报错信息最后才去搜索引擎这个过程锻炼的排查能力比会写代码本身更值钱。希望这份完整的项目拆解能帮你把这个网站真正做成一个经得起推敲的作品而不是一个只能截图放PPT里的空壳。本文还有配套的精品资源点击获取