
简介这份面向高校计算机相关专业毕业设计与课程设计场景的停车场管理系统项目包围绕车位管理、车辆进出识别、费用结算与后台数据处理等典型业务展开适合需要完成综合项目实践、参考系统架构或进行二次开发的学生与开发者。压缩包共592个文件、约142.93MB包含89个java源码、184个class编译文件、135个xml配置、66个jar依赖以及js、html、sql、properties等前端页面、数据库脚本与配置资源整体覆盖从界面到业务逻辑的较完整工程结构。项目描述涉及车牌识别、用户界面、后台计费、预约车位与异常报警等模块并体现数据库表设计、接口集成和输入安全等实现思路。借助现有源码与配置读者可梳理停车管理系统的模块划分、服务层调用关系和数据库交互方式也可对照class与jar依赖排查运行环境问题。已有2529人学习下载适合用作毕业设计参考与功能扩展基础。1. 需求先立住停车场管理系统的难点不在道闸而在计费很多人接到「停车场管理系统」这个毕业设计或课程设计题目第一反应是画一张进出场流程图配两张表做一个车牌号增删改查就收工。真正被追问的地方一定在计费跨零点怎么切段、免费时长内出场再进场算不算新单、月卡在到期当天失效还是次日失效、车牌识别错一位怎么补救。这道题看着是管理系统本质是一个带时间窗口的计费系统道闸只是它最外面的一层皮。这套方案面向两类人一是要在几周内交出一份能跑、能演示、能讲清楚的毕设或课设的同学二是需要给一个小型车场搭一套可交付后台的开发者。下面按需求、架构、实现、避坑、验收的顺序展开中间给的建表语句和计费代码可以改改参数直接用。2. 需求拆解与角色边界三类使用者与五个必做场景需求阶段最容易犯的错是把「停车场管理系统」当成一个单体 CRUD 项目功能列表一口气列二十条最后每一条都只做了个空壳。我一般会先砍到五个必做场景每个场景都能从头走通、都能演示、都能说出预期结果剩下的功能标注为「扩展」写在文档里。这样做的好处是答辩时被问到任何一个场景你都能现场跑一遍而不是翻着 PPT 说「这个功能也做了」。2.1 三类使用者权限不是装饰这套系统里真实存在的角色只有三类权限划分要落到菜单和接口两个层面不能只在前端藏按钮。角色能做什么不能做什么系统管理员车场与车位维护、计费规则配置、操作员账号、报表导出不能直接修改已结算的停车记录金额岗亭收费员入场补录、出场结算、收款放行、当班对账不能改计费规则、不能删记录车主查询自己的停车与缴费记录、月卡续费申请只能看自己车牌的数据权限表通常用operator、role、permission、role_permission四张表接口层用注解拦截。这里有个容易忽略的点收费员和车主看的是同一张parking_record表但车主端必须按车牌做数据隔离不能靠前端传车牌参数否则改个 URL 就能查到别人的记录。我一般会在 Service 层强制从登录态取车牌把前端传进来的车牌参数直接丢弃。毕设里很多同学把权限做成一个role字段判断 if-else两三个角色还行一旦要加「财务只读」就得改一堆代码。表结构化的成本不高值得一开始就做对。2.2 五个必做场景与各自的验收口径场景一临时车完整闭环。车辆入场生成一条在场记录并占用一个车位出场时按规则算出金额缴费成功后记录置为已出场、车位释放。验收口径是入场后车位余量减一出场缴费后余量加一金额与手算一致。场景二月卡车辆免费通行。入场时按车牌查月卡有效期覆盖入场日期则标记为月卡车出场直接放行、金额为零。验收口径是月卡到期日当天入场仍然免费次日入场按临时车计费。场景三免费时长内进出。入场后十五分钟内出场金额为零但仍然生成一条完整的进出记录用于统计。验收口径是记录存在、金额为零、车位正常释放。场景四跨天停车。前一天晚上入场、第二天上午出场费用按自然日切段每段分别套用当日封顶。验收口径是总费用等于两段费用之和且任一段都不超过封顶值。场景五异常补救。车牌识别失败时收费员手工补录入场或者车辆无入场记录直接到出口系统要走异常处理流程而不是报错。验收口径是异常记录能被查到、能被人工定价、处理后状态正确。这五个场景写进需求文档每个配一张「输入—处理—预期输出」的表答辩时直接照着表演示比讲十页架构图有用。2.3 需求边界先砍掉哪些功能明确不做的部分和做了的部分一样重要。这套方案通常不接真实道闸硬件用一个GateAdapter接口模拟抬杆和落杆答辩时点一下按钮打印日志即可不接真实支付渠道用模拟支付生成流水号不做多车场连锁和跨场结算不做无感支付和车牌识别算法的自研识别环节用「手动输入 模拟识别结果」代替。这么砍不是偷懒而是把有限的时间压在计费准确性和数据一致性上。硬件对接的代码量不小但答辩老师基本不会追问串口协议反而一定会问「你这笔钱是怎么算出来的」。把边界写进开题报告和系统说明的第一节是给自己留后路也是让评审知道你想过范围管理这件事。3. 总体架构与数据模型一条从道闸到报表的完整链路架构不用画得太花能说清楚「一次出场请求从哪进、经过谁、落到哪张表」就够了。这套系统的链路很短道闸或岗亭发起请求应用层做校验和计费数据层落记录报表层做聚合。短链路的系统最怕的是层次混乱比如把计费逻辑写进 Controller或者让前端算好金额传给后端后面改规则就得全链路返工。3.1 分层架构与技术选型为什么是 Spring Boot 加 Vue后端的常规组合是 Spring Boot 3 MyBatis-Plus MySQL 8缓存按需加 Redis前端 Vue 3 Element Plus报表用 ECharts。这套组合在毕设里出现的频率高不是因为它最优而是因为参考资料多、出错时容易搜到答案。对于一个以事务和 CRUD 为主的系统生态成熟度比语言优雅度重要得多。如果团队只有 Python 基础Flask 或 Django 加 SQLAlchemy 也完全能做完只是事务边界要自己写清楚报表聚合最好直接写 SQL 而不是在 ORM 里绕。选型的分歧点不在语言在有没有现成的权限框架和代码生成器能省下的那部分时间才是关键。分层按四层来切。接入层放 PC 管理端、岗亭端和车主 H5应用层拆成车辆服务、计费服务、车位服务、月卡服务、报表服务数据层是 MySQL 主库加可选的 Redis 车位缓存外设适配层单独抽出来把道闸和识别设备包成接口。适配层这层薄薄的东西在答辩时是个加分项因为它证明你想过换硬件怎么办。/** * 道闸适配器真实硬件、模拟器、测试桩都实现这个接口 * 上层业务只依赖接口换硬件不用改业务代码 */ public interface GateAdapter { /** 抬杆返回是否成功 */ boolean open(String gateCode); /** 落杆 */ boolean close(String gateCode); /** 查询当前杆状态0落下 1抬起 2故障 */ int status(String gateCode); } /** * 模拟实现毕设演示用动作写日志并入库方便回放 */ Service public class MockGateAdapter implements GateAdapter { private static final Logger log LoggerFactory.getLogger(MockGateAdapter.class); Override public boolean open(String gateCode) { // 演示环境下不接硬件只记录动作时间和杆号 log.info(gate open, code{}, at{}, gateCode, LocalDateTime.now()); return true; } Override public boolean close(String gateCode) { log.info(gate close, code{}, gateCode); return true; } Override public int status(String gateCode) { return 0; } }接口里三个方法的职责分别是动作下发、动作下发和状态查询gateCode对应物理杆编号通常一个车场有入口杆和出口杆两个。业务层调用open之后要判断返回值再决定是否更新记录状态不能假定硬件一定成功这一步在演示时故意让适配器返回 false就能讲清楚异常分支。3.2 数据表设计八张核心表与字段取舍表不在多在于每张表都有一个明确的写入方。下面这几张是绕不开的。-- 车场 CREATE TABLE parking_lot ( id BIGINT PRIMARY KEY AUTO_INCREMENT, lot_name VARCHAR(64) NOT NULL, total_space INT NOT NULL DEFAULT 0 COMMENT 总车位数冗余字段, status TINYINT NOT NULL DEFAULT 1 COMMENT 1启用 0停用, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT车场; -- 车位 CREATE TABLE parking_space ( id BIGINT PRIMARY KEY AUTO_INCREMENT, lot_id BIGINT NOT NULL, space_no VARCHAR(16) NOT NULL COMMENT 如 A-001, zone VARCHAR(16) NOT NULL DEFAULT A, space_type TINYINT NOT NULL DEFAULT 1 COMMENT 1普通 2充电 3无障碍, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空闲 1占用 2锁定 3维修, UNIQUE KEY uk_lot_space (lot_id, space_no), KEY idx_lot_status (lot_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT车位; -- 停车记录全系统最核心的一张表 CREATE TABLE parking_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, lot_id BIGINT NOT NULL, plate_no VARCHAR(16) NOT NULL COMMENT 车牌统一大写去空格, space_id BIGINT DEFAULT NULL, card_type TINYINT NOT NULL DEFAULT 1 COMMENT 1临时车 2月卡 3内部免费, entry_time DATETIME NOT NULL, exit_time DATETIME DEFAULT NULL, entry_gate VARCHAR(16) DEFAULT NULL, exit_gate VARCHAR(16) DEFAULT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0在场 1待缴费 2已出场 3异常, amount DECIMAL(10,2) DEFAULT NULL, -- 生成列只有在场和待缴费的记录参与唯一约束出场后可重复入场 active_plate VARCHAR(16) GENERATED ALWAYS AS ( IF(status IN (0, 1), plate_no, NULL) ) STORED, UNIQUE KEY uk_active_plate (active_plate), KEY idx_plate_time (plate_no, entry_time), KEY idx_lot_status (lot_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT停车记录; -- 计费规则按时间段生效改规则不覆盖历史 CREATE TABLE billing_rule ( id BIGINT PRIMARY KEY AUTO_INCREMENT, lot_id BIGINT NOT NULL, rule_name VARCHAR(64) NOT NULL, free_minutes INT NOT NULL DEFAULT 15, unit_minutes INT NOT NULL DEFAULT 60 COMMENT 计费单位分钟, unit_price DECIMAL(10,2) NOT NULL DEFAULT 5.00, day_cap DECIMAL(10,2) DEFAULT 40.00 COMMENT 当日封顶NULL 表示不封顶, effective_from DATETIME NOT NULL, effective_to DATETIME DEFAULT NULL, enabled TINYINT NOT NULL DEFAULT 1 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT计费规则; -- 月卡 CREATE TABLE monthly_card ( id BIGINT PRIMARY KEY AUTO_INCREMENT, lot_id BIGINT NOT NULL, plate_no VARCHAR(16) NOT NULL, start_date DATE NOT NULL, end_date DATE NOT NULL COMMENT 含当天, status TINYINT NOT NULL DEFAULT 1 COMMENT 1有效 0停用, KEY idx_plate_lot (lot_id, plate_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT月卡; -- 支付流水 CREATE TABLE payment_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, record_id BIGINT NOT NULL, amount DECIMAL(10,2) NOT NULL, pay_method TINYINT NOT NULL DEFAULT 1 COMMENT 1现金 2扫码 3余额, pay_status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已退款, paid_at DATETIME DEFAULT NULL, UNIQUE KEY uk_order_no (order_no), KEY idx_record (record_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT支付单;parking_record里那个active_plate生成列是整张表的关键设计。车辆在场期间同一车牌不允许有第二条在场记录出场之后status变成 2生成列变 NULL唯一索引不再约束同一辆车可以再次入场。没有这个约束双击入场按钮就会造出两条记录出场时按车牌查出来两条代码直接抛异常。billing_rule用生效区间而不是直接改字段是为了让历史订单能复盘。规则改了以后老记录仍能找到当时用的那一条答辩时被问「你这单为什么收 12 块」你能把规则快照翻出来。3.3 状态流转一条停车记录的一生停车记录的状态只有四个在场、待缴费、已出场、异常。入场写 0出场计算完金额写 1收款成功写 2识别失败或人工介入写 3。车位状态三个空闲、占用、维修。两者要联动占用时车位改 1出场时改回 0且必须在一个事务里。状态流转的常见错误是允许跳跃比如从 0 直接改到 2 而跳过 1导致这笔钱没有支付流水。通常的做法是在 Service 层写一个transition(from, to)校验方法只允许相邻状态迁移非法迁移直接抛业务异常。这个东西代码量不到三十行但能把后面一半的对账问题挡在门外。4. 核心业务实现计费引擎、车位分配与月卡校验前面两章把边界和表定下来真正花时间的部分在这里。计费和入场出场是整个系统里唯二需要认真对待并发和精度的地方其余模块基本是标准 CRUD。4.1 计费引擎规则表驱动而不是 if-else 堆叠最常见的写法是把「首小时 X 元、之后每小时 Y 元、封顶 Z 元」直接写成一串 if-else。规则一改就得改代码、重新部署而且没有办法回答「这个金额怎么来的」。我一般用规则表加分段计算把参数外置代码只负责切段和累加。/** * 停车费用计算 * param record 停车记录必须已设置 exitTime 或在此方法内取当前时间 * param rule 本次结算适用的计费规则按入场时间匹配生效区间 * return 应收金额保留两位小数 */ public BigDecimal calculate(ParkingRecord record, BillingRule rule) { LocalDateTime in record.getEntryTime(); LocalDateTime out record.getExitTime() ! null ? record.getExitTime() : LocalDateTime.now(); long totalMinutes Duration.between(in, out).toMinutes(); // 免费时长注意用 恰好卡在边界上按免费处理避免用户投诉 if (totalMinutes rule.getFreeMinutes()) { return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); } // 按自然日切段跨零点的区间分别套用当日封顶 ListTimeSlice slices splitByNaturalDay(in, out); BigDecimal total BigDecimal.ZERO; for (TimeSlice slice : slices) { long minutes Duration.between(slice.getStart(), slice.getEnd()).toMinutes(); // 向上取整到计费单位停 61 分钟按 2 个单位算 long units (minutes rule.getUnitMinutes() - 1) / rule.getUnitMinutes(); BigDecimal fee rule.getUnitPrice() .multiply(BigDecimal.valueOf(units)); // 当日封顶day_cap 为空表示不封顶 if (rule.getDayCap() ! null fee.compareTo(rule.getDayCap()) 0) { fee rule.getDayCap(); } total total.add(fee); } return total.setScale(2, RoundingMode.HALF_UP); } /** 按自然日切分零点为界同一天返回一段 */ private ListTimeSlice splitByNaturalDay(LocalDateTime in, LocalDateTime out) { ListTimeSlice list new ArrayList(); LocalDateTime cursor in; while (cursor.toLocalDate().isBefore(out.toLocalDate())) { LocalDateTime midnight cursor.toLocalDate().plusDays(1).atStartOfDay(); list.add(new TimeSlice(cursor, midnight)); cursor midnight; } list.add(new TimeSlice(cursor, out)); return list; }totalMinutes用toMinutes()取整秒级差异被抹掉避免出现 59 分 59 秒被算成一小时的情况。units那段是向上取整的标准写法unitMinutes默认 60、unitPrice默认 5 元改这两个参数就能把「每小时 5 元」变成「每半小时 3 元」。splitByNaturalDay用isBefore判断日期而不是比对时间戳跨年跨月都不会出问题。day_cap为 NULL 时跳过封顶这个分支必须有否则规则表里填一个空值就会抛空指针。免费时长的边界处理值得多说一句。用而不是是因为现场最容易吵起来的就是「我明明 15 分钟出来的」。宁可少收一笔也不要在演示现场被卡住。4.2 入场与出场事务边界与幂等处理入场做三件事查车牌是否已有在场记录、分配车位、写记录并把车位置为占用。三步必须在一个事务里车位分配用条件更新防超卖。Transactional(rollbackFor Exception.class) public Long entry(String plateNo, Long lotId, String gateCode) { String plate normalize(plateNo); // 去空格、转大写 // 1. 幂等已有在场记录直接返回原记录号 ParkingRecord exist recordMapper.selectActiveByPlate(lotId, plate); if (exist ! null) { return exist.getId(); } // 2. 抢车位条件更新保证只有一个请求能成功 ParkingSpace space spaceMapper.selectFreeOne(lotId); if (space null) { throw new BizException(车位已满); } int affected spaceMapper.occupy(space.getId()); if (affected 0) { throw new BizException(车位已被占用请重试); } // 3. 写记录card_type 在入场时就确定避免出场时规则已变 ParkingRecord record new ParkingRecord(); record.setLotId(lotId); record.setPlateNo(plate); record.setSpaceId(space.getId()); record.setCardType(resolveCardType(lotId, plate)); record.setEntryTime(LocalDateTime.now()); record.setEntryGate(gateCode); record.setStatus(0); recordMapper.insert(record); return record.getId(); }selectActiveByPlate依赖前面那个唯一索引兜底即使并发绕过查询插入时也会被索引挡住捕获DuplicateKeyException转成业务异常即可。occupy的 SQL 是UPDATE parking_space SET status 1 WHERE id ? AND status 0靠影响行数判断是否抢到比先查后改稳得多。card_type在入场时定这一点很多人会忽略。如果出场时才判断是不是月卡一辆车在停车期间月卡刚好过期按出场时间算就要收费按入场时间算免费。业务上通常以入场时刻的身份为准写进入场记录出场直接读字段既符合直觉也避免了重复查询。出场比入场多一步金额计算和状态迁移同样要放在事务里并且先对记录加行锁防止两次点击同时结算。Transactional(rollbackFor Exception.class) public ExitResult exit(Long recordId, Integer payMethod) { // 行锁住记录第二个请求会阻塞到第一个提交后再读读到 status2 直接返回 ParkingRecord record recordMapper.selectByIdForUpdate(recordId); if (record null) { throw new BizException(记录不存在); } if (record.getStatus() 2) { return ExitResult.alreadyExited(record); } if (record.getStatus() 3) { throw new BizException(异常记录需人工处理); } record.setExitTime(LocalDateTime.now()); BigDecimal amount record.getCardType() 2 ? BigDecimal.ZERO : billingService.calculate(record, ruleService.match(record.getEntryTime())); // 金额为零直接放行否则先生成待支付单 if (amount.compareTo(BigDecimal.ZERO) 0) { paymentService.createOrder(record.getId(), amount, payMethod); record.setStatus(1); recordMapper.updateById(record); return ExitResult.needPay(amount); } record.setStatus(2); record.setAmount(BigDecimal.ZERO); recordMapper.updateById(record); spaceMapper.release(record.getSpaceId()); gateAdapter.open(record.getExitGate()); return ExitResult.free(); }月卡车的金额直接置零而不是走计费再减免是为了让报表里「月卡通行」和「临时车免费时段」能分开统计。这两类零元订单在运营口径上完全是两回事。4.3 月卡与免费时长校验时间窗口怎么写才不出错月卡用DATE类型存起止日期end_date含当天。判断逻辑是拿入场时间的日期去比而不是拿当前时间比-- 判断某车牌在指定时刻是否持有有效月卡 SELECT COUNT(1) FROM monthly_card WHERE lot_id #{lotId} AND plate_no #{plateNo} AND status 1 AND #{at} start_date AND #{at} end_date;at传的是入场时刻的LocalDate。写成 end_date而不是 end_date因为用户理解的「到 3 月 31 日」包含 31 号全天。如果存成DATETIME并把结束时间写成2024-03-31 00:00:0031 号白天进来的车就会被判成过期这是踩过不止一次的坑。免费时长的校验点在计费方法内部不在入场逻辑里所以「免费时段内进出」不需要特殊处理正常出场走一遍计费返回零即可。唯一要注意的是免费车也要释放车位、也要写完整记录不能因为金额为零就跳过状态更新否则车位会被永久占住。5. 避坑与排查五类最容易在答辩现场翻车的问题下面这几条不是我凭空想的是这类系统在联调和演示阶段反复出现的故障。每条按现象、原因、解决写看的时候可以对照自己的代码过一遍。5.1 金额算出来差几分钱现象是同一笔停车记录本地算出来 12.00演示机器上算出来 11.99或者两笔金额相加比手算多一分。原因基本是两个用double存金额或者每次计算单独做四舍五入再累加。解决办法是把所有金额字段改成DECIMAL(10,2)Java 侧统一用BigDecimal构造时用字符串而不是浮点数。累加过程不做舍入只在最终结果上setScale(2, RoundingMode.HALF_UP)一次。如果确实需要分段舍入就在切段时明确写下舍入规则并写进文档答辩时能解释清楚。5.2 点两次放行扣了两次钱现象是收费员手快点了两下「确认放行」生成两笔支付单对账时多出一笔。原因是没有幂等控制请求进来就无条件走一遍结算。解决办法分三层数据库层给payment_order.record_id加唯一索引同一记录只允许一笔已支付单Service 层用SELECT ... FOR UPDATE锁住记录判断状态已是已出场就直接返回前端在请求发出后立即置灰按钮。三层里数据库那层最可靠因为它是最后一道闸。5.3 车位显示还有空位实际已经停满现象是首页显示剩余 3 个车位结果连续三辆车入场后系统提示「车位已满」或者反过来分配了同一个车位给两辆车。原因是剩余数量用「总数减在场记录数」实时算而分配车位用的是「先查空闲再更新」。解决办法是分配时用条件更新UPDATE ... WHERE id ? AND status 0判断影响行数展示用的剩余数量允许有秒级延迟不必强求实时一致。如果一定要实时就把统计结果放 Redis 并设置短过期别每次都扫全表。5.4 跨天订单的封顶算错了现象是一辆车停了两天按封顶规则应该收 80系统算出 60 或者 100。原因是切段时按 24 小时切而不是按自然日零点切或者封顶套在了总价上而不是每一段上。解决办法是切段逻辑严格以零点为界封顶值在每段内各自生效、段间求和。写完之后拿三个边界用例验一遍23:50 入场次日 00:10 出场、连续停满 48 小时、跨月末入场。这三个过了基本不会再出问题。5.5 时间字段在不同环境对不上现象是本地跑出来入场时间是 10:00部署到另一台机器变成 02:00或者日志时间和数据库时间差八小时。原因是连接串没指定时区或者代码里混用了Date和LocalDateTime。解决办法是统一用LocalDateTime表达业务时间连接串显式带上时区参数数据库列用DATETIME而不是TIMESTAMP。展示层做时区转换存储层不转换。这套约定写进项目 README团队里所有人照着来能省掉大量排查时间。6. 验收与答辩用一份可复现的测试清单证明系统能用到了这一步代码写完了剩下的事是证明它真的能用。我一般会准备一份测试清单每条都写清楚输入、操作和预期输出答辩现场按顺序跑跑完一页勾一页。清单不用长十几条覆盖住核心链路就够。编号场景输入预期输出T01临时车正常出场入场后 90 分钟出场金额等于 2 个计费单位车位释放T02免费时长内出场入场后 10 分钟出场金额 0记录完整车位释放T03恰好卡在免费边界入场后 15 分 00 秒出场金额 0T04跨零点停车23:50 入次日 00:10 出按两段计算各段不超封顶T05连续停满 48 小时两天整总额等于两日封顶之和T06月卡有效期内到期日当天入场金额 0标记为月卡通行T07月卡过期次日到期日次日入场按临时车计费T08重复入场同一车牌连续两次入场请求第二次返回原记录不新增T09重复放行同一记录两次出场第二次返回已出场不新增订单T10车位并发剩余 1 个车位两辆车同时入场一成一败无超卖T11无入场记录出场直接请求出场进入异常流程可人工定价T12权限隔离车主 A 查询车主 B 的记录返回空或拒绝跑这张表的顺序有讲究先跑 T01 到 T05 证明计费对再跑 T06、T07 证明月卡对最后跑 T08 到 T12 证明边界稳。计费是评委最关心的放在最前面第一分钟就建立信任。答辩演示还有个小技巧故意制造一次异常。比如在 T11 里当场演示无入场记录的车怎么补录、怎么定价、怎么放行比演示十次正常流程更能说明你考虑过现实情况。评委提问往往集中在异常分支你主动演一遍问题就被引导到你准备好的地方。另外把计费规则的参数做成可配置的现场改一个单价再跑一遍 T01金额随之变化这个动作的说服力比任何架构图都强。它直接回答了「如果车场改价你怎么办」这个必问题。从那以后我每次做这类项目都会在写第一行业务代码之前先把这张测试表填出来哪条写不出预期输出就说明需求还没想清楚。希望帮到你。本文还有配套的精品资源点击获取