简介:这份资源是基于Java的学生档案管理系统毕业设计文档,采用JSP、Servlet、Tomcat与MySQL技术栈实现,面向计算机相关专业学生和有JavaWeb开发需求的初学者。内容从系统分析出发,涵盖需求分析、性能要求与运行环境,并采用B/S架构将系统分为展示层、业务逻辑层和数据访问层;数据库设计部分给出E-R图以及学生表、教师表、课程表等主要数据表,功能模块包括登录、院系专业管理、学生信息管理、教师信息管理、课程与成绩管理等。资源包共1个docx文件,大小约410KB,目录完整、正文结构典型,可直接作为毕业设计论文撰写参考。该文档已有458人学习下载,其中还包含用户登录代码和db.java数据库操作代码等关键实现片段,有助于理解JavaWeb项目从需求分析、架构设计到代码实现的全过程,适合需要快速把握系统设计思路和核心编码方法的读者。
1. 学生档案管理系统要做什么:从课设题目到能用代码的距离
又到了课程设计集中交付的季节。“基于Java学生档案管理系统设计与实现”是Java课设里出现频率最高的题目之一,题目文档往往只给了功能模块图和数据流程图,真正动手时才发现卡点全在细节里:表怎么建才不会被老师追问,登录怎么做才不算“抄的”,分页查询为什么一翻页就丢条件,导出Excel为什么中文全是乱码。这篇笔记就按我实际交付过的一套方案来讲,技术栈取的是当前课设和毕设里最能打、也最好解释的 Spring Boot + MyBatis + MySQL + Thymeleaf,尽量让新手跟着步骤能跑通,也让想拿高分的同学看到参数边界和可以扩展的点。如果你正在找 Java 课程设计案例源码、又不想直接照抄网上那些缺胳膊少腿的旧项目,这篇应该能省下你好几天调试时间。
2. 数据模型先行:学生档案管理系统的表设计与字段边界
2.1 核心实体拆解:学生、院系、班级、用户怎么建模
学生档案管理系统的核心是“学生”这个实体,但直接建一张大宽表把所有字段塞进去是课设最常见的错误。档案系统要管的不只是学生的基本信息,还牵扯到院系、班级、专业、学籍状态变更记录。以我习惯的建模方式,第一版只建四张表:院系表、班级表、学生表、用户表。这四张表已经能覆盖“登录 + 档案CRUD + 条件查询 + 导出”这条主链路,也符合面向对象编程里“高内聚低耦合”的基本思路。
表与表之间的关系是典型的父子结构:一个院系对应多个班级,一个班级对应多个学生,学生和档案是1:1。Java实体类设计时直接把这个结构映射出来,不要在学生表里冗余存院系名称和班级名称,而是存外键,查询时用JOIN把名称带出来。这样老师问起“为什么这么设计”时,你能从数据冗余和更新异常两个角度讲清楚,而不是只能说“网上都这么做”。
用户表单独拆出来的原因更实际:登录账号、密码、角色不应该是学生表里的字段。教务老师可能管理多个院系的学生,他本人不是学生;学生也可能自助登录查看自己的档案。混在一张表里,权限逻辑就没法写。角色字段用tinyint区分(1-管理员 2-辅导员 3-学生),后面做拦截器时直接根据角色放行或者拒绝,省掉一张权限关联表。
2.2 建表SQL与约束设计:外键、唯一索引、时间字段
建表SQL是后面所有代码的地基,建议直接用下面的结构做第一版,字段不多但足够应付答辩。注意字符集统一utf8mb4,否则插入生僻字或者表情符号时客户端会直接报错。
CREATE TABLE `t_student` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `student_no` VARCHAR(32) NOT NULL COMMENT '学号,不参与业务,仅作唯一标识', `name` VARCHAR(64) NOT NULL COMMENT '姓名', `gender` TINYINT NOT NULL DEFAULT 1 COMMENT '性别:1-男 2-女', `birthday` DATE DEFAULT NULL COMMENT '出生日期', `class_id` BIGINT NOT NULL COMMENT '班级ID,关联t_class', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `email` VARCHAR(128) DEFAULT NULL COMMENT '邮箱', `photo_url` VARCHAR(255) DEFAULT NULL COMMENT '照片存储路径,不存二进制', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '学籍状态:1-在读 2-休学 3-毕业', `deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除:0-未删除 1-已删除', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_student_no` (`student_no`), KEY `idx_class_id` (`class_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生档案表';学号上建唯一索引,这是为了防止同一学号被重复建档。业务上真正会频繁筛选的字段是class_id和status,所以给它们建普通索引。create_time和update_time两个字段虽然不起眼,但答辩时老师基本必问“如何排查一条数据是什么时候被改的”,有这两个字段就能答上来。
外键约束我没有在SQL里直接写,而是保留了逻辑外键关系。原因很实际:选课系统、成绩系统未来要关联学生表,物理外键在大表场景容易引发锁竞争和删除顺序问题。课设阶段你把关系画清楚、代码里做校验即可,这个取舍在答辩时可以主动讲出来,属于亮点而不是扣分项。班级表、院系表结构更简单,id + name + deleted + create_time四件套就够了。
2.3 字段设计踩过的坑:学号类型、照片存储、时间类型
这三个问题是我帮学生调课设代码时见过最多的翻车点,每一个都能让系统看起来“能用但经不起问”。
第一个是学号类型。有人图省事用int,录入一个全市统招的学号0020230101,数字前导零直接被吞掉,变成20230101。更危险的是超过int上限的长学号直接报错。正确做法是用VARCHAR(32),校验规则里只允许数字和字母,查询时按字符串匹配,永远不要参与加减运算。
第二个是照片存哪里。把照片以BLOB字段存进数据库是最差的选择,单条记录几十KB还好,如果还要存身份证扫描件、成绩单附件,数据库体积会快速膨胀,备份和查询都会变慢。正确方案是上传到服务器本地目录或对象存储,数据库只存相对路径。课设没有对象存储条件,就定义一个上传目录配置项,把相对路径存进photo_url,页面用<img src>直接拼静态资源映射。
第三个是时间字段的数据类型。MySQL 用DATETIME,Java 实体不要用java.util.Date,用LocalDateTime,MyBatis 3.4 以上版本对 JSR-310 有原生支持,不用配额外的 typeHandler。不要在实体里塞两个字符串字段代替时间,排序和范围查询都会变得极其痛苦。这些字段类型的选择本质上都在考 Java 基础数据类型和 SQL 类型的映射关系,属于面试题里反复出现的考点,做课设时顺便搞清楚,后面求职也能用上。
3. 用Java Spring Boot跑通最小系统:骨架、依赖与登录认证
3.1 项目骨架与Maven依赖:spring-boot-starter-web加MyBatis就够了
先说环境,JDK 1.8 或者 17 都行,JDK 和 Maven 的下载与环境变量配置是前置条件,网上教程很多,这里不重复。真正能让你省时间的是选对起步依赖,别一上来就把spring-boot-starter-security也加进去,那会让课设复杂度陡增,光是登录流程就能卡你好几天。我常用的依赖组合很克制:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-crypto</artifactId> </dependency>spring-security-crypto只用来做 BCrypt 密码加密,不引入整个 Security 框架,既拿到了密码安全加分项,又避开了复杂的过滤器链配置。视图层我用 Thymeleaf 而不是 JSP,原因也实在:Spring Boot 对 JSP 的支持属于兼容模式,需要额外配置provided依赖;Thymeleaf 的模板语法对前端新手更友好,循环和条件判断写起来直观。如果你所在学校硬性要求 JSP,那就在 3.2 节之后自己再加一套视图解析配置,业务代码完全不用改。
写到这里,项目的包结构建议直接按controller / service / mapper / entity / interceptor分层。包名用com.school.archives这类简洁名,不要用com.example.demo,答辩老师看到规范包名会明显更认可,面试聊项目时也会顺带展示你有工程化意识。MyBatis 的映射文件放在src/main/resources/mapper下,并且在application.yml里显式声明。
3.2 登录与Session拦截:用一个注解守住所有档案接口
登录是档案系统的第一道门。我的做法是:登录成功把用户对象放进session,然后写一个拦截器统一校验,放行登录页和静态资源,其余接口一律检查。这比在每个 Controller 方法里自己加判断干净得多,也方便后续加角色判断。
@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object loginUser = session.getAttribute("loginUser"); if (loginUser == null) { // 未登录,重定向回登录页 response.sendRedirect("/login"); return false; } return true; } }拦截器注册到WebMvcConfigurer里,并明确放行路径,否则静态资源会被一并拦截,页面样式全部丢失:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns("/**") .excludePathPatterns("/login", "/doLogin", "/css/**", "/js/**", "/images/**"); } }拦截器和过滤器是两个常被拿来考的概念:过滤器是 Servlet 规范里的东西,拦截器是 Spring MVC 的组件;过滤器在请求进入 DispatcherServlet 之前执行,拦截器在进入 Controller 之前执行。课设用拦截器完全够用,答辩被问到也不慌,直接把这个区别讲出来就是加分回答。Session 有效期建议在application.yml里设置一下,不要用默认的 30 分钟,课设演示现场往往隔得很久才操作一次,设成 120 分钟更稳妥。
3.3 档案CRUD落地:Controller-Service-Mapper三层怎么写
三层架构是 Java 课设和 Java 基础面试题都在考的主线,代码可以简单,但分层职责必须清晰。我的习惯是:Controller 只接收参数和返回视图,Service 层放业务判断与事务,Mapper 层只负责 SQL。下面这段代码是整个系统的核心,注释里标出了课堂设计最容易忽略的异常处理位置。
@Service public class StudentServiceImpl implements StudentService { @Autowired private StudentMapper studentMapper; @Override @Transactional(rollbackFor = Exception.class) public boolean addStudent(Student student) { // 防止学号重复,提前做一次唯一性校验 Student exist = studentMapper.selectByStudentNo(student.getStudentNo()); if (exist != null) { throw new BusinessException("学号已存在,请检查后重试"); } return studentMapper.insertStudent(student) > 0; } }这段代码里最关键的是@Transactional注解。rollbackFor = Exception.class表示不管是受检异常还是运行时异常,只要方法执行过程出错就整体回滚。不写这个参数,Spring 默认只对运行时异常回滚,业务层抛出的自定义异常不会触发回滚,就等着数据不一致翻车。
@Controller @RequestMapping("/student") public class StudentController { @Autowired private StudentService studentService; @GetMapping("/list") public String list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String keyword, Model model) { PageHelper.startPage(pageNum, pageSize); List<StudentVO> list = studentService.listWithCondition(keyword); PageInfo<StudentVO> pageInfo = new PageInfo<>(list); model.addAttribute("pageInfo", pageInfo); return "student/list"; } }Controller 用PageHelper.startPage开启分页,紧接着第一条查询语句才生效,这个调用顺序是硬规则。PageInfo里已经封装了总条数、总页数、当前页、是否有下一页这些字段,直接把对象塞给视图就能在页面上渲染,不用自己手算分页导航。参数上我一般把pageSize限制在 1 到 100 之间,防止有人恶意传 99999 把全表炸出来。这套分页参数设计在内存溢出的边界问题上也说得通,属于可以写进课设文档的性能考虑。
4. 档案系统的三个实用功能:检索、分页、Excel导出
4.1 综合条件检索:学号、姓名、院系的动态SQL
档案管理系统的查询页必然是核心页面,需求通常是:按学号、姓名、班级、学籍状态任意组合筛选。这正好是动态 SQL 的典型场景,MyBatis 的<where>标签会自动处理多余的AND,不用自己写字符串拼接逻辑。
<select id="selectByCondition" resultType="com.school.archives.entity.StudentVO"> SELECT s.id, s.student_no, s.name, s.gender, s.birthday, c.name AS className, d.name AS deptName, s.status FROM t_student s LEFT JOIN t_class c ON s.class_id = c.id LEFT JOIN t_dept d ON c.dept_id = d.id <where> <if test="studentNo != null and studentNo != ''"> AND s.student_no LIKE CONCAT('%', #{studentNo}, '%') </if> <if test="name != null and name != ''"> AND s.name LIKE CONCAT('%', #{name}, '%') </if> <if test="classId != null"> AND s.class_id = #{classId} </if> <if test="status != null"> AND s.status = #{status} </if> AND s.deleted = 0 </where> ORDER BY s.create_time DESC </select>这里有几个细节值得说。学号和姓名都用模糊查询,但模糊查询必然放弃索引,数据量到几十万以后会变慢,课设阶段没问题,文档里要承认这个边界。状态和班级用等值查询,可以走索引。deleted = 0的过滤条件也放进<where>,保证任何组合下都不会查出已经被逻辑删除的档案,这一步是防止“删除后又出现”的血泪经验。
组合查询的回显问题也容易翻车,很多人的搜索表单点完第二页之后条件全丢了,那是因为分页导航的链接里只带了pageNum,没带上查询参数。正确做法是在前端生成分页链接时把当前表单的查询条件全部拼接进 URL,Thymeleaf 里可以传一个queryParams字符串模板,或者用表单GET提交模式并把条件隐藏字段写在分页表单里,二选一都可以。
4.2 分页参数怎么定:PageHelper的合理配置与默认值
PageHelper 是国人写的分页插件,原理是在 MyBatis 执行 SQL 前拦截,自动改写为带LIMIT的查询并同时执行COUNT查询。它的强大功能背后有几个参数需要你自己掌控,不然会踩到“多数据源分页错乱”“count查询巨慢”这些坑。
pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true params: count=countSqlreasonable设为true时,页码小于 1 会被纠正为第 1 页,超过最大页会被纠正为最后一页。这个参数建议开,能避免前端参数异常时后端报越界错误。support-methods-arguments允许从方法参数里直接读取pageNum和pageSize,配合 Controller 里的@RequestParam使用。params: count=countSql表示自动读取 COUNT 查询的 SQL 生成逻辑,不要手动关闭,否则分页时总数会算错,页面底部就会显示“共 -1 条”这种诡异数据。
分页大小我通常设为 10 条,页面不会太长,演示也方便。超过 100 的需求大多不是课设场景,后端直接用异常兜底拒绝。还有一点,PageHelper.startPage只会对紧接着的第一条 SQL 生效,有的同学在方法里先执行了一次计数器查询或者日志插入,再查列表,分页就失效了。排查办法是看日志里列表 SQL 有没有带LIMIT,没有就是调用顺序乱了。
4.3 用Apache POI导出Excel:表头样式、空值与日期格式化
导出功能是档案系统里最能体现工程细节的部分。方案上有两个选择,一个是 Apache POI、一个是阿里 EasyExcel。如果数据量上万,EasyExcel 的内存优势很明显;但课设场景数据量在几百到一两千行,POI 写起来直观、可控性也更强,“java poi word能生成图表吗”这类问题在面试语境里也绕不开 POI 的体系,用它做导出反而能锻炼底层理解。下面给出一个可直接用的简化版本。
public void exportStudentList(HttpServletResponse response, List<StudentVO> list) throws IOException { try (Workbook workbook = new XSSFWorkbook()) { Sheet sheet = workbook.createSheet("学生档案"); // 表头放第一行,字体加粗并设置背景色 CellStyle headerStyle = workbook.createCellStyle(); Font font = workbook.createFont(); font.setBold(true); headerStyle.setFont(font); String[] headers = {"学号", "姓名", "性别", "班级", "院系", "学籍状态", "出生日期"}; Row headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { Cell cell = headerRow.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(headerStyle); sheet.setColumnWidth(i, 20 * 256); } // 数据从第二行开始写,空值统一写 "-" for (int i = 0; i < list.size(); i++) { Row row = sheet.createRow(i + 1); StudentVO vo = list.get(i); row.createCell(0).setCellValue(nullToDash(vo.getStudentNo())); row.createCell(1).setCellValue(nullToDash(vo.getName())); row.createCell(2).setCellValue(vo.getGender() == 1 ? "男" : "女"); row.createCell(3).setCellValue(nullToDash(vo.getClassName())); row.createCell(4).setCellValue(nullToDash(vo.getDeptName())); row.createCell(5).setCellValue(statusText(vo.getStatus())); // 日期格式化,避免出现 Excel 的序列号数字 if (vo.getBirthday() != null) { row.createCell(6).setCellValue(vo.getBirthday().toString()); } else { row.createCell(6).setCellValue("-"); } } response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment;filename=student_archive.xlsx"); workbook.write(response.getOutputStream()); } }代码里有两点是容易翻车的地方。第一是日期必须转成字符串再写入,POI 的Cell.setCellValue(Date)写入的是 Java 原生 Date,单元格显示出来会是05-Dec-2027这种英文格式或者一串数字,课设答辩时极不美观。第二是空值处理,数据库字段为NULL时 POI 默认写入空单元格,看起来像数据缺失,统一替换成-能提升演示观感。StringBuilder在拼接超大批量数据时推荐使用,但这里直接写入 Workbook,每次createCell的性能损耗已经可控,不需要再做额外缓冲。
导出的文件名要注意中文编码问题,浏览器直接下载时中文文件名容易乱码。解决办法是在设置Content-Disposition时对文件名做 URL 编码,或者干脆用英文文件名,我一般偷懒用英文,规避掉一类兼容性风险。
5. 学生档案管理系统的避坑指南:事务、编码与文件上传
5.1 档案更新丢失:@Transactional没加或加错位置
现象:页面上成功提示“修改完成”,刷新后数据库里还是旧值,也没有任何报错。
原因:最常见的是更新方法里没有加事务,或者事务被加在了同一个类的内部调用上。Spring 事务是基于 AOP 代理实现的,同类里this.method()调用不会经过代理,@Transactional就变成了摆设。另一个原因是更新多条表只对主表做了update,关联表更新失败被吞掉异常,整体数据就处于半新半旧状态。
解决:事务注解统一放在 Service 实现类的 public 方法上,注意不要同类内部调用;写更新逻辑时优先做幂等设计,比如更新操作带上update_time条件,UPDATE ... WHERE id = ? AND update_time = ?,防止多人同时编辑把一方覆盖。这个场景和“java怎么保证数据一致性”的面试题是可以连起来讲的,把乐观锁、事务边界放在一起说,答辩时非常有说服力。
5.2 中文乱码:从表单提交到JSON响应的编码链路
现象:档案里输入“张三”,列表页显示“寮犱笁”或者“????”
原因:编码问题不是单点故障,而是链路故障。常见情况是数据库连接串没带characterEncoding=utf8,或者表字符集不是utf8mb4,又或者 POST 表单取值时没有经过 UTF-8 解码。三处只要有一处不对,中文就乱。
解决:连接串统一写jdbc:mysql://localhost:3306/archives?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai;Spring Boot 2.x 以上版本要在配置里注册CharacterEncodingFilter,或者直接在application.yml里设置server.servlet.encoding.force=true。表结构和客户端字符集全部对齐utf8mb4,乱码问题基本根除。排查乱码时不要改一个地方就重新启动试,把 MySQL 连接、页面渲染、前端请求三处都检查完再下手,一次调通比反复横跳效率高得多。
5.3 照片上传失败:MultipartFile大小限制与保存路径
现象:上传照片时明明选了文件,控制台报MaxUploadSizeExceededException,或者FileNotFoundException说目录不存在。
原因:Spring Boot 默认最大上传文件大小只有 1MB,现在手机拍出来的档案照片动辄两三MB,必然被拒。还有一类情况是上传路径写成了绝对路径/usr/local/upload/,本地 Windows 开发时存在D:\upload,部署到 Linux 服务器后目录不存在就抛异常。
解决:在application.yml里调整上传限制,同时把路径做成配置项,不写死在代码里:
spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB file: upload-dir: ./upload上传时用UUID重命名文件,截掉扩展名,避免中文文件名导致静态资源访问 404,也避免路径穿越攻击。file.upload-dir用相对路径./upload,以项目启动目录为基准,无论本地还是服务器,创建目录的逻辑要在上传前先执行,Files.createDirectories保证多层目录一次性生成。静态资源映射里把upload目录暴露给/files/**,前端图片路径就可以正常访问了。
5.4 数据一致性:删除档案时关联数据怎么办
现象:删除一个学生后,他的选课记录、成绩记录、奖惩记录全部变成“孤儿数据”,重新统计班级人数时数字对不上。
原因:学生档案表被多张业务表引用,物理删除直接把主表行删掉,关联记录还在原地,外键约束如果没建,数据库不会报错,但逻辑已经脏了。
解决:档案系统里的一律用逻辑删除,deleted字段置 1,查询过滤条件带上deleted = 0,本质上没有任何一行数据真正消失。成绩表等关联表按学号关联而不是按自增主键关联,这样即使档案被逻辑删除,历史成绩依然可以通过学号回溯。学号原则上不允许修改,学校教务系统里学号是全局唯一且伴随整个学业周期的,在代码层面直接禁用修改学号的功能,这是最简单也最稳妥的策略。这条链路讲清楚后,“java怎么保证数据一致性”的追问你就能从业务层面给出完整回答,而不只是背八股文。
6. 把系统从“能跑”做到“能答辩”:接口验证与加分扩展
6.1 用一个JUnit冒烟测试串起登录、查询、导出
答辩前最怕的是演示到一半页面白屏。我现在的习惯是写一个最小冒烟测试,用MockMvc把登录、列表查询、导出三个核心接口串起来跑一遍。这个测试只验证状态码和响应头,不追求覆盖率,但能抓住 90% 的“项目换机器跑不起来”问题。
@SpringBootTest @AutoConfigureMockMvc class SmokeTest { @Autowired private MockMvc mockMvc; @Test void shouldPassLoginAndExport() throws Exception { mockMvc.perform(post("/doLogin") .param("username", "admin") .param("password", "123456")) .andExpect(status().is3xxRedirection()); mockMvc.perform(get("/student/list") .param("pageNum", "1") .param("pageSize", "10")) .andExpect(status().isOk()); mockMvc.perform(get("/student/export")) .andExpect(status().isOk()) .andExpect(header().string("Content-Type", "orgContainsValue(\"spreadsheetml\")")); } }这个测试的价值在于:它强制你在没有浏览器的情况下也能验证最核心的业务链路。如果换了一台电脑、换了数据库,跑一遍测试立即知道配置哪里断了,不用打开浏览器一步步点。
6.2 答辩现场演示的HTTP验证清单
答辩时不要只靠浏览器,我一般会把下面这张清单贴在课设文档附录里,演示和自查都用得上。
| 验证项 | 验证命令/操作 | 预期结果 |
|---|---|---|
| 登录鉴权 | 未登录直接访问 /student/list | 重定向到登录页 |
| 分页查询 | /student/list?pageNum=2&pageSize=5 | 返回第二页且总数正确 |
| 组合检索 | 输入学号分段 + 选择院系 | 结果同时满足两个条件 |
| 数据唯一性 | 重复提交相同学号 | 提示“学号已存在” |
| Excel导出 | 点击导出按钮并打开文件 | 中文不乱码、空值为 - |
| 错误兜底 | 传入 pageSize=99999 | 不报错,被限制到100 |
这张表本身就是你的测试文档,比空写“系统测试通过”有说服力得多。
6.3 三个可以直接加分的扩展点
如果时间还来得及,优先加这三个扩展点。第一是操作日志,用 Spring AOP 拦截 Controller 写一个切面,记录谁在什么时间做了什么操作,这个功能在档案系统里是有真实业务含义的,不是炫技。第二是档案变更历史表,每次 update 前把旧值快照存进t_student_log,演示时故意改一次数据,再展示历史记录里能看到旧值和新值。第三是权限细化,把角色从单一管理员扩展成管理员、辅导员、学生三种,辅导员只能看到自己院系的学生,这一条已经能触到行级数据权限的边了。
我现在的习惯是把“能演示出问题”和“能演示出边界”当成两个验收标准。很多课设能跑通正常流程,但问一句“如果操作错了会怎样”就答不上来。把那几个异常分支写清楚,比你多写两个花哨页面更有价值。希望这篇笔记帮到你,祝答辩顺利。
本文还有配套的精品资源,点击获取