1. 选题动机与这个系统的真实难度
每年到了课程设计或者毕业设计的季节,后台总会收到一堆类似的咨询:老师,基于Spring Boot的停车场收费管理系统好做吗?这个题目是不是烂大街了?答辩的时候会不会被问倒?
先说结论:这个题是好题,但属于典型的"看起来简单、做起来容易翻车"的类型。停车场收费管理系统之所以成为Java Web方向的高频选题,根本原因在于它的业务闭环足够完整。一次停车行为贯穿了车辆入场登记、车位状态变更、出场计费、订单生成、支付状态回写这条完整链路,恰好把Spring Boot、MyBatis框架、MySQL事务、RESTful接口、前端页面交互这些毕业设计最常考察的知识点全部串了起来。对于学校而言,这种题目能有效检验学生是否掌握了"从数据库表设计到业务接口实现"的全栈基本功;对于学生而言,业务场景熟悉、演示效果好,答辩时也容易讲清楚。
但"容易讲清楚"不等于"容易做好"。我观察过大量做崩的案例,原因基本集中在四个方面。
第一,照抄网上源码却跑不起来。下载的压缩包解压后,要么JDK版本不匹配,要么数据库脚本导入报错,要么Maven依赖下载到一半失败,最后连登录页面都打不开,更别提部署演示了。第二,计费逻辑全用if else硬堆,而且堆错了。首小时收费、超过时长部分按半小时计费、单日封顶、免费停车时长,这些规则叠加在一起,边界条件极其容易出问题,不少人把"停车1分钟按1小时收费"和"不足30分钟按30分钟收费"混为一谈。第三,数据库表结构设计得过于随意,车辆表、订单表、车位表之间的关联字段对不上,业务逻辑写到一半发现缺字段、缺状态,只能回头改表和重写代码。第四,对系统缺乏整体理解。代码能跑,但答辩时导师问一句"你说说这个系统的核心表有哪几张,它们之间是什么关系",当场卡壳。
所以,这篇文章我打算把停车场收费管理系统从选题定位、技术选型、数据库设计、核心模块实现,到源码部署、文档撰写、答辩准备这一整套东西拆开讲一遍。既讲该怎么做,也讲为什么要这么做,把那些容易踩的坑提前标出来。
如果你是Java方向的学生,正在为课设或毕设发愁,这篇文章能帮你少走很大一段弯路;如果你已经下载了某个开源项目但折腾半天跑不起来,我建议你先翻到第5章,那里专门整理了启动排错的全套思路。
2. 技术选型:先别急着写代码,想清楚四件事
很多同学拿到题目后第一反应是打开IDEA新建项目,把Spring Boot骨架生成出来就开始写实体类。这个顺序其实是错的。在做任何编码之前,先把四个技术决策定下来,否则写到一半回头改技术栈,代价远比你想象中大得多。
2.1 第一件事:Spring Boot版本到底用几.x
这道题网上能搜到的教程和源码,用的Spring Boot版本五花八门,从2.1到3.2都有。我的建议非常明确:除非你本机装的JDK是17或更高版本且你非常清楚Spring Boot 3.x的变化,否则老老实实选Spring Boot 2.7.x。
为什么?Spring Boot 3.0起强制要求JDK 17,而很多学校机房和大多数学生笔记本上装的都是JDK 1.8。用Spring Boot 3.x配JDK 8,项目根本起不来。反过来,Spring Boot 2.7.x最高支持到JDK 8,同时兼容JDK 11、17,容错空间大得多。另一个很现实的原因是:网上大量课设参考代码、博客Demo是基于Spring Boot 2.x写的,遇到问题搜解决方案时命中率高得多。热搜榜上"springboot版本太高"常年挂着,本质上就是一群人选错版本后求助时留下的泪。
顺带说一句,用Spring Boot 2.7.x时,对应的Spring Cloud、MyBatis-Plus等组件版本也要选兼容的。MyBatis-Plus用3.5.x系列即可,它和Spring Boot 2.x配合非常成熟。
2.2 第二件事:持久层框架选择与理由
持久层框架主流就三个选项:原生MyBatis、MyBatis-Plus、Spring Data JPA。
原生MyBatis需要手写大量Mapper XML,对于课设这种以CRUD为主的系统来说效率偏低;Spring Data JPA的确很省事,但答辩时导师如果问起JPA的懒加载、N+1查询、一级缓存这些问题,准备不充分的同学很容易露怯。MyBatis-Plus是我最推荐的方案:它在MyBatis基础上封装了通用的单表CRUD方法,像车辆表、车位表这种标准单表操作,一行代码都不用写就能完成增删改查和分页查询;复杂查询(比如多条件组合搜索、联表统计)又可以退回到XML里手写SQL,灵活度足够。
对课设而言,MyBatis-Plus还自带分页插件,直接new一个Page对象传给Mapper方法就能实现分页,连编写分页SQL的功夫都省了。
2.3 第三件事:前端页面用什么方案
这是很多人的选择困难点。选项有三个:服务端渲染的Thymeleaf模板、前后端分离的Vue + Element UI、传统的JSP。
JSP现在基本可以排除了,Spring Boot对JSP的支持比较别扭,官方也不推荐新项目使用。Thymeleaf的好处是不需要单独启动前端工程,所有页面放在src/main/resources/templates下,一个Spring Boot应用就是一个完整可运行的系统。这对课程设计来说非常友好,部署、演示、打包都简单。Vue + Element UI的前后端分离方案观感更好,做出来显示效果明显更"现代",但代价是你要维护两个工程、处理跨域、考虑Token鉴权,项目的复杂度和工作量会显著上升。
我的建议是:课程设计课时紧张、以"把系统做完整"为首要目标时选Thymeleaf;毕业设计想冲高分、且你已经有前端基础时,选Vue + Element UI。这篇文章后面讲解以Thymeleaf方案为例,但所有后端接口设计对Vue方案同样适用。
2.4 第四件事:权限认证与登录方案
停车场收费系统天然有管理后台属性,登录功能、会话保持、操作权限这些是躲不开的。简单项目用过滤器加Session,再配合拦截器校验登录状态就够了,这也是课设最稳妥的做法。稍微想加点技术含量的话,可以引入JWT,把Token放在前端请求头里,每次请求做一个自定义拦截器校验Token。这里我不建议课设上Shiro或Spring Security全家桶,光是自己配置一套认证过滤器链,就够你折腾好几天。
认证方案选定后,密码存储一定不要用明文。最省事的做法是用Spring Security的BCryptPasswordEncoder生成哈希,不引依赖手写MD5加盐也行,但别把admin、123456直接扔进数据库里,这一条在答辩时会被很多老师问起。
3. 数据库设计:停车收费的表结构比你想的更讲究
如果说技术选型是开工前的图纸,那数据库设计就是地基。地基打歪了,后面代码写得再漂亮也白搭。停车场收费系统虽然数据量不大,但表与表之间的业务关系非常清晰,设计得好不好,直接决定你写业务代码时是行云流水还是各种别扭。
3.1 五张核心表及其关系
一个功能完整但不过度设计的停车场收费系统,最少需要这五张表:管理员用户表、车辆信息表、停车位表、收费订单表、计费规则表。
管理员用户表负责后台登录,字段就是常规的id、用户名、密码哈希、姓名、手机号、创建时间。车辆信息表字段包括主键id、车牌号、车主姓名、手机号、车辆类型(临时车/月租车/内部车)、入场时间、出场时间、车辆状态、所属车位id。停车位表字段包括主键id、车位编号、所在区域、车位类型、当前状态(空闲/占用/锁定)。收费订单表字段包括主键id、订单编号、车牌号、车位编号、入场时间、出场时间、停车时长(分钟)、费率、应收金额、实收金额、支付状态、创建时间。计费规则表则保存不同车辆类型对应的计费策略。
这些表之间的关联关系是这样的:车辆入场时,车辆表写入一条记录并指向一个空闲车位,同时车位表的状态从"空闲"变为"占用";车辆出场时,根据车辆表的入场时间和当前时间计算时长,查计费规则表得到费率参数,生成一条收费订单,再把车位状态释放为空闲。
核心业务逻辑就是一次正常的入场、出场操作,把三张业务表的字段串了一遍。如果你画ER图,这会非常清晰,导师一眼就能看出你理解了系统的数据流。
3.2 关键字段设计思路与常见反例
有几个字段类型和约束在设计时就要确定,不然后面很痛苦。
车牌号字段建议用varchar(10),加唯一索引。唯一索引的意义在于防止同一辆车同时占用两个车位。入场接口先查车辆表是否存在"入场中"状态的记录,有就直接拦截,双保险。
停车时长字段我建议直接用int类型存"分钟数",不要存"小时数",更不要存"出入场两个时间差的时间段字符串"。分钟数方便后续的计费计算:所有计费逻辑都基于分钟数做数学运算。同理,金额字段用decimal(10,2),禁止用double或float,后者在涉及小数点运算时会出现精度问题。车位编号字段用varchar还是int都行,但建议和区域信息合起来,比如"A-0102"这种带区域前缀的编码,方便演示时展示。
时间字段建议用datetime,Java实体类对应LocalDateTime,避免使用java.util.Date。数据库默认值可以设置成CURRENT_TIMESTAMP,但入场、出场时间最好由业务代码显式写入,这样能保证逻辑可读性。
还有一个容易忽略的字段:订单编号。不要用自增id直接当订单号展示给用户,建议生成一个有业务含义的字符串,比如"yyyyMMddHHmmss加随机四位",既好看又避免简单的枚举攻击。
3.3 让数据流动起来:一个完整的进出场生命周期
我们用一条具体数据把表之间的联动走一遍,这样你对表结构设计的理解会一下子通透。
假设车牌"京A12345"驶入停车场。入场接口收到请求后,先查车辆信息表里有没有状态为"在场"的同车牌记录,没有就插入一条新记录,状态设为"在场",并分配一个空闲车位,比如"B-0306",同时把停车位表里B-0306的状态改为"占用"。
三小时后这辆车出场。出场接口先根据车牌查到车辆信息表里"在场"的记录,取出入场时间t1,用当前时间t2算出停车时长3小时0分钟。然后查出计费规则表里临时车对应的费率:首小时10元,超过后每小时5元,不足一小时按一小时算,20元封顶。算出来金额是10加2乘以5等于20元,但没超过封顶,所以应收20元。订单表插入一条记录,支付状态设为"已支付",车辆信息表状态改成"已出场",车位表B-0306改回"空闲"。
注意这一系列操作跨越了三张表,中间任何一步失败都会造成数据不一致。所以入场接口和出场接口必须加事务控制,在Service方法上标注事务注解,保证"要么全部成功,要么全部回滚"。这一条你在文档的事务设计章节里也要写一笔。
4. 车辆、车位、收费三个核心模块的落地实现
数据库设计完了,下面进入编码阶段。我不会把整个项目的代码贴一遍,那不现实,这里挑三个最关键的业务环节,把实现思路、边界处理和踩坑点讲透。
4.1 车辆管理模块:入场登记与历史轨迹
车辆管理分为两部分:正在场内车辆的实时管理,以及历史进出场记录的查询。正在场内车辆的展示条件是"车辆状态等于在场",历史记录则直接查车辆表加订单表做联表查询。注意区分一个语义:车辆表随时可以查历史记录,但只有"在场"状态的记录才是真正占用车位的。
入场接口是核心。我建议接口接收参数为DTO对象而不是直接接收实体类,字段至少包含车牌号、车辆类型、车主姓名、手机号。逻辑分三步:
第一步做重复入场校验。查询数据库有没有"在场"状态的同号牌记录。第二步查询是否有空闲车位,取得一个车位编号。第三步插入车辆记录并修改车位状态。这三步必须在一个方法里完成并加上事务。
实际业务中还要考虑的一个交互细节是:入场时车位是否由系统自动分配。如果系统自动分配,前端页面只需显示"请将车辆停至B-0306车位",用户体验较高,实现也简单。如果你想让用户自己选择停车区域,多一个传参而已。对课设而言,自动分配更省事,也更好在答辩时演示。
4.2 停车位管理:状态流转与查询
车位模块本质上就是一张单表的CRUD,但有两个点要注意。
一是车位的状态流转必须有约束。空闲车位可以被占用、被锁定;占用车位只能变为空闲,不能直接变锁定;锁定车位只能解锁为空闲,不能直接占用。这个约束在业务层写清楚,不要让前端页面直接修改状态字段,否则会出现逻辑漏洞。为什么要锁定位?因为月租用户或者内部预留车位需要被保留,不允许临时车使用。
二是车位列表要与车辆信息联动展示。用户最关心的信息是"哪些车位是空的、哪些被占了、被占的话停的是什么车"。列表页展示的应该是车位信息联表车辆信息,查出当前占用该车位的车牌号和入场时长。这一步在Mapper自定义SQL里用一个左连接搞定。
代码层面,车位的分页查询可以用MyBatis-Plus的分页插件。配置一个MybatisPlusInterceptor Bean,把PaginationInnerInterceptor加进去,后面所有分页查询都直接new Page对象传入Mapper方法即可。
4.3 收费模块:计费规则的边界条件处理
计费是停车场系统的灵魂模块,也是网上源码翻车率最高的地方。计费规则本身不复杂,但边界条件极多。
假设规则设定为:临时车首小时10元,超过1小时后每小时5元,不足一小时按一小时计算,24小时封顶20元。这个规则映射成代码时,最忌讳的就是大段if else堆逻辑。我建议把计费抽象成一个独立方法,输入参数为入场时间、出场时间、车辆类型,输出为应收金额,并把"不足一小时按一小时"的向上取整逻辑单独写清楚。
核心计算逻辑参考如下:
public BigDecimal calculateFee(LocalDateTime entryTime, LocalDateTime exitTime, Integer carType) { // 停车总分钟数 long minutes = Duration.between(entryTime, exitTime).toMinutes(); if (minutes <= 0) { minutes = 1; // 防止负数或者0分钟 } // 向上取整到小时,不足1小时按1小时算 long hours = (minutes + 59) / 60; // 按车辆类型匹配费率,这里以临时车为例 BigDecimal firstHourFee = new BigDecimal("10"); BigDecimal perHourFee = new BigDecimal("5"); BigDecimal capFee = new BigDecimal("20"); BigDecimal total; if (hours <= 1) { total = firstHourFee; } else { total = firstHourFee.add(perHourFee.multiply(BigDecimal.valueOf(hours - 1))); } // 超过封顶按封顶算 if (total.compareTo(capFee) > 0) { total = capFee; } return total; }这段代码里有几个关键细节值得留意。取整方式使用数学运算而不是Math.ceil,可以避免浮点数误差;金额计算全程用BigDecimal,保证精度;封顶逻辑在累加之后统一判断,而不是累加过程中判断。这套逻辑跑出来的结果,用下面这几个测试用例验证一下:
| 业务场景 | 停车时长 | 期望结果 |
|---|---|---|
| 刚进场立即出场 | 5分钟 | 10元 |
| 刚满1小时 | 60分钟 | 10元 |
| 1小时零1分钟 | 61分钟 | 15元 |
| 3小时整 | 180分钟 | 20元 |
| 停了一整天 | 1500分钟 | 20元(触发封顶) |
你会发现最后一个用例很关键,如果代码里忘记封顶,按线性计费算出来是远超20元的,而真实停车场系统的计费场景里封顶是一个再常见不过的规则。答辩时导师如果让你现场改一个计费参数,你能在代码里迅速找到规则配置的位置,这比背熟十个设计模式都管用。
计费规则表在这里可以发挥作用:费率参数不要硬编码在Service里,而是放在数据表里用一条记录维护,页面提供修改入口。这样一来,文档章节可以写"系统支持计费规则灵活配置",答辩分值一下子就上去了。
5. 从源码到可运行:环境配置与启动排错
假设你已经拿到了某个停车场项目的源码压缩包,里面是Spring Boot后端、Thymeleaf前端、SQL脚本和一篇文档。这是最常见的状态。接下来你要做的是把它在本地跑起来,这个过程对很多人来说才是真正的地狱模式。我把完整链路和最容易出问题的点挨个过一遍。
5.1 环境准备清单与版本对应关系
项目能不能跑起来,第一道关卡就是环境版本匹配。先确认三样东西。
JDK:打开命令行输入java -version。如果你是Spring Boot 2.7.x项目,JDK 8、11、17都可以;如果是Spring Boot 3.x,必须17或更高。版本不匹配时,启动会报UnsupportedClassVersionError这类错误,在IDEA里看具体是"major version"数字不对就明白了。
Maven:IDEA自带的Maven版本通常够用,但要注意Maven使用的JDK。在IDEA的Settings -> Build Tools -> Maven -> Importing里,把JDK for importer设置成你项目用的JDK版本。这一步很容易被忽略,结果就是Maven编译报错。
MySQL:看一下SQL脚本的建表语句里用的存储引擎和字符集。如果脚本里写了utf8mb4,而你的MySQL版本过老不支持,会有语法错误。推荐MySQL 5.7或8.0,并且导入时指定字符集,例如在Navicat中右键数据库选择"运行SQL文件"时,勾选"遇到错误继续"。
5.2 导入数据库与配置文件必改项
拿到SQL脚本后,先新建一个数据库,名字和连接配置里写的一致,比如叫parking_system。然后运行SQL脚本。跑完后进去看一眼表是否齐了,不要全信脚本,万一半途报错只建出来一半的表。
然后找到项目的application.yml或application.properties文件。必改三项:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/parking_system?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 你自己数据库的密码 driver-class-name: com.mysql.cj.jdbc.Driver如果你用的是MySQL 5.7但驱动是8.x的,driver-class-name写成com.mysql.cj.jdbc.Driver没有问题,MySQL 8的驱动向上兼容连接5.7的库。MySQL 5.1老驱动才需要用com.mysql.jdbc.Driver,但Spring Boot 2.7.x里你大概率不需要关心这个。
文件上传路径如果项目里涉及图片上传(比如车位照片、用户头像),配置文件里一般会有file.upload-path之类的自定义属性,改成你本机一个已存在的目录,否则上传功能会报"目录不存在"。
5.3 启动常见报错的完整排查思路
按下启动按钮后报错信息五花八门,但根因翻来覆去就那么几类,我按出现频率排个序。
"Application run failed"然后提示数据库连接失败,十有八九是三种情况:数据库没启动、用户名密码不对、数据库名对不上。直接把config里的url复制到Navicat里测试连接是最快的定位方法。
"Invalid bound statement (not found)",这是MyBatis项目非常容易遇到的问题,意思是Mapper接口找不到对应的XML文件。排查思路是看XML文件的namespace是不是Mapper接口的全限定类名、看mapper接口的方法名和XML里的id是不是完全一致、看target/classes目录下有没有生成XML文件。如果是最后一个原因,在pom.xml的build标签里加一个resources配置,把src/main/java目录下的XML和properties文件都包含进构建产物。
还有一类是Maven依赖相关的坑。启动时一直卡在下载依赖,最后失败。原因是Maven默认中央仓库在国外,访问速度极慢。改法是去Maven的settings.xml文件里配置阿里云镜像。改完之后,重新import项目,依赖会飞速下载。
最后一个常见问题是端口被占用,报Web server failed to start的错。用命令行执行netstat -ano | findstr :8080找到占用进程的PID,杀掉对应进程,或者把配置文件里server.port改成8081。
跑通一遍之后,建议你从头再梳理一遍整个项目结构,把启动流程和配置项记明白。因为答辩的时候,导师很大概率会问"你这个项目在本地怎么部署",你如果只回答"点运行按钮",印象分会大打折扣。
6. 万字设计文档的结构安排与答辩加分技巧
很多同学把精力全花在写代码上,到文档阶段才发现这比写代码还痛苦。写设计文档的关键不是凑字数,而是把"你做了什么、为什么这么做、怎么证明它有效"这三件事清晰地讲出来。文档结构合理了,字数自然就上去了,而且每一段都有实际内容支撑。
6.1 标准五章结构与各章字数分配
虽然各学校格式要求有差异,但计算机类毕业设计文档基本都是五章起步。
第一章绪论,写课题背景与研究意义、国内外研究现状、论文组织结构。这一章重点是"背景与意义",不要长篇大论抄百科,要结合停车场管理这个具体场景说明痛点。第二章相关技术介绍,写Spring Boot、MyBatis-Plus、MySQL、Thymeleaf的基本介绍。写这一章时有个原则是"只写你项目里真正用到的技术",不要为了凑字数把Redis、RabbitMQ这些没用的东西写进去。第三章需求分析,写系统可行性分析、功能性需求、非功能性需求,配用例图和用例说明书。第四章系统设计,写总体架构设计、功能模块设计、数据库设计(数据库E-R图、表结构)、接口设计。这一章是文档的重头戏,也是篇幅最大的章节。第五章系统实现,配页面截图加核心代码片段加一句实现说明,切忌大段粘贴代码,导师看着累,你也解释不清。
字数分配上,如果你需要一万字左右,大致分布是:摘要和目录800字、第一章1500字、第二章1200字、第三章1800字、第四章3000字、第五章1800字、结束语加参考文献致谢500字。按这个比例写,结构是匀称的,评审老师一眼看过去重点突出。
6.2 文档图表怎么画、数据怎么造
图表是文档的加分核心。E-R图推荐用Draw.io或Navicat直接导出的数据模型,企业版导航栏里就有。用例图、时序图、流程图这类UML图,推荐用ProcessOn在线画,免费的足够用,画完直接导出高清图片插入Word。插图的规范是每章配两到三张图就够了,图必须带图号和图名,正文里要有"如下图所示"的引用句号。
测试数据要讲求边界覆盖。不要只测试"正常停车两小时出场"这一个路径。用第4章那组测试用例的思路,把"刚进场立即出场""免费时长边界""超过封顶""月租车出场不产生费用"这些场景都记录下来,每个用例写清楚输入、预期输出、实际输出、是否通过。这样测试章节就活了。
表格方面,数据库表结构用Word的三线表格式,字段名、类型、长度、允许为空、默认值、说明,六列清清楚楚。每个表一张表,截图方式不建议直接用Navicat界面截图,输出不美观。
6.3 答辩高频问题与应答思路
答辩的好坏,很大程度上取决于你对项目的熟悉程度。导师时间有限,问的基本都是围绕"为什么"和"怎么做"展开的。
第一个高频问题:为什么选这个选题?应答思路:停车场收费系统贴近实际生活场景,业务逻辑清晰,能完整覆盖Spring Boot全栈开发的常见知识点,同时停车场行业本身也有真实的管理痛点,系统有实际应用价值。这句回答既说了技术层面的原因,又提到了应用价值,比"因为简单"要好得多。
第二个高频问题:表和表之间是什么关系?你必须能把车辆表、车位表、订单表的关系讲清楚。建议准备一个"车辆入场到出场"的完整数据流转过程,顺带说明哪个字段是关联键、哪一步用了事务。能脱口而出,导师基本就信服你是自己做的。
第三个高频问题:系统有什么不足,哪里可以改进?别回答"没有不足",也别答"到处都是坑"。说两三个具体且合理的改进点就好,比如可以考虑引入Redis缓存高频查询数据、可以增加支付回调的异步通知机制、可以引入RBAC权限模型细粒度控制操作权限。这些不用真做,体现你有思考就够了。
第四个容易被问的是Spring Boot相关的原理性题:什么是自动配置、Spring Boot启动流程是什么、为什么要用MyBatis-Plus。这类题热搜词"springboot面试题"里很多总结,建议提前刷一刷,尤其是自动配置和Starter机制这两个核心点,属于问烂了但还是很多人答不上来的题。
6.4 关于"附源码、数据库、文档"资料的正确使用姿势
最后说一个犀利的观点:拿到一份配套源码和文档,交给老师当作业提交之前,你至少要做三件事。
第一件事,把项目从零跑通一遍。如果跑不通,先自己排查报错,再去搜解决方案,这个过程本身就是在学习。第二件事,完整的代码看不完,但核心的入口类、配置类、Controller层、Service层、Mapper层这几个层次至少要读一遍,弄清楚一个请求从前端页面到数据库再返回页面的完整路径。第三件事,做一处小改动。比如把计费规则里的免费时长改成15分钟,或者新增一个车位类型字段,强迫自己改完功能还能正常跑。这三件事做完,你对这个项目的掌控力会完全不一样,答辩时导师的任何提问你都不会心里发虚。
我在带项目的时候反复说一句话:拿到源码只是起点,能把它讲清楚才是终点。这篇博文里的所有思路和代码片段,本质上是帮你把"停车场收费管理系统"这个题目从点到面串起来。你当然可以直接拿着配套资源完成作业,但更重要的是理解每一部分为什么这么设计。等你真把一辆车的进出场生命周期从前端页面到数据库完整走一遍,你就明白Spring Boot项目的核心套路其实万变不离其宗了。最后分享一个小技巧:跑通项目之后,把自己代入管理员角色,把所有功能按钮点一遍,顺手截图保存,这些截图就是你第五章系统和测试用例的素材,写文档的时候你会感谢自己当初多点了那么几下鼠标。