资讯动态

SpringBoot实战:从零搭建“衣依”服装销售平台全流程解析

发布时间:2026/10/18 11:46:58 来源:尧图企业网站定制
从零搭一个能用的服装销售平台这事儿说难不难但要说省心那也是骗人的。“衣依”这个项目是我用 SpringBoot 完整走完的一套服装销售平台从需求梳理、数据库建模、后端接口到页面渲染、模拟支付、部署上线最后还配了一篇像模像样的说明文档。如果你正准备做类似的 Java Web 项目或者正愁毕设选题不知道怎么落地那这篇东西就是给你写的。我会把整个设计过程、核心代码思路、还有我实际踩过的坑全部摊开来讲你照着走一遍基本就能摸清这类项目的完整套路。1. 先把需求盘明白服装销售平台到底要做什么1.1 为什么选服装销售平台而不是通用电商很多人一上来就想做“电商系统”结果做着做着发现功能越加越多购物车、优惠券、秒杀、直播带货全都想要最后要么草草收场要么代码烂成一锅粥。我的建议是选题一定收敛到具体领域。服装销售平台的好处在于它有明确的行业属性商品有分类男装、女装、童装、有规格尺码、颜色、有库存概念比单纯卖书卖数码更贴合真实业务但又不至于像全品类电商那样复杂到失控。“衣依”这个名字也是当时随手起的寓意“衣”字打头做服装生意嘛顺口好记就行。项目最终定位是面向消费者的服装购买平台 面向管理员的商品运营后台用户能浏览商品、加入购物车、下单支付、查看订单管理员能管理商品分类、上下架商品、处理订单状态。1.2 用户端和管理端的边界划分我见过很多新手项目把用户端和管理端混在一个 Controller 里一个方法里面既查用户数据又改管理员配置最后权限完全失控。所以动手写代码前先把边界画清楚用户端注册、登录、浏览商品、查看商品详情、管理购物车、提交订单、模拟支付、查看/取消订单、维护收货地址。管理端管理员登录、商品分类管理、商品信息管理上下架、改价、改库存、订单列表处理发货、完成、简单的数据统计。两边的共性只有“用户体系”和“商品体系”这两块底层数据其余逻辑完全独立。这样分工清晰后续写 Controller 的时候也不容易乱。1.3 技术选型不追新只求稳这个项目的技术栈非常经典我强烈建议第一次做完整项目的人不要搞花活就用这套Spring Boot 2.7.x主流版本资料多遇到问题一搜就有答案MyBatis Plus单表 CRUD 基本上不用写 SQL专注业务逻辑MySQL 8.0存储业务数据Thymeleaf服务端渲染页面不搞前后端分离Bootstrap 5 少量 jQuery快速搭建界面不自己写 CSS 框架Maven 管理依赖Lombok 简化实体类代码。为什么不选 Vue 前后端分离因为对于单人或双人完成的项目来说前后端分离意味着要维护两套工程、处理跨域、还要额外部署前端静态资源工作量直接翻倍。服务端渲染虽然“土”但是一个 SpringBoot 工程搞定所有事情跑起来就是一个整的系统出问题也好排查。提示毕设答辩时老师问“为什么用 Thymeleaf”你可以理直气壮地说为了降低系统复杂度让部署更简单同时保证 SEO 友好。这就是实话。2. 数据库建模是整栋楼的承重墙别急着写代码2.1 核心表结构从用户到订单的闭环我建表的时候一共设计了 9 张表分别是用户表、管理员表、商品分类表、商品表、购物车表、订单表、订单明细表、收货地址表、轮播图表。其中最关键的关系是订单表与订单明细表的“一对多”关系。CREATE TABLE orders ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号, user_id bigint(20) NOT NULL COMMENT 下单用户ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0待付款 1已付款 2已发货 3已完成 4已取消, address_snapshot varchar(500) DEFAULT NULL COMMENT 收货地址快照, create_time datetime NOT NULL, pay_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单明细表也很关键它记录每一件商品的下单时刻信息CREATE TABLE order_item ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id bigint(20) NOT NULL, product_id bigint(20) NOT NULL, product_name varchar(100) NOT NULL COMMENT 下单时的商品名快照, product_image varchar(255) DEFAULT NULL, price decimal(10,2) NOT NULL COMMENT 下单时的单价快照, quantity int(11) NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;2.2 订单快照 vs 实时关联查询这一个决定我少改了很多 bug刚开始我把订单明细设计成“下单时只存 product_id查订单详情的时候再去 join product 表拿名称和图片”。后来测试的时候发现一个大问题如果管理员把商品下架了甚至删除了用户历史订单里的商品信息全变成空白。这是电商系统里最典型的错误。正确的做法就是订单明细表里冗余商品名称、图片、单价快照。用户下单那一刻价格是多少就存多少商品叫什么就存什么以后再改商品表里的信息完全不影响历史订单。同理订单表里也存了收货地址快照而不是只存一个 address_id。这就是“快照”设计思路你记下来以后做任何交易类系统都用得上。2.3 库存扣减我选择下单时扣不选择支付时扣库存扣减有两种常见的时机加入购物车时扣、提交订单时扣或者支付成功时扣。我最终选择了“提交订单时扣减并锁定库存支付超时自动释放”。具体是用户点击“提交订单”系统先检查库存如果够就扣减库存并创建待支付订单如果用户一直不支付超过 30 分钟订单自动取消并回补库存。为什么不选择支付成功后再扣库存因为这样子会导致“下单成功但支付时发现没货”的尴尬局面用户体验很差。而边下单边扣库存的方式只要库存扣成功订单就一定成立后面支付只是完成交易动作。注意真实高并发场景里这个方案还不够要引入 Redis 分布式锁或者消息队列异步扣减。但单机项目里只要把“扣库存”和“创建订单”放在一个数据库事务里就已经足够安全了。3. 后端实现细节从用户注册到订单支付的全链路3.1 注册登录密码不能明文存Session 管理别偷懒用户模块看似简单但很多项目都挂在密码上。我在项目里使用了 Spring 自带的 BCryptPasswordEncoder 对密码做哈希存储绝对不会用明文。注册的逻辑就是先查重邮箱或用户名再加密入库登录就是比对哈希值成功后把用户信息放进 Session。拦截器这个点我要重点提一下。用户端很多接口购物车、下单、订单列表必须登录后才能访问管理员端更是如此。我当时写了一个 WebMvcConfigurer 注册两个拦截器Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new UserLoginInterceptor()) .addPathPatterns(/cart/**, /order/**, /user/**) .excludePathPatterns(/user/login, /user/register, /static/**); registry.addInterceptor(new AdminLoginInterceptor()) .addPathPatterns(/admin/**) .excludePathPatterns(/admin/login, /static/**); }用拦截器统一处理登录校验比在每个 Controller 里手动判断 Session 要干净得多。这里有个小坑Thymeleaf 模板渲染静态资源时路径要记得排除/static/**否则页面加载不出 CSS 和 JS。3.2 商品列表与分页MyBatis Plus 的 IPage 用起来非常顺手商品列表是用户浏览的主入口。我在前端做了一个分类导航栏点击不同分类就请求对应该分类下的商品同时支持按价格和上架时间排序。后端查询直接用了 MyBatis Plus 的分页插件public PageResultProductVO getProductPage(long categoryId, int pageNum, int pageSize, String sort) { PageProduct page new Page(pageNum, pageSize); LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); if (categoryId 0) { wrapper.eq(Product::getCategoryId, categoryId); } wrapper.eq(Product::getStatus, 1); // 只查上架商品 if (price_desc.equals(sort)) { wrapper.orderByDesc(Product::getPrice); } else if (price_asc.equals(sort)) { wrapper.orderByAsc(Product::getPrice); } else { wrapper.orderByDesc(Product::getCreateTime); } productMapper.selectPage(page, wrapper); // 组装商品名称、图片、价格信息返回前端 }这里有一个很影响体验的细节商品列表不能把下架商品查出来给用户看。所以查询条件里务必加上status 1。很多新手忘了这个过滤条件导致用户在下单时才发现商品已经下架白白流失一波流量。3.3 购物车与订单生成事务保证一致性购物车表很直接就五个字段id、user_id、product_id、quantity、create_time。用户加购时我先查一下购物车里有没有同样商品有就数量加一没有就新插入一条。这里要提醒一下加购操作要考虑商品是否上架、库存是否足够否则用户满怀期待加购结算时才发现没货体验很差。订单生成是整个系统最需要谨慎的地方。我把它设计在一个事务方法里先生成订单主记录然后遍历购物车中的商品逐条生成订单明细扣减库存最后清空购物车。如果中间任何一步失败了数据库回滚保证不会出现“订单生成了但库存没扣”或者“库存扣了但订单明细缺失”的情况。Transactional(rollbackFor Exception.class) public OrderVO createOrder(Long userId, Long addressId) { // 1. 查询购物车列表 // 2. 校验商品是否上架、库存是否充足 // 3. 计算总金额 // 4. 插入订单记录和订单明细 // 5. 批量扣减库存 // 6. 清空购物车 // 7. 返回订单号 }3.4 模拟支付别真的去接支付宝但状态流转要逼真毕设项目肯定不能真的接入真正的支付网关除非你打算完成企业级真实支付。所以这里我用了一个“模拟支付”的方式用户点击“立即支付”弹出一个确认支付页面展示订单号和金额点击“确认支付”后后端直接把订单状态从“待付款”改为“已付款”并记录支付时间。但状态流转的严谨性不能丢。我定义了一组订单状态常量0-待付款、1-已付款、2-已发货、3-已完成、4-已取消。订单生成的初始状态是 0模拟支付后是 1管理员发货后是 2用户确认收货后是 3。取消订单分为用户主动取消和超时自动取消取消时都要回补库存。超时自动取消我用的是最简单的方案在订单详情页请求的时候检查创建时间是否超过 30 分钟如果超过且状态仍是待付款就自动置为已取消并回补库存。这个方法不精准也没有定时任务但对单机毕设项目来说完全够用而且答辨起来也好解释。4. 页面从哪来用 Thymeleaf 组织前台和后台4.1 前台的页面骨架与公共片段抽取Thymeleaf 最大的优势就是可以直接在 HTML 里使用th:each、th:if这些属性去渲染动态数据配合th:fragment抽取公共片段就不用每个页面重复写头部导航栏和底部版权信息了。我当时的页面结构是这个样子templates/ ├── layouts/ │ ├── user-header.html用户端导航栏 │ └── admin-sidebar.html管理端侧边栏 ├── user/ │ ├── index.html首页 │ ├── list.html商品列表 │ ├── detail.html商品详情 │ ├── cart.html购物车 │ ├── confirm.html订单确认页 │ ├── pay.html模拟支付页 │ └── order-list.html我的订单 └── admin/ ├── login.html管理员登录 ├── dashboard.html控制台 ├── category.html分类管理 ├── product.html商品管理 └── order.html订单管理首页其实是整个系统颜值的门面我用 Bootstrap 的轮播组件做 Banner 展示下面再按分类展示四到六个推荐商品卡片。商品列表页用卡片式布局每个商品显示图片、名称、价格和一个小按钮。整个过程没有自己写多少 CSS完全依赖 Bootstrap。4.2 表单提交回显与参数校验后端校验不能省页面上的表单注册、登录、添加商品、编辑商品都要处理一个问题用户提交的数据不合法报错了怎么办如果直接弹回一个错误页面体验很糟糕。正确的做法是后端校验失败后返回原表单页面并且把用户之前输入的内容回显出来让他修改而不是重新填。Spring Boot 里我用的是Validated注解配合 BindingResult 来做参数校验比如用户名非空、价格大于零、库存不能为负这些基础规则。同时Controller 里不要直接接受实体类而是用专门的 VO 或 DTO 接收前端参数这样既能控制字段边界又能做校验不会把数据库字段全暴露到前端。提示关于参数接收我一贯的原则是前端传什么就定义什么不要偷懒直接拿实体类接收。比如新增商品时用户不可能传 create_time 字段但你如果用 Product 实体接收就等于给了别人一个绕过逻辑直接造数据的机会。4.3 商品图片存储本地目录 虚拟映射别把图片塞进数据库商品图片怎么存也是新手容易踩坑的地方。我采用的方案是图片文件上传到服务器本地目录/upload/数据库里只存相对路径然后通过 Spring Boot 的虚拟资源映射把/upload/**映射到本地磁盘路径。Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath file: System.getProperty(user.dir) /upload/; registry.addResourceHandler(/upload/**).addResourceLocations(uploadPath); }这样做的好处是页面直接img th:src${product.image}就能访问图片不需要额外的图片服务。部署到服务器时只要把 upload 目录一起拷贝走就行。图片名我用 UUID 重新生成避免中文名和重复名导致的问题。5. 实测中的拦路虎这些坑我一个个替你蹚过了5.1 日期格式化LocalDateTime 返回给前端时变成了一长串数字这是我调试时最先发现的问题。数据库里存的是 datetime实体类用的是 LocalDateTime结果接口返回给前端时日期字段变成类似2025-01-15T10:30:00这样的格式甚至有时候直接显示成时间戳数字页面根本没法看。解决方式是在 application.yml 里配置全局日期格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8同时实体类上的日期字段可以加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)做兜底。Thymeleaf 模板里也可以用#temporals.format(order.createTime, yyyy-MM-dd HH:mm:ss)来控制展示格式。三种方式任选其一别全写上去否则反而乱。5.2 库存并发两个人同时买最后一件衣服系统崩溃了我最初测试时只有自己一个账号点击下单后库存完全够用根本没发现并发问题。后来开了两个浏览器窗口同时下单同一件库存只剩 1 件的商品结果两个订单都创建成功了库存变成了 -1。这就是典型的超卖问题。修复方案是在扣库存的 SQL或者 Mapper 方法上加一个条件UPDATE product SET stock stock - 1 WHERE id ? AND stock 0。这样一来数据库层面就能保证扣减操作是原子的两个事务同时执行时只有一个能成功另一个影响的记录数为 0就可以抛出“库存不足”的异常。5.3 部署时端口与数据库配置本地好好的服务器上全乱套在本地开发时数据源配置直接写死 localhost端口默认 8080一切正常。但打包部署到云服务器时这些问题全冒出来了MySQL 是远程的密码不一样端口被占用上传目录路径变了。我最后的做法是把公共配置放到application.yml把环境相关的配置放到application-prod.yml打包时通过启动参数指定环境java -jar yiyi-shop-0.0.1.jar --spring.profiles.activeprod同时配置里全部使用环境变量占位符比如${DB_HOST}、${DB_PASSWORD}在服务器上用 export 设置好下次部署就不用改代码了。这个经验非常实用不管什么项目环境配置从第一天开始就分离好能省下大量时间。6. 这套代码怎么变成一份体面的项目文档6.1 归档源码包结构要干净注释要像人话源码交付出去之前我花了整整一个下午去清理包结构和注释。不夸张地讲一个包结构混乱的项目不管功能多好给人的第一印象都是大打折扣。我的包结构是这样组织的com.yiyi.shop ├── controller用户端 管理端分目录存放 ├── service接口 impl 实现类 ├── mapperMyBatis Plus 的 Mapper 接口 ├── entity数据库实体类 ├── vo给前端展示用的值对象 ├── dto接收前端参数的传输对象 ├── config配置类如拦截器、资源映射、MyBatis Plus 配置 └── common统一返回结果、常量类、异常处理注释我控制在“每类注释一次不逐行注释”的粒度。类上面的注释写清楚这个类负责什么方法上的注释写清楚参数含义和返回值业务关键节点写一行原因说明比如“这里用快照防止商品信息变更影响历史订单”。彻底删掉那些// 获取订单列表废话注释那只会让人觉得代码写得不清楚。6.2 文档结构设计说明书的正确打开姿势配套文档不一定要写成八股文但基本骨架必须有项目背景与意义、需求分析、系统设计架构图 功能模块 数据库设计、核心功能实现、系统测试、总结与展望。我在数据库设计部分花了最多篇幅每张表都配了字段说明表还在关键表之间画了关系说明用文字描述不用复杂的 ER 图工具。核心功能实现部分我只挑下单和支付两个最有含金量的流程贴关键代码片段配上文字说明这个流程是怎么跑的、为什么这么设计。切忌把整个 Controller 全部贴进论文没人看。6.3 答辨预案老师最爱问的几个问题先准备好答案答辨的时候与其被动挨问不如提前把所有“被问穷”的点都自查一遍。我遇到的最高频问题如下“为什么用 SpringBoot 而不用 SSM”答SpringBoot 自动配置简化了项目搭建内嵌 Tomcat 让部署变简单生态成熟适合快速开发。“订单表为什么要存地址快照”答保证历史订单不随基础数据变更而失真是电商领域常见的冗余设计。“库存扣减怎么防止超卖”答使用条件更新UPDATE product SET stock stock - ? WHERE id ? AND stock ?数据库层面保证原子性。“前后台共用一个后端吗”答共用一套 SpringBoot 服务通过拦截器和路径前缀区分用户端与管理端权限。这四条答得顺畅答辨基本不会难堪。剩下的问题基本都是细节按自己项目的实际代码如实回答就行。写在最后我从一个查数据库的“菜鸟新手”到能独立跑通这个“衣依”服装销售平台最大的体会是做项目这件事动手是第一位的方案再好也要落到代码里验证踩了坑才能真正理解为什么要那样设计。白纸黑字的表结构、事务注解、快照字段这些在课件上都是零碎知识点但当它们共同支撑起一个能运行、能下单、能发货的系统时知识就变成了经验。如果你正准备开工类似项目我的建议是先花一个晚上把表结构定清楚把订单状态机的流转画明白再动笔写代码至少能帮你省下两天的返工时间。等你跑通一单“从注册到支付到发货”的完整流程你就能底气十足地说这个系统是我的作品。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