ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Java的高校毕业生实习管理系统开发实战解析

基于Java的高校毕业生实习管理系统开发实战解析 简介基于Java的高校毕业生实习管理系统毕业设计资料包面向计算机专业毕业生及Java Web方向学习者切中高校实习实训管理中流程繁琐、数据分散、审批滞后等痛点。压缩包约29.05MB内含6项内容毕业论文、开题报告、任务书、数据库SQL脚本、Excel表结构预览以及Stutrain项目完整源码目录覆盖课题研究、文档撰写、数据设计到代码实现的全链路。已有595人浏览学习适合作为同类毕业设计的借鉴。文档部分详细阐述了系统需求分析、架构设计、功能模块划分重点讲解Servlet、JSP、JDBC、MVC模式及MySQL优化思路配套源码和SQL脚本可直接导入运行帮助还原用户登录注册、实习岗位发布、学生申请、成绩录入、统计分析等功能并为二次开发提供基础。适合作为毕业设计选题参考也可用作Java Web课程综合实训案例。1. 毕业设计选“基于java的高校毕业生实习管理系统”它到底在解决什么谁需要它每年毕业季高校的实习管理都靠辅导员在 Excel 和微信群里救火。一个基于 java 的高校毕业生实习管理系统本质上是把这套线下流程搬到线上学生浏览岗位、提交申请企业导师接收学生、批阅周报辅导员汇总实习成绩管理员维护基础数据。它不是单纯的增删改查而是多角色、多状态、带文件上传和统计报表的完整业务闭环。这个选题适合两类人一是想做 Java 后端方向毕业设计的学生二是想用一个能展示业务深度的项目来应付面试的 Java 工程师。反直觉的是这种项目最难的不是界面好看而是流程状态和并发边界的数据一致性后面会专门展开。2. 基于 Java 的技术选型Spring Boot MyBatis-Plus 为什么是毕业设计的稳妥答案毕业设计最怕的不是写不出代码而是技术选型撑不到答辩。我见过太多人选了冷门框架遇到问题在中文社区搜不到答案最后在环境配置上耗掉一个月的例子。我的原则是“能跑起来、能讲清楚”优先Spring Boot MyBatis-Plus 就是现实中最稳的搭配。2.1 Java 技术栈的取舍面向对象设计与 Java 基础之外的现实考量先说语言。高校课程大部分是 Javajava 工程师岗位也多用 Java 做毕设答辩和后续找工作都能复用。Java 是面向对象编程的典型代表实习管理系统里的“学生、企业导师、岗位、周报”都是天然的对象用类来表达业务实体比直接用 SQL 硬写要清晰得多。面试被问 Java 八股文时这个项目也能当例子封装了状态枚举用继承提取公共 BaseEntity用接口定义审核服务。这些都是 java 基础里的概念但能在项目里真正用出来的人不多。经常有人问 Python 与 Java 的优缺点Python 开发快类型约束弱适合机器学习作业但这个系统里要频繁处理事务、锁、状态迁移Java 的强类型和生态能让你少踩很多坑。尤其做毕业设计周三写的东西周五再看Java 的强类型能帮你快速找回上下文Python 的“随手改”反而容易把状态改乱。如果正在纠结 java 学习路线我的建议是先做一个小项目再回头补并发和 JVM这个顺序比看书刷题快。Spring Boot 解决的是配置黑匣子的问题内置 Tomcat、简化依赖管理让一个 java 项目从 main 方法跑起来只要几分钟。MyBatis-Plus 在 Spring Boot MyBatis 生态上是主流选择专注单表 CRUD配合分页插件和代码生成器能省掉大量重复的 Mapper 方法。很多同学担心只用 MyBatis-Plus 会让 SQL 能力退化我的做法是简单查询用 LambdaQueryWrapper复杂报表写自定义 XML两头都练到。落到执行层面第一步是新建一个 Spring Boot 工程勾选 Web、MySQL、Lombok再手动加入 MyBatis-Plus 依赖。别急着写业务先把工程跑出一个空接口确认环境没问题再开始拆模块。这个顺序能让 java 启动失败的坑提前暴露而不是拖到代码写了一半才炸。2.2 系统角色与功能模块拆解辅导员、企业导师、学生各管什么我习惯先画一张角色-权限表把业务边界定死避免后期一个角色管了不该管的数据。以常见流程为例角色主要操作核心数据对象学生维护个人信息、浏览岗位、提交申请、上传实习材料、写周报、查看成绩申请记录、周报企业导师发布岗位、审核申请、批阅周报、评定成绩岗位、申请、周报、成绩辅导员审核实习备案、批量导入学生、汇总成绩、导出统计报表学生名单、成绩、统计表管理员用户管理、角色分配、菜单权限、系统参数用户、角色、菜单在这个划分里学生和企业导师是业务主力辅导员更像审核和出口。把每个角色能碰的数据列表写清楚下一步建表就自然了。权限控制不一定上 Spring Security毕设可以先用一个拦截器把 token 里的 userId 和 roleId 放进去再在 Service 层校验归属权。这样做的好处是代码量少答辩时也能说清楚。功能模块上我建议至少拆出用户认证、岗位管理、实习申请、周报管理、成绩鉴定、公告、统计报表。其中统计报表是容易被忽略的加分项拿 ECharts 画一个“各专业实习去向分布”比多写十个 CRUD 更出效果。实操上我给每个模块列一份接口清单开发时对照着做。认证模块是POST /api/auth/login、POST /api/auth/logout、GET /api/auth/info岗位模块是POST /api/job/publish、GET /api/job/page、GET /api/job/detail申请模块是POST /api/apply、GET /api/apply/mine、POST /api/apply/confirm周报模块是POST /api/weekly/submit、GET /api/weekly/page成绩模块是POST /api/result/finish、GET /api/result/export。这份清单直接对应后面第 6 章的自动化验证先列出来会让开发节奏快很多。2.3 核心业务流程与状态机设计让实习状态有唯一通路实习管理不是一锤子买卖。一个实习生从提交申请到最终归档要经历多个状态申请待确认、实习中、待鉴定、已结束还有可能被驳回。如果不做约束前端传一个状态值就能任意跳转比如还没实习就变成已结束成绩鉴定也就失去意义。我的处理方式是定义状态枚举在 Service 层校验状态迁移。常见写法如下public enum InternshipState { PENDING(待确认, 0), ACTIVE(实习中, 1), FINISHING(待鉴定, 2), CLOSED(已结束, 3), REJECTED(已驳回, -1); private final String desc; private final int code; InternshipState(String desc, int code) { this.desc desc; this.code code; } private static final MapInternshipState, SetInternshipState ALLOWED Map.of( PENDING, EnumSet.of(ACTIVE, REJECTED), ACTIVE, EnumSet.of(FINISHING), FINISHING, EnumSet.of(CLOSED) ); public boolean canTransferTo(InternshipState target) { return ALLOWED.get(this) ! null ALLOWED.get(this).contains(target); } public int getCode() { return code; } }参数说明枚举里的 desc 用于界面展示code 用于和数据库整数互转。canTransferTo方法会在 Service 的changeState里调用如果返回 false直接抛出业务异常。这里有一个 Java 基础考点Map.of是 Java 9 之后才有的如果还在 JDK 8 上写代码要改成两层 HashMap 赋值。毕设机房经常装的是 JDK 8别在编译器版本上栽跟头。状态机设计属于可执行步骤看起来简单但对答辩非常加分老师问“如何保证数据一致性”除了乐观锁状态机的约束也是一个亮点。第 4 章和第 5 章会围绕这个状态机写业务和踩坑。3. 数据库设计与自动建表用 MyBatis-Plus 根据 Java 实体类生成建表 SQL数据库设计在整个系统里起承上启下作用。设计得不好后面业务代码全是selectById加updateById循环补漏洞。这一章把表格结构和自动建表讲透。3.1 核心表结构用户、岗位、申请、周报、鉴定第一次做这类系统我建议先列一张“主数据 过程数据”的清单避免跟着别人代码走。核心表如下表名用途关键字段sys_user用户主表id, username, password, real_name, role_id, phone, email, avatar, statussys_role角色表id, role_code, role_nameinternship_job实习岗位表id, title, company_name, description, location, headcount, state, publish_timeinternship_apply实习申请表id, student_id, job_id, state, apply_time, confirm_time, reject_reasoninternship_weekly周报表id, apply_id, student_id, week_seq, content, score, create_timeinternship_result成绩鉴定表id, apply_id, final_score, comment, archive_timesys_notice公告表id, title, content, create_time, publisher_id这里最容易被问的问题是“为什么不用外键”。我的回答是逻辑外键足够。比如internship_apply里的student_id和job_id在 Java 实体里用Long保存查询时再 join 或查字典表避免数据库级联删除把历史数据一起删掉。毕业设计答辩时老师会接受这个理由前提是你把索引建好。索引方面internship_apply上建议建一个联合索引(student_id, job_id)查询学生已申请岗位列表会快很多internship_weekly上建(apply_id, week_seq)保证一个申请下的周报序号不重复。这些索引在自动建表脚本里要一起写进去。3.2 实体类与注解怎么把实体映射成建表 SQL先说结论MyBatis-Plus 官方没有一键“实体类生成表”的功能写一个工具类反射实体上的TableName和TableField生成建表语句是常见做法。这样改实体时不用再手工同步 SQL。下面是一个能跑通的简化版import com.baomidou.mybatisplus.core.metadata.TableInfo; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.MybatisMapperBuilderAssistant; public class SchemaDdlGenerator { public static String generate(Class? entityClass) { MybatisMapperBuilderAssistant assistant new MybatisMapperBuilderAssistant(new MybatisConfiguration(), ); TableInfo tableInfo TableInfoHelper.initTableInfo(assistant, entityClass); StringBuilder ddl new StringBuilder(); ddl.append(CREATE TABLE IF NOT EXISTS ).append(tableInfo.getTableName()).append( (\n); for (TableFieldInfo field : tableInfo.getFieldList()) { ddl.append( ).append(field.getColumn()) .append( ).append(mapType(field.getPropertyType())) .append( NULL,\n); } ddl.append( PRIMARY KEY ().append(tableInfo.getKeyColumn()).append()\n); ddl.append() ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT实体对应表;); return ddl.toString(); } private static String mapType(Class? type) { if (type Long.class) return bigint; if (type Integer.class) return int; if (type LocalDateTime.class) return datetime; if (type String.class) return varchar(255); if (type BigDecimal.class) return decimal(10,2); return varchar(255); } }代码逻辑TableInfoHelper.initTableInfo是 MyBatis-Plus 内部用来解析实体注解的入口调用后tableInfo.getFieldList()会返回包括字段名、字段类型的信息。生成 SQL 时把列名和 Java 类型映射到 MySQL 类型。这个工具类拿来应付毕设场景够用但不要直接用在生产环境。参数说明mapType只做了最小映射如果实体里出现长文本String后面有一个典型坑会在第 5 章讲。Boolean可以映射为tinyint(1)Date可以映射为datetime。调用时直接SchemaDdlGenerator.generate(InternshipApply.class)把输出打印到日志或写进schema.sql。提示不同 MyBatis-Plus 版本的TableInfoHelper包路径可能有差异生成前先确认自己依赖里的实际包名。实体类示例Data TableName(internship_apply) public class InternshipApply extends BaseEntity { TableId(type IdType.AUTO) private Long id; TableField(student_id) private Long studentId; TableField(job_id) private Long jobId; TableField(state) private Integer state; TableField(apply_time) private LocalDateTime applyTime; TableField(weekly_count) private Integer weeklyCount; }配置说明TableId(type IdType.AUTO)让主键交给 MySQL 自增TableField(student_id)显式指定数据库列名不用依赖全局驼峰转下划线配置。BaseEntity可以抽公共字段比如create_time、update_time、deleted配合TableField(fill FieldFill.INSERT)做自动填充。3.3 初始化连接与分页插件给查询加一层统一能力数据库设计最后一步是配置application.yml和 MyBatis-Plus 拦截器。很多教程会漏掉分页插件结果.page()分页失效查出全部数据自己切内存直接爆。配置如下spring: datasource: url: jdbc:mysql://localhost:3306/internship_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; } }分页插件的setMaxLimit(500L)是防止传入超大页码把数据库打垮属于安全兜底。逻辑删除配置能让deleted字段自动拼接WHERE deleted0但要注意字段里含逻辑删除标记时唯一索引可能成为二次投递的障碍这个矛盾在后面讲。这一章是数据库层面动手做的时候不要一上来就写几十个字段先让表和实体跑通再按业务加字段。实体类建好后用SchemaDdlGenerator生成一次 SQL导入数据库再启动项目避免手敲 SQL 产生字段名不一致。4. 核心业务代码实现从岗位发布到实习周报的完整链路前两章把地基打好这一章写真正会被答辩老师盯住的三段代码岗位发布与实习申请、多条件分页查询、周报提交与成绩评定。我按“常见实现”来写而不是贴一份网上抄来的全量代码。4.1 岗位发布与实习申请Controller、Service、Mapper 怎么配合很多初学项目把业务逻辑写在 Controller 里接口越来越胖。我的习惯是 Controller 只做参数接收和结果返回业务判断全部放 Service。岗位发布接口可以简化成这样RestController RequestMapping(/api/job) public class InternshipJobController { Resource private InternshipJobService jobService; PostMapping(/publish) public RBoolean publish(RequestBody InternshipJob job) { job.setState(1); job.setPublishTime(LocalDateTime.now()); jobService.save(job); return R.ok(true); } }这里有个细节setState(1)表示岗位上架。不要用0/1满天飞建议在InternshipJob里也做一个枚举JobState哪怕只是ONLINE(1)和OFFLINE(0)。枚举可以让后面的筛选代码可读性大增。学生提交申请是最容易出现并发重复的业务。常见做法是先查再插但两个请求同时查到不存在就会插入两条。更好的做法是让数据库兜底在internship_apply表加唯一索引(student_id, job_id)。如果学校允许学生多次投递不同岗位唯一索引可以改成(student_id, job_id, state)保证同岗位同状态下只有一条申请。Service 层代码如下Override Transactional(rollbackFor Exception.class) public boolean apply(Long studentId, Long jobId) { InternshipJob job jobService.getById(jobId); if (job null || job.getState() ! 1) { throw new BusinessException(岗位不存在或已下架); } InternshipApply apply new InternshipApply(); apply.setStudentId(studentId); apply.setJobId(jobId); apply.setState(InternshipState.PENDING.getCode()); apply.setApplyTime(LocalDateTime.now()); this.save(apply); return true; }为什么这里没有手动 count 再做判断因为事务隔离默认下两个事务同时 count 都返回 0还是会重复插入。把唯一索引建好数据库报错后由全局异常处理器转成“请勿重复申请”提示更可靠。Transactional(rollbackFor Exception.class)确保抛异常时回滚不会留下半条数据。参数说明BusinessException是自定义异常可以通过RestControllerAdvice统一捕获返回 JSON 里的错误码和 message。这一段属于 Java 后端基本功建议在答辩前自己写一遍别直接复制。4.2 多条件筛选分页与排序LambdaQueryWrapper 不做 SQL 拼接毕业设计里列表页往往有搜索条件岗位名称、企业名称、状态、发布时间排序。用 MyBatis-Plus 的LambdaQueryWrapper可以避免手动拼接 SQL 字符串也能防住 SQL 注入。常见写法public PageInternshipJob pageJobs(int current, int size, String keyword, Integer state) { PageInternshipJob page new Page(current, size); LambdaQueryWrapperInternshipJob wrapper new LambdaQueryWrapper(); wrapper .like(StringUtils.hasText(keyword), InternshipJob::getTitle, keyword) .eq(state ! null, InternshipJob::getState, state) .orderByDesc(InternshipJob::getPublishTime); return jobService.page(page, wrapper); }说明like的第一个参数是布尔条件为 false 时不会拼接这个条件这是 MyBatis-Plus 最实用的点。orderByDesc把排序交给数据库ORDER BY publish_time DESC不要把数据查出来后用 Java 内存排序。经常有同学翻“冒泡排序 java”想在业务里用那种算法题属于 java 基础训练放在业务列表里属于性能事故。分页插件生效后Page对象里会自动装配total、pages等属性。要注意size参数别直接用前端传值不校验setMaxLimit(500L)虽然能兜底但更好的做法是在 Controller 里限制size 50。4.3 周报提交与成绩评定事务边界与乐观锁周报提交涉及两个数据表internship_weekly插入一条周报同时更新internship_apply.weekly_count。这个动作必须在一个事务里。写法Transactional(rollbackFor Exception.class) public void submitWeekly(WeeklySubmitDTO dto) { InternshipApply apply getById(dto.getApplyId()); if (apply.getState() ! InternshipState.ACTIVE.getCode()) { throw new BusinessException(当前实习状态不允许提交周报); } InternshipWeekly weekly new InternshipWeekly(); weekly.setApplyId(dto.getApplyId()); weekly.setStudentId(dto.getStudentId()); weekly.setWeekSeq(dto.getWeekSeq()); weekly.setContent(dto.getContent()); weekly.setCreateTime(LocalDateTime.now()); weeklyService.save(weekly); LambdaUpdateWrapperInternshipApply updateWrapper new LambdaUpdateWrapper(); updateWrapper.eq(InternshipApply::getId, dto.getApplyId()) .setSql(weekly_count weekly_count 1); update(updateWrapper); }事务边界周报插入和计数更新是整体成功或失败不能出现周报有记录但计数没变。这里使用setSql(weekly_count weekly_count 1)而不是先查再updateById避免并发下互相覆盖。如果你对“数据一致性”这个词有疑问这个场景就是最典型的答案。成绩评定环节企业导师给实习生打总分。如果两个导师同时操作同一个申请后保存的会覆盖先保存的。用 MyBatis-Plus 的乐观锁可以缓解实体增加Version字段更新时框架自动拼接version version 1并在更新结果返回 0 时重试或提示“数据已被他人修改”。核心代码Transactional(rollbackFor Exception.class) public boolean finishInternship(Long applyId, Integer finalScore) { InternshipApply apply getById(applyId); if (apply null || !InternshipState.ACTIVE.canTransferTo(InternshipState.FINISHING)) { throw new BusinessException(非法的状态迁移); } apply.setFinalScore(finalScore); apply.setState(InternshipState.FINISHING.getCode()); return updateById(apply); }乐观锁字段说明Version注解字段在数据库对应version int每次 update 都会加 1。使用updateById才会触发版本判断使用LambdaUpdateWrapper直接setSql不会自动处理版本号这个边界很多人踩过。如果更新失败可以在 Service 里捕获OptimisticLockException给前端返回“请刷新后重试”。这一章代码量不大但每一段都是直接能落地的结构。你把它适配到自己项目里时把表名、字段名和路径替换掉就行。5. 避坑记录从环境配置到上线前最常翻车的 5 个问题这一章是我自己做这类项目的血泪经验汇总。很多问题在教程里不会写但实际调试时一定会撞上。5.1 JDK 与 IDEA 环境不一致java 启动失败怎么解决现象IDEA 里点了半天都正常换成java -jar app.jar启动报错UnsupportedClassVersionError。原因IDEA 的 Run Configuration 单独指定了 JDK 17而系统JAVA_HOME指向 JDK 8命令行启动时用了旧 JDK。解决先跑java -version查看默认 JDK再在 IDEA 里检查 Project Structure 的 SDK 和 Maven 的 Java 版本三个地方必须统一。如果新建的 Spring Boot 3 项目强制要求 JDK 17学校机房又是 JDK 8要么把项目改回 Spring Boot 2.x要么装一个新的 JDK 并配置JAVA_HOME。这也解释了为什么选型时先确认 Java 环境配置有多重要。5.2 IDEA 编译时 OOM进程堆大小调大仍报 OutOfMemoryError现象编译模块时报java.lang.OutOfMemoryError: GC overhead limit exceeded搜到“调大 IDEA 堆”在 Help Edit Custom VM Options 里把-Xmx调到 8000MB重启后仍然报错。原因IDEA 本身和编译进程是两个 JVM。修改idea64.exe.vmoptions只影响 IDEMaven 编译插件跑在独立进程里。解决在Settings Build Tools Compiler里把Shared build process heap size从默认的 700 调大到 1500 或 2048MB如果你用 Maven 命令行编译在MAVEN_OPTS里设置-Xmx2048m。还有一种快速验证先跑mvn clean compile -DskipTests如果命令行也报错问题在依赖或代码不是在 IDEA。5.3 MyBatis-Plus 自动建表的字段类型陷阱现象实体类里String content建表后成了varchar(255)学生周报写 300 字就被截断数据库直接报错。原因自动建表工具里String只有默认长度映射无法识别长文本。解决如果坚持用实体生成 SQL给content字段对应的TableField增加jdbcType JdbcType.LONGVARCHAR或干脆在mapType里对长文本做专门映射。另一个陷阱state字段用Integer存但业务里到处硬编码0/1/2改状态时漏改一处就出数据不一致。我一般会在枚举里加getCode()并让状态机去做迁移校验问题能避免一大半。5.4 LocalDateTime 前后端数据一致性问题现象后端返回时间字段[2025, 5, 1, 11, 30]前端怎么格式化都不是时间反过来前端传2025-05-01 11:30:00后端报 JSON 解析失败。原因Jackson 默认没有注册jsr310时间模块LocalDateTime被序列化成数组。解决在依赖里加jackson-datatype-jsr310然后在application.yml配置spring: jackson: serialization: write-dates-as-timestamps: false time-zone: GMT8注意write-dates-as-timestamps: false会让LocalDateTime输出成2025-05-01T11:30:00带一个T。如果前端要求yyyy-MM-dd HH:mm:ss需要自定义 Jackson 的LocalDateTimeSerializer而不是简单靠spring.jackson.date-format那个配置对LocalDateTime不一定生效。另外数据库连接串上的serverTimezoneAsia/Shanghai也要写否则 MySQL 和 Java 的时区不一致时间差 8 小时是常态。数据一致性不只有并发还包括时间格式的全链路统一。5.5 文件上传在本机能用Linux 部署后 404现象本地 Windows 上传头像、实习证明都正常把项目打成 tar 包丢到 Linux 服务器上上传成功但访问图片 URL 返回 404。原因代码里写死了C:\upload\...或相对路径upload/服务器上工作目录不对或者 Tomcat 没有映射静态资源。解决把上传根目录做成application.yml配置项upload: path: /var/data/internship-upload代码里用Paths.get(uploadPath).toAbsolutePath()拼接再在配置类里把/upload/**映射到物理路径Configuration public class WebMvcConfig implements WebMvcConfigurer { Value(${upload.path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath /); } }注意Linux 路径区分大小写文件名尽量不要包含中文和空格否则会出现“存了但打不开”的玄学问题。服务器上还要确保/var/data/internship-upload目录存在并具有写权限否则上传时会报AccessDeniedException。6. 一张“体检表”验证系统写接口自动化测试框架而不是点一遍页面6.1 用 RestTemplate 串起核心流程的回归测试功能开发完最忌讳的就是“手点一遍没问题”就交差。改一个字段可能连带旁边的流程全挂。我后来养成的习惯是写一个接口自动化测试框架用 RestTemplate 把登录、申请岗位、提交周报、成绩评定串成有序调用链。每次改动后跑一遍相当于给系统做体检。核心思路是定义一组测试方法按顺序调用。关键片段SpringBootTest class InternshipFlowApiTest { Autowired private RestTemplateBuilder builder; private HttpHeaders authHeaders(String username, String password) { String body {\username\:\ username \,\password\:\ password \}; HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); String resp builder.build().postForObject( http://localhost:8080/api/auth/login, new HttpEntity(body, headers), String.class); String token JsonPath.from(resp).getString(data.token); HttpHeaders auth new HttpHeaders(); auth.setBearerAuth(token); return auth; } Test void fullFlow() { HttpHeaders student authHeaders(student01, 123456); InternshipApply apply new InternshipApply(); apply.setJobId(1L); apply.setStudentId(1L); ResponseEntityString result builder.build().postForEntity( http://localhost:8080/api/apply, new HttpEntity(apply, student), String.class); assertTrue(result.getStatusCode().is2xxSuccessful()); } }这段代码的价值不是覆盖所有接口而是保证核心链路在改动后依然是通的。配合 Maven 的-DtestInternshipFlowApiTest可以直接在命令行跑交答辩时也能作为自动化验证依据。需要额外引入json-path依赖用来解析 token。6.2 把测试结果变成答辩现场的演示材料我习惯在答辩前把其中一条测试的输出整理成一份简洁报告登录是否通过、岗位申请是否成功、周报是否提交、成绩是否归档。每个步骤一条断言失败时红色通过时绿色。老师问“你如何验证系统正确性”时直接打开报告比现场点页面更有说服力。这里也可以顺便把 Java 数据一致性设计串成问题为什么用状态机为什么用乐观锁为什么唯一索引兜底这三个答案恰好对应第 2 章、第 4 章、第 5 章的踩坑点。能说清楚这些比堆一堆炫技中间件更能体现 Java 基础的扎实程度。坦白说我当年做毕设就是“点通为主”结果答辩前老师随机点了一个从驳回直接到已结束的非法路径当场翻车。后来我把这种接口自动化测试的习惯带到工作项目里每次改动先跑一遍核心流程心里就有底了。希望帮到你。本文还有配套的精品资源点击获取
返回列表