news 2026/9/28 5:32:03

二手车交易系统实战:SpringBoot+Vue+MyBatis+MySQL

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
二手车交易系统实战:SpringBoot+Vue+MyBatis+MySQL

做一个二手车交易系统,其实比想象中有意思。这个项目前后端分离、带完整管理后台,涉及了 SpringBoot + Vue + MyBatis + MySQL 这套国内中小型项目最常用的技术组合,既能作为毕业设计、面试项目,也能直接改改用在真实业务里。废话不多说,直接把这套系统的设计思路、核心功能实现、踩坑记录从头到尾拆一遍。

1. 项目整体设计与技术选型思路

1.1 为什么是 SpringBoot + Vue + MyBatis + MySQL

二手车交易系统这种业务,本质上就是典型的信息管理系统加电商交易逻辑的组合。用户要浏览车辆、发布车辆、预约看车、下单交易,管理员要审核车辆、管理订单、统计数据。业务不算复杂,但涉及的模块不少,而且需要快速开发、快速上线。

选 SpringBoot 做后端,是因为它确实是当前 Java 后端最主流的选择,没有之一。内嵌 Tomcat、自动配置、起步依赖,把以前 Spring 那一大堆 XML 配置全干掉,一个 jar 包就能跑起来。对个人开发者和中小团队来说,开发效率差距非常明显。Vue 做前端,则是前后端分离架构下最稳妥的选择,组件化开发、响应式数据绑定、路由懒加载,写二手车列表筛选这种交互频繁的页面非常顺手。

MyBatis 在这个项目里的定位要单独说。很多人纠结 JPA 和 MyBatis 怎么选,我的经验是:二手车交易系统这种业务,SQL 大多是多表联查、条件筛选、动态更新,MyBatis 的动态 SQL 处理这种场景真是天生契合。比如车辆列表要按价格区间、品牌、车龄、排放标准组合筛选,用 XML 里的<if>标签拼条件,比 JPA 的 Specification 直观得多,排查问题也方便。MySQL 就不用说了,免费、稳定、生态成熟,支撑这种量级的项目绰绰有余。

这套组合还有一个隐性的好处:招人容易、资料好查。无论你以后要在这个项目上做二次开发,还是把它写进简历,SpringBoot + Vue + MyBatis 这套组合的社区资料量都是最大的,遇到问题基本都能搜到解决方案。

1.2 项目模块拆解:用户、卖家、管理员三个视角

开发之前一定要把角色和模块理清楚,不然写着写着一锅粥。二手车交易系统按角色可以拆成三个端,但代码上是同一个系统,通过登录态区分权限。

用户端是买车的人使用的主要界面,功能包括:注册登录、浏览在售车辆、按品牌/价格/车龄/排量筛选车辆、查看车辆详情(包括车况描述、里程、上牌时间、图片)、收藏车辆、在线预约看车、发起交易订单、查看订单进度。这个端最看重浏览体验,所以列表页的筛选交互和详情页的信息展示是重头戏。

卖家端面向的是车商和个人卖家,主要功能:发布车辆(填写车辆信息 + 上传图片)、管理自己发布的车辆(上下架、修改价格、查看被预约记录)、处理订单(确认成交、关闭订单)。需要注意的一点是,真实业务里卖家发布车辆通常要经过管理员审核,防止有人乱发信息,所以设计时我加了审核状态字段,发布后默认“待审核”,审核通过才会上架展示。

管理后台则是系统管理员用的,负责用户管理(封禁/解封)、车辆审核(通过/驳回)、订单管理、数据统计(每日新增车辆数、成交订单数、成交总额)。这个端的核心是效率和审核流,所以表格要支持分页、搜索、状态筛选。

这三个视角理清楚之后,整个项目的边界就清晰了,后面写代码、建表都不会乱。

1.3 项目结构:前后端分离的目录规划

后端我用的是标准的 Maven 多模块单工程结构,包名按职责划分,不搞花活:

