ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue+MyBatis二手车交易系统全栈开发实战

SpringBoot+Vue+MyBatis二手车交易系统全栈开发实战 刚接到一个二手车交易系统的开发需求时客户说得挺轻松就一个车辆展示加后台管理不难吧实际梳理下来才发现车辆信息发布、预约看车、订单交易、管理员审核、用户权限控制再加上车辆图片和状态流转管理零零散散几十个功能点。更麻烦的是车辆数据天生带有一对多关联——一辆车有多张图片、多条保养记录、多个询价记录这些关联关系如果处理得不干净后面排查数据问题会非常痛苦。这篇文章就是我做完这套基于SpringBootVueMyBatisMySQL的二手车交易系统之后把整个技术方案、核心表设计、接口实现思路、前端对接方式以及实际部署中踩过的坑完整梳理一遍。项目本身比较典型适合正在做毕设、做私活或者想学习全栈项目的读者参考尤其是对MyBatis关联查询和Vue组件通信不熟的朋友这篇能帮你少走不少弯路。1. 需求拆解一套二手车交易系统到底需要做什么做系统第一步永远不是写代码是把业务角色和流程理清楚。二手车交易跟普通电商不一样它牵扯到一个核心问题车辆的归属权和交易过程不是纯线上能闭环的买家必须实地看车、验车才能成交。所以系统不能做成简单的下单支付而是要围绕信息展示预约撮合来设计。1.1 角色划分是第一步这套系统我分了三个角色管理员、卖家车商或个人车主、买家注册用户。三个角色在系统中的权限和操作边界完全不一样划分清楚了后面设计权限和接口时才有据可依。管理员主要负责车辆信息审核、用户管理、订单跟踪、数据统计。卖家可以发布车辆、修改车辆状态、查看买家留言和预约记录。买家能浏览车辆、搜索筛选、预约看车、发起询价、收藏车辆。在SpringBoot端我用拦截器加注解的方式控制了接口权限没有引入Spring Security这么重的框架。因为项目角色只有三种权限逻辑不复杂用Spring Security反而增加学习和配置成本拦截器配合自定义注解已经足够干净。1.2 核心业务流程梳理业务流程是系统的骨架。我梳理出四条主流程第一条是车辆发布审核流程卖家提交车辆信息包括车况描述、价格、图片、手续信息- 管理员审核 - 审核通过后车辆上架展示。这个流程很关键二手车平台必须审核车辆信息不然很容易出现虚假车源所以我在车辆表里设计了一个status字段分别标记待审核、已上架、已下架、已售出四种状态。第二条是预约看车流程买家浏览车辆 - 预约看车选择时间- 卖家收到通知 - 线下看车成交。预约功能是二手车的刚需我在预约表里记录了预约时间、状态待确认、已确认、已完成、已取消卖家端有专门的预约管理页面。第三条是询价流程买家对某辆车感兴趣 - 发起询价留言 - 卖家查看并回复。这个功能技术上不复杂但需要跟车辆ID做关联查询前端还要在车辆详情页展示历史询价记录。第四条是管理员运营流程管理员管理所有车辆、用户、预约、询价数据同时需要能查看基本的统计信息比如车辆总数、上架车辆数、今日预约量。1.3 功能清单与工作量评估理完流程我列了一张功能清单模块功能点用户模块注册、登录、个人信息管理、身份选择卖家/买家车辆模块发布车辆、车辆列表、车辆详情、搜索筛选、车辆状态管理预约模块发起预约、预约列表、状态流转、取消预约询价模块发起询价、回复询价、历史记录管理后台审核车辆、用户管理、数据统计、全量订单查看前端页面首页、列表页、详情页、个人中心、管理后台这份清单看着不多真正做起来车辆模块最耗时因为牵扯图片上传、多图展示、条件组合筛选前后端都要重点设计。预约和询价模块逻辑相对简单前后端加起来一个模块大概一天半就能搞定。2. 技术选型SpringBootVueMyBatisMySQL这套组合的取舍理由选型这件事必须结合项目实际情况来谈。这套系统之所以选SpringBootVueMyBatisMySQL不是因为技术新颖而是因为这个组合在中小型管理系统里确实有实实在在的优势。我分别分析一下每个组件的选型理由。2.1 后端为什么锁定SpringBootSpringBoot在Java后端领域已经是事实上的标准选择对开发者来说最大的价值是约定大于配置。你不需要再像传统SSM整合那样写一堆XML配置文件一个启动类加几个注解就能把Web容器、数据源、事务管理全部拉起来。这个项目里我用了SpringBoot 2.7.x版本依赖管理用Maven。实际开发中最爽的是starter机制做Web接口就引入spring-boot-starter-web操作数据库就引入MyBatis的starter分页就用PageHelper根本不需要手动管理版本兼容问题。另外SpringBoot内置的Tomcat让打好的Jar包能直接java -jar运行部署环节省了很多事。如果用了Spring Boot 3.x需要注意javax.servlet迁移到jakarta.servlet的问题很多老代码贴过来会报错需要适配。做私活和毕设建议用稳定的2.7.x网上资料多任何报错基本都能搜到解决方案。2.2 持久层为什么用MyBatis而非JPA二手车交易系统的数据查询有个明显特点复杂动态SQL多。车辆列表页需要支持品牌、价格区间、车龄、里程数、变速箱类型等多种条件的组合筛选而且筛选条件是不确定的——用户可能只选品牌也可能同时选品牌加价格区间加车龄。MyBatis对这种动态SQL的支持非常灵活if标签就能轻松拼接条件SQL语句完全由自己掌控优化起来心里有底。相比之下JPA虽然写CRUD很爽但面对动态多条件查询时要么写复杂的Specification要么直接用Query写JPQL调试成本反而更高。另外MyBatis对一对一、一对多关联查询的控制粒度也精细比如查询车辆详情时同时查车辆图片列表、卖家信息写一个resultMap就能映射得明明白白这点在后文会详细展开。2.3 前端用Vue的理由Vue在中小型管理系统里的统治力不容质疑。它的响应式数据绑定、组件化开发、渐进式引入方式非常适合这种页面多但每个页面交互不算极复杂的后台管理系统。我用的是Vue 2 Element UI的组合说实话这套组合在2025年已经显得有点老但胜在稳定、文档全、问题好查。如果你现在从零开始学我会建议直接上Vue 3 Element Plus组合更新、组合式API写起来也更顺手。我之所以用Vue 2是因为项目里需要对接一份已经写好的前端模板基于Vue 2的生态做的重新改造工量太大。这个取舍需要根据你手里的资源决定。2.4 MySQL是稳妥且够用的选择MySQL作为存储层在数据量几百上千万以内的业务场景下完全够用。二手车交易系统数据量最大的也就是车辆表和用户行为记录表单表数据量到不了千万级别MySQL配合合理的索引设计查询性能根本不是瓶颈。数据库这块我用的是MySQL 5.7版本稳定成熟部署资料丰富。如果你服务器是新装的装MySQL 8.0也没问题注意驱动依赖用mysql-connector-java对应的8.x版本就行。这套组合还有一个隐形优势招聘市场上会这套技术栈的人最多客户后续找人维护也容易这个因素在做私活时其实挺关键。客户不关心技术多新关心的是将来出了问题有没有人能接盘。3. 数据库设计从业务需求到落表的关键决策数据库设计直接决定系统的扩展性和查询效率。二手车交易系统的表结构不算复杂但有几个设计决策值得说清楚这些决策直接影响代码实现和工作量。3.1 核心表结构说明我设计了七张核心业务表外加用户角色关联等辅助表整体结构如下表名用途关键字段user用户表id, username, password, phone, role(1买家/2卖家/3管理员), avatarcar车辆表id, seller_id, brand, model, price, mileage, car_age, gearbox, color, description, status, create_timecar_image车辆图片表id, car_id, image_url, sort_orderappointment预约看车表id, car_id, user_id, appoint_time, status, remarkinquiry询价表id, car_id, user_id, content, reply, create_timecollection收藏表id, car_id, user_id, create_timeadmin_log操作日志表id, admin_id, operation, detail, create_time车辆的详细参数信息比如排放标准、过户次数、是否有事故记录我单独建了car_detail表用car_id一对一关联。基本表加上扩展表的好处是列表页查询不会携带大量不需要的文本字段SQL查询性能更好。3.2 关联关系的落表思路车辆图片和车辆之间是一对多关系我用car_image表存储每一行记录一张图片地址通过car_id关联车辆。这样设计的好处是车辆图数量不固定有的车三张图有的车八张图一对多表能灵活适配。查询车辆详情时用一条SQL把车辆主信息和图片列表一次性查出来代码里配置一个collection节点即可映射成List。预约、询价、收藏这三个表都是标准的业务关联表跟车辆和用户都有关联。设计时我统一加了create_time字段后面做我发出的预约我收到的询价这类列表查询时直接按时间排序即可。用户的角色字段我直接冗余在user表中用Integer类型存储1代表买家、2代表卖家、3代表管理员。没有单独建角色表因为角色只有三种且权限固定建表反而过度设计。3.3 索引设计与查询性能考虑车辆列表页是最常被访问的页面筛选查询必然用到。我的索引设计思路是在car表的brand、price、status上建了组合索引具体SQL是ALTER TABLE car ADD INDEX idx_brand_price_status (brand, price, status)。这样前端按品牌价格区间筛选、按状态过滤时MySQL都能走索引加速。另外user表的phone字段要建唯一索引一是防止重复注册二是支持手机号登录时快速查询。car_image表的car_id、appointment表的car_id等外键关联字段也都建了索引避免关联查询时全表扫描。这里有一个实际开发中的体会索引不是越多越好每个索引都会增加写入开销和磁盘占用。我是等项目核心功能开发到一半、确认了高频查询场景后才统一加的索引而不是建表时就一次加满。3.4 SQL文件的初始化与管理数据库表结构我统一放在项目根目录的sql目录下用init.sql文件保存文件顶部注释写明建库语句。项目交付时客户只需要在MySQL里执行一遍init.sql就能把整个库结构拉起来。这个习惯一定要保持。很多开发者喜欢用Navicat直接建表然后导出SQL但导出的SQL里经常带一些多余的字符集属性可读性差。手工维护一份干净的表结构SQL不仅交付时方便项目上生产环境时也能保证建表语句完全可控。4. 后端核心实现SpringBootMyBatis的业务开发细节后端这块是整个系统的中枢从项目结构划分到车辆发布流程、多条件查询、关联映射每一步都有不少值得展开的细节。我做项目时习惯先把分包结构定好再逐个模块纵向开发这样思路清晰不会写到后面自己都迷路。4.1 项目分包结构与初始化后端工程我按标准的三层架构分包controller、service、mapper另外加了entity、dto、common三个辅助包。entity放数据库实体类dto放接口传输对象common放统一返回结果、异常处理、工具类。项目初始化的核心依赖如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.6/version /dependency配置文件application.yml中核心配置如下spring: datasource: url: jdbc:mysql://localhost:3306/car_trade?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.cartrade.entity configuration: map-underscore-to-camel-case: true这里有两个值得强调的配置。第一个是map-underscore-to-camel-case它能把数据库的snake_case字段自动映射成Java的驼峰属性比如数据库的create_time能直接映射到实体类的createTime省掉大量手动映射配置。第二个是url参数里的serverTimezone必须设置否则连接MySQL 8.x时会报时区错误。4.2 车辆发布功能多图上传与数据落库车辆发布是卖家端最复杂的操作。前端表单包含车辆基础信息、车况描述、车辆图片上传最多八张提交时需要一个接口同时处理主信息和图片列表。我的实现思路是前端一次性提交整个表单对象图片上传单独走一个文件上传接口先把图片传到服务器并返回图片URL列表然后组装到表单数据里一起发到后端。车辆主信息插入car表拿到自增主键后再循环插入car_image表。后端发布接口的核心逻辑Transactional public Long addCar(CarAddDTO dto) { Car car new Car(); BeanUtils.copyProperties(dto, car); car.setStatus(0); // 待审核 carMapper.insertCar(car); Long carId car.getId(); if (dto.getImages() ! null !dto.getImages().isEmpty()) { for (String imageUrl : dto.getImages()) { carImageMapper.insert(carId, imageUrl); } } return carId; }这里必须加Transactional事务注解否则可能出现车辆主信息插入成功、图片插入失败的情况造成数据不一致。车辆状态初始为0待审核管理员审核通过后才会展示在前台。上传图片这块我用的是本地文件存储方案在服务器上建一个/upload目录存放图片nginx做静态资源映射提供图片访问。如果客户有对象存储服务阿里云OSS、腾讯云COS替换成云存储SDK即可。小项目不建议一上来就上OSS本地存储加Nginx足够省成本也省配置精力。4.3 多条件车辆检索MyBatis动态SQL的艺术车辆列表页的搜索筛选是二手车系统最具技术含量的查询场景。用户可选的条件组合非常多品牌、价格区间、车龄、里程数、变速箱、出售状态。写死SQL是不可能的必须用MyBatis的动态SQL按需拼接查询条件。Mapper XML中的核心查询语句select idsearchCars parameterTypemap resultTypeCar SELECT * FROM car WHERE status 1 if testbrand ! null and brand ! AND brand #{brand} /if if testminPrice ! null AND price gt; #{minPrice} /if if testmaxPrice ! null AND price lt; #{maxPrice} /if if testcarAge ! null AND car_age lt; #{carAge} /if if testmileage ! null AND mileage lt; #{mileage} /if if testgearbox ! null and gearbox ! AND gearbox #{gearbox} /if ORDER BY create_time DESC /select这个写法的精妙之处在于每个if标签独立判断前端传哪些条件就拼接哪些条件不传就自动忽略。实际开发中记得用gt;和lt;转义大于等于和小于等于不然XML解析器会直接报错。PageHelper分页使用是另一个重点记忆点。在Service层查询数据库之前调用PageHelper.startPage(pageNum, pageSize)接着执行Mapper查询查询结果就会自动被分页返回时用PageInfo封装即可携带总记录数PageHelper.startPage(pageNum, pageSize); ListCar cars carMapper.searchCars(params); PageInfoCar pageInfo new PageInfo(cars);需要注意PageHelper的分页参数是线程绑定的startPage调用后必须紧跟一条Mapper查询中间不要穿插其他耗时操作否则分页会不生效甚至把SQL搞乱。4.4 车辆详情关联查询resultMap配置一对多映射车辆详情页要一次性展示车辆主信息、卖家信息、全部图片列表。如果用循环查询每查一辆车就要执行多条SQL性能差且代码冗余。MyBatis的resultMap可以解决这个问题。我在CarMapper.xml里配置了关联映射resultMap idCarDetailMap typeCar id propertyid columnid/ result propertysellerId columnseller_id/ result propertybrand columnbrand/ result propertymodel columnmodel/ result propertyprice columnprice/ result propertystatus columnstatus/ association propertyseller javaTypeUser id propertyid columnseller_id/ result propertyusername columnseller_name/ result propertyphone columnseller_phone/ /association collection propertyimageList ofTypeCarImage id propertyid columnimg_id/ result propertyimageUrl columnimage_url/ /collection /resultMap select idgetCarDetail resultMapCarDetailMap SELECT c.*, u.username AS seller_name, u.phone AS seller_phone, ci.id AS img_id, ci.image_url FROM car c LEFT JOIN user u ON c.seller_id u.id LEFT JOIN car_image ci ON c.id ci.car_id WHERE c.id #{id} /select关联查询用了两个LEFT JOIN把车、卖家、图片三个表的数据一次拉出来。resultMap中的association处理一对一关系车辆对卖家collection处理一对多关系车辆对图片列表。MyBatis会自动根据id字段把结果集拆分成一个Car对象并装配好内部的Seller和ImageList非常方便。性能上这种连表查询在单条详情页访问场景下完全没有问题比循环查询少了几倍的数据库往返代码也更简洁。4.5 预约与询价模块接口设计要点预约看车设计了两组接口买家端发起预约、取消预约、查看自己的预约列表卖家端查看收到的预约、修改预约状态。这里有三个状态字段0待确认、1已确认、2已完成、3已取消。游客无法发起预约必须登录这个校验放在前端路由守卫和后端拦截器双重保障。询价模块跟预约类似买家提交询价内容卖家回复。询价列表需要按车辆ID过滤同时要查出车辆信息一起展示给卖家看方便卖家知道是哪辆车被询价了。我在InquiryMapper里也配置了一个association映射直接用LEFT JOIN把车辆数据关联过来避免了卖家端再拿carId去单独查车辆。这两个模块是典型的CRUD但有一个需要注意的业务细节预约和询价数据量虽然不大但都要支持按时间倒序分页展示和按状态筛选。所以设计Mapper时我都在SQL中保留了status和create_time的查询条件controller层接参时直接透传。5. Vue前端页面结构、组件通信与接口联调前端这部分如果只是会写组件远远不够更关键的是搞清楚页面结构、路由守卫、状态管理、接口封装这些辅助工程问题。这套二手车系统前端页面大概有十多个把基础设施写好页面开发就是体力活。5.1 前端工程结构与路由设计前端工程用Vue CLI创建目录结构大概如下src/ ├── api/ # 接口请求模块 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── router/ # 路由配置 ├── store/ # Vuex状态管理 ├── views/ # 页面组件 │ ├── home/ # 首页 │ ├── car/ # 车辆列表/详情/发布 │ ├── order/ # 预约管理 │ ├── user/ # 个人中心 │ └── admin/ # 管理后台 └── main.js路由设计上前端路由分两部分前台用户路由和管理后台路由。后台路由统一放在/admin路径下。每个路由配置了meta信息比如meta: { requiresAuth: true, role: 3 }表示需要登录且管理员角色才能访问。路由守卫统一在router/index.js中处理router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); } else if (to.meta.role) { const role localStorage.getItem(role); if (Number(role) ! to.meta.role) { next(/403); } else { next(); } } else { next(); } });这个守卫虽然简单但能把未登录用户拦在需要认证的页面外面把非管理员拦在后台外面。后端接口同样有权限校验前端守卫只是用户体验层。5.2 车辆列表页筛选组件与数据加载车辆列表页是核心页面界面布局是左侧筛选栏加右侧车辆卡片列表。筛选栏包含品牌下拉框、价格区间两个输入框、车龄下拉框、变速箱单选框。因为Element UI的表单组件本身支持v-model绑定我直接在data里定义searchForm对象data() { return { searchForm: { brand: , minPrice: , maxPrice: , carAge: , gearbox: }, carList: [], total: 0, pageNum: 1, pageSize: 8 }; }点击搜索按钮时把searchForm和分页参数一起传给API方法API内部用axios的GET请求带params发送export function searchCars(params) { return request({ url: /api/car/search, method: get, params }); }axios请求封装比较关键我在api/request.js里统一配置了baseURL、超时时间、请求拦截器加token、响应拦截器统一处理错误码。响应拦截器里当后端返回401时自动跳转登录页403时跳转403页面。这套封装看起来简单但能避免每个页面都写重复的错误处理逻辑。5.3 车辆详情页图片画廊与信息展示车辆详情页的布局是左侧大图加缩略图列表右侧车辆关键信息和操作按钮。图片画廊我直接用Element UI的el-carousel组件实现传入车辆图片URL数组效果就是轮播图展示。操作区域放了预约看车发送询价收藏车辆三个按钮。点预约看车弹出el-dialog对话框选择预约时间提交后调用预约接口。点发送询价同样弹对话框填写询价内容提交。这里的关键是dialog内部表单的校验我用的是Element UI自带的rules规则比如预约时间必填、询价内容最小长度5个字符。收藏功能是切换按钮状态收藏过就是实心图标没收藏就是空心图标。点击时调用收藏或取消收藏接口这个功能需要实时更新UI状态我在按钮点击逻辑里处理toggleCollect() { if (!this.isCollected) { addCollection({ carId: this.carId }).then(() { this.isCollected true; this.$message.success(收藏成功); }); } else { removeCollection({ carId: this.carId }).then(() { this.isCollected false; this.$message.success(已取消收藏); }); } }5.4 卖家发布车辆表单验证与图片上传发布车辆页面的表单字段比较多我分了几个分组基本信息品牌、型号、价格、车况信息车龄、里程、变速箱、描述、图片上传。表单校验用Element UI的rules重点校验必填字段和价格格式必须为正数精确到小数点后两位。图片上传组件我用el-upload的自动上传模式action指向后端的文件上传接口同时带了token请求头。上传成功后在回调里拿到返回的图片URLpush到图片列表数组。编辑车辆时可以展示已有的图片列表删掉的图片调用删除接口。发布页面还需要一个富文本描述区域二手车车况描述可能比较长我用了el-input的textarea类型加上了字数统计和最大长度限制。这个字段对应数据库的description字段注意MyBatis插入时要设置jdbcType否则有些情况下长文本可能被截断。5.5 组件复用与通用方法抽取开发多个页面后发现车辆卡片在首页、列表页、收藏页三个地方都有用到。我抽了一个CarCard全局组件接收car对象作为prop内部展示车辆主图和关键信息点击跳转到详情页。全局注册在main.js里完成import CarCard from /components/CarCard.vue; Vue.component(CarCard, CarCard);同理分页器是一个高频组件我的做法是封装一个PaginationWrapper内部集成el-pagination用props接收total和pageNum在page-change事件中emit给父组件。这样所有列表页面只需要引入这个组件并监听事件即可不用重复写分页逻辑。6. 项目部署与上线从本地调试到服务器运行的完整过程项目开发完成后部署环节同样藏着不少坑。我把自己实际部署这套系统时走过的流程和遇到的问题整理一下这部分的经验价值比代码本身更值得记录。6.1 前端打包与后端构建前端打包运行命令npm run build打包产物在dist目录下。由于前端通过axios请求/api路径访问后端而部署时前端静态资源和后端接口分别跑在不同端口需要配置nginx做代理。后端构建需要先确保application.yml中的数据库连接和生产环境一致然后执行Maven打包mvn clean package -DskipTests若想用外部配置文件覆盖jar包内的配置启动时加参数java -jar car-trade.jar --spring.profiles.activeprod生产环境我单独建了application-prod.yml数据库地址、密码等与开发环境隔离。这个习惯一定要养成避免不小心把测试库数据覆盖了。6.2 Nginx配置与反向代理我在云服务器上用Nginx托管前端dist目录并配置了反向代理转发/api请求到后端的8080端口server { listen 80; server_name car.example.com; root /usr/share/nginx/html; index 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; } location /upload/ { alias /usr/share/nginx/upload/; } location / { try_files $uri $uri/ /index.html; } }try_files这行配置是关键它保证前端路由在刷新页面时能正确回退到index.html否则部署Vue Router的history模式后刷新非首页路径会直接404。如果不想碰这个坑也可以在创建Router时直接改成hash模式地址栏会带个#号观感稍差但配置量更小。6.3 部署中遇到的真实坑与排查过程这轮部署我踩了几个比较典型的坑记录如下。第一个坑是MySQL 8.x的用户认证插件问题后端连数据库时报Public Key Retrieval is not allowed错误。原因是最新MySQL默认使用caching_sha2_password认证插件JDBC驱动连接时不允许自动获取公钥。解决办法是在JDBC URL后面加allowPublicKeyRetrievaltrue参数或者在建用户时指定mysql_native_password认证插件。第二个坑是前端打包后图片资源404排查发现是因为后端上传的图片存在了本地的绝对路径下而Nginx的alias配置指向的目录跟上传目录不一致。最终把上传目录统一在application.yml里配置成/usr/share/nginx/upload保证前端通过/upload前缀能访问到同时注意文件存储时不要存绝对路径存相对路径组装URL时用服务器域名拼接。第三个坑是跨域问题。开发模式下Vue跑在8080端口后端跑在8081端口跨域是必然的。我的做法是在后端实现统一CORS配置类允许所有来源跨域开发环境。上线后同域部署后就没有跨域问题但如果前后端分别部署在不同域名CORS配置就必须保留。实际部署时我更推荐用Nginx反向代理同一个域名的不同路径省去跨域麻烦。6.4 系统优化与后续扩展思路这套系统跑起来之后有几处性能优化的空间值得考虑。比如车辆列表页每次请求都全量加载字段如果车辆数据上了千条可以考虑用覆盖索引优化查询或者只select列表页需要的字段详情数据进入详情页再加载。车辆图片目前存在本地磁盘如果图片量持续增长建议迁到云存储节省服务器带宽并提升访问速度。这需要改一下上传组件的后端实现把OutputStream改成调用云SDK上传即可接口返回的URL从云存储域名拼接。如果客户需要在线交易闭环直接线上支付定金需要接入第三方支付接口并在订单表中增加支付流水号、支付状态字段。这块涉及支付资质和接口对接成本做私活时尽量先确认客户是否真的需要。7. 从开发到交付的经验反思与避坑总结项目从接手到交付上线前后大概用了三周时间。技术实现之外我总结了一些项目管理层面的经验这些比代码细节更值得分享。首先是需求边界必须一开始就谈清楚。二手车交易系统范围可大可小客户说做个车辆展示加预约听起来简单但实际开发中他会不断加需求加个收藏功能吧、加个热门车型推荐吧、加个后台数据导出吧。这些增量需求如果不在合同范围里说明白很容易变成免费加班。我这次是把功能清单列出后让客户确认签字后续新增需求单独报价沟通效率明显提高。其次是数据库设计不要贪多求全开始只建核心表随着功能迭代增量加字段。我第一版设计车辆表时考虑了非常多的扩展字段后来发现一半用不上反而让代码里实体类非常臃肿。后来精简表结构只保留真正会用的字段代码简洁了很多。面试和答辩时面试官更关注你对核心表之间关系的理解和字段设计的合理性而不是字段数量多不多。前端权限控制要跟后端接口权限联动。前端路由守卫只是看起来安全真正的数据安全在后端。我用拦截器校验每个请求的token和用户角色白名单放行了登录注册、车辆列表、车辆详情等公开接口其余接口全部要求登录。这样即使有人绕过前端直接调用后端接口没有合法token也拿不到数据。分页插件PageHelper使用时要注意嵌套查询的情况。如果一个查询中包含了子查询或者多表联查PageHelper会尝试自动COUNT但如果SQL中存在GROUP BY等聚合操作COUNT语句可能会出错。保险做法是关键查询手动写好COUNT语句或者在SQL中避免使用复杂聚合。我在统计后台数据时遇到过一次最终改成手动COUNT解决了。最后是日志的重要性。后端我使用了Logback作为日志框架在车辆发布、审核、预约成功等关键操作处打了日志记录了请求参数和操作结果。项目上线后排查问题全靠这些日志没有日志的开发就像蒙着眼走路。特别是权限相关的拦截器我每次拦截都打印了被拦截的请求路径和用户信息方便分析恶意请求和误拦截情况。这套系统的完整代码量大概一万多行结构上就是一个标准的SpringBootVue全栈项目难度中等。如果你正在准备毕业设计或者接私活按照我上面梳理的步骤一步步做下来每天投入几小时预计两到三周能完成一个功能完整、能上线的版本。做完之后对SpringBoot的接口开发、MyBatis的动态SQL和关联映射、Vue的组件通信和路由守卫都会有一个整体的掌握这个项目经历放在简历上也是实打实的加分项。
返回列表