做毕业设计选电商系统的人很多,但真正能把“服装购物商城”做出完整度、能顺利通过答辩的,靠的不是堆功能,而是对整个前后端分离架构的理解。我用SpringBoot和Vue搭过一套基于前后端分离的在线服饰销售平台,从选题、表设计、接口开发到前端联调、打包部署全程走了一遍,这篇就把我在这个过程中踩过的坑、想明白的道理、可以复用的代码思路一次性说出来。不管你是准备做类似题目,还是手里已经有项目不知道从哪下手,这篇都值得你花十分钟看完。
1. 项目整体设计与思路拆解
1.1 为什么选“服装购物商城”作为毕业设计题目
计算机毕业设计选题有个普遍规律:太简单显得工作量不够,太复杂容易烂尾。“服装购物商城”这个题目刚好卡在了一个很舒服的位置——它有电商系统的通用性,做的是商品、购物车、订单、用户这套核心链路;同时服装品类又有自己的特点,比如颜色、尺码这种多规格属性,比单纯做个图书或数码商城多了一层设计难度。这个度把握好,工作量看着饱满,实际做起来也不会失控。
再从展示角度说,服装商城的视觉效果天然比后台管理系统好。SpringBoot和Vue这套组合,前端页面有发挥空间,轮播图、商品卡片、购物车动画、订单状态标签,这些都能让答辩PPT和演示视频好看很多。面试聊项目时也容易讲清楚业务逻辑,不至于说着说着自己都绕晕。
还有一个很现实的原因:服装电商系统相关的参考代码、文档、开源项目非常多,遇到搞不定的细节能找到人讨论,不至于一个人闷头卡死。对大多数同学来说,这个题目的“容错率”是比较高的。
1.2 技术选型:SpringBoot和Vue为什么是绝配
技术选型看着是顺手的事,其实决定了后面三个月是舒服还是遭罪。
后端选SpringBoot,核心理由是“约定大于配置”。它内置了Tomcat,一套SpringMVC就够处理接口请求,配合Spring Data JPA或者MyBatis-Plus操作数据库,不用写一堆XML配置。对比传统的SSM框架,SpringBoot省掉的不只是配置文件,更是调试和部署时的心理负担。
前端选Vue,因为它对新手最友好。数据双向绑定让表单处理、购物车数量加减这类交互变得很直观;Vue Router管页面跳转,Vuex或Pinia管全局状态,加上Element UI之类的组件库,一个商品列表页面半天就能搭得有模有样。和React相比,Vue的中文资料更全,遇到问题搜一下就能找到解法。
前端和后端通过HTTP接口通信,这就是前后端分离的核心。前端专注渲染和交互,后端专注业务逻辑和数据存储,两个项目可以并行开发,联调时只要保证接口格式对齐就行。这种模式不只是毕业设计的主流,也是目前中小型公司做业务系统的标准姿势,做完这套你对企业开发流程会有一个很具体的认识。
1.3 整体功能架构和模块划分
我的项目把功能分成两个端:
- 用户端(前台商城):用户注册登录、浏览首页、商品分类筛选、商品详情查看、加入购物车、提交订单、模拟支付、查看订单状态、个人中心管理。
- 管理端(后台系统):管理员登录、商品管理(上下架、库存维护)、分类管理、订单管理(发货、退款处理)、用户管理、基础数据统计。
模块划分遵循的原则是“低耦合高内聚”。商品模块管商品和分类,订单模块管订单和订单项,用户模块管登录鉴权和收货地址,每个模块的接口边界清晰,不互相兜底。这样做的好处是开发时能一块一块独立完成、独立测试,答辩被问“某个功能是怎么实现的”时也能快速定位到对应代码,不慌。
2. 核心功能模块解析与数据库设计
2.1 用户端四个核心流程
注册登录流程。
注册时前端做一次格式校验(手机号位数、密码长度),真正重要的是后端校验。第一次做很容易忽略“用户名是否已存在”这个判断,导致重复注册。我当时在Service层里先根据用户名查一次库,存在就抛异常,由全局异常处理器统一返回提示信息,前端弹个消息提示就完事。登录用的是JWT令牌,用户登录成功后返回一串Token,前端存在localStorage里,之后每次请求在请求头加上Authorization: Bearer <token>,后端通过拦截器解析出用户信息。
商品浏览流程。
首页展示轮播图、新品推荐、热卖榜单,这些接口都做了简单的数据缓存。最开始我每次请求都直接查数据库,后来发现刷新一下页面就要等半秒多,体验很糟。加了Redis做缓存后,热销商品和推荐位的数据直接走缓存,快了很多。
分类筛选参数包括大类(上衣、裤装、裙装)、子类(T恤、衬衫、牛仔裤)、价格区间、尺码和颜色。这里我一开始踩了个坑:把所有筛选参数拼成一个大SQL,结果索引失效,查询很慢。后来改成了组合查询,并根据查询条件动态构建SQL,性能从两秒降到了几百毫秒。
购物车流程。
购物车的核心不是一个简单的“加入就完事”,而是要处理已有商品数量累加、规格变更、库存校验。我的做法是后端先查出购物车中是否已存在同款商品(商品ID+规格ID做联合判断),存在则数量累加,否则新增一条记录。结算时对购物车内的每一项做库存二次校验,防止下单时才发现某个商品已经没货了。
订单流程。
提交订单是前端所有操作里最需要小心的一步。我当时的做法是:前端把收货地址ID、购物车选中的商品项列表一并提交给后端,后端在自己那边重新计算价格并生成订单,而不是直接信任前端传过来的金额。这个点即便答辩时不展开讲,自己心里也要清楚,这是电商系统最基本的“金额安全”意识。
订单状态我设计了五种:待付款、待发货、待收货、已完成、已取消。前端通过订单状态码展示对应的操作按钮和进度条样式,后端负责状态流转的控制,比如只有待付款状态才能点取消。
2.2 管理端核心功能
管理端用同一个SpringBoot项目提供接口,前端独立一套页面。登录鉴权是后台最常见的场景:管理员登录后,后端接口用拦截器判断请求路径是否在放行名单内,不在名单内的请求都会校验Token,防止未登录访问数据接口。
商品管理这边,我封装了独立的文件上传接口,管理员在商品编辑表单里选择图片后上传到服务器本地磁盘,数据库里存的是访问路径。这个方案单机演示完全够用,如果后来要部署到云服务器,再考虑接对象存储也不迟。
订单管理是后台最忙的模块。列表默认按创建时间倒序排列,状态筛选支持多条件联动,还可以针对单笔订单做发货操作,发货时填写物流单号。测试时我在一个账号下连续下好几单,通过不同的状态看前台展示是否同步更新,这样一套流程跑下来基本能把核心链路验证完。
2.3 数据库设计:核心表的字段与关系
数据库设计直接决定开发时的痛苦程度。我最终用了6张核心表:用户表、分类表、商品表、商品规格表、购物车表、订单表、订单项表,以及一张用来存轮播图和公告的附加表。
商品表和商品规格表是一对多关系。一件衣服可以有好几个颜色、好几个尺码,我把价格和库存都放在规格表里,商品表只保留标题、主图、描述、销量这些公共字段。这样商品详情页展示SKU信息时,只要根据商品ID查规格表就能拿到全部选项和对应价格。
订单表和订单项表是一对多关系。订单表存收货人、总金额、状态等汇总信息,订单项表存每个商品的快照信息,包括商品名称、规格描述、单价、数量和图片。为什么存快照而不关联商品表?因为商品下架或改价后,历史订单显示的信息不能跟着变,快照方案保证订单内容是“下单那一刻”的真实状态。
这个设计在答辩时非常能体现你对业务的理解,因为很多人做的订单表就是简单一张表,根本没考虑历史快照的问题。
3. 实操过程与核心环节实现
3.1 后端接口设计与JWT登录鉴权
后端接口设计遵循RESTful风格,资源名用复数名词,比如/api/products、/api/orders。用HTTP方法表达操作类型:GET查询、POST新增、PUT修改、DELETE删除。这样接口风格统一,也方便前端理解和对接。
JWT登录是这部分的重点,我写一下关键思路:
// 登录成功后生成Token String token = Jwts.builder() .setSubject(userId.toString()) .claim("username", username) .claim("role", user.getRole()) .setExpiration(new Date(System.currentTimeMillis() + 3600_000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();用户每次请求时,携带Token。拦截器获取Token后解析,把用户ID放到请求上下文里,后面的业务代码随时能取到当前登录用户的身份。比如修改个人信息前先判断用户ID是否匹配,防止操作别人的数据。
JWT的好处是服务端不用存储会话状态,非常适合前后端分离的分布式场景。它的缺点是Token一旦签发就不能主动作废,所以在用户注销或修改密码时,前端直接删除本地Token即可,把安全问题控制在可接受范围内。
3.2 前端Vue页面结构与核心组件实现
前端页面用Vue Router管理路由,页面组件分包存放。views目录下按登录注册、首页、商品列表、商品详情、购物车、订单、个人中心来组织,后台页面放在views/admin里。
商品列表页是重点组件,用Element UI的卡片布局展示商品,分页组件绑定数据变化,点击分页时重新向后端发起请求。加了一个价格区间的筛选条,通过v-model绑定输入值,点击查询后向列表接口发送筛选参数。
购物车页面要处理的数据比较杂:每一行的勾选状态、数量、小计,底部实时汇总选中商品的总价。我用了Vue的计算属性(computed)而不是在方法里手动算,只要勾选状态或数量一变化,总价自动更新,代码非常干净。
订单结算页面用Steps步骤条让用户看出当前处于哪个阶段,地址选择用对话框弹出一个地址列表让用户选择默认的地址,再通过确认按钮提交订单。这里要注意的是,地址列表是异步加载的,我在组件挂载时从后端拉取一次,用户新增地址后再刷新一次,保证表单里拿到的地址数据是最新的。
3.3 前后端联调与接口对接
前后端分离的项目,最容易出问题的不是在单独调试时,而是双方约好的接口格式对不上导致的反复联调。
我的实际做法是:首先在项目初期用接口文档工具把每个接口的路径、请求参数、返回结构定死,开发时后端自己用Postman先跑通,前端在Vue项目里给接口请求封装一个统一的axios实例,统一处理请求头、超时时间和错误拦截。前端拿到返回结果时,data统一包在response.data.data这层,这样不管后端返回什么数据结构,前端解析方式都是固定的。
联调过程中最常遇见的几个问题我记在下面:
- 后端返回的日期格式是时间戳,前端显示不了,就统一在实体类上加
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"),或者前端在渲染前格式化。 - 后端接口要求接收的字段名是
goodsId,前端传的是productId,联调时怎么都拿不到数据,最后逐个字段核对才找到,这种低级错误一旦发生就很浪费时间。 - 文件上传接口返回的地址是服务器相对路径,前端拼接时要确认是否有域名和上下文路径,拼接错了页面就显示裂图。
建议从一开始就使用Swagger或者Knife4j自动生成接口文档。接口文档能让前后端并行开发,减少大量不必要的沟通成本。
3.4 项目打包部署与答辩准备
开发完成后,前后端需要分别打包。
后端SpringBoot项目打包成可执行Jar包,我用的是标准的Maven命令:
mvn clean package -DskipTests java -jar target/shop-api.jar前端Vue项目执行:
npm run build构建完成后,dist目录里的静态文件可以放到Nginx的静态目录下。为了让刷新页面时不出现404,需要在Nginx配置里把路由请求都重定向到index.html。
location / { try_files $uri $uri/ /index.html; }实际答辩时,演示环境可以全部在本地跑:后端Idea里启动,前端npm run dev启动,浏览器直接打开。为了保险,我又把Vue打包后的文件放到了SpringBoot的static目录里,让后端口径统一能访问到前端页面,这样即使不单独启动前端也能够正常演示,不会出现断电断网导致演示失败的情况。
答辩PPT里我放了系统架构图、功能模块图、数据库ER图、几个核心页面的截图,再加上一段事先录好的操作演示视频。这样就算现场网络出了问题,也能靠视频走完整个流程。
4. 常见问题与排查技巧实录
4.1 跨域请求配置问题
前后端分离后,前端跑在localhost:8080,后端跑在localhost:9090,浏览器默认会拦截跨域请求。报错信息一般是“CORS policy”字样,非常显眼。
解决方法是在SpringBoot里配置全局跨域支持:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }注意一个细节:如果使用了自定义拦截器,预检请求OPTIONS要先放行,否则前端请求会被拦截器拦掉。当时我排查这个问题花了一个多小时,最后在拦截器里加了一行对OPTIONS请求直接放行的判断,瞬间就好了。
4.2 登录状态刷新后丢失
前端登录后把Token存到了localStorage,页面一刷新Vuex里的用户信息就空了。原因是Vuex的数据是存内存里的,刷新就没了。解决办法是在页面加载时重新从localStorage读取用户信息并初始化Vuex状态。
我封装了一个getCurrentUser()的接口,在应用入口判断本地是否存在Token,存在就调用该接口拉取用户信息,再更新Vuex里的状态。这样刷新页面后登录状态依然保持,整个体验连续性很强。
4.3 图片上传后不能正常访问
本地开发时,图片上传到了后端项目的upload目录,前端访问时直接用localhost:9090/upload/xxx.jpg,加了一层代理转发,问题就出现了。Nginx或者开发环境代理没有把图片访问路径转发到后端端口,所以显示裂图。
解决办法有两个方向:一是前端图片地址直接写全路径,缺点是耦合度高;二是配置代理把/upload这个路径转发到后端,我这里因为本地是直接用Vite开发,所以在vite.config.js里加了代理配置,把上传相关的请求转发到9090端口,问题就解决了。
如果是打包部署在同一个端口下,则由于后端静态资源映射默认生效,一般不会出现这个情况。
4.4 结算时的库存并发问题
同一个商品同时被两个人下单,可能会出现超卖。我在库存扣减时使用了乐观锁,核心SQL变成带条件更新:
UPDATE product_sku SET stock = stock - 1 WHERE sku_id = ? AND stock > 0;受影响行数为0就说明库存不足或已被抢空,此时抛出业务异常并回滚事务。这个处理方式非常简单有效,在面试场景中说出来会显得你思考过数据一致性问题。
4.5 订单状态显示混乱
订单状态数字和前端展示名称对不上,原因基本是前后端状态枚举没有对齐。我后来将状态定义写在一个共享接口文档里,前后端都从同一个状态常量表取用。前端用状态码映射到对应的文案和标签颜色,后端在返回时额外带一个状态描述字符串。这样即使以后状态增加,前端也不需要大量改动。
5. 时间规划与个人实操体会
5.1 项目开发的时间怎么分配
完整走完这套项目,我的时间分配供参考:
- 需求整理和功能列表梳理:3到5天
- 数据库设计和表格关系整理:2到3天
- 后端基础框架搭建和用户、商品模块接口:7到10天
- 购物车、订单模块接口开发:5到7天
- 前端页面开发和组件设计:10到14天
- 前后端联调和问题修复:5到7天
- 部署、文档撰写和答辩准备:5到7天
总周期在一个半月到两个月之间,每天保证三个小时以上的有效编码时间。如果时间紧张,前端可以优先做核心链路,砍掉优惠券、积分这类非核心功能,但商品、购物车、订单这三条主流程一定要完整。
5.2 开发过程中的一些经验教训
调试接口时先看清后端日志。SpringBoot的报错堆栈信息非常详细,很多时候问题一眼就能定位,但新手容易只看前端控制台的方向,后端明明写了明确异常却被忽略。把前后端分开排查,先确认接口返回的状态码和响应体,再去翻业务代码。
关于代码命名,实体类、字段名的命名风格前后端保持一致。当时前端习惯用驼峰,后端数据库字段用的是下划线。SpringBoot默认配置能帮我们自动做驼峰和下划线的映射,但如果你用了MyBatis并关闭了驼峰映射,就得手动写映射关系,很容易漏掉字段导致返回的数据全是null。第一次踩到这个问题时我在每个字段上写@TableField,后面直接调整为开启全局驼峰映射,少写了很多重复代码。
备份管理好数据库。开发中我改了好几次表结构,每次都忘记导出SQL备份,结果有一次改字段类型把已有数据搞坏了,折腾了半天才恢复数据源。建议每完成一个小模块就导出一份完整的SQL脚本,提交到Git里作为存档。
用Git管理代码这个习惯值得尽早养成。每天提交一次,提交信息写清楚当天做了什么,回滚代码、对比差异都会变得非常轻松。答辩前如果改出问题,不至于因为找不到之前的可用版本而紧张。
6. 这套项目还能怎么扩展
横向扩展有几个方向:接入支付宝沙箱支付或微信支付模拟,集成定时任务做订单超时自动关闭,利用Elasticsearch做商品全文搜索,引入RabbitMQ做下单后的异步通知和库存扣减。纵向扩展则可以增加用户积分体系、优惠券系统、秒杀活动等。
这些扩展点不用全做,挑一个做成“亮点功能”就足够在答辩或面试时讲了。我当时挑了支付宝沙箱支付,在模拟环境下走通了从提交订单到跳转支付、支付成功回调修改订单状态的完整链路。光是这个点,答辩时老师追问了很多细节,都对答如流,效果比我预想的要好。
前端的扩展相对容易,比如把首页改成支持懒加载图片,商品列表加入防抖搜索,购物车做滑动删除的手势交互。但实操上不要贪多,核心功能稳定、逻辑严谨、有思考深度才是最重要的。
最后说一句实际的体会:毕设项目没有你想象中那么看重“功能多”,它更看重你对每一块功能的理解程度。面试官或答辩老师问一句“你订单状态是怎么流转的”,你要是能一口气从提交订单到支付回调、发货、确认收货、超时取消讲清楚,这个项目就已经是你的加分项了。SpringBoot和Vue这套组合课外的坑是有限的,踩过一遍、记录下来,这些经验就都是你的了。