我大概一周前把整套商城系统的代码又从头到尾整理了一遍,正好趁这个机会把实现过程完整复盘出来。标题写着“SpringBoot+Vue网上购物商城系统”,本质上就是一个典型的前后端分离电商项目:前端用Vue做页面交互,后端用SpringBoot提供接口,MySQL存数据,MyBatis管数据库操作。这套组合在目前的毕业设计和中小型项目里出现频率极高,原因是它覆盖面广、难度适中、又能把主流技术栈串起来。这篇就把我自己的设计思路、数据库建表细节、核心接口实现、踩坑记录全部铺开来讲,尽量让照着做的朋友少走弯路。
如果你正准备做类似的项目,或者正在纠结技术选型,可以先明确一下这篇文章能给你什么:一套可以直接参考复现的商城系统整体设计,从数据库字段级别到接口返回结构都有说明;同时把几个最容易翻车的地方——比如订单状态管理、库存扣减、登录鉴权、前后端联调——拿出来单独讲。文章全程用我实际开发的经验说话,不堆概念,直接给方案。
1. 技术选型背后的真实考量:为什么是SpringBoot+Vue+MyBatis
1.1 这套技术栈解决的核心痛点
很多人在选型时容易陷入“追新”的误区,总想用最新的框架、最火的中间件。但真实项目里最重要的是稳妥和够用。SpringBoot能成为Java后端的主流选择,核心原因是它把Spring生态里最繁琐的配置工作全部自动化了。以前搭一个SpringMVC项目,XML配置能写几百行,现在SpringBoot靠自动配置和Starter机制几行依赖就能跑起来。这对商城这类CRUD密集型业务来说,开发效率提升是实打实的。
Vue这边的情况类似。它的上手曲线比React平缓,模板语法更接近传统的HTML思维,再加上ElementUI这类组件库,后端出身的人也能很快写出像样的管理界面。商城系统的用户端和管理端都需要大量表单、表格、弹窗交互,Vue的双向数据绑定让表单处理变得非常直接。如果你去做技术调研就会看到,国内大量中小型电商项目和毕业设计都选了Vue做前端,生态成熟、资料多、出问题搜得到答案,这一点在中后期调试时价值很大。
MyBatis和SpringBoot的组合也经历了市场检验。相比JPA那种“全自动”的ORM,MyBatis把SQL控制权完全交还给开发者。商城系统里有大量复杂的多表联查、条件筛选、统计报表,这些场景下MyBatis的灵活性优势特别明显。写SQL的人能精确控制每一条查询语句,性能优化、索引调整都心里有数。另一个隐藏优势是排错方便:SQL是显式写出来的,哪里慢、哪里结果不对,直接看语句基本能定位到问题。
1.2 前后端分离架构下的模块划分
前后端分离不是说“前端一个项目、后端一个项目”就完了,关键在接口契约和联调规范。我在做这个商城系统时,把整体划分成三个模块:后端服务端、前端用户端、前端管理端。后端只负责提供RESTful接口,不做任何页面渲染;用户端面向普通消费者,包含首页、商品列表、商品详情、购物车、订单等页面;管理端面向运营人员,包含商品管理、订单处理、用户管理、分类管理、轮播图配置等。
三个模块之间通过HTTP接口通信,数据格式统一用JSON。这里有一个很多人容易忽略的点:接口返回的结构一定要统一。我在项目里定义了一个通用的返回体Result,包含code、message、data三个字段。code为200表示成功,400表示参数错误,401表示未登录,500表示服务器内部错误。所有接口都走这一个结构,前端封装request工具时就能统一处理错误码和拦截器,代码能少写一大半。
模块划分还要考虑Maven的多模块结构还是单模块。考虑到商城系统体量不算太大,我选择了单模块SpringBoot工程,内部按业务分包:controller、service、mapper、entity、config、common、utils。如果你的项目预期会扩展得很大,那用多模块结构会更合适,但中小项目真没必要一上来就拆五六个模块,维护成本反而更高。
2. 数据库设计:购物商城最关键的12张表到底怎么建
2.1 用户、商品、分类这三张基础表的字段设计
数据库设计是整个商城系统的地基,后面接口写得再漂亮,表结构不合理也是白搭。我这次总共设计了12张核心表,先把最基础的三张说清楚。
用户表在商城系统里不算复杂,但字段设计的有几个细节要留意。除了常见的id、username、password、nickname、phone、email、avatar、create_time之外,我额外加了两个字段:status用来控制账户是否启用(0禁用、1正常),is_admin用来区分普通用户和管理员。密码存储我直接用的MD5加盐处理。生产环境肯定要升级成BCrypt这类更安全的算法,但对教学型项目来说MD5加盐已经能演示清楚“不能明文存密码”这件事。
商品表是字段最多也最需要斟酌的一张表。我设计的核心字段如下:id、category_id(关联分类表)、name、subtitle(副标题)、main_image(主图)、detail(富文本详情)、price(原价)、promotion_price(促销价)、stock(库存)、sales(销量)、status(上下架状态)、create_time、update_time。这里重点说明一下price和promotion_price分开设计的原因——电商系统的价格经常需要做活动,如果只存一个价格,改价就得直接动主字段,容易出问题。分开存之后,展示时优先取促销价,没有促销活动就取原价,逻辑清晰且方便后续扩展限时折扣这类功能。
还有一个字段要提醒:status最好用int类型而不是boolean。虽然Java里Boolean更直观,但数据库层面int的扩展性更好,以后如果有“草稿”“上架”“下架”多个状态,int直接加枚举值就行,Boolean就死了。这是我在项目里吃过大亏后总结的经验。
分类表比较简单,id、name、icon、sort_order、status五个字段就够了。商城系统的分类一般最多两级,不需要做成无限极分类,否则前端渲染和后端查询都会复杂化。sort_order控制同级分类的展示顺序,前端列表按这个字段升序排即可。
2.2 购物车与订单表的状态流转设计
购物车表在中小型项目里常常被人做过简化,有些同学直接不做购物车表,只在Redis里存。但如果你要交付源码,建议还是建表。Redis方案在真实商城里的确用得多,因为购物车读写频繁、不要求强一致性,但纯教学项目里后端一套完整表结构更直观易懂,而且能在MySQL里清晰演示关联查询。
我的购物车表字段是:id、user_id、product_id、quantity、checked(是否选中)、create_time、update_time。唯一约束加在(user_id, product_id)上,这样同一个用户添加同一件商品时走的是数量累加逻辑而不是新增一条记录,避免脏数据堆积。
订单相关表是商城系统里最复杂的部分,我拆成了三张:订单主表、订单明细表、订单日志表。因为一张订单可能要买多种商品,如果只建一张表,字段就会冗余得非常厉害。
订单主表主要字段:order_no(订单编号,唯一)、user_id、total_amount(总金额)、pay_amount(实付金额)、pay_type(支付方式)、status(订单状态)、consignee_name(收货人)、consignee_phone、consignee_address、pay_time、deliver_time、receive_time、create_time。订单明细表:id、order_id、product_id、product_name(快照)、product_image(快照)、price(下单时价格快照)、quantity、total_price。明细表里存“快照”是电商系统设计中非常重要的一环:下单时把商品名、图片、价格都复制一份到订单明细里,后续就算商品改名涨价,历史订单依然能展示当时的真实信息。
订单状态我用了int类型的枚举:0待付款、1待发货、2待收货、3已完成、4已取消。这里有一个需要想清楚的逻辑:订单状态不是用户想怎么改就怎么改的,必须按固定方向流转。待付款可以取消或付款;付款后进入待发货;卖家发货后进入待收货;用户确认收货后进入已完成。这个流转规则在接口层就要严格限制,不能在代码里随手setStatus。
2.3 下单时事务与库存扣减的实现思路
下单是整个商城系统里最容易出问题的功能,核心难点在库存扣减和事务一致性。我不止一次看到有同学这么写:先从商品表查出库存,判断库存是否大于购买数量,如果满足就把库存减掉,然后插入订单。这个逻辑在并发量低的时候看起来没错,但一旦两个请求同时进来,就可能出现超卖——两个人都查到库存还剩1件,都执行了减库存,最后库存变成负数。
正确做法有几种,我在这个项目里选的是“乐观锁+事务”的组合。具体来说,更新库存的SQL语句直接带上库存条件:update product set stock = stock - #{quantity} where id = #{productId} and stock >= #{quantity}。MyBatis执行后返回影响行数,如果影响行数是0,说明库存不够或者数据已被修改,这个时候直接抛出异常让事务回滚。
整个下单操作必须放在同一个事务里。我的实现是在Service层的方法上加@Transactional注解,方法内依次执行:校验商品状态和价格、扣减库存、写订单主表、写订单明细表、清空购物车对应商品。任何一个环节失败,前面已经执行的数据库操作全部回滚。这里有一个容易踩的坑:库存扣减一定要先于订单写入的执行顺序思考清楚。虽然事务最终会保证一致性,但如果先写订单再扣库存失败,回滚没问题;而如果先扣库存后写订单,订单写了失败,库存也要回滚。实际执行顺序我习惯把订单写入放在前面、扣减库存放在后面,这样库存操作尽可能晚地持锁,能稍微减少锁竞争时间。
3. 后端代码如何分层:Controller、Service、Mapper的职责边界
3.1 SpringBoot核心配置与MyBatis的整合细节
后端代码不是把功能写出来就完了,分层清晰不清晰直接影响后期维护。我在这次项目里严格遵循了经典的四层结构:Controller负责参数接收和响应封装,不写任何业务逻辑;Service负责业务规则,比如下单流程、库存校验、状态流转;Mapper负责数据库操作,每一个方法对应一条SQL;Entity对应数据库表的映射类。这样做的好处是当接口出问题,你能直接定位到层,不用在大杂烩代码里找线索。
SpringBoot整合MyBatis其实非常快。pom.xml里加两个关键依赖:mybatis-spring-boot-starter和mysql-connector-java。配置文件里指定数据库连接信息、Mapper XML文件位置、实体类别名包路径。有一个细节要提醒:Mapper接口和XML文件的匹配规则。我用的是XML映射方式,所以Mapper接口路径和XML文件路径必须一致,而且XML的namespace要严格写成Mapper接口的全限定名,少写一个字符启动就会报错。
配置文件我习惯用application.yml,比properties格式可读性更好。连接池选的是HikariCP,这是SpringBoot 2.x之后的默认连接池,性能好且无需额外引入。数据库连接的配置里有一项要特别留意:serverTimezone必须设置,否则连接MySQL 8.x时会报时区错误。我一般设成Asia/Shanghai。
3.2 登录鉴权与拦截器的完整链路
商城系统里不是所有接口都能随便访问的。查看商品列表谁都可以,但提交订单、查看个人订单列表就必须先登录。管理端的商品上架、订单发货更是只有管理员能做。这个需求靠SpringBoot拦截器加自定义注解就能实现,不需要引入Spring Security这种重量级框架。
我的实现思路是:用户登录成功后,后端生成一个token字符串(用UUID即可,简单靠谱),把token作为key存进Redis,value是用户ID,同时设置过期时间(我设了2小时)。前端拿到token后存在本地存储里,每次请求都在Header里带上Authorization字段。后端定义一个HandlerInterceptor,在preHandle方法里取出Header里的token,根据token去Redis查用户。
这里有一个关键点:不是所有接口都要校验登录。我在项目里定义了一个自定义注解@LoginRequired和@AdminRequired,Controller方法上加了哪个注解才做对应的校验。拦截器里通过HandlerMethod拿到方法上的注解,再做判断,这样灵活性极高。有一个坑需要提醒:SpringBoot的拦截器默认不拦截静态资源,但前后端分离项目里前端资源由Nginx托管,后端基本不涉及静态资源,所以这个问题不大。
管理员校验要比普通登录校验多一步:从Redis取出用户ID后,还要查用户表的is_admin字段,不是管理员直接返回403。这部分逻辑我单独写了一个注解和对应的拦截校验逻辑,和登录校验分开,职责更单一。
3.3 商品查询、购物车管理的接口设计实例
商品查询接口看起来简单,但真正设计时要考虑的细节不少。首先是列表接口要支持多条件筛选:按分类筛选、按价格区间筛选、按关键词模糊搜索、按销量或价格排序。如果筛选条件都靠写死SQL来实现,那接口就没法复用了。
我的商品列表接口设计为:GET /product/list,参数包括categoryId、keyword、minPrice、maxPrice、sort、pageNum、pageSize。Mapper里用动态SQL拼接,根据参数是否存在来追加对应的查询条件。排序我通过一个sort参数控制,内部用switch判断映射到不同的ORDER BY语句(sort=1按销量、sort=2按价格升序、sort=3按价格降序)。这里没有用SQL拼接字符串的方式来拼接排序字段,避免SQL注入风险。
商品详情接口就是GET /product/detail/{id},返回内容包括商品基础信息、富文本详情、所属分类名称。前端详情页打开时,这个接口要把所有能展示的数据一次性返回,不要让前端再调三四个接口拼数据,那样会显著拖慢页面加载。
购物车管理我设计了四个接口:加入购物车、修改购物车商品数量、删除购物车商品、查看购物车列表。加入购物车时后端先查购物车表里是否已有该用户和该商品的记录,有就数量加一,没有就新增记录。这里要处理好一个并发问题:如果用户快速点击两次加入购物车,两个请求同时到达,可能都会发现没有记录,然后各插入一条。前面的唯一索引(user_id, product_id)在这里就发挥作用了,第二次插入会直接报唯一约束冲突,代码里捕获这个异常再转成数量累加即可。
4. 前端Vue页面:从路由骨架到购物车交互
4.1 Vue项目初始化与ElementUI组件引入
前端部分用Vue CLI初始化工程是最快的。运行vue create mall-web创建项目,选Router和Vuex这两个插件就够了。ElementUI的引入有一点要说清楚:完整引入虽然省事,但打包体积很大。我在这套商城系统里用的是按需引入方案,通过babel-plugin-component插件,在main.js里只注册实际用到的组件,比如Button、Input、Table、Dialog、Form等。这样首屏加载会快不少,尤其像商城这种图片很多的系统,首屏速度直接影响用户留存。
路由设计上要区分用户端和管理端。用户端是普通商城页面,比如首页、列表页、详情页、购物车、结算页、个人中心。管理端是另一个独立的布局结构,我用嵌套路由来实现:Layout组件做侧边栏加顶栏的整体框架,里面嵌套子路由作为各个管理功能页。路由守卫里做登录校验:进入购物车、个人中心页面时判断是否有token,没有就跳到登录页。进入管理端路由时额外校验是否为管理员,这个校验我建议在后端接口层面再做一次拦截,前后端双重校验防呆。
4.2 商品列表、详情、购物车三个核心页面的实现
商品列表页是用户进入商城后接触最多的页面。我采用了两级结构:顶部是分类栏,点击不同分类切换商品数据;下面是商品卡片网格,每张卡片显示主图、名称、价格和销量。分类数据在页面加载时一次性拉取存入Vuex,这样切换分类时不用重复请求。商品数据通过接口动态获取,每次切换分类或触发搜索时重新请求。
列表页有一个性能细节要提一下:图片懒加载。ElementUI的Image自带lazy属性,但el-image外面再包一层卡片会更复杂,所以我直接用自定义指令v-lazy配合原生img标签实现,加载前显示一张轻量的占位图,滚动到可见区域才加载真实图片。用户滚动列表页时体验会顺滑很多。
商品详情页的布局我参考了主流电商的做法:左侧是商品主图,右侧是价格、标题、库存、数量选择器、加入购物车按钮和立即购买按钮,下方是富文本详情区域。数量选择器用el-input-number组件,最大数量动态绑定为商品库存,这样前端先拦一道,避免用户选到超过库存的数量。加入购物车成功后我做了两步反馈:第一步弹出提示“已加入购物车”,第二步操控右上角购物车图标做一个小动画,增强操作确认感。
购物车页面的核心是选中状态管理和合计金额计算。这里我把购物车列表存在Vuex里,每一个购物车条目都有checked字段。页面下方有个全选checkbox,控制所有条目的选中状态;每个条目右侧有自己的checkbox;底部工具栏实时计算选中商品的总金额。计算的总金额直接通过computed属性实现,根据Vuex里checked为true的条目,用单价乘以数量累加。这里要特别注意:价格金额在前端展示时必须做保留两位小数的格式化,否则会出现0.30000000000000004这种浮点误差的尴尬情况。
4.3 前后端联调中的接口封装与状态管理
前后端分离项目里,前端的请求封装决定了联调效率。我在utils目录下建了request.js,基于axios封装。核心逻辑包括:baseURL统一配置、请求拦截器自动携带token、响应拦截器统一处理code。后端返回的code不是200时,响应拦截器全局弹出错误提示,省去每个页面单独写错误处理的重复代码。当code是401时自动跳转登录页,并把当前页面的路由记下来,登录成功后可以跳回原来想访问的页面。
Vuex的状态管理我建议不要把太多数据放进去。很多人写商城系统时恨不得把所有接口返回都存到Vuex里,导致页面关系混乱、状态难以追踪。我的原则是:共享且跨页面使用的数据才放Vuex,比如用户信息、购物车条目数;单个页面内部的数据就放组件自己的data里。购物车条目数在用户添加商品后、购物车页面修改数量后都要更新,放在Vuex里配合mutations来统一更新最合适。
联调过程中还有一个经验:接口路径尽量保持前后端一致。比如后端是/product/list,前端就不要自己改成/listProduct再在代理配置里转来转去。前后端各自独立,但契约要稳定。我在开发时用Vite的proxy配置把/api前缀的请求代理到本地后端的8080端口,上线后由Nginx统一转发,两边不用改代码。
5. 订单流程的完整实现:从购物车提交到订单状态变更
5.1 订单生成的业务逻辑与幂等性处理
从购物车跳到结算页,填好收货地址后点击提交订单,这一系列动作背后是商城系统最核心的业务逻辑。提交订单接口接收的参数包括收货人信息、购物车条目ID列表。后端先根据用户ID和条目ID列表查出对应的购物车记录,再批量查出商品信息,逐条计算总价。
这里要考虑一个安全问题:价格不能信任前端传的。有些开发者图省事,让前端直接把订单总金额传到后端,这是绝对错误的做法。用户可以通过浏览器开发者工具修改请求参数,把1000元的订单改成1元。我的做法是前端只传购物车条目ID,后端根据数据库里的商品单价和数量重新计算金额,计算结果才是唯一可信的金额。
幂等性处理是订单提交的另一个关键点。用户在结算页点“提交订单”按钮时,如果网络延迟导致前端没有拿到响应,用户很可能会再点一次,这时候就会产生两条重复订单。解决方案有两种:前端层面是提交后按钮立刻置灰加loading;后端层面是生成一个唯一的幂等标识,提交订单接口先校验标识是否存在。我在这次项目里选了相对简单的方案:前端维护一个orderToken,每次进入结算页生成一个UUID,提交订单时一起传给后端。后端用这个UUID作为唯一约束存入订单表,重复提交时数据库直接报冲突,代码捕获后返回“订单已提交,请勿重复操作”的提示。
5.2 订单状态机的实现与用户操作映射
订单状态机听起来很高端,但其实本质就是规定好“什么状态下能做什么操作”。我用一个状态枚举加一个状态流转工具类来集中管理。核心代码如下:
public enum OrderStatus { WAIT_PAY(0, "待付款"), WAIT_DELIVER(1, "待发货"), WAIT_RECEIVE(2, "待收货"), COMPLETED(3, "已完成"), CANCELED(4, "已取消"); private final Integer code; private final String desc; }用户的每一个操作都只对应一个合法的状态流转方向。用户取消订单只允许从待付款状态发起;管理员发货只允许从待发货状态发起;用户确认收货只允许从待收货状态发起。代码里我写了一个简单的校验方法,入参是当前状态和目标状态,内部配置好合法迁移映射,不匹配就直接抛业务异常。
订单日志表在整个流程里的作用容易被新手忽略。但你想一下:如果用户说“我明明付款了,为什么商家不发货”,这时候你查订单表只能看到当前状态,完全不知道状态是什么时候变的。有了订单日志表就能完整追溯操作轨迹。我在下单、支付、发货、收货、取消这几个关键节点都会插入一条日志,记录订单号、操作前状态、操作后状态、操作人、操作时间,后面排查问题、处理售后都靠它。
支付环节在真实生产环境要对接微信支付、支付宝支付,但教学项目里我做了模拟支付。用户在订单列表里点“去支付”,后端直接走一个模拟支付接口:校验订单状态是待付款,把状态流转为待发货,写入支付时间和支付日志,同时把对应商品的销量增加。如果你后续要接真实支付,只要替换这个模拟接口的内部实现即可,外部接口路径不用动。
6. 本地跑通与上线部署的经验总结
6.1 本地环境配置与常见启动报错
我把整个项目从零跑通的整个过程中总结出几个高频报错点,按出现的概率排序:
连接数据库报错是出现率最高的。常见的原因是MySQL版本和驱动版本不匹配。我用的MySQL 8.x,驱动必须在5.1.47以上或者用8.x对应的驱动,否则会抛异常。另外连接URL里的serverTimezone不设置、用户名密码配置错误、MySQL服务没启动也都是常见问题。排查顺序建议是:先命令行ping一下3306端口通不通,再用Navicat测一下账号密码,最后才看SpringBoot的错误日志。
MyBatis的Mapper XML文件位置配置错误是第二大高频问题。报错通常是Invalid bound statement (not found),意思是接口方法找不到对应的SQL语句。排查思路:先确认application.yml里的mapper-locations路径和实际存放目录一致;再确认XML文件里的namespace是Mapper接口的全限定名;最后确认接口方法和XML里SQL的id完全一致,包括参数类型和返回类型。这三个地方任何一个不对都会报这个错。
Vue前端启动失败多数是依赖安装问题。npm install中途断网、Node版本过低都会导致报错。我习惯用cnpm或者pnpm把依赖重新拉一遍,再执行npm run serve。还有一次遇到端口被占用,Vue默认跑在8080端口,和SpringBoot默认端口冲突了,我还以为是后端没起来,实际是前端开发服务器的端口问题。解决方法是前端在vue.config.js里把devServer端口改成3000,或者关闭占用8080的程序。
6.2 部署到服务器时最容易被忽视的三件事
项目本地跑通后要部署上线,有三个坑我当年是踩过几次才总结出来的,提前写在这里帮你避开。
第一件事:SpringBoot的打包配置。默认的spring-boot-maven-plugin会把项目打成可执行jar包,但如果你的pom.xml里没有显式配置mainClass,打包时偶尔会报“找不到主类”的错误。更常见的问题是打包后运行jar却提示端口被占用或者配置文件读取不到,这个往往是因为你直接运行了原本的Application类所在的target目录,但没有把配置文件一起带过去。用mvn clean package -DskipTests打完包,确认target目录下生成了xxx.jar,然后直接java -jar xxx.jar跑起来,配置文件本身就会打进jar包里,不要额外手动拷配置文件,否则会覆盖出错。
第二件事:MySQL的字符集没设成utf8mb4。如果你在本地开发时MySQL用的默认字符集是latin1,而本地测试数据都是中文且没有乱码,那大概率是建库时手动指定过字符集。部署到服务器时如果图省事直接建库不写字符集,商品详情页的中文大概率会变成问号。建库语句我用的是CREATE DATABASE mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,这个一定要牢记。utf8mb4比utf8多了对emoji表情的支持,现在用户的收货地址备注里真的会带表情,不用utf8mb4就等着乱码吧。
第三件事:Nginx的静态资源缓存策略。前端打包后的dist目录里,index.html和assets目录需要区分处理。assets里的JS、CSS文件名带有hash值,可以设置长时间的浏览器缓存,大幅提升二次访问速度;index.html不能缓存,否则每次发版后用户访问的还是旧版页面。我的Nginx配置里对静态资源设置了30天缓存,对index.html设置了no-cache。如果你不区分,每次发布新版后用户浏览器还在用旧的打包文件,排查起来会非常困惑——接口明明是新版,页面却还是老样子。
还有一个部署细节是跨域问题。前后端分离部署时,如果前端和后端不在同一个域名下,浏览器会拦截跨域请求。我的做法是用Nginx反向代理:把所有/api路径的请求转发到后端的8080端口,在Nginx层面把接口路径和页面路径统一到同一域名下,彻底避免跨域问题。这种方式也方便后续负载均衡的扩展,是我在多个项目里验证过最省心的方案。
7. 这套商城系统的后续扩展建议
项目本身已经能跑通完整的购物流程,但距离一个生产级的商城系统还有一些距离。如果你有精力和时间,我建议从这几个方向继续扩展:接入真实的第三方支付,把模拟支付替换成微信或支付宝的支付SDK,同时补上支付回调通知的处理逻辑,这里能学到很多关于接口安全签名的知识。引入Redis缓存热门商品数据,减轻数据库压力,同时可以用Redis实现购物车临时数据的存储。再加一个定时任务来处理超时未付款订单的自动关闭,SpringBoot自带的@Scheduled注解就能实现轻量级的定时任务。
管理端这边可以继续完善的是数据统计模块。用ECharts做销售趋势图、分类占比饼图、热销商品排行,这类功能既能在展示上提升项目效果,也是面试时能聊上几句的业务亮点。商城系统的核心业务链路你已经打通,在这个基础上加功能都是水到渠成的事情。