com.example.carbiz ├── controller // 接口层,只做参数接收和结果返回 ├── service // 业务逻辑层,核心逻辑都在这里 │ └── impl ├── mapper // MyBatis 的 Mapper 接口 ├── entity // 数据库实体类 ├── dto // 前端交互的数据传输对象 ├── vo // 视图对象,比如列表页的聚合数据 ├── config // 配置类,拦截器、跨域、静态资源映射等 ├── common // 统一返回结果、异常处理、工具类 └── utils // JWT、日期处理等工具

前端用 Vue CLI 或 Vite 初始化工程,目录结构一般是:

src ├── api // 接口请求封装,按模块拆分文件 ├── assets // 静态资源 ├── components // 通用组件,比如车辆卡片、筛选栏 ├── router // 路由配置,含懒加载和守卫 ├── store // 全局状态,登录用户信息、购物车等 ├── views // 页面视图 │ ├── user // 用户端页面 │ ├── seller // 卖家端页面 │ └── admin // 管理后台页面 └── utils // 工具函数、axios 封装

这种结构的好处是职责清晰,前端页面按角色分目录之后,维护哪个端的功能就进哪个目录,不会迷路。

2. 数据库设计与核心表结构

2.1 核心表设计:从用户到订单的链路

数据库是整个系统的地基,表设计得好不好,直接决定后面写 SQL 的时候是享受还是折磨。我当时设计的主要表有七张:用户表、车辆表、品牌表、收藏表、预约表、订单表、轮播图表。

用户表不用多说,字段包括用户名、密码、手机号、角色(user/seller/admin)、头像、状态。密码一定要存密文,我用的是 BCrypt 加密,后面讲登录的时候细说。

车辆表是核心中的核心,字段比较多,我列出来:

字段类型说明
idbigint主键
seller_idbigint卖家用户ID
brand_idbigint品牌ID
titlevarchar车辆标题
pricedecimal(10,2)价格,单位万元或元,展示时统一处理
original_pricedecimal(10,2)新车指导价,用于展示折价信息
mileagedecimal(10,2)里程,单位万公里
first_reg_datedate首次上牌日期
gearboxtinyint变速箱类型:1自动 2手动
emissionvarchar排放标准,如国六
colorvarchar外观颜色
descriptiontext车况描述
cover_imagevarchar封面图URL
statustinyint0待审核 1在售 2已下架 3已售出
view_countint浏览量,详情页每次访问加一
created_timedatetime发布时间

收藏表就是 user_id + car_id 的组合,唯一索引防止重复收藏。预约表记录用户对哪辆车发起了哪天的看车预约,包含状态字段(待确认、已确认、已完成、已取消)。

订单表记录交易信息,字段包括订单号、买卖双方ID、车辆ID、成交价、状态(待付款、已付款、已完成、已取消)、创建时间、支付时间。二手车交易流程比电商简单,没有太复杂的售后链路,但订单状态流转还是要画清楚。

2.2 索引设计与状态字段的细节处理

数据库设计最容易忽略的就是索引。二手车系统最常见的查询场景有两个:车辆列表筛选、车辆详情。列表筛选的条件组合千变万化,不可能针对所有组合建索引,我的策略是:状态字段(status)必须建索引,因为所有列表查询都带 status 条件;品牌和价格字段建联合索引,因为筛选时这两个条件出现频率最高;时间字段建索引,因为后台要按时间统计。

还有一个小细节:价格字段我用的是 decimal(10,2),金额相关的字段绝对不能用什么 float 或 double,因为浮点数会有精度丢失问题,一斤大米的钱都能给你算错。MySQL 里金额就是 decimal 是最稳的。

另一个经验是状态字段的注释要写清楚,比如 status 的 0/1/2/3 分别代表什么,一定在建表语句里写好COMMENT。这项目要是放个三个月再回头看,没有注释的表结构就是天书。

2.3 分页插件与 MyBatis 的集成配置

车辆列表必须分页,不然几百辆车一次查出来,前端渲染也扛不住。我用的是 PageHelper,这是 MyBatis 最常用的分页插件,用法极其简单。先加依赖:

