拿到一套SpringBoot+Vue的商城源码,最怕的不是代码看不懂,而是不知道从哪一行看起。这套米家风格的商城系统我前前后后跑过两遍,第一遍踩了不少环境坑,第二遍才算把前后端的数据流转、订单状态、库存扣减这些核心链路彻底吃透。技术栈就是国内最主流的组合——SpringBoot、Vue、MyBatis、MySQL,对于做毕设、找工作写简历项目,或者单纯想搞明白“一个商城系统到底涉及多少张表、多少个接口”的朋友来说,它都是很适合拿来解剖的样本。
这篇文章不打算把代码一行行贴给你,而是按我实际梳理项目的顺序,把整套系统的模块结构、核心业务链路、数据库设计、启动步骤以及那些最容易让人卡住的坑,一次性讲清楚。你拿到源码之后,对照这个思路去读,会省很多时间。
1. 这套商城源码在解决什么问题
1.1 项目整体认知:从目录结构开始看起
很多同学下载完源码,习惯性先打开Controller层开始读代码。我的建议是反着来,先看整个工程的目录结构,搞清楚这套系统到底分几块。这类前后端分离项目通常包含三个核心部分:
backend:SpringBoot后端工程,负责业务逻辑、数据库交互、接口输出。frontend:Vue前端工程,负责页面渲染和交互,通过HTTP请求调用后端接口。sql(或db)目录:数据库初始化脚本,建库建表数据全在这里。
如果你拿到的是一个简化版源码包,有时前后端会放在一起,有时还会把SQL脚本放在docs里。但无论怎么放,你首先要回答三个问题:后端启动入口在哪、前端启动入口在哪、数据库脚本在哪。这三个问题搞清楚了,项目基本就能跑起来。
米家商城的定位是仿米家风格的电商系统,前台面向C端用户,提供商品浏览、购物车、下单、订单查询等功能;后台是ABO管理模块,面向运营人员,负责商品上架下架、分类维护、订单处理和用户管理。整个系统本质上就是一个“前台展示交易 + 后台支撑管理”的完整电商闭环。
1.2 技术栈选择:为什么是这四个件组合
这套技术栈的组合逻辑非常清晰。SpringBoot负责把后端服务搭起来,内置Tomcat,省去繁琐的XML配置,通过spring-boot-starter-web、spring-boot-starter-jdbc这些起步依赖,快速集成Web能力和数据访问能力。Controller层暴露RESTful接口,Service层做业务处理,Mapper层通过MyBatis操作MySQL。
Vue负责前端页面,用组件化的思路搭建商品列表、商品详情、购物车、结算页和管理后台。Vue Router管理页面路由,Vuex或Pinia管理登录用户信息、购物车数量这类全局状态,Axios负责向后端发请求。
MyBatis在这个体系里扮演的是“半自动ORM”角色。它不像JPA那样帮你把所有SQL都自动生成好,而是让你自己写SQL、自己控制执行逻辑。对于商城这种查询条件多样、表关系复杂的业务,手写SQL反而更可控。配合Mapper接口和XML映射文件,既能灵活拼SQL,又能通过@Param注解传递参数、通过resultMap自定义结果集映射。
MySQL则是整个系统的数据底座,用户、商品、购物车、订单这些数据最终都落在这里。四个件各司其职:SpringBoot管业务、Vue管界面、MyBatis管SQL、MySQL管存储。这套组合在中小企业项目里出现频率极高,学它的性价比也最高——你在一套源码里同时接触了前后端、ORM、数据库,简历和面试都有东西可讲。
1.3 两个端的分工:前台购物与后台运营
前台和后台拆开看,职责完全不同。前台关注的是“转化率”,页面要流畅、下单路径要短;后台关注的是“效率”,信息要清晰、操作要直接。
前台模块一般包括:
- 用户注册、登录、个人信息维护。
- 商品分类浏览、商品搜索、商品详情查看。
- 购物车添加、修改数量、删除条目。
- 订单确认、提交订单、模拟支付、订单列表与详情。
- 收货地址管理。
后台ABO管理模块包括:
- 管理员登录、权限校验。
- 商品管理:新增商品、编辑商品、上下架、维护库存和价格。
- 分类管理:商品分类的增删改。
- 订单管理:查询订单、修改订单状态(发货、完成)。
- 用户管理:查看注册用户、禁用用户。
理解了两端分工,看代码时就会很自然地做划分。遇到页面前端渲染异常,去Vue组件里找原因;遇到接口返回错误,去Controller和Service里追踪逻辑;遇到数据对不上,去Mapper查SQL。
2. 核心业务数据链路:从商品到订单怎么串起来
2.1 商品与SKU的数据模型设计
商城的核心对象是商品,但“商品”这个词在数据库里通常不是一张表,而至少是两张:商品表(SPU)和规格库存表(SKU)。SPU是“标准的商品单元”,比如“米家智能台灯”;SKU是“最小库存单元”,比如“米家智能台灯 白色款 220V”。
分类表负责树形分类,一级类目、二级类目通过parent_id自关联。商品表存放标题、主图、描述、价格区间、销售状态这些公共信息。如果项目做了多规格,会有单独的商品规格表,把规格名和规格值存起来,SKU表再通过spec_info字段或关联表指定唯一规格组合。
理解这个模型的实操意义在于:你写商品列表页的时候,展示的往往是SPU信息;但下单的时候,用户选中的必须是具体的SKU。很多新手在做购物车时只记了商品ID,后端下单时笃定能拿到唯一的库存,结果发现自己做的是单规格版本,一旦加多规格就乱套。这套源码里如果你看到商品列表接口返回的列表中还嵌套了SKU数据,不要惊讶,前端商品详情页需要通过SPU查询可用的SKU列表。
一个关键点是主图上传和图片表的设计。电商项目通常把商品图拆到独立表,一个SPU对应多张图片,主图和详情图字段分开存。前端轮播图组件读取图片列表时,通过商品ID关联查询即可。
2.2 购物车:临时想买也得有持久化
购物车是典型的“看起来简单,实际上有门道”的模块。两种常见设计:一种是纯前端存储,存localStorage,用户不登录也能加购物车,下单前再要求登录;另一种是后端持久化,购物车数据存MySQL,用户登录后随时可以同步。
这套商城源码里我建议你重点关注它是怎么合并登录前后数据的。如果前端在用户未登录时把商品加到了本地存储,登录后本地数据就要提交到后端合并,否则用户一刷新购物车就空了。如果你拿到的源码是后端持久化方案,表结构一般是这样:
id购物车条目ID。user_id所属用户。sku_id关联的SKU。quantity数量。checked是否选中,下单时只结算选中的条目。create_time、update_time记录时间。
购物车表设计成“一件商品一个条目、数量字段单独维护”而不是“一个用户一行JSON”,好处是方便做数量增减、选中状态修改和库存校验。下单时事务流程要先读取选中的购物车条目,根据SKU查库存,计算总价,创建订单,最后删除对应购物车条目。
这里有一个很容易被踩的坑:删除购物车条目时,如果用DELETE FROM cart WHERE id IN (...),但当用户修改了数量同时点击下单时,两次请求之间的并发可能造成数据不一致。虽然单体商城项目并发量不大,但下单流程里加上事务和状态校验是更稳的写法。
2.3 订单状态流转与库存扣减
订单是电商系统里最核心、也最容易写错的部分。订单表一般拆成订单主表和订单项表:主表记录订单号、用户ID、订单状态、支付金额、收货信息、下单时间;订单项表记录每个SKU的单价、数量、商品快照(商品名称、图片)。
为什么订单项要存商品快照?因为商品的价格和标题随时会改,但你下单那一刻的成交价和商品信息不能跟着变。如果订单项表不冗余这些字段,用户回查历史订单时看到的可能是当前价格,这显然不合理。一个合格的商城源码,订单项表里必然有商品名称、商品图片、成交价格这几个冗余字段。
订单状态机至少包含这几个状态:待支付 -> 已支付/待发货 -> 已发货 -> 已完成,另外还有已取消。源码里通常用一个status字段或枚举类管理。前端是根据状态码来展示“支付”“发货”“确认收货”这些按钮的,所以状态码必须跟前端约定好,不能这边改了数字那边没改页面渲染逻辑。
库存扣减是并发安全的重灾区。最直接的错误写法是“先查库存,再判断是否够,最后UPDATE减去数量”,因为查和更新之间有时间窗口,高并发下两个请求同时读到库存为1,各自都以为能买,结果超卖。正确做法是用一条带条件的UPDATE:
UPDATE sku SET stock = stock - #{quantity} WHERE id = #{skuId} AND stock >= #{quantity}如果影响行数为0,说明库存不足,下单失败。这个写法把校验和扣减合并成原子操作,在单体架构下足够可靠。下单涉及的“创建订单、插入订单项、扣库存、清购物车”这几步必须放在同一个事务里,任意一步失败都整体回滚,SpringBoot里就是给方法加一个@Transactional注解。
2.4 管理后台ABO模块的功能清单
ABO管理后台,按源码里的命名习惯就是运营后台管理模块。它解决的痛点是:商品数据不能靠开发直接改数据库,运营人员需要一套界面来完成日常操作。
典型功能包括:
- 商品列表管理:分页查询所有商品,支持按名称、状态筛选。
- 商品编辑:改价格、库存、上下架、轮播图。
- 分类管理:树形展示,拖拽调整层级(简化版可能只是列表)。
- 订单列表:查看全部订单、按状态筛选、点击发货。
- 用户管理:查看注册用户列表、禁用账号。
后台模块的鉴权逻辑和前台用户不同。前台走用户表,后台走管理员表。管理员登录后得到独立的Token或Session,后端通过拦截器校验后台接口是否需要管理员权限。很多源码为了简化,会在前台用户表里加一个role字段区分普通用户和管理员,这种写法在毕设场景里可以接受,但真正的商用项目会把管理员和用户分成两套体系。
3. SpringBoot后端的代码阅读路径
3.1 分层结构与MyBatis Mapper的工作方式
SpringBoot工程的包结构通常是controller、service、mapper、entity(或domain)、config、common、utils这几个包。阅读顺序应该是:先看entity理解数据结构,再看controller了解接口清单,接着看service理清业务,最后进入mapper看SQL实现。
MyBatis的Mapper接口和XML文件是成对出现的。接口里声明方法,比如List<Product> selectHotProductList(@Param("limit") int limit),对应的XML里写SQL:
<select id="selectHotProductList" resultType="com.example.entity.Product"> SELECT id, title, main_image, price, sales, status FROM product WHERE status = 1 ORDER BY sales DESC LIMIT #{limit} </select>MyBatis的resultType会自动把数据库列名映射到实体类的同名字段。如果表字段是下划线命名(如main_image),实体类是驼峰命名(如mainImage),要么开启map-underscore-to-camel-case: true,要么手写resultMap。这套源码里通常会开启驼峰映射,你可以在application.yml里看到相关配置。
读Mapper时注意两类容易出错的地方:一是动态SQL,if、where、foreach标签写多了以后很容易出现条件拼接错误;二是一对多查询,比如查商品同时要带出他的轮播图列表和SKU列表,如果用简单联表查询会遇到“一查多”导致的重复行问题,源码里一般会用resultMap的collection标签来解决,或者干脆拆成两次查询。
3.2 登录鉴权与用户会话管理
登录鉴权在前后端分离架构里最常见的方案是JWT。流程是:用户输入账号密码,后端校验通过后生成一个Token返回给前端,前端把它存到localStorage或vuex/pinia里,之后每次请求在HTTP头上带Authorization: Bearer <token>,后端通过拦截器取出Token、解析出用户信息、放行请求。
这套源码中你大概率会在config包或interceptor包下找到类似LoginInterceptor的类。拦截器的核心逻辑是:
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); // 解析token,校验有效期 // 若无效则返回401,阻止Controller执行 return true; }还需要在WebMvcConfigurer里注册拦截器,并设置excludePathPatterns放行登录、注册、商品列表这类公开接口。读到这里要注意一个细节:后台管理员接口的拦截器和前台用户拦截器往往是分开的,或者通过角色判断,否则会出现“普通用户也能调用管理员接口”的越权问题。
密码存储也是一个必须检查的点。源码里如果直接存的是明文密码,建议你在二次开发时换成BCryptPasswordEncoder加密。如果看到的是MD5加盐,那也算比明文强,但安全级别不如BCrypt。
3.3 文件上传与图片静态资源处理
商品主图和轮播图的上传是后台管理的标配。SpringBoot处理文件上传并不复杂:接口接收MultipartFile,把文件保存到本地磁盘,然后数据库里存的是访问路径。
但这里有一个新手特别容易卡住的点:文件保存到本地磁盘后,浏览器要怎么访问到这个文件?答案是必须配置静态资源映射。一种方式是在WebMvcConfigurer里加:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler("file:" + uploadDir + "/"); }或者把保存目录直接放在src/main/resources/static/upload下,SpringBoot会把static当作静态资源根目录自动映射,但这种方式不利于数据分离,重新部署容易丢图片。线上部署更稳妥的是把图片存到单独的目录或云存储,数据库里只保存相对路径。
前端上传组件(如Element Plus的el-upload)通常把图片提交到一个接口,后端返回图片URL,前端再把URL放进商品表单一起提给保存商品接口。这个交互顺序你读前端代码时也要留意。
3.4 统一返回、全局异常与事务处理
商城接口数量多,如果没有统一返回结构,前端处理起来会非常痛苦。好的项目会定义Result类,包含code、message、data三个字段,所有Controller统一返回Result.success(data)或Result.error("xxx")。前端Axios拦截器读取code,根据约定好的状态码判断业务成功与否。
全局异常处理用@RestControllerAdvice配合@ExceptionHandler,把未捕获的异常统一包装成Result返回,这样即使用户传入非法数据,接口返回的也是结构化的JSON而不是500页面。如果你发现启动后某些接口报错信息特别简陋,可以检查common包下有没有这个全局异常类,没有的话二次开发时建议自己添加。
事务处理集中在Service层。典型的下单方法上你会看到:
@Transactional(rollbackFor = Exception.class) public Order createOrder(OrderCreateDTO dto) { // 1. 校验地址和SKU // 2. 计算总价 // 3. 插入订单 // 4. 插入订单项 // 5. 扣减库存 // 6. 清空购物车 // 7. 返回订单 }注意rollbackFor要指定Exception.class,因为Spring默认只回滚RuntimeException,如果用@Transactional但不写rollbackFor,遇到受检异常时事务不会回滚,库存扣了订单没成,数据就不一致了。
4. MySQL表结构与查询性能注意点
4.1 核心表清单与字段设计思路
电商类型源码的数据库表数量一般在10张左右,核心表大致如下:
| 表名 | 用途 | 关键字段 |
|---|---|---|
user | C端用户 | id, username, password, nickname, avatar, status |
admin | 后台管理员 | id, username, password, role |
category | 商品分类 | id, name, parent_id, level, sort |
product | 商品主表 | id, category_id, title, subtitle, main_image, price, stock, sales, status |
product_sku | 商品规格库存 | id, product_id, spec_info, stock, price, image |
cart | 购物车 | id, user_id, sku_id, quantity, checked |
order | 订单主表 | id, order_no, user_id, total_amount, pay_amount, status, address_id |
order_item | 订单项 | id, order_id, sku_id, product_title, product_image, price, quantity |
address | 收货地址 | id, user_id, receiver, phone, province, city, detail |
上述设计是简化版的。实际商用系统还会拆出支付流水表、退款表、优惠券表、SMS记录表,但毕设和入门源码到这个粒度已经足够表达业务逻辑了。
读表结构时有个判断项目质量的方法:如果order表里没有order_no(订单号)字段,而是直接使用自增ID当订单号,那说明作者图省事了。合格的订单表必须有独立的订单号字段,且具备唯一索引。
4.2 订单号生成、并发幂等与索引
订单号不仅仅是给用户看的编号,还承担着幂等作用。生成订单号常见方案是“时间戳 + 随机数”,比如yyyyMMddHHmmss + 6位随机数,但高并发时可能有重复风险,更稳的做法是用数据库自增的单表序列间接生成,或者利用Snowflake算法。源码里如果只是System.currentTimeMillis()加随机数,小规模场景能用,大规模不建议。
防止重复提交下单是电商的经典问题。简单做法是在前端把提交按钮在点击后置灰、加loading;后端防线是在订单表创建时通过order_no唯一索引兜底。如果用户重复点击提交,第二次插入会因为唯一索引冲突而失败,配合全局异常处理返回“订单已提交”,体验比重复下单好很多。
索引的设计原则是“查询优先”。按照商城最频繁的查询场景,至少要给以下字段建索引:
product.status + product.category:前台商品列表按分类查询。cart.user_id:用户购物车查询。order.user_id + order.status:用户订单列表查询。order_item.order_id:订单详情查询。
如果表数据量增长后,商品名模糊搜索变慢,那就需要给商品标题加全文索引或引入ElasticSearch。在入门阶段,建立合理的联合索引通常就够用了。
4.3 常见的慢查询与分页优化
商城后台的商品列表、订单列表都是分页查询。MyBatis分页插件(PageHelper)用得比较多,它本质上是在SQL后面拼接LIMIT。但深分页问题依然存在:LIMIT 100000, 20时,MySQL要扫描前面10万行再跳过,性能会明显下降。优化方式是延迟关联或记录上次查询位置,但作为入门项目,先把常规分页逻辑跑通就够了。
另一个容易忽略的点是统计类SQL。后台首页的销售统计往往需要SUM、COUNT、GROUP BY,这些查询如果每天都跑一次且数据量大,建议按日期单独建一张统计表,通过定时任务生成前一天的数据,而不是每次实时扫订单表。
写SQL时还要注意避免N+1查询问题。比如查商品列表后再循环查每个商品的图片,会在循环里多次查询数据库,数据量大了就很慢。应该用WHERE product_id IN (...)一次把图片查出来,在Java内存中组装。如果源码里没有这样处理,也不一定算错,但你可以把它作为优化点记录到面试项目里。
5. Vue前端的联调与部署细节
5.1 环境准备与工程初始化
拿到前端工程,第一步不要急着npm install,先打开package.json看依赖版本。这决定你本机要用哪个Node版本。Vue2项目通常依赖node-sass,它在高版本Node下编译极易失败;Vue3项目相对好一些,但也要注意Vite或Webpack版本对Node的要求。建议Node版本参考:
| 项目类型 | 推荐Node版本 |
|---|---|
| Vue2 + Node-Sass | Node 14.x |
| Vue2 + Sass(Dart Sass) | Node 14/16 |
| Vue3 + Vite | Node 16/18 |
如果依赖安装一直失败,优先检查镜像源。执行npm config set registry https://registry.npmmirror.com换镜像,再删掉node_modules和package-lock.json重新安装。这一步能解决大部分node-sass编译报错问题,node-sass实在装不了就改成sass依赖,做法是卸载node-sass后安装sass。
启动前还要看.env文件里有没有后端接口地址配置,比如VUE_APP_BASE_URL。前端工程默认请求的IP和端口是不是你本地后端端口,不一致就要改,否则联调时会全屏报错。
5.2 路由、状态管理与API请求封装
Vue Router负责页面跳转。前台米家商城的路由基本是:首页、分类页、商品详情页、购物车页面、登录页、注册页、订单列表页、订单详情页;后台ABO管理模块的路由则挂载在/admin路径下。通常使用路由懒加载:
const router = [ { path: '/home', name: 'Home', component: () => import('@/views/Home.vue') } ]路由守卫通常用来做登录校验:没有Token又想访问“个人中心”“订单页”这类地址时,跳转登录页,登录后再跳回原页面。如果源码里没做这套逻辑,二次开发时要加上。
状态管理是Vue2用Vuex、Vue3用Pinia。商城项目中全局状态主要存三样东西:用户信息、Token、购物车数量。页面组件里频繁调用户状态,用状态管理比各组件自己调接口更高效,刷新后还能从Storage恢复。
Axios请求封装是必看的一个文件。好的封装要处理三件事:
// 请求拦截器:给每个请求带token service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) // 响应拦截器:统一处理业务错误和登录失效 service.interceptors.response.use( res => { const code = res.data.code if (code === 401) { /* 跳转登录 */ } return res.data }, err => { /* 网络异常提示 */ } )如果你发现前端接口报错但看不到具体信息,八成是拦截器把错误吞了或者返回结构解析不对。调试时可以直接在浏览器Network面板看原始响应体。
5.3 前后端联调和跨域处理
开发环境下前端运行在localhost:8081,后端运行在localhost:8080,端口不同,必然出现跨域。最省事的方案是配置Vue的devServer.proxy代理:
devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }前端请求的地址写/api/user/login,代理会把请求转发到http://localhost:8080/api/user/login。这里有一个约定,代理的路径前缀和后端的@RequestMapping前缀要保持一致,比如后端Controller统一是/api,前端所有请求都以/api开头。如果发现请求404,先检查路径前缀是否对应。
跨域还有一种方案是后端开启CORS,写一个CorsFilter或@CrossOrigin。但生产环境更推荐前端打包后直接放在SpringBoot的静态资源目录里,或者用Nginx反向代理,前后端同域,从根本上避开跨域问题。
5.4 打包放进SpringBoot的方法
前后端分离项目最终部署方式通常有两种。第一种是把前端打包后交给Nginx托管,Nginx配置反向代理把/api转发到后端服务;第二种是前端打包后的dist目录内容直接复制到SpringBoot的src/main/resources/static目录下,再跟着后端打成一个jar包。
第二种方式很适合毕设和个人演示项目,因为最后只需要启动一个服务,访问同一个端口就能同时拿到页面和接口。操作步骤很简单:
npm run build # 完成后把 dist 目录下的文件复制到后端的 static 目录如果前端路由使用是最新版的history模式(url里没有#),打成jar包之后,刷新二级页面会404,因为SpringBoot没有对应的页面路由。两个解法:后端加一个转发接口,把非接口路径转发到index.html;或者前端路由改回hash模式,生成URL带#,这样刷新不会发请求到后端。作为演示项目,改hash模式是成本最低的方案。
6. 跑通整个项目的操作步骤与避坑指南
6.1 环境清单和版本参考
跑通这套项目之前,先把环境装齐,版本不必最新,但必须匹配源码要求。参考清单如下:
| 软件 | 参考版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | SpringBoot 2.x 推荐JDK8/11 |
| Maven | 3.6+ | 后端依赖管理 |
| MySQL | 5.7 或 8.0 | 导入SQL脚本 |
| Node | 14~18 | 视前端依赖版本 |
| IDE | IDEA / VSCode | 后端建议IDEA,前端VSCode即可 |
如果你下载的源码是SpringBoot 3.x,那要求JDK17,MyBatis依赖也用mybatis-spring-boot-starter新版本。先看pom.xml里的spring-boot-starter-parent版本号,再决定装哪个JDK,这是最稳妥的顺序。
6.2 数据库初始化与配置
打开application.yml,重点看这两段:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mijia_mall?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456用MySQL命令行或Navicat先创建数据库,再导入SQL脚本:
CREATE DATABASE IF NOT EXISTS mijia_mall DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE mijia_mall; SOURCE /path/to/mijia_mall.sql; -- 命令行导入方式数据库名要和url里的库名一致。MySQL8默认的加密插件是caching_sha2_password,如果后端JDBC驱动版本太旧,会出现连接失败。解决方案是升级驱动到com.mysql.cj.jdbc.Driver对应的新版本,或者把用户加密方式改为mysql_native_password。
6.3 后端启动的细节
IDEA导入Maven项目时,选择pom.xml,等Maven下载完依赖再启动,不要着急点运行。如果依赖下载卡住,把Maven的settings.xml里的镜像换成阿里云私服。
启动主类一般在src/main/java下的Application.java或MallApplication.java,直接右键运行main方法。启动成功后,SpringBoot默认端口8080。如果端口被占用,日志会报Port 8080 was already in use,解决方案是:
# Windows netstat -ano | findstr :8080 taskkill /PID <进程号> /F或者改server.port。
启动日志中出现Tomcat started on port(s): 8080且没有红色异常,后端就起来了。这时打开浏览器访问http://localhost:8080/接口路径,能返回JSON说明接口正常。
6.4 前端启动和依赖安装
前端工程根目录下执行:
npm install npm run serve如果npm install报错说某些包需要更高的Node版本或编译失败,那就按前面说的换镜像源或调整Node版本。还有一种坑是源码里带有package-lock.json,锁定的依赖版本和当前Node不兼容,删掉锁文件再装。
启动成功后终端会输出:
App running at: - Local: http://localhost:8081/浏览器打开这个地址,能访问页面就说明前端起来了。这时访问页面上的接口,如果请求报跨域或404,回看5.3节的代理配置。
6.5 高频问题排查表
把最容易遇到的报错集中列一下,方便你遇到问题时快速定位。
| 报错现象 | 可能原因 | 处理方向 |
|---|---|---|
Access denied for user 'root'@'localhost' | 数据库密码错误 | 检查application.yml密码,测试数据库连接 |
Cannot create PoolableConnectionFactory | 数据库未启动 / 库名不存在 | 确认MySQL进程和库名 |
java.net.BindException: Address already in use | 端口被占用 | 杀进程或改端口 |
| 页面能打开但接口500 | 后端日志有SQL异常 | 看控制台完整堆栈,多查表字段 |
| 前端请求跨域 | 未配置代理或未开启CORS | 配置proxy或加CORS过滤器 |
node-sass安装失败 | Node版本不匹配 | 换Node或改用sass依赖 |
| 中文数据乱码 | 连接串没加characterEncoding=utf8 | 改JDBC URL并确认数据库是utf8mb4 |
| 商品图片不显示 | 静态资源映射没生效 | 检查addResourceHandlers配置路径 |
排查问题的核心是看日志。后端日志在IDEA控制台,前端接口报错在浏览器F12的Network和Console面板。把两边的报错信息对齐到同一时刻,基本能定位80%的联调问题。
7. 这套源码怎么变成自己的项目
7.1 功能扩展:支付、搜索、评价
跑通源码只是第一步,能让它写入简历的关键是“改造”和“增强”。支付模块是最典型的增强方向:接入支付宝沙箱支付,流程是后端生成支付二维码,用户扫码支付后支付宝回调你的后端接口,后端收到回调后把订单状态改成“待发货”。这个流程能完整跑通,说明你对订单状态机的理解到位了,面试聊起来也很有料。
搜索增强可以做ElasticSearch方案的重构:把商品表数据同步到ES,搜索接口改成先查ES再回源MySQL,支持标题分词、价格范围过滤、销量排序。商城数据量不大时也可以先用MySQL的LIKE和全文索引,但要如实说明ES方案是横向扩展的设计。
评价系统则跟订单强关联:只有已完成的订单才能评价,评价后商品的好评率字段更新。这又要改动订单查询和商品详情两个链路,看起来功能不大,实际涉及的表和接口改动却很全面,适合作为二次开发练手。
7.2 性能优化:缓存、异步与读写分离
流量稍微上来一点,第一个要动手的就是Redis缓存。把商品详情、首页展示位这些热点数据放入Redis,设置过期时间,接口先查缓存再用DB兜底。缓存更新的经典做法是“先更新数据库,再删除缓存”,防止并发下的脏数据。
订单创建的流程可以做异步化:用户提交订单后,接口立刻返回“处理中”,后端通过消息队列异步扣库存、发通知。这样即使库存扣减和后续步骤耗时增加,用户感知不到延迟。Apache RocketMQ或SpringBoot集成的RabbitMQ都可以作为选型,但引入MQ后要处理“消息发送失败”“消息重复消费”这类问题,项目复杂度会上一个台阶。
数据库层面的优化是读写分离:主库写、从库读,中间用MySQL主从复制或中间件。真实项目里生产读写分离很常见,但如果只是学习阶段,这个方向了解原理即可,重心还是放在“多表查询怎么走索引”和“缓存怎么和DB保证一致性”。
7.3 安全与规范:密码、配置、接口校验
安全不是可选项,是基本功。
密码加密优先用BCryptPasswordEncoder,同一个密码尽量不直接存储,即使数据库泄露,也不能轻易反推出原始密码。看源码时检查一下是明文还是哈希,是哈希的话了解一下用的算法。
配置文件管理上,数据库密码、密钥这类敏感信息不要硬编码到application.yml里推上Git。学习项目里可以写死,但简历上如果写“了解配置中心”,至少要会用@ConfigurationProperties拆分配置或了解环境变量的用法。
接口层面,商品列表、商品详情这类公开接口要放行;但后台管理接口必须做权限校验。前端隐藏按钮只是界面上的处理,真正的防线是后端拦截器。如果一个接口直接返回所有用户数据而没有任何身份校验,那就是越权漏洞,这种细节面试时被问到要能答出来。
7.4 二次开发中的个人体会
我第一遍梳理这套源码的时候,花最多时间的不是写代码,而是“搞清楚哪里改了什么会影响哪里”。比如想加一个优惠券,到底要动哪些表、哪些接口、哪些页面,跟着数据的流动路径走一遍,整个系统的骨架就有了。比单纯记住某个Controller怎么写更有用。
这套项目每次跑通一遍,你对SpringBoot目录结构、MyBatis的Mapper机制、Vue路由和状态管理的理解就会更扎实一分。比起下载一百个开源项目烂在网盘里,把一个项目拆开、拼上、再改造,学到的东西要多得多。