
做服装类电商系统和做通用的商城并不一样SKU维度、库存口径、订单状态流转都有自己的特殊性。这个“衣依”服装销售平台项目采用SpringBootVue3MyBatis前后端分离架构配合MySQL存储是一个非常适合拿来练手也适合做二次开发学习项目实践或业务原型验证的完整系统。从标题看它是一套现成的源码工程但如果你只把它当成“下载即用”的东西就错过了最有价值的部分。我实际把它完整搭建、调试、再修改过一遍从数据库初始化到前端页面跑通再到后端接口联调中间踩了不少文档里没写的坑。这篇文章就围绕这套系统的落地过程把这几个层面的细节一次讲透。1. 项目定位服装电商比通用商城多出来的那些复杂性1.1 服装业务的“一衣多规格”本质大多数商品系统在最简单的情况下一张商品表配一个库存数字就能跑通商品A库存100件来了订单就扣1件。但服装销售平台没法这样建模。一件衣服至少有三个必须同时确认的属性颜色、尺码、款式有时候还有面料、版型、厚薄等延展属性。用户看到的是一个商品详情页但要买的其实是“黑色M码圆领款”这组属性组合。如果库存只挂在商品维度上就会出现一个非常典型的错误A用户下单买黑色L码B用户下单买白色L码系统都把商品总库存减了可实际上白色L码已经断货黑色L码还剩很多。这种库存口径的错乱在真实业务里会直接导致超卖和漏发。“衣依”系统的数据库设计里商品、规格、SKU这套三层结构是整个系统能否成立的地基也是我在筛选项目时最先验证的部分。没有按SPU和SKU拆开的服装电商源码后面改起来会非常痛苦。这套系统里每一件衣服都是以SPU商品维度展示SKU具体规格组合维度管理价格和库存前端详情页再根据SKU列表做规格联动整条链路是通的。1.2 前后端分离为什么是这次选型的正确姿势这套系统采用前后端分离意味着前端Vue3工程和后端SpringBoot工程是两个独立部署的应用。前端通过HTTP接口与后端通信接口负载JSON数据两者各自可以独立开发、独立测试、独立部署。选这种架构的好处有三个层面。第一是团队协作层面前端同事不需要在本机装Java环境后端同事也不需要关心页面UI长什么样只要把接口契约定清楚两条线可以并行推进。第二是部署弹性层面前端静态文件可以扔到任意静态服务器或CDN上后端接口可以单独扩容数据库连接压力也更容易隔离。第三是让前端打包产物和后端Jar包互不干扰后期接手的人只需要按端口区分服务排查问题路径清晰很多。如果你之前主要写单体JSP或模板渲染项目切换到这套前后端分离架构时最容易出现的不适感是“联调成本变高”——以前模板渲染直接在服务端拼好页面现在前后端各跑各的CORS、Token、接口路径、字段命名全都要靠约定来约束。这套系统里这些点都有对应处理方案后面我会专门展开讲。2. 数据库建模实操商品中心、购物车与订单状态机2.1 商品、规格、SKU三层拆解先明确三张核心表的分工product商品表也叫SPU表存一件衣服的公共信息比如商品名称、主图、详情描述、品牌、上下架状态。这一层的记录描述的是“这件衣服”而不是“哪个颜色哪个尺码”。product_spec规格项表存可选属性维度比如颜色、尺码。在实际项目中会以spec_name和spec_value的形式存成多行例如“颜色:黑色”一行“尺码:M”一行。product_skuSKU表存具体的销售组合比如“黑色M码”这一行才会绑定实际的价格、库存、SKU编码通常是商家自定义的货号。三者关系一句话一个SPU下挂多个规格维度规格维度交叉组合生成多个SKU。在MySQL落地时SKU表通常会冗余存一份spu_id和规格组合的快照比如color:黑色,size:M的JSON或逗号拼接串因为前端购物车和订单详情都需要按这个快照展示用户当时选中的组合。这里有个新手经常踩的坑以为在product表里加一列stock字段就能解决库存查询。实际上只要把库存放在SKU表上再配合一个简单的库存流水表stock_log记录每一次入库、出库、锁定、释放操作整个库存链路才真正可追溯。我在改造这个系统时给库存流水补上了order_sn关联字段这样一旦发生超卖纠纷可以直接查某个订单对应的库存变动记录不用靠猜。2.2 购物车表和“选中状态”的特殊处理逻辑购物车表cart_item在多数商城项目里会被设计得很简单用户ID、SKU ID、数量、加入时间。但实际做服装项目时我建议再加一个selected字段表示勾选状态。为什么这个字段重要因为在服装场景下用户经常一次加入多件不同颜色尺码的衣服结算时只勾选其中一两件。如果购物车接口不返回选中状态前端就只能用本地状态硬记刷新页面就乱了。把selected直接落到库表后端做“结算下单”接口时就可以只读取选中状态的购物车项逻辑更干净。订单表order_master的设计则需要考虑一条典型的订单状态链路。我在这套系统里用的是“待支付→已支付→已发货→已完成”四态流转同时留了“已取消”和“售后中”两个分支状态。这里不建议把状态设计得过于碎片化否则前端每一页都要适配一堆状态组合。用一个status整数字段配合状态枚举类比用字符串状态更省空间也更好写判断条件。3. SpringBoot与MyBatis的后端联调核心3.1 分层结构、Mapper与Service的边界划分后端工程本身的分层基本沿用经典结构controller接收HTTP请求、service承载业务逻辑、mapper负责数据库访问、entity对应表结构。真正值得注意的是MyBatis在Mapper层写法上的细节。我用的是MyBatis的注解和XML混合方式。简单查询用注解比如Select(SELECT * FROM product WHERE status 1)拼接逻辑复杂的动态查询放进XML例如按商品名模糊搜索、按多个分类筛选、按价格区间过滤。动态SQL用where标签和if标签组合避免用字符串拼SQL导致注入风险。实体类字段与数据库字段的映射需要统一遵循下划线转驼峰规则。比如数据库里的create_time在Java实体里写成createTimeMyBatis配置里设置map-underscore-to-camel-case: true这一项能省掉大量手写resultMap的功夫。但要注意如果某些字段名涉及order这种SQL关键字必须反引号包裹不然MySQL会直接报语法错误。Service层的边界我习惯卡在“一个业务动作对应一个事务方法”这个粒度。比如submitOrder、cancelOrder、payOrder每个都是一个完整的事务单元不要在Controller里写业务代码也不要在一个Service方法里塞两个互不相关的动作。这样后期接真实支付接口、加消息通知只需要在对应方法里扩展就好。3.2 下单扣库存的并发保护服装销售平台最敏感的接口就是“提交订单”。用户点下单按钮后后端需要做校验购物车选中项、计算总价、生成订单号、扣减SKU库存、清空对应购物车项。这几个步骤必须在一个事务里完成否则会出现订单生成了但库存没扣的严重事故。我在实现时把扣库存的SQL写成了原子操作Update(UPDATE product_sku SET stock stock - #{quantity} WHERE id #{skuId} AND stock #{quantity}) int deductStock(Param(skuId) Long skuId, Param(quantity) Integer quantity);这里的关键是WHERE stock #{quantity}条件如果影响行数为0说明库存不足Service层直接抛异常回滚事务。这种方式比“先查库存再判断是否大于0”更安全因为两个并发请求同时读到库存为1时后者执行UPDATE会因为条件不满足而返回0不会出现双双扣成功的超卖情况。订单号我采用“时间戳用户ID后四位随机数”的组合方式长度控制在20位以内既保证了并发场景下的唯一性也方便客服根据订单号直接看出大致下单时间。如果需要绝对不重复可以再加一张流水表用数据库唯一索引兜底。4. Vue3前端的组织方式与购物流畅度优化4.1 组件拆分与组合式API的实践Vue3工程这次我没有选择Options API风格而是全部用组合式APIscript setup来写。好处是同一个业务逻辑的代码可以聚在一起比如商品详情页里获取商品信息的请求、规格选择的状态、加入购物车的交互都被整理成一段一段的watch、computed和事件函数读起来非常直接。页面级组件按典型电商目录划分首页、商品列表、商品详情、购物车、订单确认、订单列表、用户中心。路由用Vue Router统一在路由守卫里做登录校验未登录用户访问购物车、订单等页面时直接重定向到登录页。商品详情页的规格选择是一个很细节的交互。用户先点颜色、再点尺码只有当前颜色下实际存在的尺码组合才允许被选中。这个逻辑如果放在后端每次动态查询会太慢所以我把SKU列表一次性加载到前端由前端根据当前已选颜色过滤出可选尺码集合。这个方案在SKU数量不超过几十个的服装项目里完全够用用户体验比每次点选都发请求要流畅很多。4.2 接口封装、状态管理与请求拦截器前端所有请求我统一封装在一个request.js模块里基于axios的实例配置了baseURL和超时时间。请求拦截器在每次请求时从localStorage取出Token放到Authorization头里响应拦截器统一解析后端返回的code字段如果遇到401则清除本地登录态并跳转登录页。这样业务代码里不用每个页面都写一遍异常处理。状态管理用Pinia存两类核心状态用户登录信息和购物车数量角标。这两类数据在多个页面都要读放全局状态比每进一个页面都发一次请求要高效。特别是购物车角标在首页、列表页、详情页头部都在展示把它放入Pinia后在任何一个页面触发了加购操作都可以直接更新整个应用的状态同步非常顺畅。5. 部署、联调过程中的真实坑5.1 跨域问题与Token过期体验前后端分离在本地开发时第一个遇到的就是跨域。前端跑在5173端口后端跑在8080端口浏览器默认会拦截非同源的请求。后端需要配置允许的来源域名和HTTP方法同时允许Authorization头。这里要注意如果前端用了withCredentials发送Cookie后端allowCredentials(true)和允许来源不能使用通配符*必须写死具体的来源地址否则浏览器控制台会报错且无法携带凭证。Token过期也是服装电商平台实际使用中容易踩的体验问题。用户把页面挂在那里很久再点结算Token已经失效接口返回401。如果前端不统一处理用户看到的是一堆加载失败或者白屏。我在这套系统里给响应拦截器增加了一个“401单点登录踢出”的处理遇到401时先尝试刷新Token刷新失败再跳登录页同时用路由参数带回用户之前想访问的路径登录成功后自动跳回。这个小细节对整体体验的提升非常明显。5.2 数据库连接参数与默认时区SpringBoot连接MySQL时JDBC URL里的几个参数看似不起眼但出问题时会非常难排查。serverTimezone建议直接写成Asia/Shanghai不要用GMT%2B8这种容易在Linux环境下解析异常的写法useUnicodetruecharacterEncodingutf8必须保留否则查询中文条件会出现乱码。还有一次我在测试环境里遇到“表或视图不存在”的报错排查了半天发现是MySQL大小写敏感配置的问题。Linux下MySQL默认对表名大小写敏感Windows下不敏感同一套建表脚本在两套环境里行为不一样。解决办法是统一在数据库配置里设置lower_case_table_names1并且在建表时全部使用小写表名。这个坑在只写过Windows本地项目的同学手里几乎必踩一次。5.3 初始数据的完整性与演示账号前后端都跑通之后还有一个非常现实的问题系统里没有数据前端页面全是空状态。我在初始化时导入了一套服装演示数据包含商品、SKU、分类、轮播图、库存流水等数据量不大但覆盖了所有典型场景。演示数据的价值在于能够直接验证商品列表分页、SKU规格联动、购物车结算、订单状态流转这几条主链路不需要自己一个个手工录数据。我个人在实际操作中验证这套系统最顺利的方式是按“数据库初始化→后端启动→前端启动→登录验证→商品浏览→下单支付流程模拟→订单状态变更”的顺序走一遍。只要这条主流程通了这套源码的核心逻辑基本就理解透了剩下的事情就是按业务需求做二次开发。最后再分享一个扩展思路这套系统的订单支付目前是模拟支付也就是点击支付按钮直接改状态。如果想接入真实支付可以在订单状态流转处预留支付回调接口的位置把paid_time字段的更新放到回调接口里处理这样既能保持现有体系统一又不会破坏订单状态机的完整性。