每年开学季,实验室管理员都要面对一轮"空间与时间大考":一届又一届的实验课要排进有限的多媒体设备、仪器和房间,学生的自由预约需求又总是在课表缝隙里见缝插针。纸质登记和Excel排课在这种混合场景下很快就会失控——冲突、漏记、事后追溯困难。所以我基于Java + Vue技术栈做了一套实验室教学管理系统,覆盖学生预约、教师审批、管理员排课、设备管理、实验报告提交与统计的完整闭环。这篇文章会把从需求拆解、数据库设计、前后端实现到部署排错的完整链路和踩坑记录一次讲清,适合正在做高校实验室信息化项目或准备毕业设计的同学参考。
1. 从纸质登记到在线预约:实验室管理系统的需求拆解
很多第一次接触实验室教学管理系统的同学会把它理解成"一个预约小程序",其实这是低估了这类系统的复杂度。实验室管理牵扯的角色多、状态多、时间约束多,如果需求不拆透,后续做数据库和权限时会反复返工。
1.1 实验室使用场景里的三个典型角色
这套系统里的核心角色是三类人,每类人的诉求完全不同。
学生关注"什么时候有空实验室、怎么预约、实验报告怎么交";教师关注"我能不能在特定时间段使用某实验室上实验课、学生提交的报告怎么批量批阅";管理员关注"实验室和设备的整体使用率、预约冲突的仲裁、以及期末的课时统计"。三类诉求叠加在一起,系统就不是简单的"增删改查",而是一个带着时间约束、状态流转和审批链路的业务系统。
这里有一个很容易忽略的点:实验室管理天然具备"资源预约 + 课程管理"双重属性。学生自由预约是资源型需求,教师排课则是计划型需求。两者如果共用一个时间维度,必然产生冲突。所以需求阶段就要明确:排课优先级高于自由预约,系统要能在排课已经占用的时段,自动拒绝学生的自由预约请求。
1.2 核心业务闭环:预约、审批、实验、报告、归档
把三类角色的操作串起来,就是一条完整的业务闭环:
- 学生或教师发起预约申请,选择实验室、开始时间、结束时间,填写实验项目名称。
- 管理员或拥有审批权限的教师审核预约。审核通过后,该时段被正式占用。
- 学生按预约时间进入实验室,使用设备和耗材。
- 实验结束后,学生提交实验报告,报告中可以附带图片或数据附件。
- 教师批阅报告,给出成绩或评语。
- 管理员按实验室、按课程、按时间段做统计归档。
这个闭环是系统的"主动脉"。后续所有表结构和接口,都是围绕这个闭环去展开的。我在设计的时候,把预约单从"用户提交申请"到"归档完成"的每一个阶段都定义成了一个状态,这个状态机是后面章节的核心。
1.3 这套系统与普通教务系统的边界
很多人会问,学校不是已经有教务系统了吗?为什么还需要单独的实验室管理系统。这里要做一个清晰区分:
| 维度 | 教务系统 | 实验室教学管理系统 |
|---|---|---|
| 核心对象 | 课程、班级、学生 | 实验室、设备、预约单 |
| 时间粒度 | 一个学期、周课时 | 精确到小时级别 |
| 核心功能 | 选课、成绩录入 | 资源预约、审批、报告归档 |
| 资源约束 | 教室容量 | 实验室设备可用性、维护时段 |
教务系统管的是"这节课在哪儿上",实验室管理系统管的是"这个实验室这节课之外的阿时间谁能用、设备在不在、用完之后有没有报告"。所以两者不是替代关系,而是互补关系。在设计时也不用考虑和教务系统做对接,除非你明确对接教务API,否则把预约和审批做扎实,就足够撑起一个完整项目了。
2. 为什么用Java + Vue做前后端分离:技术选型复盘
关于技术栈,标题里已经写清楚了是Java + Vue。但很多人在做类似项目时,会在后端用Spring Boot还是用更轻的Node.js、前端用Vue 2还是Vue 3之间反复纠结。这里我把我最终选型时考虑的逻辑写一下。
2.1 后端选型:Spring Boot + MyBatis Plus的理由
实验室教学管理系统本质上是一个典型的业务管理系统,它的核心是稳定的权限模型 + 复杂的状态判断 + 报表统计,而不是高并发流量处理。所以后端选Spring Boot是完全合理的,以下优势在这类项目里体现得非常明显:
- Spring Security或Shiro提供了成熟的权限控制方案,RBAC模型可以直接用注解 + 拦截器落地,比自己在每个接口里判断"当前用户是不是管理员"要安全得多,也不容易漏。
- MyBatis Plus的LambdaQueryWrapper在处理条件查询时极其方便,比如"查某个实验室在某时间段内没有被占用的记录",用一条链式查询就能写完。
- 事务管理是Spring的强项,预约创建、扣减设备状态、写入日志这三步需要同时成功或同时失败,声明式事务让这类操作变得可靠。
相比之下,如果你用Node.js或Python做,也不是不行,但在"源码+数据库+文档"这种交付型项目中,Spring Boot的标准化程度更高,后来者接手阅读代码的门槛更低,一个刚学过Java的学生拿到项目也更容易跑起来。
2.2 前端选型:Vue 3 + Element Plus还是Vue 2 + Element UI
前端标题写的是Vue,具体版本需要自己定。到我写这套系统的时候,Vue 3已经是主流,Element Plus也已经非常成熟。如果是从零开始的新项目,我建议直接选Vue 3 + Element Plus + Vite的组合。理由有三:
第一,Element Plus在表格、表单、日期选择器这类管理后台高频组件上,组件质量明显优于Element UI,比如日期范围选择器自带禁用逻辑,配合预约时间校验非常好用。
第二,Vite的构建速度比webpack快一个量级,在开发调试阶段体验提升非常明显,不必为了"熟悉webpack配置"而刻意选老方案。
第三,Vue 3的组合式API(Composition API)在管理系统的复杂表单场景下,可以把"表单校验逻辑" "提交逻辑" "状态切换逻辑"按功能模块抽离,避免一个大页面上千行代码全部堆在methods里。
2.3 源码、数据库、文档这份交付物到底怎么用
标题里特别提到"源码+数据库+文档",其实这就是一个完整项目交付的三件套。很多人拿到这样的项目第一反应是打开源码从头读到尾,这是低效的。我个人的阅读顺序是:
- 先看数据库脚本,通常叫
lab_management.sql,里面所有的表结构和初始数据能让你快速理解业务实体。 - 再看后端项目的
application.yml配置文件和pom.xml,了解端口、数据库连接、依赖版本。 - 然后跑起来,用Postman调几个核心接口,观察返回结构。
- 最后才看前端页面,把页面和接口对应起来。
文档(通常是部署文档或开发文档)里的部署步骤和建库流程是关键信息。很多项目跑不起来根本不是代码问题,而是数据库配错或依赖版本冲突。
3. 数据库设计:预约排序与设备状态的表结构
数据库是这类管理系统最见功底的部分。表设计得好不好,直接决定后续预约冲突检测和统计查询的复杂度。我在第一版设计时踩过一个坑:把"用户角色"做成了一张表里的一个字段,结果后面权限扩展的时候改得想哭。后来老老实实按RBAC设计。这里分享最终版的表结构思路。
3.1 用户与权限三张基础表
用户、角色、用户角色关联这三张表是目前主流的基础模型,结构大同小异:
sys_user:存放用户基本信息,字段包括user_id、username、password、real_name、role_type、email、phone、status。sys_role:存放角色信息,字段包括role_id、role_name、role_code、description。sys_user_role:用户角色关联表,字段包括id、user_id、role_id。
角色这里我预置了三种:学生(STUDENT)、教师(TEACHER)、管理员(ADMIN)。可能你会碰到企业级项目喜欢用权限表 + 角色权限表 + 用户角色表四张甚至五张表,但在实验室管理这个业务复杂度下,角色权限直接写在接口鉴权里就足够了,不需要单独一张sys_permission表去维护细粒度权限。过度设计同样是坑。
3.2 实验室、设备和预约单的核心表
实验室表lab和使用记录核心表reservation是系统的两张核心表。字段设计上要注意几个容易出错的地方:
实验室表lab:
lab_id、lab_name、location、capacity、device_count、manager、status(启用/停用)、description。
预约单表reservation:
reservation_id、lab_id、user_id、title(实验项目名称)、reserve_date(预约日期)、start_time、end_time、status、create_time、approve_user、approve_time、remark。
这里有一个关键设计决策:时间字段到底怎么存。两种方案,一种是只存日期 + 开始时间 + 结束时间,另一种是存一个start_datetime和end_datetime。我的建议是后者,直接存完整时间戳。原因很简单:预约冲突检测时,只需要用两个时间点做区间重叠判断,省去拼接日期的麻烦;统计"本月开放时长"时也可以直接对时间戳做日期函数处理。
设备表device和实验室表是一对多关系:
device_id、lab_id、device_name、device_type、status(正常/维修/报废)、last_maintenance_time、remark。
设备状态和预约状态不能混为一谈。设备维修期间,即使实验室空闲,也该在管理员端有醒目标识。但这个逻辑可以通过"设备维修时间表"来做,不一定要在预约检测里实时关联。我第一版做了实时关联,发现性能越来越难看,后来改成维修时段单独处理。
3.3 排课表与实验报告表的关联逻辑
排课表course_schedule用于教师计划性占用的时段:
schedule_id、lab_id、course_name、teacher_id、class_name、week、day_of_week、start_time、end_time、term。
实验报告表lab_report:
report_id、reservation_id、user_id、lab_id、title、content、file_path、submit_time、score、comment、status。
这里需要理解reservation和course_schedule的关系:排课是计划,预约是执行。教师排完课之后,学生就可以根据课表进入实验室,但通常不一定要在系统里再走一遍预约。我的做法是,排课视同自动占用时段,学生在已排课的时段提交的报告直接关联到course_schedule上,自由预约提交的报告关联到reservation上。这样统计时才能区分"课内实验数据"和"课外预约数据"。
3.4 数据库脚本初始化注意事项
标题里说"源码+数据库",数据库脚本是最容易出问题的部分。我总结了几个初始化时的注意点:
- 字符集要统一:建库时指定
utf8mb4,否则中文姓名、实验名称可能出现乱码。 - 外键不要太多:很多教学项目喜欢把所有表都加上外键约束,结果在做批量删除或者初始化数据时各种报错。建议核心关联(如
reservation.lab_id关联lab.lab_id)保留外键,次要关联用逻辑关联就好。 - 预留初始管理员账号:脚本里一定要插入一个初始管理员,否则部署完系统连后台都进不去。
- 每个表都要有注释:字段注释在后期写文档和交接时救命的。
4. 后端核心业务实现:预约状态与时间冲突检测
后端模块里,最核心的不是CRUD,而是两个业务难点:预约状态流转和时间冲突检测。这两个问题解决了,其他接口都只是围绕业务做数据聚合。
4.1 预约单的状态机设计
预约单不是只有"申请"和"通过"两种状态。复杂的预约场景需要细致考虑所有可能的生命周期。我定义的状态如下:
PENDING:待审核,学生提交预约后进入。APPROVED:已通过,管理员确认可用,时段被占用。REJECTED:已拒绝,管理员不批准,可填写驳回原因。CANCELLED:已取消,发起人自己取消,或在审核前撤销。CHECKED_IN:已签到,实际到实验室使用。COMPLETED:已完成,实验结束,学生提交报告。TIMEOUT:超时未使用,系统自动或管理员手动标记。
状态流转的规则要在Service层严格控制,不能用前端乱传状态覆盖。我的做法是写一个状态流转方法:
public boolean changeStatus(Long reservationId, ReservationStatus fromStatus, ReservationStatus toStatus) { // 通过 fromStatus 做条件更新,防止并发情况下状态脏写 return reservationMapper.updateStatus(reservationId, fromStatus.name(), toStatus.name()) > 0; }这样做的好处是,接口不会出现"从CANCELLED跳到APPROVED"这种非法流转。这是很多作业代码里没有注意的点,但真实业务里必须考虑。
4.2 时间冲突检测的SQL与Java实现
实验室预约最核心的约束是"同一个实验室在同一个时间区间只能被一个有效预约占用"。判断两个区间是否重叠,有一个经典公式:
现有预约时间区间 [start1, end1],新预约时间区间 [start2, end2] 两者冲突当且仅当 start1 < end2 && end1 > start2用MyBatis Plus的LambdaQueryWrapper可以这样写:
long conflictCount = reservationMapper.selectCount( new LambdaQueryWrapper<Reservation>() .eq(Reservation::getLabId, labId) .in(Reservation::getStatus, Arrays.asList("APPROVED", "CHECKED_IN")) .lt(Reservation::getStartTime, endTime) .gt(Reservation::getEndTime, startTime) ); if (conflictCount > 0) { throw new BusinessException("该实验室在此时间段已被占用"); }这里要注意一个细节:查询冲突时只能查"占用时段"的状态,待审核的预约不应该算占用,否则管理员来不及审批,学生看到的时间全部是灰的,反而降低利用率。
除了应用层检测,数据库层面还可以增加一个约束:给(lab_id, start_time, end_time)加唯一索引或者用数据库锁。不过实验室预约场景并发量不大,应用层检测加事务就足够。真到了高并发再考虑分布式锁也不迟。
4.3 基于RBAC的接口权限拦截
后端权限拦截,我采用的是Spring Boot拦截器 + 自定义注解的方式。定义一个@RequirePermission注解:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RequirePermission { String value(); }在拦截器里解析请求路径,再结合Shiro或Spring Security,也可以简单用拦截器 + Token做。核心逻辑是:
- 用户登录后签发Token,Token里包含userId。
- 每次请求拦截器解析Token,查用户角色。
- 根据角色匹配当前接口要求的角色集合,不匹配则返回403。
我建议把"哪些接口需要什么角色"维护在一个配置文件或数据库字典中,而不是散落在每个Controller方法上,这样审计权限时一眼就能看到全景。项目规模不大时,直接在Controller方法上用注解也是高效的方式,但注意不要让这几个注解的含义重复冲突。
以一个按钮级权限为例:学生只能提交预约、取消自己的预约、提交报告;教师可以审批预约、管理课程、批阅报告;管理员可以做所有操作,包括设备维护、用户管理、数据统计。
4.4 文件上传与实验报告处理
实验报告上传是一个容易出小问题的模块。我做的时候踩了三个坑:
- 上传大小限制:Spring Boot默认单文件大小是1MB,一个带图片的实验报告很容易超限。需要在
application.yml里配置:
spring: servlet: multipart: max-file-size: 20MB max-request-size: 100MB- 文件重名覆盖:不要用原始文件名直接存磁盘,否则两个学生提交相同文件名的报告会互相覆盖。我用
UUID + 时间戳重命名,同时把原始文件名存到数据库里,便于下载时恢复。 - 访问路径权限:如果文件通过Nginx或Tomcat静态目录直接对外暴露,任何人都能猜路径下载,必须做一层拦截校验上传者的权限,或者把文件放在项目外部目录,只通过后端接口返回流。
5. 前端Vue页面开发:预约表单与状态可视化
前端用Vue开发管理后台,最忌讳的是把所有页面写得一模一样,最后看起来像"模板套模板"。实际应该按照业务角色和使用频率来设计页面优先级。
5.1 页面结构与权限路由
前端页面我按角色拆分,目录结构大致是这样:
src/ views/ admin/ LabManage.vue // 实验室管理 DeviceManage.vue // 设备管理 UserManage.vue // 用户管理 Statistics.vue // 使用统计 teacher/ CourseSchedule.vue // 排课管理 Approval.vue // 预约审批 ReportReview.vue // 报告批阅 student/ ReserveLab.vue // 预约实验室 MyReservation.vue // 我的预约 MyReport.vue // 我的报告权限路由用Vue Router的meta字段 + 全局前置守卫来判断角色。简单做法是登录后把角色存到localStorage,路由守卫里判定目标路由需要的角色是否在roles数组中。这里有个改进点:不要只在前端做路由跳转拦截,后端接口一定要做同样的校验,前端只是体验优化,后端才是安全底线。
5.2 预约流程的前端交互逻辑
预约页面是整个系统交互最复杂的页面。它需要让用户快速看清"哪天哪节课哪个实验室有空",同时完成预约提交。
我的实现方式是:顶部放一个日历组件(我就是用Element Plus的el-calendar),选中日期后,下方加载该日的实验室时段列表。每个时段显示实验室名称、位置、容量、当前状态。
交互上要注意三点:
- 时间段选择要结合时间粒度,一般是按"节次"或按"小时"来选。我采用的是
el-date-picker的datetimerange组件做开始时间和结束时间选择,然后在提交前再校验。但更好的体验是基于预定义时段让用户点击选择,避免用户选出一个跨天的无效区间。 - 冲突即时提示:用户选择时段后,前端调一个
checkAvailable接口,立即返回该时段是否可用。不要等到提交后再报错。 - 表单校验:实验名称、人数、用途这几个字段必填,用
el-form的rules做校验。人数不能超过实验室容量,这个校验要在后端也做一遍。
5.3 状态标签与按钮级权限
预约列表页,状态展示是最影响使用体验的部分。不同状态应该用不同颜色的标签区分:待审核用warning色,已通过用success色,已拒绝用danger色,已完成用info色。
按钮级权限,我做了一个简单的v-permission指令:
app.directive('permission', { mounted(el, binding) { const roles = JSON.parse(localStorage.getItem('roles') || '[]') if (!roles.includes(binding.value)) { el.parentNode?.removeChild(el) } } })用起来就是:
<el-button v-permission="'ADMIN'" @click="deleteLab">删除实验室</el-button>这个指令适合纯前端隐藏按钮,但后端接口鉴权必须同步跟上,否则随便调接口就能删除数据。
5.4 与后端API的对接和封装
前端和后端联调时的效率,很大程度上取决于axios封装的质量。我的封装包含三个部分:
- 请求拦截器:自动携带
Authorization请求头。 - 响应拦截器:统一处理返回码,比如
401跳转登录页,403提示无权访问,500弹出全局错误提示。 - 统一返回结构:后端接口统一返回
{ code, message, data },前端不用在每个页面里写try-catch。
举个例子,Axios响应拦截器:
service.interceptors.response.use( (response) => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message) if (res.code === 401) { router.push('/login') } return Promise.reject(new Error(res.message)) } return res.data }, (error) => { ElMessage.error('网络请求异常') return Promise.reject(error) } )统一了结构之后,新增一个页面接口对接的工作量会小很多,联调效率也能上来。
6. 部署与排错:把项目真正跑起来的过程
拿到源码、数据库、文档,第一步要做的不是改业务,而是先让项目跑起来。这一章我把从零到可访问的部署过程以及我遇到的高频问题整理成清单。
6.1 环境准备与版本匹配
一个典型的部署环境需要:
- JDK:后端用的Spring Boot版本决定JDK版本。如果是Spring Boot 2.x,建议用JDK 8或11;如果是Spring Boot 3.x,必须用JDK 17及以上。
- Maven:3.6以上,用来打包后端。
- MySQL:5.7以上或8.0,注意MySQL 8的默认时区如果没配置,会报时区错误。
- Node.js:前端构建需要,版本建议16以上,和Vite、Element Plus兼容性更好。
- Nginx:用来部署前端静态资源和反向代理后端接口。
这里最容易踩的大坑是版本不匹配。一个常见的失败场景是:后端用Spring Boot 2.7(JDK 8),前端用Vue 3 + Vite 5(需要Node 18),两者之间并没有直接冲突,但如果后端代码里用了Spring Security 5.7的一些新API,和JDK 8一搭配就会编译失败。所以先看pom.xml和package.json锁定版本,再决定装什么环境。
6.2 后端打包与前端构建
后端打包:
mvn clean package -DskipTests打包完成后在target目录下会得到xxx.jar。启动命令常见:
java -jar xxx.jar --spring.profiles.active=prod生产环境配置里,数据库地址、账号密码尽量通过环境变量或启动参数传进去,不要硬编码在源码中。
前端构建:
npm install npm run build构建产物在dist目录下。部署时把dist下的内容复制到Nginx的html目录,然后在Nginx配置文件里加一条反向代理规则,把/api下的请求转发到后端端口:
server { listen 80; server_name your-domain; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files $uri $uri/ /index.html;这一行是前端路由刷新404问题的关键,忘掉它刷新页面就白屏。
6.3 常见问题清单
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 前端请求接口报跨域 | 前后端不同端口未配置跨域 | 后端全局CORS配置,或Nginx反向代理解决 |
| 存入数据库时间少8小时 | JDBC连接串没有加时区参数 | jdbc:mysql://...?serverTimezone=Asia/Shanghai |
| 中文乱码 | 数据库字符集不是utf8mb4 | 建库时指定utf8mb4,JDBC串加characterEncoding=utf8 |
| 刷新页面404 | 前端路由为history模式但Nginx未配置try_files | 加上try_files $uri $uri/ /index.html |
| 上传文件报错 | 单文件大小超过默认1MB限制 | 在application.yml调大max-file-size |
| 初始化SQL执行报错 | 低版本MySQL不支持高版本字段类型 | 检查MySQL版本,或改用兼容的字段类型 |
这些属于部署中最常见的问题。如果你搭建过程中发现某个问题解决后另一个问题又冒出来,不要慌,大概率是环境版本错位,先检查一遍JDK、Node、MySQL的版本是否在项目依赖允许范围内。
6.4 基础安全配置不能省
很多教学项目不重视安全,这会导致项目能跑但不敢上线。我给出几条基础配置建议:
- 后端对登录接口和学生修改资料的接口做参数校验,防止XSS脚本注入。
- 数据库连接账号不要用root,单独创建一个低权限账号,只给业务库的增删改查权限。
- 文件上传时要校验文件类型,不能允许上传
.jsp、.html这类可执行或可被浏览器解析的文件。 - 前端登录密码用加密传输(至少用HTTPS部署,开发环境可以用BCrypt加盐后再入库比较)。
我见过一个项目把用户密码明文存在数据库里,这是完全不可接受的。至少用Spring Security的BCryptPasswordEncoder做加密。
7. 从能跑到好用:功能扩展与项目复盘
项目跑通只是起点,真正把它用起来、扩展成有想象力的系统,还需要站在业务角度想更多问题。
7.1 可以扩展的方向
实验室教学管理系统可以扩展的方向很多,我列几个实际有价值的:
- 门禁联动:把预约记录和实验室门禁系统对接,学生预约通过后生成临时门禁授权,签到和门禁刷卡联动。这是目前很多新校区已经在做的"智慧实验室"方案。
- 设备状态实时监控:给设备加物联网传感器,系统实时显示设备温度、功率、在线状态。这个扩展会让系统从"管理软件"升级成"物联网平台"。
- 大数据统计看板:做一个管理驾驶舱,显示各实验室使用率、各课程学生出勤率、设备利用率排名。这个是我认为性价比最高的扩展,因为数据都已存在,只需要写查询聚合接口 + 前端图表即可。
- 消息通知:预约审批通过、驳回、实验即将开始,可以通过邮件或站内消息通知学生。接入WebSocket后还能实时推送。
7.2 个人复盘:这类管理系统的开发套路
做了几个类似的管理系统之后,我发现它们之间是有套路可循的。如果你未来还要做教室预约、会议室管理、图书室管理,基本流程都是同一套:
- 先理清角色和权限,所有业务功能都要明确是哪个角色在什么状态下操作的。
- 设计状态机,把核心业务对象从创建到结束的所有状态穷举出来,画出流转图。
- 设计时间约束,凡是涉及资源占用的系统,时间字段都选择完整时间戳。
- 再写CRUD,因为角色、状态、时间约束确定了,CRUD只是数据搬移。
这套思路帮助我后续在扩展代码时没有出现"新功能把旧逻辑搞坏"的问题。比如加上物联网设备状态同步,只需要在设备表加一个last_heartbeat字段,不需要改动预约逻辑。
另一个体会是文档远比想象中重要。很多人觉得写文档是浪费时间,但当你三个月后重新打开这个项目,或者把项目交给下一个同学维护时,一份说明清楚表结构、接口、部署步骤的文档可以节省好几天的重新阅读理解时间。标题里特意提到"文档",说明这个交付物是包含完整文档的,这在学习和工作中都是加分项。
最后分享一个小经验:做这类项目时,尽量在开发阶段就每天保持"代码可运行"状态。每完成一个模块就打包部署一次,而不是等到全部写完之后再统一调试。否则最后联调时会遇到几十个bug同时存在,根本不知道从何查起。我当时就是一直坚持小步迭代,最后上线阶段非常顺利,几乎没有出现大面积返工的情况。