<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.7</version> </dependency>

然后在 Service 里查列表之前,直接调用PageHelper.startPage(pageNum, pageSize),后面紧跟的第一条 MyBatis 查询语句会自动带上 LIMIT:

public PageResult<CarVO> queryCarList(CarQueryDTO queryDTO) { PageHelper.startPage(queryDTO.getPageNum(), queryDTO.getPageSize()); List<CarVO> carList = carMapper.selectCarListByCondition(queryDTO); PageInfo<CarVO> pageInfo = new PageInfo<>(carList); return new PageResult<>(pageInfo.getTotal(), pageInfo.getList()); }

注意一个坑:PageHelper.startPage只对紧接着的第一条 SQL 生效。如果你在查询前做了别的 SQL 操作,分页就会被污染。还有,用PageHelper返回的List实际是Page对象,你可以在 Service 层直接拿到 total,也可以像上面这样转成PageInfo统一处理。我在项目里封装了一个PageResult,包含 total 和 list 两个字段,前端拿到直接渲染表格。

分页插件还有一个面试常问的机制:PageHelper 的物理分页底层其实是拦截器,它会在执行 SQL 之前拦截,把原来的 SQL 解析后重新拼上LIMIT参数。理解了这一点,你就明白为什么它只对第一条查询生效,也就能应对那种“查询结果正常但 total 不对”的诡异问题了。

3. 后端核心功能实现

3.1 登录鉴权:JWT + 拦截器 + 角色权限

二手车系统里,用户端、卖家端、管理后台共用一套登录逻辑,核心原理就是 JWT。用户登录成功后,后端生成一个 token 返回给前端,前端把 token 存到 localStorage,之后每次请求在请求头加上Authorization: Bearer token,后端通过拦截器校验 token,识别出当前用户是谁、是什么角色。

JWT 我推荐用jjwt库,版本 0.11.5 左右的 API 比较稳定,代码写法如下:

// 生成 token,有效期 7 天 String token = Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", user.getRole()) .setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000)) .signWith(secretKey) .compact();

解析的代码也简单,就是Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody(),拿到 Claims 后提取 userId 和 role。密钥的话我建议放在配置文件里,不要硬编码在代码中。

拦截器做成一个 HandlerInterceptor,核心逻辑是:放行登录、注册、首页轮播、车辆列表这些公开接口,剩下所有接口都校验 token,校验通过就把 userId 放到 request 的 attribute 里,后续 Controller 直接取。角色权限用注解 + 拦截器的方式处理,在方法上加@RequireRole("admin"),拦截器里校验当前用户角色,不匹配就返回 403。

密码处理必须强调一下,用 Spring Security Crypto 库里的 BCryptPasswordEncoder,不要自己搞什么 MD5 加盐,MD5 加盐虽然比纯 MD5 好一点,但 BCrypt 是专门为密码哈希设计的,自带随机盐且抗暴力破解。注册和登录时的密码校验都用这个工具类。

3.2 车辆发布与图片上传的处理

车辆发布是卖家端的核心功能,涉及数据和图片两块。数据这块无非是接收表单参数,封装成实体后插入数据库。真正麻烦的是图片上传。

图片上传我用的是本地存储方案,配置一个虚拟路径映射,把上传目录和访问 URL 关联起来。在配置文件里加:

upload: path: /data/carbiz/images/

