
很多人选毕设题目的时候都卡在“想做一个有点技术含量、又不会把自己逼死”的系统上。二手车交易管理系统这个题被选了很多年不是没有道理——它既有典型的信息管理系统骨架用户、车辆、订单的增删改查又带着真实交易场景里的状态流转和权限控制用 Java 技术栈做起来顺手答辩时也容易把业务逻辑讲清楚。如果你正在考虑“Java 毕设做什么题目”或者已经定下这个题但不知道从哪下手这篇文章就把我从选题、建模、编码到答辩准备的完整思路拆给你看。先说结论这套系统的核心价值不在“代码写得多花哨”而在把一条二手车从“卖家发布”到“买家成交”的链路用清晰的业务状态管理起来再把不同角色管理员、买家、卖家的权限边界划清楚。说白了它就是一套“车辆流通信息化系统”让车辆信息、交易订单、用户数据从线下纸质记录变成线上可查询、可追踪、可统计的系统闭环。下文会按照真实开发顺序讲先明白系统在解决什么再选技术栈然后做数据库设计接着实现核心业务逻辑最后聊几个我踩过的坑和答辩时的高频问题。1. 选题定位二手车交易管理系统到底在解决什么问题1.1 为什么这个题目能成为“经典款”毕设我见过不少同学一开始想选电商系统、外卖平台、校园二手市场这类题目最后发现要么业务太宽收不住要么边界模糊不知道做到什么程度算“完成”。二手车交易管理系统的好处在于它天然有三个清晰角色——管理员、卖家车商或个人车主、买家购车用户信息流也特别明确车辆发布 → 平台审核 → 展示上架 → 买家咨询 → 交易下单 → 状态变更。这个闭环覆盖了 Java web 开发里几乎所有高频考点CRUD 基本功用户、车辆、订单的增删改查这是任何管理系统的地基。业务状态流转车辆状态从“待审核”到“在售”再到“已售/下架”每一步都有前置条件。权限控制普通用户不能进管理后台卖家只能操作自己的车辆管理员才能审核与统计。数据关联查询订单要关联车辆信息、买家信息、卖家信息典型的多表联查。更重要的是二手车交易有强烈的“信息不对称”属性做系统时你可以名正言顺地加入车辆检测报告、价格记录、预约看车等模块既突出了业务特色又不会让系统变成毫无灵魂的“用户表车辆表”。1.2 角色划分与功能边界我在自己做的版本里把功能边界划定如下。这个划分不是拍脑袋而是参考了市面上主流二手车平台的流程简化而来管理员端用户管理禁用/启用、车辆审核通过/驳回、订单查看、数据统计成交量和成交金额、公告发布。卖家端车辆发布、车辆编辑/下架、查看自己车辆的浏览与咨询记录、处理订单。买家端浏览车辆、条件筛选、收藏车辆、预约看车、下单购买、查看个人订单。一句话总结管理员管全局卖家管供给买家管消费。三条线在“车辆”和“订单”两张核心表上汇合。1.3 我从这个项目里圈定的“核心交付物”做毕设最忌讳“什么都想要最后全都没做完”。我最终把交付范围收敛成以下几块每一块都能在答辩现场直接演示车辆信息发布与多条件检索车辆上下架与管理员审核闭环下单交易流程与订单状态管理以 Spring Boot 为后端的完整 JWT 登录与权限控制基于 ECharts 的成交量/成交金额统计图表。如果你时间充裕可以再往上叠预约看车、车辆收藏、消息通知这些加分项但前提是上面几条主链路已经稳定跑通。先把主干做完再谈枝叶。2. 技术选型Spring Boot MyBatis-Plus Vue 落地组合2.1 前后端方案的现实考量我知道很多学校教材还在教 JSP Servlet JDBC但说句实话那个组合放在毕设里无论是开发效率还是答辩观感都吃亏。我最终选择的是Spring Boot MyBatis-Plus MySQL做后端Vue 2 Element-UI做前端的管理后台与用户端页面。选这个组合的原因很实际Spring Boot把配置简化到极致你不用花一个礼拜去调 SSM 的 XML 配置注意力能集中在业务代码上。MyBatis-Plus提供了 BaseMapper 和 LambdaQueryWrapper单表 CRUD 基本不用写 SQL复杂查询再手写 XML效率翻倍。Vue Element-UI做管理界面是当下主流表格、表单、弹窗、分页都有现成组件前端开发量能压缩一半以上。更重要的是这个技术栈在你找工作时也是市场主流毕设做完简历上能写实打实的项目经验。2.2 版本选择与环境准备实测可跑通我建议直接用下面这套版本组合全部实测过兼容性没问题组件推荐版本说明JDK1.8 或 11不要用太高版本避免某些依赖没跟上Spring Boot2.7.x稳定支持 JDK 8文档丰富MyBatis-Plus3.5.3内置分页插件、代码生成器MySQL5.7 或 8.05.7 兼容性最好8.0 性能更好Node.js16.xVue 2 项目构建建议用 16不要用 18否则易出 node-sass 兼容问题Redis可选用做验证码缓存或车辆浏览量缓存加分项不是必需环境配置这点我要单独强调JDK 8 Spring Boot 2.7.x MyBatis-Plus 3.5.x这个组合是经过大量项目验证的黄金组合。如果你想追求新上 Spring Boot 3 JDK 17那 MyBatis-Plus 的版本就要换成 3.5.5动态数据源、分页插件等兼容性都要重新确认毕设期间完全没必要给自己加这个负担。2.3 后端工程结构规划我见过太多毕设代码 controller 里堆了 200 行业务逻辑service 层形同虚设。这题目的代码结构我建议这样分com.example.carplatform ├── controller // 接收请求、参数校验、返回统一结果 ├── service // 业务逻辑层 │ └── impl // service 实现类 ├── mapper // MyBatis-Plus 的 Mapper 接口 ├── entity // 数据库实体类 ├── dto // 前端交互的数据传输对象 ├── vo // 给前端的视图对象如统计结果、详情页组合数据 ├── config // 配置类拦截器、跨域、分页插件等 ├── common // 通用类返回结果封装、异常处理、常量 └── utils // 工具类JWT、文件存储等这个结构的核心逻辑是Controller 只做参数接收和结果返回业务判断全部下沉到 Service。比如“卖家下架车辆时只能下架自己的车”这种规则必须写在 Service 里校验而不是让前端隐藏按钮。这样答辩时老师问“你的权限控制怎么做”你可以理直气壮地说“前端控制展示后端控制数据”。2.4 前端项目结构前端我拆成两个部分用户前台和管理后台。技术上用同一个 Vue 工程通过路由区分或者做两个独立页面。更省力的做法是用一个 Vue 项目在菜单层面区分src ├── api // 封装 axios 请求 ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置带权限守卫 ├── store // 用户状态管理Vuex/Pinia ├── views │ ├── admin // 管理员页面用户管理、审核、统计 │ ├── seller // 卖家页面车辆发布、车辆列表 │ └── buy // 买家页面车辆大厅、车辆详情、订单 └── utils // 请求工具、token 管理等前后端分离的项目记得在 Spring Boot 里配好 CORS 跨域并在后端加一个拦截器统一处理 JWT 校验。前端 axios 请求拦截器里带上Authorization: Bearer token头后端拦截器放行登录接口其他接口校验 token 并取出当前用户信息存入 ThreadLocal 或 Request 上下文。这套流程我现在写起来闭着眼都能配你们第一次做时先把“登录拿 token → 请求带 token → 拦截器解析 token → 获取当前用户”这条链路理顺后面所有权限控制都建立在这条线上。3. 数据库建模车辆、用户、订单三张核心表如何设计3.1 用户表一个角色字段解决所有权限问题很多同学一上来就建三张用户表管理员表、卖家表、买家表。这会让代码里出现大量重复查询和关联逻辑非常难受。正确的做法是用一张用户表加角色字段CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(255) NOT NULL COMMENT BCrypt加密后的密码, nickname VARCHAR(50) DEFAULT NULL COMMENT 昵称, phone VARCHAR(20) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像地址, role TINYINT NOT NULL DEFAULT 2 COMMENT 角色: 0管理员 1卖家 2买家, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态: 0禁用 1启用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;这样设计的好处是登录逻辑只需要查一张表权限判断就是if (user.getRole() 1)的事。对于“一个人既可以是买家也可以是卖家”的真实场景将来扩展一张用户角色关联表即可毕设阶段用单一 role 字段完全够用而且更好解释。3.2 车辆信息表用冗余字段换查询效率车辆信息是整个系统的核心字段多且杂。我遇到过同学把品牌、车系、车型分别建表外键关联结果一个车辆列表查询需要 join 三张字典表。在毕设这个量级我强烈建议直接把品牌、车系等描述性字段冗余到车辆表里CREATE TABLE car_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, seller_id BIGINT NOT NULL COMMENT 卖家ID, title VARCHAR(100) NOT NULL COMMENT 车辆标题, brand VARCHAR(30) DEFAULT NULL COMMENT 品牌, series VARCHAR(50) DEFAULT NULL COMMENT 车系, model_year INT DEFAULT NULL COMMENT 上牌年份, mileage DECIMAL(10,2) DEFAULT NULL COMMENT 行驶里程万公里, gearbox TINYINT DEFAULT NULL COMMENT 变速箱: 1手动 2自动 3手自一体, displacement VARCHAR(20) DEFAULT NULL COMMENT 排量如1.5L, color VARCHAR(20) DEFAULT NULL, price DECIMAL(10,2) NOT NULL COMMENT 售价万元, original_price DECIMAL(10,2) DEFAULT NULL COMMENT 新车指导价, description TEXT COMMENT 车辆描述, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态: 0草稿 1待审核 2在售 3已售 4下架 5审核驳回, views INT NOT NULL DEFAULT 0 COMMENT 浏览次数, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_seller (seller_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT车辆信息表;为什么品牌车系直接存字符串而不是外键因为车辆检索场景主要靠status、brand、price、model_year这几个字段做筛选冗余字段配合复合索引查询走索引就行不需要 join。而二手车交易里品牌车系基本是固定值输入数据不一致的风险很低。用查询性能换冗余存储成本这是这类信息管理系统的常规操作。3.3 订单表记录交易快照别只存一个外键二手车订单和普通电商订单最大的区别是交易金额涉及线下看车、议价、过户所以订单表一定要把关键信息做成“快照”而不是随时 join 车辆表。比如车辆价格后续被卖家改了订单里存的价格不能跟着变否则买家付款时发现金额对不上就是事故级别的问题。CREATE TABLE trade_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单编号, car_id BIGINT NOT NULL COMMENT 车辆ID, buyer_id BIGINT NOT NULL COMMENT 买家ID, seller_id BIGINT NOT NULL COMMENT 卖家ID冗余, car_title VARCHAR(100) DEFAULT NULL COMMENT 车辆标题快照, car_price DECIMAL(10,2) DEFAULT NULL COMMENT 成交价快照, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态: 0待确认 1已成交 2已取消, remark VARCHAR(255) DEFAULT NULL COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT交易订单表;订单号生成建议用时间戳随机数或者雪花 ID不要用数据库自增当订单号否则订单号直接暴露了平台单量也不安全。我的做法是yyyyMMddHHmmss 4 位随机数 用户 ID 后四位基本不会重复且一眼能看出下单时间。3.4 辅助表车辆图片表与收藏表车辆图片不能直接往 car_info 里塞一个 varchar 字段用逗号拼虽然能实现但在处理“首图展示”和“删除某张图片”时非常别扭。我建议单独建一张图片表CREATE TABLE car_image ( id BIGINT PRIMARY KEY AUTO_INCREMENT, car_id BIGINT NOT NULL, url VARCHAR(255) NOT NULL, sort TINYINT DEFAULT 0 COMMENT 排序数值越小越靠前, is_cover TINYINT DEFAULT 0 COMMENT 是否封面图 1是, KEY idx_car (car_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT车辆图片表;查询车辆列表时每个车辆只取一张封面图用子查询或先查列表再批量查图都行。列表页展示封面详情页展示全部图片逻辑清清楚楚。收藏表也更简单user_id car_id唯一索引外加收藏时间即可。4. 核心业务实现从车辆发布到交易完成的关键链路4.1 车辆发布与审核业务状态机的第一道关卡很多毕设同学把车辆发布的逻辑写成“前端填表单后端 insert 一条数据”就完了。这样不是不行但完全没展示出你对业务的理解。我在车辆发布时做了三件事卖家只能为自己发布车辆Service 层从当前登录用户上下文取 userId而不是信任前端传的 sellerId。新车状态置为待审核1不允许直接上架必须等管理员在后台审核通过。发布时校验必填字段价格大于 0、里程不小于 0、品牌和车系非空。核心代码示意Transactional(rollbackFor Exception.class) public Long publishCar(CarPublishDTO dto) { // 1. 校验当前用户是否为卖家角色 User current UserContext.getCurrentUser(); if (current.getRole() ! UserRole.SELLER.getCode()) { throw new BusinessException(只有卖家才能发布车辆); } // 2. 参数校验 if (dto.getPrice() null || dto.getPrice().compareTo(BigDecimal.ZERO) 0) { throw new BusinessException(车辆价格必须大于0); } // 3. 组装实体状态置为待审核 CarInfo car new CarInfo(); BeanUtils.copyProperties(dto, car); car.setSellerId(current.getId()); car.setStatus(CarStatus.PENDING_REVIEW.getCode()); carMapper.insert(car); // 4. 保存图片列表 saveCarImages(car.getId(), dto.getImages()); return car.getId(); }这里面的关键点是Transactional。为什么发布车辆和保存图片要放在同一个事务里因为如果图片保存失败车辆主数据已经插入了就会产生“一条没有任何图片的孤零车辆数据”。事务保证这两步要么全成功要么全回滚。我在答辩时被问过“事务加在 Controller 行不行”答案是可以但不推荐——事务应该加在 Service 层的业务方法上因为一个业务用例可能跨越多个数据操作而 Controller 只负责接收参数和返回结果越薄越好。4.2 车辆列表查询条件筛选用 LambdaQueryWrapper 就够了车辆大厅是一个面向买家的检索页面筛选条件一般有品牌、价格区间、上牌年份、变速箱类型、里程范围。用 MyBatis-Plus 的 LambdaQueryWrapper 动态拼接条件非常舒服public PageResultCarVO queryCarList(CarQueryDTO query) { PageCarInfo page new Page(query.getPageNum(), query.getPageSize()); LambdaQueryWrapperCarInfo wrapper Wrappers.CarInfolambdaQuery() .eq(CarInfo::getStatus, CarStatus.ON_SALE.getCode()) .eq(StringUtils.hasText(query.getBrand()), CarInfo::getBrand, query.getBrand()) .between(query.getMinPrice() ! null query.getMaxPrice() ! null, CarInfo::getPrice, query.getMinPrice(), query.getMaxPrice()) .between(query.getMinMileage() ! null query.getMaxMileage() ! null, CarInfo::getMileage, query.getMinMileage(), query.getMaxMileage()) .eq(query.getGearbox() ! null, CarInfo::getGearbox, query.getGearbox()) .ge(query.getMinYear() ! null, CarInfo::getModelYear, query.getMinYear()) .orderByDesc(CarInfo::getCreateTime); PageCarInfo result carMapper.selectPage(page, wrapper); // 再补充分页转换把实体转 VO查封面图隐藏卖家敏感信息 }几个细节我踩过坑专门提醒一下between第一个参数是condition只有条件成立时才会拼接这个条件所以不用写大量 if/else。分页查询必须先配置 MyBatis-Plus 的PaginationInnerInterceptor新版本叫MybatisPlusInterceptor不配的话 selectPage 只能查出全表数据然后内存分页数据量一大就卡死。列表页展示的 CarVO 不要带seller_id和description大字段description 应该只在详情页出现避免列表接口响应体积过大。4.3 同一辆车被同时下单经典并发数据一致性场景这是我认为整个系统里最值得深入的一个点也是答辩时最能展示水平的一个问题。场景是这样的一辆热门车处于“在售”状态买家 A 和买家 B 同时点击“立即购买”如果后端只是先查询状态再更新那么两个请求都可能查到“在售”然后都往下走最后同一辆车被卖了两遍。处理思路按复杂度从低到高排列应用层加锁用 synchronized 锁同一辆车的 ID但在集群/多实例部署下失效毕设单机跑没问题但架构上不够专业。数据库乐观锁在 car_info 表加version字段更新时SET status已售, versionversion1 WHERE id? AND status在售 AND version?affected rows 为 0 说明已经被别人抢先了。数据库条件更新推荐不需要额外字段直接靠WHERE status在售来保证原子性public boolean trySold(Long carId, Long buyerId) { int rows carMapper.update(null, Wrappers.CarInfolambdaUpdate() .set(CarInfo::getStatus, CarStatus.SOLD.getCode()) .eq(CarInfo::getId, carId) .eq(CarInfo::getStatus, CarStatus.ON_SALE.getCode())); return rows 0; }这个UPDATE ... WHERE status在售在数据库层面是原子操作同一时刻只有一个请求能成功把状态从“在售”改成“已售”。如果rows 0说明车辆已经被买走或者下架了直接给用户提示“该车辆已被抢先下单”。这段代码我建议所有做交易类系统的同学都烂熟于心——这就是数据一致性在真实业务里最朴素的落地方式。4.4 交易订单创建的设计细节下单成功不代表订单直接“已成交”。二手车交易有线下看车和过户环节所以我设计的订单初始状态是“待确认”买家下单后卖家看到订单双方线下完成过户或交付后由任一方式在系统里确认成交订单才流转到“已成交”。这个设计的好处是避免了“线上付了钱车辆状态立刻已售但实际线下交易没成”的尴尬。真实二手车平台虽然流程更复杂还有定金、退款、违约但毕设做到“待确认 → 已成交/已取消”这个程度已经能把状态机的逻辑讲得很完整了。创建订单时同样要处理并发先尝试把车辆状态从“在售”改成“已售”成功后再插入订单。两次操作放到一个事务里配合条件更新就是一套完整的防超卖方案。我在答辩里是这么跟老师解释的“车辆表是库存表订单表是流水表库存扣减必须用条件更新保证唯一性流水插入和库存扣减必须在一个事务里保证原子性。”4.5 后台统计用 SQL 聚合给系统画上句号管理后台的数据统计是毕设加分项。ECharts 图表虽然看起来高端数据的来源其实很简单——一条 group by 的 SQL 而已SELECT DATE_FORMAT(create_time, %Y-%m) AS month, COUNT(*) AS order_count, SUM(car_price) AS total_amount FROM trade_order WHERE status 1 GROUP BY DATE_FORMAT(create_time, %Y-%m) ORDER BY month;这个查询统计每月成交订单数和成交总额。对应后端返回一个ListOrderStatVO前端用 ECharts 的折线图或柱状图渲染。同理车辆品牌分布可以用SELECT brand, COUNT(*) FROM car_info WHERE status2 GROUP BY brand做饼图。这些统计接口代码量不大但能让系统看起来完整、专业答辩时也有东西可指。5. 开发与答辩避坑这套系统里我最想分享的教训5.1 不要堆功能要深挖一两个核心点我见过太多同学做毕设败在“功能列表写得满满当当每个功能都是表面 CRUD”。二手车交易管理系统里与其做“车辆收藏”“短信通知”“在线聊天”这些功能不如把“车辆审核状态流转”“下单并发控制”“图片上传与回显”这几个点做深、做透。老师在答辩时问的通常不是你做了多少功能而是“这个功能你是怎么设计的为什么这么设计”。你如果能在任何一个点上把原理讲明白就已经赢过大多数人了。5.2 文件上传与图片回显毕设里最容易被本地路径坑到的地方我做这个系统时车辆图片上传用的是本地磁盘存储。上传接口把 MultipartFile 保存到服务器的某目录然后把“相对路径”存到数据库。结果列表页死活加载不出图片排查半天发现是因为我把D:/upload/xxx.jpg这种绝对路径直接写进了数据库浏览器根本没法访问。正确做法数据库只存相对路径如/images/2025/03/xxx.jpg再给 Spring Boot 加一个静态资源映射把本地目录映射成 URLConfiguration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath System.getProperty(user.dir) /upload/; registry.addResourceHandler(/images/**) .addResourceLocations(file: uploadPath); } }这样前端访问http://localhost:8080/images/2025/03/xxx.jpg就能命中本地文件。这个方案的好处是不依赖任何第三方存储服务毕设部署演示时也稳定。如果你有多余精力可以把图片改成 Base64 上传或接入云存储但本地映射方案在毕设阶段绝对够用而且能讲清楚原理。5.3 JWT 登录状态保存ThreadLocal 是你的好朋友登录这块我是用 JWT 做的。用户登录成功后签发 token前端存到 localStorage每次请求在 axios 拦截器里带上。后端用一个拦截器解析 token把解析出的用户 ID 放到线程上下问里public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { token token.substring(7); try { Long userId JwtUtil.parseToken(token); UserContext.setUserId(userId); return true; } catch (Exception e) { // 解析失败进入未授权流程 } } response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } }这里必须强调拦截器放行以后Controller 和 Service 要拿到当前用户 ID请用 ThreadLocal 工具类而不是再查一次数据库更不要从前端传 userId。前端传 userId 等于把数据权限的控制权交给了客户端任何用户都能伪装成别人操作数据这是答辩时老师最喜欢攻击的点。5.4 答辩高频问题提前做好准备根据我自己的答辩经历和帮别人模拟答辩的经验围绕这套系统老师最爱问的问题基本是固定的“车辆的状态有哪些状态之间如何流转”要能画出状态流转图草稿 → 待审核 → 在售 → 已售/下架待审核 → 审核驳回。每个状态之间的触发条件和操作角色要能讲清楚。“同一辆车同时被两个用户下单怎么办”就是我上面讲的WHERE status在售条件更新方案把原子性讲明白。“密码为什么用 BCrypt 加密”因为 BCrypt 自带盐值每次加密结果不同即使数据库泄露彩虹表也很难暴力破解。“你的事务加在哪一层为什么”答 Service 层讲清楚事务边界和回滚条件。“车源信息哪里来的”说实话就行是模拟数据或爬虫 Demo 数据不要虚构商业合作。5.5 演示脚本答辩前必须自己跑一遍这条建议朴素但极其重要答辩前至少完整跑通三遍主流程而且最好准备一份操作脚本。我的脚本顺序是管理员登录看到待审核车辆列表审核通过一辆车去前台看车辆状态变为在售注册一个新买家账号筛选车辆查看详情下单切回卖家账号看到新订单点击确认成交回到管理员后台查看统计数据确认成交量加一。写脚本的好处是你答辩时不会因为紧张而忘记操作步骤。我见过有同学演示到一半发现数据被自己之前测试搞乱了手忙脚乱现场删数据观感很差。提前把脚本里涉及的数据准备好现场只需要按部就班点下去。6. 最后一点个人建议这套系统做完我对“毕设到底应该怎么做”最大的体会是与其堆十个半成品功能不如把一个主链路做得滴水不漏。二手车交易管理系统的“车辆发布—审核—检索—下单—成交”这条链路上每一个环节都有值得深挖的业务细节和技术方案。你把它做透了既对得起自己半年的大四时间也能在答辩时坦然应对任何一个问题。如果你现在还在纠结技术栈我的建议是别犹豫太久。Spring Boot Vue开干就行。中途遇到问题多查多问代码有问题就调试数据有问题就查 SQL所有坑趟过去之后你会发现毕设这件事本身就是你进入职场前最好的一次系统化训练。