ARTICLE DETAIL

资讯详情

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

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

二手车交易系统实战: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卖家用户IDbrand_idbigint品牌IDtitlevarchar车辆标题pricedecimal(10,2)价格单位万元或元展示时统一处理original_pricedecimal(10,2)新车指导价用于展示折价信息mileagedecimal(10,2)里程单位万公里first_reg_datedate首次上牌日期gearboxtinyint变速箱类型1自动 2手动emissionvarchar排放标准如国六colorvarchar外观颜色descriptiontext车况描述cover_imagevarchar封面图URLstatustinyint0待审核 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 groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency然后在 Service 里查列表之前直接调用PageHelper.startPage(pageNum, pageSize)后面紧跟的第一条 MyBatis 查询语句会自动带上 LIMITpublic PageResultCarVO queryCarList(CarQueryDTO queryDTO) { PageHelper.startPage(queryDTO.getPageNum(), queryDTO.getPageSize()); ListCarVO carList carMapper.selectCarListByCondition(queryDTO); PageInfoCarVO 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 idselectCarListByCondition resultTypecom.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 teststatus ! null AND c.status #{status} /if if testbrandId ! null AND c.brand_id #{brandId} /if if testminPrice ! null AND c.price gt; #{minPrice} /if if testmaxPrice ! null AND c.price lt; #{maxPrice} /if if testminMileage ! null AND c.mileage gt; #{minMileage} /if if testgearbox ! null AND c.gearbox #{gearbox} /if /where ORDER BY c.created_time DESC /selectwhere标签会自动处理掉第一个条件前面多余的 AND这是 MyBatis 非常贴心的设计。需要注意lt;和gt;是 XML 里的转义符如果 SQL 里有比较运算符不能直接写号。动态 SQL 还有一个常用的场景是动态更新卖家修改车辆信息时前端只会提交有值变化的字段。用set标签搭配if就能实现只更新非空字段避免把所有字段都覆盖一遍。3.4 全局异常处理、XSS 防护与过滤器这部分属于“看起来不重要但生产环境必不可少”的工程化能力。全局异常处理核心是用RestControllerAdviceExceptionHandler统一捕获业务异常、参数校验异常、未知异常返回统一格式的 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 vitelatest选择 Vue 模板然后需要装的东西主要是 vue-router路由、pinia状态管理、axios网络请求、element-plusUI 组件库。路由配置有几个重点。一是页面懒加载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 的组合式 APIsetup 语法糖写起来比 Vue 2 的 Options API 清爽不少逻辑相关的代码放在一起比如车辆详情的加载、预约提交、收藏状态切换各自用独立的 function 组织比 data methods computed 分堆清晰。4.4 状态管理与跨组件数据同步这个项目里全局状态主要用于两个场景登录用户的个人信息、收藏列表的状态。用 Pinia 管理比 Vuex 简单很多代码也更简洁。定义 storeimport { 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.8Spring Boot 用的是 2.7.x 版本这个组合是最稳妥的。如果选 Spring Boot 3.xJDK 就必须是 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 要加serverTimezoneAsia/Shanghai和useSSLfalse参数不然时间不对弹一条错SSL 连接也可能出警告。MyBatis 的配置要打开驼峰映射不然数据库的created_time映射到 Java 的createdTime会一直是 nullmybatis: configuration: map-underscore-to-camel-case: true mapper-locations: classpath:mapper/*.xmlmapper-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.activeprod startup.log 21 如果你有多个 jar 包要跑或者希望宕机自动重启可以用 systemd 配置一个服务写一个 .service 文件比 nohup 更规范。当然如果系统里有 Docker 环境用 docker-compose 管理 MySQL 后端 Nginx 三个容器也方便整体看个人喜好。5.4 生产环境的安全加固部署完能跑只是第一步生产环境的安全加固绝对不能跳过。MySQL 的密码不要用 root 默认密码直接挂公网建议单独建一个业务账号只授权项目库的权限。修改默认端口 3306防火墙只放行必要端口这些做一遍花不了多少时间但能挡住大量扫描攻击。后端接口的跨域配置也要注意如果前端和后端在同一个域名下通过 Nginx 反向代理访问就不会有跨域问题但如果你单独用 IP 或不同端口访问就需要在后端加跨域配置。跨域配置要写具体允许的来源不要图省事写*否则等于把你的接口裸奔给任何网站调用。6. 常见问题与排查技巧实录6.1 数据库连接失败与账号认证问题Cant connect to MySQL server through socket /tmp/mysql.sock这类错误是本地环境最常见的原因是 MySQL 服务没有启动或者客户端连的 socket 路径不对。解决方式# 启动服务 service mysql start # 查看服务状态 service mysql status # 查看 MySQL 监听端口 netstat -tlnp | grep 3306MySQL 8.0 还有一类常见问题是客户端连接报Public Key Retrieval is not allowed或Unable to load authentication plugin caching_sha2_password。Java 连接 8.0 建议 JDBC 驱动在连接串上加allowPublicKeyRetrievaltrue同时改成使用 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 前端跨域问题与接口请求失败本地开发时前端跑在 5173Vite 默认端口后端跑在 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、文件上传这几个点无论是面试官追问还是放到真实环境中被别人扫描都是绕不开的考察点趁早养成交互校验的习惯后面会感谢自己。如果你想复现这套项目建议从环境搭建开始先把后端跑通再对接前端页面最后做部署。遇到报错先看日志大多数问题都能在日志里找到答案。这个项目覆盖的知识点足够扎实吃透了之后换任何类似的交易系统你都能快速上手。
返回列表