然后写一个WebMvcConfigurer把本地的图片目录映射到/images/**这个 URL 上,这样前端就能直接通过 URL 访问图片。生产环境建议用 OSS 或者 MinIO,个人项目本地存储也够了。

上传接口接收 MultipartFile,处理流程是:校验文件类型(只允许 jpg、png、webp)、校验文件大小(限制单张 5MB)、生成 UUID 文件名防止重名、保存到指定目录、返回访问 URL。这里有一个小技巧:图片处理时尽量压缩一下,一张 5MB 的图片放到车辆详情页,加载速度会让你怀疑人生。用 Java 的 ImageIO 读进来再缩放输出,可以控制在几百 KB。

另外上传接口必须做类型校验,只检查后缀名是没用的,有些人会把木马改成 jpg 后缀上传。稳妥的做法是用工具读取文件的真实类型,比如 Apache Tika 或者直接看文件头的 magic number。这个安全细节在很多项目里被忽视了,但真出了问题就是大事。

3.3 MyBatis 动态 SQL:多条件组合筛选的解法

车辆列表页的筛选条件,是这套系统里 SQL 复杂度最高的地方。用户可能选择品牌 + 价格区间 + 车龄范围 + 变速箱类型,也可能什么条件都不选。用动态 SQL 是最好的解法,Mapper XML 里这样写:

<select id="selectCarListByCondition" resultType="com.example.carbiz.vo.CarVO"> SELECT c.*, b.name AS brand_name FROM car c LEFT JOIN brand b ON c.brand_id = b.id <where> <if test="status != null"> AND c.status = #{status} </if> <if test="brandId != null"> AND c.brand_id = #{brandId} </if> <if test="minPrice != null"> AND c.price &gt;= #{minPrice} </if> <if test="maxPrice != null"> AND c.price &lt;= #{maxPrice} </if> <if test="minMileage != null"> AND c.mileage &gt;= #{minMileage} </if> <if test="gearbox != null"> AND c.gearbox = #{gearbox} </if> </where> ORDER BY c.created_time DESC </select>

<where>标签会自动处理掉第一个条件前面多余的 AND,这是 MyBatis 非常贴心的设计。需要注意&lt;和&gt;是 XML 里的转义符,如果 SQL 里有比较运算符,不能直接写<号。

动态 SQL 还有一个常用的场景是动态更新,卖家修改车辆信息时,前端只会提交有值变化的字段。用<set>标签搭配<if>就能实现只更新非空字段,避免把所有字段都覆盖一遍。

3.4 全局异常处理、XSS 防护与过滤器

这部分属于“看起来不重要,但生产环境必不可少”的工程化能力。

全局异常处理核心是用@RestControllerAdvice+@ExceptionHandler,统一捕获业务异常、参数校验异常、未知异常,返回统一格式的 JSON。我通常会定义自己的BusinessException,业务逻辑里手动抛出,前端能拿到明确的错误信息。未知异常在服务端打印完整堆栈,但返回给前端的只是“系统繁忙,请稍后重试”,避免把数据库信息泄露出去。

XSS 防护是很多个人项目忽略但必须做的事。二手车系统里有车辆描述、卖家留言这些用户可以输入富文本的字段,如果不过滤,恶意用户直接往里面塞一段<script>,别的用户打开详情页脚本就执行了,这就是存储型 XSS。基本的防护手段是写一个全局过滤器,拦截所有 POST/PUT 请求,把请求体里的 HTML 特殊字符转义成安全的实体编码。Spring 里可以通过包装 HttpServletRequest,重写 getParameter 和 getInputStream 方法实现。这个方案我实际测试过,对正常的文本内容没有任何影响,但把<script>、<iframe>这些标签全部干掉了。

还有一个是 SQL 注入防护。MyBatis 的#{}是预编译参数,天然防注入,但有些人图方便在 order by 后面用${}拼字段名,这就把 SQL 注入的洞打开了。我的建议是:凡是用户输入的值一律用#{},必须用${}的场景(比如动态排序列名)也要用白名单校验,确保传进来的值在允许的列名列表里。

4. 前端 Vue 实现与交互细节

4.1 项目初始化、路由配置与环境变量

前端工程我用 Vue 3 + Vite 来搭,开发体验确实比 Vue 2 的 webpack 时代流畅很多。创建项目用npm create vite@latest,选择 Vue 模板,然后需要装的东西主要是 vue-router(路由)、pinia(状态管理)、axios(网络请求)、element-plus(UI 组件库)。

路由配置有几个重点。一是页面懒加载,component: () => import('@/views/user/CarDetail.vue'),这样首屏不用加载全部页面代码,路由访问到哪才加载哪。我看过没用懒加载的项目,首屏白屏时间直接多两秒。二是路由守卫,在beforeEach里判断当前路由是否要求登录、是否要求特定角色,不满足就跳转到登录页。这个逻辑要在前端做,后端的拦截器也做,双保险。

环境变量也要注意。开发环境后端接口是http://localhost:8080,生产环境可能是https://api.example.com,写成死地址就得反复改代码。Vite 里用.env.development和.env.production两个文件分别定义VITE_API_BASE_URL,代码里通过import.meta.env.VITE_API_BASE_URL读取,切换环境只需要重新打包,不用动代码。

4.2 Axios 封装:拦截器、Token 注入与错误提示

Axios 封装是前端的核心工程化手段。很多初学者直接在页面里用axios.get(...)一把梭,请求多了之后发现到处是重复代码。我的做法是写一个request.js工具文件:

import axios from 'axios' import { ElMessage } from 'element-plus' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }) // 请求拦截器:自动带 token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截器:统一处理错误 request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message) return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response) { if (error.response.status === 401) { ElMessage.error('登录已过期,请重新登录') localStorage.removeItem('token') router.push('/login') } else { ElMessage.error(error.response.data.message || '请求失败') } } else { ElMessage.error('网络异常,请检查网络连接') } return Promise.reject(error) } ) export default request

这样封完之后,每个接口模块只需要定义具体的请求函数,公共的逻辑全在拦截器里处理掉了,代码量直接少一半。尤其 401 统一跳转登录页这个逻辑,是所有需要登录的项目都必须有的。

4.3 车辆列表与详情页的组件拆分

车辆列表页是用户端访问量最大的页面,组件拆分直接影响代码的可维护性。我把列表页拆成了筛选栏(CarFilter)、车辆卡片(CarCard)、分页器(Pagination)三块。CarFilter 负责收集筛选条件,点“搜索”按钮时把条件抛给父组件,父组件更新查询参数并重新调用接口。

这里有一个交互细节值得说:筛选条件和分页条件的联动。用户修改筛选条件后,页码要重置为第 1 页,不然用户在第 5 页筛选了一个品牌,结果还是从第 5 页开始展示,看起来就像“筛选没生效”。这是一个非常常见的 bug,我在很多项目代码里见过。

车辆详情页包含图片轮播、核心参数、卖家信息、车况描述、预约按钮这几个区域。图片轮播用 Element Plus 的 el-carousel 就行,渲染车辆多图数据。详情页注意一个细节:进入页面时调用一次浏览量的递增接口,但要在onMounted里做防抖或者直接调一次就行,不要放在某个会被频繁触发的事件里。

Vue 3 的组合式 API(setup 语法糖)写起来比 Vue 2 的 Options API 清爽不少,逻辑相关的代码放在一起,比如车辆详情的加载、预约提交、收藏状态切换各自用独立的 function 组织,比 data + methods + computed 分堆清晰。

4.4 状态管理与跨组件数据同步

这个项目里全局状态主要用于两个场景:登录用户的个人信息、收藏列表的状态。用 Pinia 管理,比 Vuex 简单很多,代码也更简洁。定义 store:

import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ token: localStorage.getItem('token') || '', userInfo: JSON.parse(localStorage.getItem('userInfo') || '{}') }), actions: { setToken(token) { this.token = token localStorage.setItem('token', token) }, setUserInfo(info) { this.userInfo = info localStorage.setItem('userInfo', JSON.stringify(info)) }, logout() { this.token = '' this.userInfo = {} localStorage.removeItem('token') localStorage.removeItem('userInfo') } } })

在导航栏组件里,只要引入这个 store,就能实时显示当前登录用户的信息,不用通过事件总线或者 props 一层层传递。收藏按钮的状态也放在 store 里管理,车辆详情页点收藏后,列表页的收藏图标状态也能同步更新,这种体验上的细节会让整个系统显得更完善。

5. 环境搭建与部署上线全流程

5.1 本地环境准备:JDK、Maven、MySQL

开发环境搭建这块,卡住最多人的其实是版本匹配。JDK 我用 1.8,Spring Boot 用的是 2.7.x 版本,这个组合是最稳妥的。如果选 Spring Boot 3.x,JDK 就必须是 17 以上,很多老项目用的依赖可能跟不上,没必要给自己找麻烦。Maven 版本对应的 3.6+ 就可以,装好之后记得配置阿里云镜像,不然下载依赖的速度会让人崩溃。

MySQL 推荐安装 5.7 或 8.0,两个版本在项目开发上基本没区别,但 8.0 默认认证插件是 caching_sha2_password,老版本 Navicat 连接不上,需要改认证方式或者升级客户端。我建议直接用 8.0 + 新版 Navicat,一步到位。

本地管理数据库,可视化工具有 Navicat 和 DBeaver 两种选择,前者商业软件,后者开源免费。初学者别在这上面花太多时间,能建库建表、执行 SQL、看数据就行。

5.2 项目构建与配置要点

项目启动之前需要改几个配置。数据库连接信息在application.yml里,driver-class-name、url、username、password 都要对齐自己的环境,MySQL 8.0 的 driver 要加serverTimezone=Asia/Shanghai和useSSL=false参数,不然时间不对弹一条错,SSL 连接也可能出警告。

MyBatis 的配置要打开驼峰映射,不然数据库的created_time映射到 Java 的createdTime会一直是 null:

mybatis: configuration: map-underscore-to-camel-case: true mapper-locations: classpath:mapper/*.xml

mapper-locations指向 Mapper XML 文件的位置,写错了启动直接报Invalid bound statement (not found),这是新手最容易碰到的报错之一。如果你把 XML 放在 java 目录下而不是 resources 目录,还需要在 pom.xml 里加一段 build 资源的配置,把 xml 文件也打包进去。

5.3 云服务器部署:jar 包 + Nginx

部署到 Linux 服务器,核心流程就三步:装 JDK、装 MySQL、跑 jar 包。项目打包用mvn clean package -DskipTests,打出来的 jar 在 target 目录下,先在自己电脑上java -jar xxx.jar跑一遍确认能用,再传到服务器。

服务器上我用 Nginx 做前端静态文件的服务和反向代理。前端打包之后是dist目录,把里面的文件传到服务器的/var/www/carbiz目录,Nginx 配置里 location / 指向这个目录。后端接口通过反向代理转发:

server { listen 80; server_name your-domain.com; location / { root /var/www/carbiz; 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;这一行。Vue 是单页应用,路由靠前端 history 模式控制,如果用户直接访问https://your-domain.com/car/123,服务器根本找不到这个文件,不加 try_files 就会 404。加了之后所有路径都回退到 index.html,由前端路由接管。这是部署 Vue 项目最容易踩的坑,没有之一。

后端 jar 包用 nohup 启动:

nohup java -jar carbiz-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > startup.log 2>&1 &

如果你有多个 jar 包要跑,或者希望宕机自动重启,可以用 systemd 配置一个服务,写一个 .service 文件,比 nohup 更规范。当然,如果系统里有 Docker 环境,用 docker-compose 管理 MySQL + 后端 + Nginx 三个容器也方便,整体看个人喜好。

5.4 生产环境的安全加固

部署完能跑只是第一步,生产环境的安全加固绝对不能跳过。MySQL 的密码不要用 root 默认密码直接挂公网,建议单独建一个业务账号,只授权项目库的权限。修改默认端口 3306,防火墙只放行必要端口,这些做一遍花不了多少时间,但能挡住大量扫描攻击。

后端接口的跨域配置也要注意,如果前端和后端在同一个域名下通过 Nginx 反向代理访问,就不会有跨域问题,但如果你单独用 IP 或不同端口访问,就需要在后端加跨域配置。跨域配置要写具体允许的来源,不要图省事写*,否则等于把你的接口裸奔给任何网站调用。

6. 常见问题与排查技巧实录

6.1 数据库连接失败与账号认证问题

Can't connect to MySQL server through socket '/tmp/mysql.sock这类错误是本地环境最常见的,原因是 MySQL 服务没有启动,或者客户端连的 socket 路径不对。解决方式:

# 启动服务 service mysql start # 查看服务状态 service mysql status # 查看 MySQL 监听端口 netstat -tlnp | grep 3306

MySQL 8.0 还有一类常见问题是客户端连接报Public Key Retrieval is not allowed或Unable to load authentication plugin 'caching_sha2_password'。Java 连接 8.0 建议 JDBC 驱动在连接串上加allowPublicKeyRetrieval=true,同时改成使用 mysql_native_password 插件用户的,或者直接用 8.0 的驱动配合新版连接方式,都可以解决。

6.2 MyBatis 常见报错与缓存问题

Invalid bound statement (not added to mapper)这个报错出现的频率极高。排查思路是:先确认 Mapper 接口方法名和 XML 里的 id 完全一致,包括大小写;再确认 XML 文件的 namespace 是 Mapper 接口的全限定名;最后确认 mapper-locations 配置扫描到了你的 XML 路径。

MyBatis 一级缓存默认开启,生命周期是 SqlSession。Spring 整合后,每个请求创建的 SqlSession 用完就关闭,所以一级缓存对业务影响不大,但事务中多次查询会命中缓存。二级缓存跨 SqlSession 共享,需要手动开启,但要注意一个坑:如果查询涉及多表 join,被缓存的数据在关联表改动后不会自动失效。我建议在二手车这种数据一致性要求高的场景,不要开启二级缓存,或者只在品牌表这种基本不变的表上使用。为了性能优化可以先把慢 SQL 通过日志分析定位,再考虑缓存方案,而不是一上来就开二级缓存,做反优化。

6.3 前端跨域问题与接口请求失败

本地开发时前端跑在 5173(Vite 默认端口),后端跑在 8080,直接请求必然跨域。两种解决方案:一是后端配置 CorsFilter 允许前端地址访问,二是前端用 Vite 的 proxy 代理,把/api开头的请求代理到http://localhost:8080,这样前端发出的请求是同源的,就不会跨域。我推荐第二种,因为代理配置只影响开发环境,生产环境用 Nginx 反向代理天然没有跨域问题。

如果接口请求时好时坏,先打开浏览器开发者工具的 Network 面板,看请求是否发出、状态码是多少、响应内容是什么。504 是网关超时,可能是后端接口执行太久或者服务挂了;401 是未登录或 token 过期;500 就要去后端日志里看错误堆栈。排查问题一定要先看数据,不要瞎猜。

6.4 项目上线后的内存与性能排查

jar 包跑一段时间之后变卡或者直接挂了,大概率是内存问题。先用free -h看内存使用情况,再用jstat -gcutil <pid>看 GC 情况,如果 Full GC 频率居高不下,就需要用jmap -dump导出堆转储文件用 MAT 分析。如果你把启动参数调优一下,比如-Xms256m -Xmx512m -Xmn128m -XX:+HeapDumpOnOutOfMemoryError,出了 OOM 会自动导出堆文件,排查起来效率翻倍。

数据库慢查询是另一个性能隐患。MySQL 开启慢查询日志,看看到底是哪条 SQL 慢,然后对症下药加索引或者优化 SQL 写法。我遇到过一个典型的案例:车辆列表页按品牌筛选很慢,原因是 brand_id 字段没有加索引,全表扫描了几千条数据。加了索引之后查询时间从几百毫秒降到个位数毫秒。二手车交易系统这个量级,合理索引基本能解决大部分性能问题。

7. 项目二次开发的扩展方向

这套系统做完之后,如果想继续深入,有几个不错的扩展方向。一个是接入地图服务,把车辆线下看车的地点标在地图上,用户约看车时能直观看到距离。一个是增加视频看车功能,卖家上传车辆视频,前端用播放器控件预览,这个需求在二手车平台里越来越常见。还有一个是推荐系统,根据用户浏览历史推荐相似车辆,用简单的标签匹配就能实现,效果相对明显。

支付方面,如果要真正跑交易闭环,可以接入支付宝当面付或微信支付 Native 支付,流程是后端生成支付二维码,前端展示,用户扫码支付后通过回调更新订单状态。这个方向涉及异步通知、订单状态机、对账这些知识点,作为项目亮点写进简历非常加分。

最后说点实在的

这套系统从零到一做完,最有价值的不是在“代码写出来了”这件事本身,而是完整走了一遍需求分析、表设计、接口设计、前后端联调、部署上线的流程。我实际开发中最大的教训是:数据库表结构一定要在设计阶段把状态字段和注释写清楚,不然业务写到一半改表结构,关联的代码全部要跟着改,那个酸爽很难形容。另外就是安全意识,登录、权限、XSS、文件上传这几个点,无论是面试官追问,还是放到真实环境中被别人扫描,都是绕不开的考察点,趁早养成交互校验的习惯,后面会感谢自己。

如果你想复现这套项目,建议从环境搭建开始,先把后端跑通,再对接前端页面,最后做部署。遇到报错先看日志,大多数问题都能在日志里找到答案。这个项目覆盖的知识点足够扎实,吃透了之后,换任何类似的交易系统你都能快速上手。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 5:31:34

GitHub镜像站搭建实战:内网仓库同步与加速指南

第一次接触 GitHub 镜像站&#xff0c;还是因为一个很现实的问题&#xff1a;团队里几个人同时从 GitHub 拉代码&#xff0c;拉到一半 CI 红了一大片。查了半天&#xff0c;不是代码问题&#xff0c;是网络问题。后来我意识到&#xff0c;与其反复等网络恢复&#xff0c;不如自…

作者头像 李华
网站建设 2026/9/28 5:31:30

SpringBoot2+Vue3+MyBatis-Plus素材管理系统实战

接手这类“XX管理系统”的项目时&#xff0c;很多人第一反应是先看技术栈新不新、界面炫不炫。但真正做过一轮你就会发现&#xff0c;SpringBoot2 Vue3 MyBatis-Plus MySQL8.0这套组合&#xff0c;最值钱的地方不在于某个框架有多前沿&#xff0c;而在于它把“多媒体素材管理…

作者头像 李华
网站建设 2026/9/28 5:30:30

服装行业--买手

OTB&#xff08;Open-to-Buy&#xff0c;采购限额&#xff09;&#xff1a;销售预测 - 期初库存 期末库存销售收入 客流量 转换率 客单价售罄率&#xff1a;卖出去的货占进货的比例。动销率&#xff1a;动销率 有销售的商品数 商品总数标额&#xff1a; 标店一天的销售额…

作者头像 李华
网站建设 2026/9/28 5:29:23

Linux进程优先级调度:renice命令实战与原理详解

日常维护Linux服务器&#xff0c;进程优先级调度是我几乎每天都要打交道的事情。尤其是碰上业务高峰期&#xff0c;CPU资源争抢严重的时候&#xff0c;能不能精准地让某个进程“让路”或者“加塞”&#xff0c;直接决定了线上服务的响应速度。今天这篇实操篇&#xff0c;就专门…

作者头像 李华
网站建设 2026/9/28 5:29:10

Mysql初入

mysql的进入mysql安装完毕后winR呼出后输入cmd打开命令提示符&#xff0c;输入mysql -u root -p-u 是以什么身份去登录-p 是登录密码quit退出mysql是一个数据库。mysql本质上是一种网络服务&#xff0c;是数据库服务的客户端。在磁盘或者内存是存储的特定结构的数据。一套数据库…

作者头像 李华
网站建设 2026/9/28 5:27:33

基于Python+Vue的宿舍管理系统开发实战:从业务建模到前后端部署

我从培训机构的宿管Excel台账说起吧。那会儿宿管老师最怕的是“调宿舍”三个字&#xff0c;改一张表要联动床位、入住记录、水电费账单&#xff0c;手动改完总能漏一处&#xff0c;月底收缴对不上账&#xff0c;吵到管理员办公室去。后来我们把那套台账流程抽出来&#xff0c;用…

作者头像 李华