前阵子群里有人问,想找一套能真正跑起来的全栈项目练手,技术栈最好就是SpringBoot + Vue + Mybatis,别整太新太偏的东西。我翻了翻自己收藏夹,直接丢了这个开源商城过去。代码已经在GitHub上开源,前端是Vue,后端是SpringBoot,持久层用Mybatis,商品、购物车、订单、支付、后台管理等一套完整流程全都有。他照着部署文档跑通之后,拿这套项目去面试,居然真的把Offer聊到手了。这不是段子,是最近真实发生的事。今天就把这个开源商城的技术选型、核心模块实现、本地跑通步骤以及我在实操中踩过的坑完整拆一遍,给正在学这三件套、想找个完整项目练手的人做个参考。
1. 项目整体设计与技术选型解析
1.1 为什么是SpringBoot + Vue + Mybatis这套组合
很多人一上来就问,都2024年了,为什么不用微服务?为什么不用Mybatis-Plus?为什么不用Vue3?我先说实话,这个项目选型没什么花哨的,核心就两个字:稳。
SpringBoot是目前Java后端最主流的框架。它把Spring那套繁琐的XML配置全部干掉了,改成自动装配和约定优于配置,你只需要加依赖、写业务代码即可。对初学者来说,SpringBoot把“能跑起来”这件事的门槛压得非常低,这对建立信心很重要。Vue在国内前端圈的地位也不用多说,组件化、响应式、生态丰富,社区资料多到看不完。Vue2虽然已经是旧时代的东西,但很多企业存量项目还在用,学会Vue2再去迁Vue3其实成本不高,所以这个项目里用Vue2 + Element-UI做后台管理端,我认为是完全合理的。
Mybatis就更不用吹了。它跟SpringBoot配合非常丝滑,SQL让你自己写,灵活性拉满,尤其是复杂的多表关联和报表统计,你要是用JPA写那些动态SQL,能把人憋死。Mybatis的XML里可以用动态SQL任意拼接,该优化的时候还能直接手写LIMIT、FOR UPDATE这些,拿捏数据库非常准。国内企业大面积使用Mybatis,也侧面说明这个选型贴近真实生产环境。
还有一个关键点,这项目是单体架构,不是微服务。我在实际操作中体会很深,对于中小型电商系统来说,单体足够用了。微服务化会引入注册中心、配置中心、网关、分布式事务等一系列复杂度,对个人学习和中小团队来说完全是负担。先把一个单体做扎实,再去看分布式那套理论,顺序才符合认知规律。
1.2 功能模块和数据库设计思路
这套商城系统从功能上分,大致有这几个模块:用户认证、商品中心、购物车、订单中心、支付中心、优惠券以及后台管理。每个模块在数据库里的表都有清晰的前缀约定,比如用户相关的叫ums_member、商品相关的叫pms_product、订单相关的叫oms_order。这个命名习惯参考了知名开源商城项目的管理风格,好处是一眼能看出表属于哪个业务域,避免几十张表堆在一起找不到方向。
我来列一下核心表的设计逻辑:
| 表名 | 模块 | 核心字段 | 设计意图 |
|---|---|---|---|
| pms_product | 商品 | id、brand_id、category_id、title、price、pic | 商品SPU表,属性继承自分类 |
| pms_sku | 商品 | id、product_id、price、stock、spec | 商品SKU表,具体规格库存价格 |
| oms_cart_item | 购物车 | id、member_id、product_id、sku_id、quantity | 冗余商品信息,减少查询 |
| oms_order | 订单 | id、member_id、order_sn、total_amount、status | 订单主表,状态用数字维护 |
| oms_order_item | 订单 | id、order_id、product_id、sku_id、product_name | 下单快照,防止商品后续变动影响历史订单 |
| ums_member | 用户 | id、username、password、phone | 密码字段用BCrypt加密存储 |
我自己拆到这套表结构的时候,有个感受特别深:设计表的人非常懂电商业务里的历史数据问题。比如订单明细里会把商品名称、单价、图片全部复制一份存下来,这就是快照设计。为什么必须要快照?因为商品价格和名称经常变,你下单时买的是99块,几个月后商品涨到129,你订单记录里如果直接关联商品表,商家后台看到的金额就变成129了,这肯定出问题。所以在下单那一刻把商品关键信息固化下来,是电商系统下最基础也最容易被新手忽略的设计。
权限设计方面,后台管理端用的是RBAC模型。角色和菜单关联,用户在角色里,通过中间表来维护多对多关系,登录成功后根据角色查询菜单树返回给前端动态渲染。权限这块要用Mybatis做多表关联查询的场景也不少,刚好能练到联表查询和动态SQL的能力。
2. 核心链路实现:从商品浏览到支付回调
2.1 商品SPU/SKU与库存扣减
电商系统里SPU和SKU这两个概念必须搞懂。SPU是商品抽象概念,比如“iPhone 15”,SKU是具体可购买单元,比如“iPhone 15 黑色 128G”。商城的商品表只存通用属性,规格、价格、库存都放到SKU表里。用户在前端选完规格后,前端就能定位到具体SKU,然后把这个SKU的ID传给后端。
SKU表用Mybatis操作时不需要多复杂,但库存扣减绝对是个容易翻车的点。我见过的初级写法是:先把库存查出来,判断是否大于要买的数,再执行update。这种写法在单线程测试没问题,但并发一上来就会超卖,因为两步操作之间有间隙,两个请求同时读到库存100,同时减到99,最终结果可能就把第100个商品卖出去了。
正确的做法是用一条原子更新SQL,把判断条件和扣减动作融在一起:
UPDATE pms_sku SET stock = stock - #{quantity} WHERE id = #{skuId} AND stock >= #{quantity}这样数据库行锁会保证同一时刻只有一个事务能更新成功,返回受影响行数为0就说明库存不够了。我在这个开源项目里看到的就是这种写法,很标准。
还有一点,购车表里存了sku_id和quantity,但页面展示时需要商品的价格和图片。初级设计容易直接查SKU表拿价格,但价格是时时变化的,购物车页面显示的价格和下单时计算的最终价格要分开。购物车里只是一个预估展示,真正锁定价格在下单接口里再重新计算一遍,这样才不会出现用户看到100元、最后支付却是120元的情况。这个逻辑我踩过坑,后来学乖了。
2.2 订单状态机与超时关单
订单模块是整个系统里逻辑最复杂的模块,它不能只是CRUD。打开oms_order表你会看到status字段,这个字段不是随便填的数字,它对应一套完整的状态机:待支付、已支付、已发货、已完成、已取消。
为什么要用状态机而不是随便改状态?因为订单每个操作都必须在合法状态下进行。比如待支付状态下才能取消订单,已支付状态下才能发货,已完成状态下不能再执行退款,这些规则如果写散在代码各处,很容易漏判。我在这个项目里看到他们把订单状态流转逻辑收敛到了Service层,每个状态变更前都校验当前状态,非法流转直接抛业务异常。虽然还没做到用状态模式那一套设计模式,但对于学习来说,这种朴素的判断已经能保证正确性了。
超时关单是电商系统里躲不开的需求。用户在支付页面待了太久没付钱,订单不能一直挂着不处理,不然库存都被占着卖不出去。这个项目用的是定时任务扫描方案:每60秒扫一次订单表,找出创建时间超过30分钟且状态是待支付的订单,把它们批量改成已取消。批量取消时还要做两件事,一是把SKU库存加回去,二是把用户下单时使用的优惠券释放。
这里提醒一下,定时关单一定要加状态条件更新,比如这样:
UPDATE oms_order SET status = 6 WHERE id = #{orderId} AND status = 0如果用户恰好正在支付,而定时任务把订单取消了,这条更新语句会因为status不匹配而更新不到数据。更新行数为0时再判断是否需要回滚库存和券,避免重复执行。我在写类似功能时用这招避免过不少并发问题。
2.3 支付流程与回调幂等处理
商城系统到了支付环节,就绕不开对接微信支付或者支付宝。流程其实不复杂:用户在订单页发起支付,后端调用支付平台的统一下单接口,拿到支付二维码返回给前端,前端展示二维码,用户扫码支付完成后,支付平台会往你配置的回调地址发一条POST通知。
回调处理是这里的重中之重。有几个细节比业务本身更重要。
第一是验签。支付平台的回调信息里有签名,必须用官方SDK里的验证方法验一遍,防止伪造回调。我在开源项目里的实现中看到他们把验签放在回调接口最前面,验签失败直接返回给支付平台一个失败标识,让它稍后重试,这个顺序绝对正确。
第二是幂等。支付回调可能因为网络抖动被支付平台重发好几次,同一个订单的回调如果处理两遍,订单状态就会被更新两次,可能导致重复发货。解决思路很简单:回调里先根据订单号查订单状态,如果是已支付就直接返回成功标识,不再重复处理。有些项目还会用数据库唯一索引来锁订单号,双保险。
第三是回调入参处理。回调数据是表单格式还是JSON格式,不同支付平台不一样,用SpringBoot接收时要注意Content-Type,否则字段绑定不上。支付回调里的金额通常以“分”为单位,跟前端展示的“元”不同,换算关系容易出错。我见过有人在金额换算上丢了一分钱对不上账,排查半天发现是类型精度问题。所以订单金额最好用BigDecimal,别用double。
3. 后端实操:SpringBoot整合Mybatis的关键细节
3.1 依赖配置和SpringBoot全局配置
跑通这个商城的第一步,是确保你的开发环境已经有JDK 1.8以上、Maven 3.6以上、MySQL 5.7或者8.0,以及一个本地Redis。看代码仓库里的pom.xml,核心依赖就几个:spring-boot-starter-web、mybatis-spring-boot-starter、pagehelper-spring-boot-starter、mysql-connector-java、spring-boot-starter-data-redis,以及JWT相关工具包。
拿到源码后第一步不是急着读代码,而是先看application.yml。我总结了这个项目里需要重点配置的几个点:
| 配置项 | 示例值 | 说明 |
|---|---|---|
| spring.datasource.url | jdbc:mysql://localhost:3306/mall?useSSL=false&serverTimezone=Asia/Shanghai | 必须加serverTimezone,否则MySQL 8会报时区错误 |
| spring.datasource.username / password | root / 你自己的密码 | 记得改成你自己的,别直接跑默认的 |
| mybatis.mapper-locations | classpath:mapper/*.xml | 指定Mapper XML文件位置 |
| mybatis.configuration.map-underscore-to-camel-case | true | 开启下划线自动转驼峰 |
| pagehelper.helper-dialect | mysql | 指定分页插件方言 |
有些同学刚拿到项目时容易忽略map-underscore-to-camel-case这个配置。如果不开启,数据库表里的create_time字段就无法自动映射到实体类的createTime属性上,查出来全是null,看起来像数据丢了,其实是映射没配对。开启之后,Mybatis会在结果映射时自动把下划线风格的列名转换成驼峰风格的属性名,省事很多。
3.2 分页插件PageHelper的正确用法
商城系统的商品列表、订单列表,只要列表就分页。Mybatis里最常用的分页方案就是PageHelper,这个项目也用了它。用法看起来很简单:查询前调用一次PageHelper.startPage(),然后写正常的Mapper查询,PageHelper会自动在SQL后面拼接LIMIT。
PageHelper.startPage(pageNum, pageSize); List<Product> products = productMapper.selectList(categoryId); PageInfo<Product> pageInfo = new PageInfo<>(products);PageInfo里封装了总条数、总页数、当前页数据等,前端拿过来直接渲染分页组件。使用时有几个坑我必须讲清楚,不然分页会莫名其妙失效。
第一个坑,startPage之后只能跟一条查询语句,遇到多条Mapper查询时,后面的全都会加上LIMIT,数据就乱了。第二个坑,不要在循环里调用startPage,这会导致每循环一次就改变页码,数据结果完全不可控。第三个坑,最好在Service层调用startPage,而不是在Controller层调用,避免查询参数还没处理好就创建了分页上下文。
我实操中遇到过的最经典问题是:自定义的统计类SQL包含子查询,PageHelper自己生成的COUNT查询在复杂SQL下计数不对。解决办法是手写一个count查询语句,配置到对应Mapper里,PageHelper会自动优先使用你写的count语句。这个办法能救活一大批复杂分页报表。
3.3 Mybatis缓存机制:哪些地方真的适合开二级缓存
热词里很多人搜Mybatis缓存,我借这个项目多说一点。Mybatis有一级缓存和二级缓存。
一级缓存是SqlSession级别的,默认开启。同一个SqlSession里执行两次相同的查询,第二次直接命中缓存,不再查数据库。但要注意,Spring和Mybatis整合后,每次查询用的SqlSession是会被关闭和重建的,所以一级缓存的效果非常有限,尤其在开启了Spring事务后,才能在一个事务里共享同一个SqlSession从而用到一级缓存。别指望它能在高并发下分担数据库压力。
二级缓存是namespace级别的,也就是每个Mapper一个缓存区域。在XML里加一行<cache/>就能开启。开启后,同一个Mapper的查询结果会被缓存,但有一个很大的坑:缓存的数据在执行增删改时会被自动清空,可是如果项目里有多个Mapper操作同一张表,或者用到了多表联查,缓存数据可能产生脏读。
缓存理解起来很直观:一级缓存像你手里的草稿纸,用完了就扔;二级缓存像你的笔记本,换个Session还在;Redis则是大家共用的共享文件柜,多个应用节点都能访问。
对于商城系统这种场景,我的建议是不要轻易开Mybatis二级缓存,而是把Redis缓存用在更高价值的场景上,比如首页轮播、商品分类树、热门商品列表这些读多写少的数据。这个开源项目里,商品详情接口就用了Redis缓存,第一次查库后写入Redis,后面直接查Redis,缓存穿透、缓存雪崩的代码也处理了,这点非常值得学。
3.4 全局XSS过滤、上传PDF安全检查与统一异常处理
热词里有一条“SpringBoot项目全局过滤器处理上传pdf文件时xss攻击”,这个问题确实存在,而且不少新手项目根本没管。用户提交的富文本内容里如果带<script>标签,存到数据库再渲染到页面上,就可能执行恶意脚本盗取Cookie。商城系统的商品评价、后台公告这种带文本输入的场景,很容易中招。
这里用全局过滤器处理。继承OncePerRequestFilter,重写doFilterInternal方法,核心逻辑是把请求参数(包括JSON body)里的HTML标签清洗掉。处理JSON body有一个坑:如果直接调用request.getInputStream()去读body,Controller层再读就什么都没有了,因为输入流已被消费。解决办法是用一个包装类缓存请求body,过滤器里解析、清洗,再把清洗后的内容传给后面。
如果你想用SpringBoot做PDF上传拦截,只检查扩展名和Content-Type是远远不够的,因为PDF文件可以内嵌脚本,比如通过JavaScript触发的漏洞或者利用JBIG2编码的恶意内容。稳妥的做法是用PDFBox库把PDF内容提取出来,检查是否包含Javascript对象或者可执行代码片段,同时限制上传文件大小、设置存储路径可读写权限。这些都是我自己处理安全问题时常用的方案。
除了XSS,商城项目还需要一个全局异常处理。用@RestControllerAdvice加@ExceptionHandler就可以兜底,把业务异常、参数校验异常、系统异常分别转成统一结构的JSON返回。这样Controller层就干净了,不用每个方法都try-catch。我在这个项目里看到他们的Result对象里包含了code、message、data三个字段,前端配合axios拦截器统一处理错误提示,体验很好。
事务管理上,@Transactional要注意自调用失效问题。如果类内部一个方法调用另一个带@Transactional的方法,事务注解是无效的,因为Spring事务是基于AOP代理的,内部调用不会经过代理。解决办法是拆到不同的Bean里调用,或者通过ApplicationContext获取代理对象。如果在商城项目的订单服务里看到类似问题,可以往这个方向排查。
3.5 写Mapper XML时容易踩的细节
这个项目的Mapper XML文件里有几个非常值得学习的点。
一个是对#{}和${}的选择。#{}是预编译参数占位符,最终变成?,Mybatis会帮你加单引号并做转义,能防SQL注入;${}是字符串拼接,直接把内容拼到SQL里,有注入风险,但某些场景必须用,比如动态排序字段。写动态排序时如果兜不住,用户传一个order by 1 desc进来,整个查询就可能被拖慢甚至报错。正确做法是用白名单校验排序字段。
还有Like查询的写法。我见过很多人直接写LIKE '%${keyword}%',这是又注入又不优雅。正确写法是:
SELECT * FROM product WHERE name LIKE CONCAT('%', #{keyword}, '%')这样既能避免注入,也能正常走预编译。另外,动态SQL里用if判断时,注意参数为数字时0的判断。if test="status != null and status != ''"在status为0时是成立的,但如果写成了status == true之类的判断,0会被当成false导致条件失效,这种Bug极难排查。
4. 前端实操:Vue项目搭建与商城页面核心要点
4.1 Vue开发环境配置与项目初始化
很多新手卡在前端环境搭建这一步,不是代码问题,是工具链问题。这里我按常见的实操顺序梳理一遍。
先装Node.js,不要装最新版,建议用LTS版本,因为新版的npm和工具链偶尔会有兼容问题。装完在终端里执行node -v和npm -v,能输出版本号就说明成功。接着设置npm镜像,国内直接用官方源下载依赖慢到崩溃,换成国内镜像会舒服很多。你可以用cnpm,也可以在用户目录下建一个.npmrc文件,把registry换成国内镜像地址,效果一样。我个人的习惯是保留npm,只改registry配置,因为cnpm有时候对package-lock.json的处理有差异。
依赖装完后,用Vue CLI创建项目。执行vue create mall-admin,选择默认预设或者手动勾选Router、Vuex,等一两分钟脚手架就生成完了。然后npm install再npm run serve,浏览器打开localhost:8080,看到Vue的默认页面就说明环境没问题。
如果要用VSCode写Vue代码,我建议装的插件有:Volar(Vue3语法高亮和类型提示,Vue2项目用Vetur)、ESLint(代码规范检查)、Prettier(格式化)。装好这些,代码风格自动统一,省心不少。
4.2 前端路由参数传递与Axios封装
商城项目里最典型的场景就是商品列表点击进入详情页,需要把商品ID从列表页传到详情页。Vue Router有两种传参方式,一定要分清。
一种是params传参,配合路由配置里定义动态路径参数:
// 路由配置 { path: '/product/:id', component: ProductDetail } // 跳转方式 this.$router.push({ name: 'ProductDetail', params: { id: 123 } }) // 接收方式 this.$route.params.id另一种是query传参,链接上会带问号参数:
this.$router.push({ path: '/product', query: { id: 123 } }) // 接收 this.$route.query.id区别在哪里?params传参如果配合name跳转,刷新页面后参数容易丢失,而query参数因为挂在URL上,刷新后还能拿到。所以详情页的商品ID我强烈建议用path加query的方式传,刷新不丢。
接口请求这块,商城前端项目一般会对axios做封装。核心工作有三件:设置baseURL、请求拦截器里注入Token、响应拦截器里统一处理业务错误码和HTTP状态码。比如后端返回code等于500时,拦截器直接弹出Message错误提示,前端业务代码就不需要每个接口都重复写错误处理逻辑。这个封装在各后端项目里大同小异,核心在于拦截器里不要做太重的同步逻辑,否则容易影响请求响应时间。
4.3 商品视频m3u8播放与调试工具链
现在商城系统很多商品详情页会放视频,m3u8格式很常见。Vue里播放m3u8,我惯用的方案是video.js加videojs-http-streaming插件,或者用hls.js。如果你不想引入太重的东西,用hls.js简单处理:
if (Hls.isSupported()) { const hls = new Hls(); hls.loadSource('https://example.com/demo.m3u8'); hls.attachMedia(videoElement); }hls.js的优势是能在不支持原生HLS的浏览器上通过MSE实现播放,兼容性比原生video标签好很多。而且调试m3u8时,F12面板里能看到hls.js的日志和分片请求,定位源码是七牛云还是腾讯云,很快就能分清。
调试Vue项目,Vue DevTools是必装工具。浏览器扩展装好后,在开发模式下可以直接查看组件树、props、data,还能在Vuex面板里看到状态变更记录。调试路由跳转时,切换到Routing面板就能看到当前路由名称、路径和参数,结合VueRouter的导航守卫断点,定位问题效率翻倍。
4.4 VSCode + Vue 怎么打包成手机App
热词里有条“vscode +vue 怎么制作手机软件”,我顺带说一句。如果商城做完H5之后想打包成App,最简单的方式是用Capacitor或者uni-app。Capacitor可以把你的Vue项目打包成Android/iOS应用,它在WebView里加载打包后的静态资源,同时通过桥接层调用相机、推送、文件系统等原生能力。用起来就是在项目根目录初始化Capacitor,把webDir指向前端构建输出目录,然后编译Android工程。
打包后要重点测的是移动端路由刷新问题。因为App里的WebView加载的是本地资源,直接使用history模式的路由,刷新可能会404。解决思路是改hash模式,或者在原生层处理路由回退。商城这种项目本身不复杂,用hash模式是最省心的方案。
5. 本地跑起来:完整部署流程与常见问题排查
5.1 后端启动流程
启动后端前,先确认基础设施都准备好了。MySQL要建好数据库并执行项目里的init.sql脚本,Redis要启动服务,别漏了哨兵密码配置。然后打开application.yml,把数据库用户名密码改成你自己的,Redis连接信息也检查一下。确认没问题后,在项目根目录执行mvn spring-boot:run,或者用IDE直接运行主类。
启动时如果报端口被占用,最常见的是8080端口被之前某个服务占用。用netstat -ano | findstr 8080找到PID,然后在任务管理器里结束进程就行。还有一点,SpringBoot项目启动时的Banner就是控制台那一大段字符,很多看不懂的地方其实是你自己配的banner文件,别紧张。
我用idea跑这个后端时踩过一个坑:编译时Mapper XML文件没有复制到target目录,启动后Mybatis一直报“Invalid bound statement (not found)”。原因是pom.xml里没有把src/main/resources下的xml文件打包进去。解决办法是在pom里加一个resources配置,把mapper目录和xml包含进来。这个问题查了半小时,最后看到target目录里没有xml文件才反应过来。
5.2 前端启动流程与跨域处理
前端项目拿到后,先npm install装依赖,再npm run serve启动。如果端口配置冲突,在vue.config.js里改port就行。启动后如果页面能打开但接口请求全部失败,基本就是跨域问题。
前后端分离开发时,前端跑的端口是8080(或其它),后端是8080(或9090),两个端口不一样,浏览器就会因为同源策略拦截跨域请求。简单的做法是在前端配置devServer代理,把/api开头的请求都转发到后端地址:
devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }代理的好处是浏览器看到的前端地址就是请求地址,没有跨域,后端也不需要额外写CORS配置。生产环境的话,用Nginx把/api反向代理到后端服务,思路一模一样。
5.3 高频问题与排查技巧速查表
把我在实操中遇到过的问题整理成一张表,方便你对照排查。
| 现象 | 原因 | 解决方式 |
|---|---|---|
| 分页查询结果越界或总页数为0 | PageHelper.startPage之后跟了多条SQL | 把startPage移到目标查询前,或使用PageHelper.clearPage() |
| 实体类日期字段查出来为null | 没开启驼峰映射 | application.yml里加map-underscore-to-camel-case=true |
| 数据库时间比本地晚了8小时 | 连接串缺少时区参数 | url加serverTimezone=Asia/Shanghai |
| 上传图片后访问404 | 静态资源路径没映射 | 配置WebMvcConfigurer映射磁盘目录到虚拟路径 |
| npm install报node-sass错误 | Node版本和高版本node-sass不兼容 | 升级到sass或docker,或者切换Node版本 |
| 前后端联调跨域 | 开发环境无代理或后端CORS未配置 | 配置devServer.proxy或全局CORS过滤器 |
| Mybatis报Invalid bound statement | XML没有被打包或者namespace写错 | 检查pom.xml打包配置,核对mapper接口与XML namespace |
还有一个热词搜得比较多:“怎么将SpringBoot jar反编译成项目”。这个在商业项目里偶尔也会用到,比如只有生产jar没有源码,要排查问题。用JD-GUI打开jar包能看到class文件的源码,用CFR或者Procyon可以把class反编译成Java文件,class文件里的注解和泛型大多能还原。要注意的是,反编译只能用于学习或者合法授权的场景,不要拿来搞别人的闭源代码。把lib目录下依赖的jar也反编译的话,可以分析出每个类之间的关系,不过代码可读性会比较差,通常只用来应急定位问题。
5.4 这个商城项目后续可以怎么扩展
你要真把这套商城跑通了,后面可以做的事情非常多。
文件存储可以接入MinIO。现在商品图片存在本机磁盘,搬到分布式环境就废了。MinIO是开源的,跟SpringBoot整合也不难,用SDK把文件传到MinIO里,再配置Nginx做访问代理,前端图片地址就变成http://你的域名/桶名/文件名了。这个改造过程能学到对象存储的设计思路。
搜索可以引入Elasticsearch。商品表和SKU表MySQL查询性能很有限,等数据量到几十万条,关键词模糊搜索会越来越慢。把商品数据同步到ES,用分词能力做全文检索,响应时间可以从秒级降到毫秒。同步的话可以在商品维护接口里加逻辑,也可以监听数据库binlog,后者更贴近生产实践。
秒杀可以加Redis + Lua脚本。Redis的原子性操作天生适合做库存扣减,配合Lua脚本可以把判断库存、扣减库存、记录用户等操作合并成原子操作。但这套方案做好也不简单,需要处理超卖、限流、接口防刷,刚好用于练习高并发场景。
我个人认为,从这套商城项目学到的最核心的东西是分层思维。后端Controller层只做参数接收,Service层做业务逻辑,Mapper层只做数据访问;前端页面组件和API请求分离,公共逻辑抽成工具类。只要这些分层做清晰了,代码再多、功能再复杂也不至于混乱。
最后分享一个小经验:跑通这个项目后,我建议你把它当成自己的代码重构一遍。第一步不改功能,只把每个Controller、Service、Mapper的职责重新梳理一遍,去除重复代码;第二步尝试加一个自定义功能,比如商品收藏;第三步把订单模块改造成Redis缓存方案。这三步做完,你对SpringBoot、Vue、Mybatis这套技术栈的理解,会比单纯看教程强十倍。