做毕业设计,十个人里有八个会选“XX管理信息系统”,最后做出来的东西,常常只是给数据库的增删改查套了一层网页皮肤。汽车租赁管理系统这个题目,听起来也像是其中一员,但真正动手以后你会发现,它有一个普通管理系统没有的硬核内核——订单状态流转。租车不是简单的“登记一条记录”,它牵扯到选车、下单、押金、取车、计费、还车、结算、违约处理这一整条闭环,任何一个环节的状态变了,后面所有操作都要跟着联动。正因为它业务足够完整、又有技术挑战,很多人选它做SpringBoot毕业设计,同时这套需求也天然贴近“共享汽车运营平台”“轻量级汽车在线租赁与订单追踪系统”的落地形态。
这篇文章,我想把开发这类系统过程中的需求拆解、架构设计、编码实现和踩坑经验做一次完整复盘。不管你是正在选题的本科生,还是想快速搭一套可演示系统的开发者,都能从中拿到能直接用的方案,也能理解每个设计决策背后的原因。
1. 选这个课题之前,先把业务复杂度想明白
很多人第一反应是:汽车租赁系统不就是一个车辆表加一个订单表吗?和图书管理系统差不多。图省事的人确实可以做成那样,但这不是这个题目的正确打开方式。
1.1 它不是普通管理系统,核心是订单状态机
汽车租赁的核心业务规则是一整条订单生命周期:用户挑选空闲车辆、提交订单、支付或冻结押金、到店或扫码取车、用车、还车验车、费用结算、押金释放、订单归档。每一步都对应一种订单状态,每两种状态之间的转换都有前置条件。
这听起来不难,难的是你要在这些状态之间设定严格的流转规则。比如待取车订单不能直接变成已完成,已取消订单不能重新进入使用中,车辆在“租赁中”时不能被另一个订单重复选中。这些规则如果只在代码里靠 if 判断硬撑,后期改一个状态就会连带改出三个 bug。正确做法是把状态设计成一套显式的状态机,所有状态变更都从同一个入口走。
1.2 为什么是SpringBoot而不是其他框架
这个项目标题已经给了明确答案:SpringBoot。从实际开发角度看这个选型确实站得住脚。
首先,内嵌Tomcat解决了最烦人的部署问题。毕设阶段你不需要再去找一个外置Tomcat、配置一堆 XML 再去打 war 包,IDEA 里直接 run main 方法就能起服务,答辩演示时环境不容易翻车。
其次,“轻量级”这个定位很适合用 SpringBoot 的单体应用模型来承载。一个 jar 包、一份 MySQL 建表脚本、一套前端静态资源,就能把整套系统交付出来,评审老师看系统、看代码、看文档都很方便。
第三是生态成熟度。做用户登录有 Spring Security 或 Sa-Token,做数据库操作有 MyBatis-Plus 或 Spring Data JPA,做参数校验有 spring-boot-starter-validation,遇到问题搜索一下基本都是现成的解决方案。这个生态意味着即使你之前没怎么用过 SpringBoot,也能在短时间内把项目跑起来,而不是卡在某个冷门配置上。
1.3 给自己定位:及格线、加分线还是商用线
动手之前先问自己一句:我打算做到什么程度算完?我一般把这种课题分成三档。
第一档是“及格线”:用户注册登录、车辆列表、下单租车、后台订单管理、还车结算,主流程走通,数据库五张表以内,能完整演示十分钟。适合基础一般、时间紧张的同学。
第二档是“加分线”:在及格线基础上加上状态机约束、超时自动关单、押金冻结与释放、订单轨迹模拟、多角色权限、简单的数据统计,数据库做到七八张表,答辩时能对着设计图讲清楚为什么这样设计。这一档是大多数想拿优秀论文的人应该瞄准的目标。
第三档是“商用线”:接入真实地图定位、对接支付网关、做小程序端、上消息推送。工作量直接膨胀到一个小团队级别,单枪匹马做毕设非常吃力,除非你已经有企业开发经验,否则不太建议硬冲。
我的建议是:除非你时间极其充裕,否则做到第二档的七成,性价比最高。既不会因为太简单被评委一句话问住,也不会因为工程化细节太多而没时间写论文。
2. 需求拆解:把“租车”翻译成能落地的功能点
有了整体定位,下一步是画业务流程图。别急着写代码,先拿笔把角色和动作列出来。
2.1 用户侧:从找车到退押金的完整闭环
用户端的功能不是简单的一个“我要租车”按钮。拆开看是这样一条链:
注册登录后,用户需要上传身份证照片和驾驶证照片。这一步在实际项目里对应实名认证,在毕设里可以做简化版——管理员在后台审核,审核通过后用户才能下单。这个功能虽然多写一点代码,但会让角色权限系统立刻丰满起来。
车辆列表页要支持按车型、按日租价区间、按空闲状态筛选。这里要注意一个细节:车辆状态除了“空闲/租赁中”,还应该有“维修中”。维修中的车不应该出现在用户的可用列表里。
下单时用户要选择取车时间、预计还车时间。此时系统要做两件事:一是再次校验车辆在该时间段内是否被占用,二是计算预估费用并展示。费用展示非常关键,评委很可能从这里开始追问计费规则。
下单后进入押金支付环节。这里可以做一个虚拟钱包体系:用户账户里有可用余额,下单时冻结一笔押金,用车结束后从冻结金额里扣取实际租金并释放剩余金额。这个设计比“订单表里存一个押金字段”要真实得多,也是后面章节会展开讲的重点。
取车时,系统生成一个取车凭证号或者模拟扫码动作,订单从“待取车”变成“使用中”。还车时用户确认还车位置,系统按实际用车时长计费,扣除费用后释放押金,订单归档。
整条链路走下来,用户端的功能点大概有十来个,但核心动作只有四个:下单、取车、还车、结算。
2.2 运营侧:后台管理的几个关键闭环
后台的角色是管理员,核心操作对应车辆管理、订单管理、用户管理、价格规则管理和数据统计。
车辆管理除了车型、车牌、日租价这些基础字段的增删改查,还要维护车辆状态。特别要注意的是,车辆不要提供简单的“删除”按钮,因为历史订单还引用着车辆信息。正确的做法是“上架/下架”或“标记维修”,而不是物理删除。这个小细节很能体现你对数据一致性的理解。
订单管理是后台最复杂的功能。管理员需要按状态筛选订单,查看某个订单的完整操作轨迹,还要能处理异常情况,比如用户超过取车时间未取车,后台可以手动关闭订单或者标记违约。再比如用户还车时产生了争议费用,管理员可以调整结算金额。
用户管理这块,除了封禁/解封,更重要的是驾照审核。这里我建议专门做一个“待审核列表”,管理员逐条点击查看用户上传的证件照片,通过或驳回。这个功能本身不难,但它能天然带出一个审核状态字段(待审核/已通过/已驳回),让整个系统不再是扁平的单角色 CRUD。
数据统计可以做三个指标:订单总量、营收总额、车辆利用率。车辆利用率的计算方式是“某辆车在租赁中的时间总和 / 可用时间总和”,公式不复杂,但能撑起论文里的“系统测试与数据分析”章节。
2.3 容易丢分的边界功能:谁在做、怎么做
评审老师最爱问的一句话是:“如果用户下单后一直不去取车怎么办?”或者“如果用户超时没还车怎么办?”
这两个问题在需求阶段就应该接着答上。超时未取车的处理方案是:系统定时扫描待取车状态的订单,超过约定取车时间一定时间后自动取消订单,并记录一次违约。超时未还车的处理方案是:订单进入超时计费模式,按超时时长计算额外费用,超过一定宽限期仍未还车则标记为违约订单,运营人员介入处理。
还有一个很容易被忽视的并发问题:同一辆车在同一时间段被两个用户同时下单。这个问题我放在后面数据库和事务章节展开,但需求阶段你就应该意识到,车辆被占用不是“一个状态字段”就能完全解决的,还需要数据库层的约束配合。
可以用下面这张表快速梳理系统要支持的边界场景:
| 场景 | 系统动作 | 后台/用户可见反馈 |
|---|---|---|
| 下单后未支付押金 | 定时任务取消订单 | 订单状态变为已取消 |
| 待取车超时未取 | 自动取消并记违约 | 用户收到违约记录 |
| 使用中超时未还 | 开始计算超时费 | 订单费用实时增加 |
| 用户押金余额不足 | 下单时拦截 | 提示余额不足 |
| 车辆处于维修状态 | 不出现在可用列表 | 后台可手动恢复 |
| 民警/管理员强制关单 | 后台调用状态变更接口 | 订单标注人工关闭 |
3. 后端骨架:分层、统一返回和异常机制
需求理清楚之后,进入工程搭建阶段。这个阶段最重要的不是把功能写出来,而是搭一个“不坍台”的骨架。我见过不少项目代码全部堆在 Controller 里,两三百行一个方法,业务逻辑和参数校验搅在一起。这样写短期内很快,但后面每加一个功能都要拆东墙补西墙。
3.1 分层架构怎么分才不显得单薄
标准的分层是 controller → service → mapper/repository,再加一个常量/工具包。我习惯把包结构设计成下面这样:
com.example.carrental
├── controller # 接口入口,只做参数接收和结果返回
├── service # 业务接口
│ └── impl # 业务实现
├── mapper # 数据访问层,MyBatis-Plus 或 JPA Repository
├── entity # 数据库实体
├── dto # 接口入参/出参对象
├── common # 统一返回结果、错误码、全局异常
├── config # 配置类
└── task # 定时任务
Controller 里的方法最好控制在十行以内。它的职责是拿到前端参数、补全当前登录用户信息、调用 service、返回 Result。真正的业务逻辑全部下放到 service 层。这样做的直接收益是:接口文档好写,代码走查的时候不尴尬,测试也好做。
举个例子,还车接口的 Controller 可能只有这么几行核心逻辑:
@PostMapping("/return") public Result<ReturnVO> returnCar(@RequestBody ReturnDTO dto) { User currentUser = UserContext.get(); return Result.success(orderService.returnCar(currentUser.getId(), dto.getOrderNo())); }实际要做的事情——校验当前用户是否持有该订单、计算费用、释放押金、更新车辆状态、生成违约记录——全在 orderService.returnCar 方法里。这样如果还车逻辑出问题,你直接看 service 即可。
3.2 统一返回与全局异常:毕设里最容易被忽略的工程化细节
很多毕设项目的前后端交互很混乱:有的接口返回 Map,有的返回 JSONObject,出错时有的返回 200 带个 message,有的直接抛 500。答辩时容易把评委绕晕。正确做法是订一套统一的返回结构。
最基本的结构是三个字段:code、message、data。
public class Result<T> { private Integer code; private String message; private T data; // 静态方法 success / error }配套的还有错误码枚举。错误码不需要设计得很复杂,覆盖几个关键场景即可:参数错误、未登录、无权限、业务冲突、系统异常。有了这套东西,前端 Ajax 封装、后端接口文档都能统一处理,评委看代码时也会觉得你是按规范来做系统的。
全局异常处理也是一个经常被扣分的地方。SpringBoot 里用 @RestControllerAdvice 可以统一拦截业务异常和系统异常。业务异常是你在代码里主动抛出的,比如“车辆已被预订”,系统异常则是意料之外的错误。统一拦截后返回格式一致的错误信息,而不是让堆栈直接暴露给前端。
3.3 配置文件的取舍:多环境与关键参数
application.yml 里应该区分开发环境和生产环境,用 spring.profiles.active 切换。毕设阶段可能不需要真的部署到服务器,但至少配置两个 profile 结构,会让导师觉得你有工程意识。
MySQL 连接串、用户名、密码放在 dev profile 里。生产 profile 可以只保留一个结构。另外,MyBatis-Plus 的日志配置要打开 SQL 打印,开发时能直接看到执行的 SQL,排查问题会方便很多。如果用的是 MyBatis-Plus,还有一个非常实用的全局配置:逻辑删除。在实体字段上加 @TableLogic,删除操作就会自动变成 update 一个删除标记,历史订单里的车辆信息就不会因为“删除”而丢失。
4. 订单状态流转的设计与实现:租车系统的心脏
如果说前面的内容都是准备工作,那这一章就是整套系统的核心。订单状态机设计得好不好,直接决定项目上限。
4.1 状态总表与流转条件
我设计的订单状态一般是这样一套:
| 状态 | 状态含义 | 进入条件 | 可流转去向 |
|---|---|---|---|
| 待支付押金 | 用户已下单但押金未付 | 下单成功 | 待取车、已取消 |
| 待取车 | 押金已支付,等待用户取车 | 支付成功 | 使用中、已取消 |
| 使用中 | 用户已扫码/确认取车 | 取车动作触发 | 待结算、违约 |
| 待结算 | 用户已确认还车,费用待结算 | 还车动作触发 | 已完成 |
| 已完成 | 费用结算完成,押金释放 | 结算成功 | 无 |
| 已取消 | 订单被用户或系统取消 | 用户取消/超时关单/后台关闭 | 无 |
| 违约 | 超时未还或违规使用 | 系统判定/后台标记 | 已结算 |
有一个容易忽略的细节:已取消和违约不应该再回流到任何可继续使用的状态,但这个限制要在代码里硬约束,不能靠前端按钮控制。否则用户绕过前端直接调接口,订单状态就能乱套。
4.2 用枚举约束非法流转
最好的做法是定义订单状态枚举,比如OrderStatusEnum,然后在状态变更的入口统一校验。
先看枚举:
public enum OrderStatusEnum { PENDING_PAY(0, "待支付押金"), PENDING_PICKUP(1, "待取车"), RENTING(2, "使用中"), PENDING_SETTLE(3, "待结算"), COMPLETED(4, "已完成"), CANCELLED(5, "已取消"), OVERDUE(6, "违约"); private final int code; private final String desc; // 构造方法和 getter }再定义一个状态迁移路线表,用 Map 结构存储每个状态允许跳转的目标状态。状态变更时统一调用一个方法:
public boolean canTransit(OrderStatusEnum from, OrderStatusEnum to) { Set<OrderStatusEnum> allowed = TRANSITIONS.get(from); return allowed != null && allowed.contains(to); }这个做法的好处是:所有状态流转规则集中在一个类里可视化,想给订单增加一个新状态,只需要修改这一处配置,其他地方不需要动。答辩时把这张状态迁移图打印出来,比自己临时讲“我用了很多 if 判断”要清晰得多。
实际开发中,我还在状态变更前加了一个前置校验:下单前确认车辆状态为“空闲”,还车前确认订单状态为“使用中”。每个 Service 方法开头先查一次当前状态,如果不符合就抛业务异常。这样状态机是第二道防线,service 是第三道,前端按钮只是第一道表象。
4.3 超时关单与定时任务幂等
订单状态机跑起来之后,你会发现有些状态会“卡住”。用户下单不付款怎么办?用户付了款不去取车怎么办?这些都需要定时任务来兜底。
我用 Spring 的 @Scheduled 注解做了两个扫描任务。第一个任务扫描超过 30 分钟未支付的“待支付押金”订单,自动取消;第二个任务扫描超过约定取车时间 1 小时仍未取车的“待取车”订单,自动取消并生成一条违约记录。
定时任务有一个关键问题:幂等性。如果同一笔订单被扫描两次,不能重复执行取消动作。解决办法是执行状态变更前先检查当前订单状态是否为“待支付”或“待取车”,只有状态匹配时才执行更新。再加一层保险,在 SQL 更新时带上状态条件:
UPDATE rent_order SET status = 5 WHERE order_no = #{orderNo} AND status = 0这个 SQL 会返回受影响行数。如果影响行数为 0,说明订单状态已经被别人改过了,本次操作直接跳过。这个模式在并发场景下非常好用,我后面讲数据库并发时还会再提。
5. 计费规则、押金冻结与订单追踪的实现
订单状态跑通后,系统已经能演示了。但汽车租赁和单纯的状态管理还有一个本质区别:它涉及钱。计费和押金这块实现得好坏,决定了系统是“玩具”还是“可用系统”。
5.1 计费模型的代码化
先明确计费参数的来源。每辆车在价格规则表里配置时租价格。订单下单时根据车型查出租车的单价,下单时显示的预估费用是“预计小时数 * 单价”。还车时按实际取车时间和还车时间计算时长。
最简单可靠的时长计算方式是:
long minutes = ChronoUnit.MINUTES.between(pickupTime, returnTime); // 按小时向上取整,不满 1 小时按 1 小时计 long hours = Math.max(1, (minutes + 59) / 60); BigDecimal baseFee = pricePerHour.multiply(BigDecimal.valueOf(hours));这里有一个经验值:计费按分钟向上取整到小时,演示时比按分钟计费更直观,也更容易解释。如果项目想显得更有深度,可以再加一条超时规则:超过预计还车时间 1 小时以内的部分按 1.5 倍单价计费,超过 1 小时的部分按当日全天价格计费。把超时规则单独抽成一个方法,不要和基础计费混在一起。
public ComputeResult computeFee(Order order, RentalPriceRule rule) { BigDecimal baseFee = calculateBaseFee(order, rule); BigDecimal overtimeFee = calculateOverTimeFee(order, rule); return new ComputeResult(baseFee, overtimeFee); }还要考虑一个业务场景:用户提前还车。这时候就应该按实际用时结算,而不是按预计费用结算,同时可以返还部分差额。这个细节很多同学做不出来,但一旦做出来,答辩时能很自然地讲出你的计费逻辑考虑到了用户利益。
5.2 押金“冻结—结算—释放”逻辑
押金不要做成简单的“支付一笔钱”,最好的方案是在系统里建一张虚拟钱包表,字段至少包含三个:总余额、可用余额、冻结余额。
用户下单时,从可用余额里冻结对应押金:
walletService.freeze(userId, depositAmount); // 逻辑:available subtract, frozen add还车结算时,先从冻结金额里扣除实际租金,然后释放剩余冻结金额。如果用户扣费之后可用余额为负数,那就要进入“欠费补缴”流程。这一步在毕设里可以用后台手动调整来兜底,不一定要做完整的催缴链路,但逻辑上要能说清楚。
押金设计要注意一个坑:不能把押金直接加到订单表里当作一个展示字段完事。因为订单可能会被取消,取消时押金要原路退回;订单可能被强制结束,结束时押金要按规则结算。如果押金只是订单的一个字段,这些后续操作会变得非常别扭。独立钱包表搭配流水表,每一笔资金的变动都有记录,既是开发体验上的优化,也是系统可信度的加分项。
5.3 让订单“被追踪”:轨迹模拟方案
“订单追踪系统”这个词从标题里就出现了,说明这是这个课题的差异化亮点。你不可能在毕设里真的接入车辆 GPS,但那不代表你不能做出“追踪感”。
我的做法是设计一张车辆轨迹表,表里存着订单号、经纬度、记录时间。然后写一个模拟任务,每十秒给所有“使用中”的订单生成一个新的轨迹点。为了让轨迹看起来合理,可以在订单取车时给一个起点,还车时给一个终点,中途按固定速度和方向生成点。
前端订单详情页拉取轨迹接口时,会看到一串按时间排序的位置点。把这些点按顺序连起来,就是一条车辆行驶路径。如果你再用一个小地图展示,系统的演示效果会直接上一个台阶。
这个模块的核心价值在哪?在于它把“订单追踪”从口号变成了可演示的功能。评委可以看到每一个在租订单的当前位置、行驶轨迹、里程数,这比单纯靠状态字段感受要强太多。实现上,只需要一个定时任务加一张表,成本很低但收益很大。
6. 数据库设计与查询优化:从能跑到跑得顺
后端代码写得再好,数据库设计不合理一样会卡住。
6.1 核心表设计与关系
以我的习惯,这套系统的表可以做八九张:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| sys_user | 用户表 | id、手机号、密码、身份状态、驾照审核状态 |
| car_info | 车辆信息表 | id、车牌号、车型、时租单价、状态 |
| rent_order | 订单表 | id、订单号、用户id、车辆id、状态、各种时间 |
| wallet | 钱包表 | id、用户id、总余额、可用余额、冻结余额 |
| wallet_flow | 钱包流水表 | id、钱包id、变动金额、业务类型、关联订单号 |
| car_track | 车辆轨迹表 | id、订单号、经度、纬度、记录时间 |
| price_rule | 价格规则表 | id、车型、时租价、超时系数 |
| violation_record | 违约记录表 | id、用户id、订单号、违约类型 |
订单表是最核心的,字段应该包括订单号、用户 id、车辆 id、状态、押金金额、租金金额、超时金额、预计取车时间、预计还车时间、实际取车时间、实际还车时间、创建时间。订单号我习惯用“前缀 + 时间戳 + 随机数”生成,这样每个错误提示里都能把订单号直接追溯过去。
6.2 分页联表和N+1问题
订单管理页要展示用户手机号、车辆车牌、车型、订单金额这些信息。最笨的写法是查出订单列表后,循环去查用户表和车辆表,也就是 N+1 问题。数据量小的时候感觉不出来,但只要订单多起来,接口就会肉眼可见地变慢。
正确做法是用一张联表 SQL 把订单分页数据一次性查出来。用 MyBatis-Plus 可以直接写分页插件,再配合自定义 SQL:
SELECT o.order_no, u.phone, u.user_name, c.plate_no, c.car_model, o.status, o.rent_amount, o.create_time FROM rent_order o LEFT JOIN sys_user u ON o.user_id = u.id LEFT JOIN car_info c ON o.car_id = c.id ORDER BY o.create_time DESC这里要注意,联表查询不要 SELECT *。只查需要的列,SQL 的可读性和执行效率都会好很多。
6.3 索引、事务与并发扣费
数据库设计的下一步是加索引。订单表的 status 字段会频繁用于筛选,create_time 用来排序,建议建一个联合索引(status, create_time)。车辆表的 status 字段同样建索引。钱包表可以不用额外加太多索引,因为基本按用户 ID 单条查询。
事务控制方面,还车结算涉及多个表更新:订单状态改待结算、钱包冻结金额转扣费、车辆状态改空闲。这三个操作任何一个失败,都不能出现“车已经空闲但钱没扣”的怪现象。所以结算方法需要加 @Transactional 注解,并且注意事务中不要捕获异常之后继续执行,要让异常抛出去触发回滚。
并发问题是毕设答辩的高频追问点。同一辆车能不能被两个订单同时选中?前面说了,光靠应用层检查不够。最简单的数据库层解决方案是:更新车辆状态时使用带条件的 update。
boolean success = carService.updateState(carId, CarStatus.RENTING, CarStatus.FREE); if (!success) { throw new BizException("车辆已被预订,请刷新后重试"); }这条 SQL 底层是UPDATE car_info SET status = 2 WHERE id = ? AND status = 1。只有当前状态确实是空闲,这条更新才会影响一行。如果两个用户同时发起订单,只有一个用户能让这条 update 成功,另一个会拿到影响行数为 0。这个方案被称为乐观锁思想,实现成本极低,但能挡住 99% 的并发问题,值得写进论文。
7. 答辩环节的展示思路与后续演进建议
系统做完不代表万事大吉。答辩时如何把工作量呈现出来,其实比编码更讲究策略。
7.1 demo怎么演才像“真系统
不要从登录页开始操作,那样会浪费大量的演示时间。建议提前准备一套演示数据,并且预演到“使用中”或“待结算”的关键状态。演示时直接从订单列表进入,展示一个正在进行的订单,然后现场进行还车操作,让评委看到费用计算、押金释放、车辆状态变化整个链路跑起来。
同时准备一张覆盖面较全的订单状态表,在演示某个状态的系统反应时,顺手指出当前状态在状态机中的位置。这套串联的讲解方式比零碎的功能点罗列要专业得多。
7.2 论文里值得浓墨重彩的几个点
论文不要写成“操作手册”,重点放在设计推理过程。需求分析里强调订单状态机的设计动机;总体设计里画出系统的分层架构图;详细设计里把状态迁移表、关键表结构、核心接口的时序写清楚,并用代码片段解释状态约束;系统测试部分可以针对边界场景多写几个用例,比如超时取消订单、并发下单、押金不足拦截。
这几个点都是能体现系统深度的内容,比汇报页面功能更容易打动评委。
7.3 从毕设走向可部署项目:加什么、不加什么
如果这套系统想进一步落地,有几个方向可以考虑:接入真实地图定位替换轨迹模拟;在小程序端做移动端入口;加上短信验证码登录;用 Redis 做热点车辆状态缓存。但我不建议一上来就做微服务拆分,单体应用把业务做扎实,在当前的题目范围内已经足够优秀。技术栈的复杂度应该跟着业务复杂度走,而不是为了写微服务而微服务。
写到这里,我自己也想起刚开始碰这类业务时,在数据库状态更新上被坑过的经历。那时明明两个接口都返回成功,订单状态却变成了不可预测的值,排查了很久才发现是状态变更没有统一收口。后来把状态机做成显式约束,项目突然就从“能跑”变成了“可信”。这个改动,是整个项目里性价比最高的一次重构。如果你现在正在做类似的毕业设计,我建议你先别急着堆页面,把订单状态流转这条主线理清楚,后面的工作会顺很多。