第14至20周 测试部署与求职准备
并发派单、真实数据库测试、打包部署、备份恢复、优化和项目表达。
完整学习目录 · 14 篇
本篇目录
这七周把已能运行的设备运维工单平台变成能验证、能交付、能讲清楚的作品。按每周约 15—18 小时安排,表中的小时已经包含阅读、动手和验证,不需要额外再加一套课程。顺序可以移动,但先通过本周验收,再进入依赖它的下一周。考试、春节安排或环境问题导致延期时,先取消第 19 周扩展,把时间还给主线。
这部分接续前十三周:Java 21、Spring Boot 4.1.x、MyBatis Starter 4.0.x、MySQL,登录使用 Spring Security 的 Session 和 CSRF。依赖沿用前面已验证的同一组版本;本阶段不为了追新而升级。独立部署优先使用 MySQL 8.4,本地既有 8.0 可以继续初练。
开发库固定为 ops_ticket_dev,测试库固定为 ops_ticket_test。两者使用分开的连接配置和限定库权限的账号。恢复练习另建空库,不向开发库覆盖导入。本文的代码和命令是局部教学示例,要放入自己前面完成的工程并调整包名、方法签名和文件名,不是一套已经完成的项目源码。
自测先写自己的答案,再看第14至20周自测答案。整个阶段结束后转入2027年春季投递与面试。
第14周 1月11日至17日 并发派单 #
目标:同一张 OPEN 工单被两次同时派单时,只允许一个请求成功,失败者得到明确冲突。 先确认上周的角色与数据权限已经通过检查。
| 本周任务 | 小时 | 做完留下什么 |
|---|---|---|
用两个线程运行简单计数实验,认识共享变量、原子性、start 与 join | 3 | 一段小实验和自己的运行解释 |
| 画出两个管理员先查询再更新的交错过程,复现旧写法的覆盖问题 | 3 | 六步时序记录,保留失败证据 |
| 写条件更新 SQL,检查受影响行数,把状态更新和操作记录放进一个事务 | 5 | 派单 Mapper、Service、冲突响应 |
| 两个独立连接或两个独立请求并发派单,重复验证并检查数据库 | 4 | 成功一次、冲突一次、有效日志一条 |
| 复盘事务边界,做两道哈希表或二分题,完成自测 | 3 | 三题答案和一条有意义的提交 |
两个请求都曾经读到 OPEN,并不代表它们更新时仍然有权派单。事务保证几条操作共同成功或失败;“本次修改必须基于我读到的旧状态”还需要写入条件。Java 的 synchronized 可以保护同一个 JVM 内共享的锁对象,却不能独自协调两台应用之间的数据库修改。
下面是 MyBatis XML 中的局部 SQL。Java 参数 expectedVersion 对应数据库字段 version;assigneeId 先验证确实是启用的 ENGINEER,操作者身份来自登录会话。
UPDATE ticket
SET assignee_id = #{assigneeId},
status = 'IN_PROGRESS',
version = version + 1,
updated_at = CURRENT_TIMESTAMP
WHERE id = #{id}
AND status = 'OPEN'
AND version = #{expectedVersion};更新返回 1 才写入 ASSIGN 操作记录;返回 0 表示这次操作没有获准,不能假装派单成功。本项目先检查是否存在和是否有权限,再把条件更新失败映射为 HTTP 409;页面提示刷新。ticket_action_log 的 actor_id 来自会话,from_status 为 OPEN,to_status 为 IN_PROGRESS。Service 对外事务方法要经过 Spring Bean 调用,不能只在同一个类的内部自调用处贴注解。Spring 事务说明
实验采用测试库中的新工单,记录初始版本。两个工作线程都先拿到同一版本,再用 CountDownLatch 同时放行;各自调用 Service 或分别发出 HTTP 请求。不要用一个 JDBC Connection 给两个线程共享。仅连续点击两次页面,只验证了重复请求,不能证明发生过并发。
验收: 同一初始版本下恰好一次成功;最终状态 IN_PROGRESS,版本只增加 1,处理人是胜出请求选择的人,有一条有效派单日志。没有第二位工程师账号时可以都派给 engineer_a,仍然检查两次结果;需要验证覆盖时,再创建独立测试工程师。连续执行至少五轮,每轮创建新工单,保存结果。一次请求失败并不意味着数据库故障。
卡住时: 两次都成功,先看 WHERE 是否带旧版本和状态;两次都失败,先检查初始状态、版本、角色与 CSRF;日志有两条,检查是否在判定更新结果之前插日志。不要先加入 Redis 锁。
自测: 14.1 为什么 SELECT 后判断 OPEN,再无条件 UPDATE 会出错?14.2 为什么有事务还需要条件更新?14.3 派单已经更新,写操作记录时抛出异常,最终数据库应是什么样?
第15周 1月18日至24日 自动测试与缺陷复盘 #
目标:把“我试过了”变成可重复运行的行为证据。 保留第 14 周并发实验,本周把它纳入测试或清楚的独立验证脚本。
| 本周任务 | 小时 | 做完留下什么 |
|---|---|---|
| 用 JUnit Jupiter 写三个纯 Java 规则测试,练习 arrange、act、assert | 3 | 正常值、非法值和边界测试 |
| 为登录状态、角色和资源归属写接口测试,保留 CSRF | 4 | REPORTER、ENGINEER、ADMIN 测试矩阵 |
| 在真实 MySQL 测试库验证约束、回滚和并发结果 | 5 | 可重复的数据库集成测试 |
| 从真实开发错误中选一项,先保留失败测试,再修复和回归 | 3 | 一份缺陷复盘 |
| 连续运行两次测试,核对残留数据,复习事务与 HTTP 状态码 | 3 | 测试执行记录和自测答案 |
单元测试检查某条规则,接口测试检查输入、会话、返回码,数据库集成测试检查真实 SQL 和事务。模拟 Mapper 抛异常可以验证 Service 如何响应,但模拟数据库不能证明 MySQL 回滚正确。测试名称写行为,例如 reporterCannotReadOthersTicket,不要只叫 test1。测试依赖使用所选 Boot 版本管理的 spring-boot-starter-test,Web MVC 和 Security 的测试支持按同版本文档添加,不单独拼装一组旧 JUnit 版本。Boot 测试文档
最少覆盖这八项:有效创建、空标题、报修人越权查询、错误工程师处理、非法状态迁移、重复验收、并发派单、操作记录写入失败后的回滚。登录验证再保留正确密码、错误密码、退出后三条。对 Session 应用,修改请求的测试需要有效 CSRF;缺少 CSRF 的 403 不能证明角色授权做对了。Spring Security 测试 CSRF
先做数据库隔离护栏:测试连接只指向 ops_ticket_test,测试账号无权写开发库;开始测试时查询 SELECT DATABASE() 并断言库名。每个测试创建自己的数据,记录主键,按依赖顺序清理自己创建的记录。不得用“清空所有表”解决残留问题。使用固定样本时,明确它属于测试库,不与正在演示的实例共用。
验证回滚时,真实调用 Spring 管理的 Service:让工单更新实际执行,再让日志写入因测试故障失败,最后重新查询工单与日志。测试本身若带自动回滚事务,可能掩盖业务事务缺失;这项测试不要依赖外层测试事务。真实端口的 HTTP 请求在服务端线程里执行,也不会自动跟随测试线程回滚。Boot 应用测试说明
验收: 测试失败时能看到哪条业务规则破坏;至少一次回滚测试验证真实数据库;测试连续执行两遍都通过;测试后开发库内容未变。回滚故障注入只在测试配置生效,不能留下公开的“模拟失败”业务接口。
卡住时: 全部 403,先分别检查登录与 CSRF;测试绿但页面错,检查是否把真实权限组件全替换成了 mock;第二次失败,检查测试数据是否独立;测试一直等,检查并发放行条件、连接池数量和等待超时。
自测: 15.1 mock Mapper 返回成功,能证明数据库事务正确吗?15.2 测试方法自动回滚,为什么仍可能漏掉 Service 的事务错误?15.3 “别人的工单返回 403”这一测试,要怎样排除其实只是缺少 CSRF?
第16周 1月25日至31日 可以交付的第一版 #
目标:离开 IDEA 的运行按钮,仍能启动并演示项目。达到本周标准就可以匹配投递,不必等扩展完成。
| 本周任务 | 小时 | 做完留下什么 |
|---|---|---|
| 核对 JDK、Maven Wrapper、依赖与配置,把数据库凭据移出源码 | 3 | 可复现版本记录和配置样例 |
运行测试并打包,在新终端使用 java -jar 启动 | 4 | 可执行 jar 和真实启动记录 |
| 编写 README、初始化说明与已知限制 | 3 | 新读者按步骤运行的文档 |
| 准备虚构演示数据,录一次 5—8 分钟讲解 | 3 | 演示脚本、录屏或截图 |
| 写一页简历草稿,核对每个技术词对应的实现和证据 | 3 | 可投递草稿与修订记录 |
配置和程序应分开:程序描述“需要哪个配置项”,运行环境给出具体数据库地址、用户和密码。提交 application-example.yml 或写好带占位符的说明,真实 .env、日志、备份和密码不提交。Spring 的环境变量可以覆盖配置;变量名必须与你实际使用的配置方式一致。使用标准数据源配置时可设置 SPRING_DATASOURCE_URL、SPRING_DATASOURCE_USERNAME、SPRING_DATASOURCE_PASSWORD,不要同时再维护另一套含义重复的变量。
Windows PowerShell 在项目根目录执行下面的构建示例。ops-ticket-0.0.1-SNAPSHOT.jar 只是示例,先查看 target 中的实际文件名,并确认它是包含启动信息的可执行 jar。
java -version
.\mvnw.cmd -version
.\mvnw.cmd clean verify
Get-ChildItem -LiteralPath .\target -Filter *.jar
java -jar .\target\ops-ticket-0.0.1-SNAPSHOT.jar上述最后一条会在当前终端持续运行,结束演示用 Ctrl+C 正常停止。不要为了得到“构建成功”默认跳过测试。若 MySQL 集成测试设计成单独 profile,README 必须分别给出普通测试命令和数据库测试命令,并说明实际运行了哪一个。
README 至少包含:项目解决什么问题、三种角色、状态流转、技术版本、JDK 与数据库准备、配置填写、建表和虚构演示数据、测试、构建启动、接口与演示顺序、已知限制。把账号密码的演示规则单独说明:演示账号只用于本地虚构环境,密码仍通过编码器存储,不能把明文直接填进密码哈希列。
推荐演示顺序:reporter_a 创建报修单 → admin_a 派给 engineer_a → 工程师提交处理说明 → 报修人退回 → 再次处理并验收 → 查看操作记录 → 展示 reporter_b 无权读取该单 → 简要解释并发测试。用新的演示工单走流程,保留 101、102、103 基础样本作为查询对照。
验收: 从新终端可以按 README 启动;重启后工单还在;三个角色走通完整业务;测试通过;每句简历描述都能指到代码、SQL、测试或实际测量。部署地址不是这一周的必需品,本机演示可以作为作品展示。
卡住时: no main manifest attribute,检查打包插件与选择的 jar;端口被占用,确认自己已有实例,或指定空闲端口;连接数据库失败,依次检查服务、地址、库名、用户权限和实际生效的配置。
自测: 16.1 同一份 jar 为什么能在不同数据库地址运行?16.2 README 中只有“点击 Run”缺少了什么?16.3 没做性能实验,简历能不能写“高并发、高性能”?
第17周 2月1日至7日 部署与备份恢复 #
目标:能说明应用在哪里运行、数据放在哪里、出错怎么查,并真正恢复一次数据库。 不需要购买云服务器。Linux 环境可以是已有虚拟机、WSL 或可用的本地环境;电脑资源不足就先完成本机 jar 路线,Linux 操作另记为待练,不冒充已经部署。
| 本周任务 | 小时 | 做完留下什么 |
|---|---|---|
| 在 Linux 练习目录、权限、进程、端口、日志六类操作 | 3 | 自己验证过的命令小卡片 |
| 二选一:在已有 Linux 环境跑 jar,或完成本机独立运行与配置检查 | 4 | 环境信息、启动停止与日志说明 |
| 环境允许时学习 Docker Compose;否则用本机数据库加 jar 复现同样业务 | 4 | 运行说明和数据目录说明 |
| 导出开发库副本,恢复到全新的空库,核对中文和关联记录 | 4 | 一份备份及恢复核对记录 |
| 模拟一次配置错误并恢复,复习进程、端口与 TCP,完成自测 | 3 | 排错记录和本周验收 |
Linux 常用操作先掌握 pwd、ls、cd、cat、tail -n 100、ps、ss -ltnp。读日志时先定位最新异常与 Caused by,再关联时间、请求和数据库情况。只处理自己项目的进程,不能见到 Java 就全部终止。前台启动适合初次排错;稳定运行与服务管理可以以后再深入。
Docker 的镜像是启动模板,容器是运行实例,数据库持久化卷承载容器外的数据。Compose 里应用访问数据库通常使用服务名,如 db:3306;容器内 localhost 指当前容器。宿主机访问数据库则使用实际映射端口。先学 docker compose config、up -d、ps、logs、stop,不把 down -v 当重启命令;-v 会删除相关卷。Docker Compose down 文档
不安装 Docker 也可以通过本周核心验收:本机独立 MySQL、可执行 jar、外部配置、查看日志、备份恢复、另一个空目录按说明启动。把尚未练习的 Linux 或容器技能在学习记录中标为待完成。开发与学习工具的下载、许可和可用条件以使用时为准;主线不要求订阅 AI 或购买云资源。
备份示例先在项目根目录创建 backups 文件夹,将 your_backup_user 换成自己有相应权限的账号。-p 后不写密码,让客户端交互提示;文件名使用当天日期。
mysqldump -u your_backup_user -p --single-transaction --no-tablespaces --set-gtid-purged=OFF --result-file=backups/ops_ticket_dev_20270201.sql ops_ticket_dev这里不加 --databases,便于恢复到不同名称的空库。--single-transaction 面向本项目 InnoDB 表;备份时不要执行建表、改表等 DDL。Windows 使用 --result-file 可避免 PowerShell 重定向编码带来的问题。MySQL 备份文档
恢复完整步骤在自测答案中:先核对备份内容,再建立新空库,导入,核对数据并用临时配置启动。不能在尚未确认库名时执行清表、删库或覆盖导入。
验收: 备份文件存在且非空;恢复库有六张业务表;用户、设备、工单、评论、操作记录和处理记录能关联;中文没有乱码;恢复库能支撑一次查询演示。只得到一个 .sql 文件还没通过恢复验收。
自测: 17.1 为什么容器启动了,应用仍可能无法连接 MySQL?17.2 为什么备份成功不等于可以恢复?17.3 docker compose stop 与删除持久化卷,对工单数据意味着什么?
第18周 2月8日至14日 查询性能与线程池 #
目标:做一次有证据的小优化,理解资源为什么有限。 春节与学校安排可以挪动本周,先做索引实验;线程池或 JVM 内容可以使用第 19 周补齐。
| 本周任务 | 小时 | 做完留下什么 |
|---|---|---|
| 在测试库生成合理分布的几千条虚构工单,写出一个实际列表查询 | 3 | 数据数量、分布和查询条件 |
| 保存 EXPLAIN,设计一个候选联合索引,在相同条件下前后比较 | 5 | 执行计划与测量记录 |
| 写有界线程池小实验,观察队列、拒绝、异常与关闭 | 4 | 独立实验代码和现象解释 |
| 认识 JVM 堆、线程栈、GC,观察一次本机 Java 进程 | 3 | 一页概念与实际观察记录 |
| 独立解释优化取舍,完成两道链表或栈题和自测 | 3 | 优化说明与答案 |
先优化真实存在的列表查询,如报修人查看自己的某种状态工单。下面只是候选设计,是否有效由数据分布和实际执行计划决定,不要求 MySQL 必须选择它。
EXPLAIN
SELECT id, title, status, created_at
FROM ticket
WHERE reporter_id = 1 AND status = 'OPEN'
ORDER BY created_at DESC, id DESC
LIMIT 20;
CREATE INDEX idx_ticket_reporter_status_time
ON ticket (reporter_id, status, created_at DESC, id DESC);索引能缩小定位范围或帮助排序,但会增加空间与写入维护成本。不能给每个字段都加一个索引就称作优化。阅读 key、rows、Extra;传统 EXPLAIN 的行数是估计,支持的 SELECT 可以用 EXPLAIN ANALYZE 看真实执行情况,它会实际执行查询。MySQL EXPLAIN 文档
固定数据量、过滤条件、机器和应用版本,先预热,再重复测量,例如各测 10 次,保留中位数和执行计划。只有几十行或耗时接近计时噪声时,如实写“差异不足以判断”,不要生成一个漂亮百分比。不要把 SQL 客户端耗时直接当成整个接口响应时间。
线程池实验使用 corePoolSize=2、maximumPoolSize=2、容量为 2 的 ArrayBlockingQueue 和 AbortPolicy。让前两项任务在门闩等待,再提交另外三项,观察第五项被拒绝;最后释放门闩、等待结果并关闭池。池限制正在执行的任务,队列限制等待任务;任务异常也要通过结果或日志观察。Java 21 ThreadPoolExecutor
JVM 本周只要求分清:对象通常占用堆;线程执行方法需要自己的栈帧;GC 回收不可达对象,并不保证数据库连接等外部资源被及时关闭。可以用 jcmd -l 找到自己的 Java 进程,再按 JDK 帮助检查堆信息。不要为了练习故意把个人电脑内存耗尽。
验收: 一条 SQL 优化有前后证据与局限;能解释线程池拒绝为何发生;实验退出后没有挂住的工作线程;能说出增加索引、线程数、堆大小各自不能解决的至少一个问题。
自测: 18.1 EXPLAIN 显示使用了索引,就能断言查询一定更快吗?18.2 两线程、两队列的池,在任务都不结束时连续提交五项会怎样?18.3 反复创建线程或把所有查询结果永久存到 List,会分别带来什么风险?
第19周 2月15日至21日 补短板或小扩展 #
目标:把时间花在最影响面试与交付的地方。 核心验收有任何一项未通过,就选补课。已经全部通过,才从统计缓存或故障知识库中选一个;不要求付费模型,不再新开一套项目。
| 本周任务 | 小时 | 做完留下什么 |
|---|---|---|
| 不看答案做一次 SQL、权限、事务和项目复述检查,选一个方向 | 2 | 明确本周唯一目标 |
| 补课路线修两个薄弱点;扩展路线先写需求与边界 | 4 | 两个可验证问题或一页小需求 |
| 编码实现并覆盖边界,不改动已验证的主线契约 | 5 | 可独立解释的小变更 |
| 核心测试回归、异常与停用场景验证 | 3 | 验证记录与一条缺陷修复 |
| 复习基础和算法,更新 README 与项目讲稿 | 3 | 扩展说明或补课验收 |
补课建议按影响排序:不能手写 JOIN 与 GROUP BY → 先补 SQL;能背事务但回滚实验失败 → 先补 Spring 调用与事务边界;只能照着教程新建接口 → 独立加一个筛选条件;面试一问就绕 → 用自己的代码重讲一次。补课完成同样是有效进度,不因为没有新技术词而降低评价。
统计缓存路线: 只针对管理员的“各状态工单数量”,先用数据库查询给出正确结果,再在单实例内做 30 秒过期的小缓存,或在资源允许时使用本地 Redis。标明更新时间;创建、派单、处理、验收和退回都可能影响统计。明确可以接受最多 30 秒陈旧数据,不缓存报修人私有列表到公共 key。Redis 不可用时回落到数据库,是否继续启用由你验证。
知识库路线: 准备 10—20 条虚构设备故障条目,字段含标题、症状、排查步骤、适用设备类型和来源说明。先完成关键词检索、空关键词、无结果和停用条目处理;匹配排序先简单可解释。页面把结果称为“参考排查步骤”,保留来源,不让文章直接触发派单、关闭或修改设备。模型与 RAG 都是后续可选项,本周不依赖它们。
不论选哪条,先写一个手工核对表:输入、预期结果、实际结果、结论。缓存至少查首次请求、命中、过期、更新后的可接受陈旧期和故障回落;知识库至少查命中、多个结果、无结果、空白输入和停用记录。只展示正常页面不足以验收。
验收: 能用两分钟解释为什么做、怎么验证、有什么代价;核心工单流程和权限测试仍通过;没有把扩展写成未实现的简历成果。如果到周末仍不稳定,保留独立分支或提交记录,演示版继续使用已经通过测试的 v1.0。
卡住时: 缓存数据串用户,检查 key 和访问范围;更新后数量没变,先确认预期陈旧窗口与失效策略;知识库常搜不到,先检查分词或关键词规则,不马上接入模型;补课反复失败,把任务缩到一个 SQL 或一个接口重新验收。
自测: 19.1 报修人 A 的列表可以和报修人 B 共用同一个缓存 key 吗?19.2 缓存上线后发现数字滞后,什么时候属于约定行为,什么时候是错误?19.3 没接模型的关键词知识库,有什么可以验收的价值?
第20周 2月22日至28日 项目表达与模拟面试 #
目标:你能凭自己的代码和证据说明能力,并能在追问下做一个小修改。 第一轮允许暴露问题,第二轮检查是否真正修复,不追求背一段流利稿子。
| 本周任务 | 小时 | 做完留下什么 |
|---|---|---|
| 冻结可演示版本,核对 README、测试、配置和真实简历内容 | 3 | 可投递版本与清单 |
| 录制第一次模拟面试,按表达、基础、项目、编码四项评分 | 3 | 原始录音或文字记录 |
| 修复最高优先级的两个问题,做一次现场限时小改 | 4 | 修复证据及小改提交 |
| 做第二轮模拟面试和完整演示,测试陌生启动流程 | 4 | 前后对照和剩余弱项 |
| 筛选岗位、整理投递表,复习两道算法与本周自测 | 3 | 下一周投递名单及计划 |
项目讲解按一个具体请求展开:谁报修,Controller 接收什么,Service 怎样校验角色与业务规则,Mapper 执行哪条 SQL,事务怎样覆盖工单和日志,失败怎样返回。再选择一个难点,比如并发派单,讲出原来的错误、两个请求的交错、修复条件、验证结果和仍有的限制。
5—8 分钟演示可以这样分配:1 分钟业务与角色;2—3 分钟工单流程;1 分钟权限边界;1 分钟并发或回滚证据;1 分钟部署、真实问题与下一步。不要花一半时间读依赖名称。单人学习项目就说个人项目;请同学帮忙测试可以如实说测试协助,不能改写成企业团队交付。
第一次模拟面试用 45 分钟:自我介绍 3 分钟,Java 与集合 10 分钟,SQL 与事务 10 分钟,项目追问 12 分钟,编码或 SQL 10 分钟。独自练习时随机抽问题,用手机录音,回答结束后再看笔记。第二轮换问题顺序,检验是否依赖背诵。
限时小改任选一项:列表增加设备筛选且保留资源权限;限制评论长度并增加边界测试;管理员统计增加一个状态条件。先说影响范围,再修改 DTO、Service/Mapper、测试和文档,40—60 分钟内完成可用的小部分,不要求一次做完全部可能扩展。
验收: 不看稿能讲清三项设计取舍;能展示一条真实排错记录;权限、事务和并发至少各回答一次追问;README 在新目录或另一环境可复现到已完成的程度;简历的每项功能确实存在。未掌握的问题写进待补清单,不填成“精通”。
卡住时: 忘词就画“请求 → Service → SQL → 返回”;遇到不会的问题先说明已知边界,再给出准备怎样验证;面试反馈涉及基础就补一个实验,不整套重学。未完成扩展时,把时间优先留给申请和反馈修补。
自测: 20.1 用一分钟说明你怎样防止两次派单互相覆盖。20.2 面试官问“你项目支持多少并发”,没有正式压测时怎样答?20.3 列表新增设备筛选时,要改哪些地方,怎样证明权限没有退化?
本阶段完成记录 #
每周结束复制一段到自己的学习记录,不把“看懂”当作“独立完成”。
周次与实际日期:
独立完成:
参考示例后完成:
验收步骤和结果:
失败案例及修复:
测试命令或操作:
仍不能解释的问题:
下周只优先解决的两件事:网络资料只用于查询补充,课程正文与自测答案可以离线阅读。项目首次下载 JDK、依赖或数据库仍需要可用的软件来源;已装好、依赖已缓存后,可以继续离线编码、测试和复盘。