Spring Boot汉服商城管理系统实战:从数据库设计到订单状态机
2026/9/17 17:42:39 网站建设 项目流程

1. 这个项目能解决什么问题:从“毕业设计交差”到“扛得住面试追问”

先说点实在的。每年这个时间点,大批Java学习者开始迷茫:学校课程讲完了语法和框架基础,但真正要动手做一个“能写进简历、能在面试时讲清楚”的项目时,大部分人第一个想到的还是图书管理、学生选课这种被写烂了的系统。不是说这些项目不行,而是面试官一天能收到几十份几乎一模一样的“图书管理系统”,你一开口他就知道下一句要讲什么,这样的项目很难拿出亮点。

汉服商城管理系统是个不错的切入点。它属于电商领域,但又比普通的“凑合版商城”多了一层文化属性和特色业务逻辑。汉服这个品类有非常鲜明的特点:同款分码数、颜色、形制(比如齐胸襦裙、明制、宋制)、预售和现货并存、限时团购、还有“尾款补款”这种特殊订单状态——这些都不是普通的增删改查能糊弄过去的。把这些业务规则落地到代码里,项目的含金量一下就不一样了。

这篇博文里的项目,总结下来就是:用Java做后端、Spring Boot做基础框架、MySQL存数据、前端用Thymeleaf加Bootstrap,同时保留了完整的商家端和用户端,覆盖商品管理、购物车、订单流转、库存扣减、会员折扣这一整条电商核心链路。整个项目的代码结构、数据库设计、接口划分都按照企业级项目的习惯来组织,而不是学校实训课那种“所有代码堆在一个类里”的写法。无论是用来作为毕业设计,还是作为Java后端求职简历里的主导项目,这个选题都站得住。

我写这篇文章不打算只给你贴一堆代码截图,我会把每个关键模块的设计思路、当时踩过的坑、遇到性能或并发问题时的排查过程也一并讲清楚。这才是一个真实项目最有价值的部分——面试官追问的,往往就是这些代码之外的决策过程。

2. 技术选型不是越新越好:这套组合为什么适合练手也适合落地

2.1 核心框架组合与版本选择

这个项目我选用的是Spring Boot 2.7.x,不是最新的Spring Boot 3.x。原因很实在:很多学校的教学资源、网上现成的资料、公司的旧项目,都还停留在2.x生态。如果选3.x,Java版本最低要求17,而不少人的电脑上装的是JDK 8,环境问题就能卡掉一拨人。技术选型要考虑团队和受众的实际情况,不能只看新。

配套技术栈如下:

层次技术选型用途说明
后端基础Spring Boot 2.7.14项目骨架、依赖管理、自动配置
ORM框架MyBatis-Plus 3.5.3数据访问层,单表CRUD几乎不用写SQL
数据库MySQL 5.7 / 8.0业务数据存储
权限认证Spring Security + JWT登录认证与接口鉴权
模板引擎Thymeleaf服务端渲染页面
前端样式Bootstrap 5 + jQuery页面布局和交互
构建工具Maven依赖管理和打包
接口文档Knife4j调试接口、生成API文档

这套组合最大的好处是“中间没有冷门环节”。每一个组件都有海量的中文资料,出了任何问题几乎都能搜到解决方案。同时又不至于太老旧——JWT鉴权、MyBatis-Plus这种用法在企业里非常普遍,用人单位看到了会觉得你的技术栈跟实际工作是匹配的。

2.2 为什么不用前后端分离

这两年前后端分离很流行,Vue + Spring Boot的组合成了培训班标配。但在这个项目里,我刻意选了服务端渲染。不是说Vue不好,而是要看你做这个项目的目的是什么。

如果你是想去面纯前端岗位,那Vue项目自然更有说服力。但Java后端岗位面试,核心考察的是Java基础、Spring原理、数据库设计和业务逻辑能力。用Thymeleaf做服务端渲染,所有页面跳转、参数传递、权限控制都发生在后端,你反而能把登录拦截、会话管理、数据渲染这套后端逻辑理解得更透彻。而且省去了跨域处理、前端构建、反向代理这些环境复杂度,一个项目跑起来的成本大大降低。

对于学习阶段来说,先掌握服务端渲染把后端基本功打牢,之后再学前后端分离时,你理解Vue请求后端接口的过程会比直接从分离项目入手的人清晰得多。

3. 数据库设计:汉服业务的特殊字段和状态机是整张表的灵魂

3.1 核心表结构与字段设计思路

数据库我拆了九张表,分别是用户表、分类表、商品表、商品SKU表、购物车表、订单表、订单明细表、收货地址表和轮播图表。其中商品和订单两张表是设计的重点,尤其要把汉服的特殊性体现进去。

商品表的核心字段如下:

CREATE TABLE `product` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `category_id` bigint(20) DEFAULT NULL COMMENT '分类id,关联分类表', `name` varchar(120) NOT NULL COMMENT '商品名称', `subtitle` varchar(200) DEFAULT NULL COMMENT '副标题,如【现货】或【预售】', `main_image` varchar(500) DEFAULT NULL COMMENT '主图', `detail` text COMMENT '商品详情,HTML富文本', `stock` int(11) NOT NULL DEFAULT '0' COMMENT '总库存', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1上架 0下架', `is_presale` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否预售:1是 0否', `presale_deposit` decimal(10,2) DEFAULT NULL COMMENT '预售定金', `presale_final` decimal(10,2) DEFAULT NULL COMMENT '预售尾款', `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里面最关键的是is_presalepresale_depositpresale_final三个字段。汉服圈非常流行“预约-尾款”的购买模式,商家会先上架一个预售链接,顾客支付定金锁定名额,等工厂出货后再补尾款发货。这个模式如果只用一个price字段根本表达不了,所以在设计阶段就为这种业务场景预留了独立的定金尾款字段。

SKU表设计也跟普通服饰不同。汉服的码数体系不是S/M/L这么简单,而是按“胸围/裙长/袖长”的组合来算的,不同形制的尺码表差异很大。我用SKU表存具体规格组合:

CREATE TABLE `product_sku` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `product_id` bigint(20) NOT NULL COMMENT '所属商品id', `size_name` varchar(50) NOT NULL COMMENT '尺码名:如S/M/L或定制', `color_name` varchar(50) NOT NULL COMMENT '颜色:如豆绿/绯红/月白', `stock` int(11) NOT NULL DEFAULT '0' COMMENT '当前SKU库存', `price` decimal(10,2) NOT NULL COMMENT '当前SKU售价', `sku_code` varchar(50) DEFAULT NULL COMMENT 'SKU编码,用于后端追库存', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这样设计之后,用户在商品详情页选择“绯红色 + S码 + 齐胸襦裙”时,前端通过SKU id来查询具体价格和库存,而不是直接从商品表读取一个笼统的价格。真正的电商系统都是这么处理的,不是只做一张商品表就完事。

3.2 订单状态机设计:从“待付款”到“已完成”的流转规则

订单表是另一个值得展开设计的地方。汉服商城的订单流转比普通商品多出几个状态,完整的订单状态:

待付款 -> 待发货 -> 待收货 -> 已完成 待付款 -> 已取消(用户主动取消或超时取消) 待发货 -> 售后中 -> 退款完成

但对预售商品来说,下单流程变成了两段式:用户先下一笔“定金订单”锁定库存,等商家设置“尾款开始时间”之后,用户再补缴尾款,此时才真正进入待发货流程。这对数据库设计的影响是订单表需要一个order_type字段区分定金订单和普通订单,同时需要一个deposit_status字段来跟踪定金支付状态。

我在订单表里加了这样几个关键字段:

`order_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '订单类型:1普通订单 2预售定金订单 3尾款订单', `parent_order_no` varchar(64) DEFAULT NULL COMMENT '尾款订单的关联定金单号', `deposit_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '定金/尾款支付状态:0未付 1已付', `pay_time` datetime DEFAULT NULL, `delivery_time` datetime DEFAULT NULL, `finish_time` datetime DEFAULT NULL

这套状态流程的落地,是我觉得这个项目跟那些“只有一个状态字段然后代码里硬写if else”的脚本式系统最大的差异。像“定金订单在未支付尾款期间库存是什么状态”“取消定金订单退款后库存怎么回补”这类业务问题,在设计数据库时就必须想清楚,代码实现才会顺理成章。

4. 后端核心模块实现:权限控制、购物车与订单事务处理

4.1 基于JWT的登录认证与拦截器实现

系统分两个身份角色:普通用户和管理员。用户操作商城前端页面,管理员进后台管理商品和订单。我用JWT生成令牌,配合Spring Security做接口鉴权,没有用传统的Session-Cookie方案。原因是JWT无状态,后端不需要存会话记录,无论以后扩展成前后端分离还是接小程序端,现有的认证逻辑都可以复用。

核心配置类里通过SecurityFilterChain定义规则:

@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/user/login", "/user/register", "/product/list", "/product/detail/**").permitAll() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/cart/**", "/order/**").authenticated() .and() .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); }

JWT过滤器每来一个请求就从Header里取token、解析用户信息、把用户身份塞进SecurityContext。这个过程中最容易踩的坑是“token过期后前端拿到的错误信息不友好”,我在全局异常处理器里单独捕获了ExpiredJwtException,返回401加明确的提示文案,而不是让Spring Security返回一长串默认错误页面。

4.2 购物车的临时态与库存预占

购物车模块看起来简单,但有一个细节容易被忽略:购物车里的商品状态是会变化的。用户把一件汉服加进购物车,可能过了一周才结算,这期间商品可能已经下架、价格变动、库存不足。所以购物车表里存的不只是商品id和数量,我还会冗余一份商品当前的快照价格:

`price_snapshot` decimal(10,2) NOT NULL COMMENT '加入购物车时的价格快照',

结算下单时,后端会重新校验商品状态、最新价格和SKU库存,如果价格有变动,会提示用户以最新价格为准。库存预占的逻辑是:进入下单页面时执行一次库存检查,点“提交订单”时再次检查并预扣库存,订单取消或超时未支付后释放库存。我用MySQL的乐观锁实现库存扣减:

@Update("UPDATE product_sku SET stock = stock - #{quantity}, version = version + 1 " + "WHERE id = #{skuId} AND stock >= #{quantity}") int deductStock(@Param("skuId") Long skuId, @Param("quantity") Integer quantity);

这种写法的好处是原子性由数据库保证,并发情况下不会出现超卖。返回0说明库存不足,事务直接回滚。

4.3 下单事务:一主多从的表结构如何保证数据一致性

一笔订单牵扯到四张表:订单主表、订单明细表、SKU库存表、购物车表。我用了@Transactional来保证原子操作:一切校验通过后,生成订单号,插入主表和明细,扣减SKU库存,清空对应用户的购物车记录。任何一个步骤抛异常,全部回滚。

订单号我采用的是“时间戳+用户id后四位+随机数”的方式。单纯用数据库自增id做订单号有两个问题:一是容易被猜到业务量,二是多张表关联时不够直观。自定义生成规则可以做到全局唯一,也方便按订单号快速定位问题。

下单接口的校验顺序很重要,这也是一个真实经验:先验用户、再验地址、再验商品状态、再验库存、最后算价格。校验顺序一旦反了,可能出现用户填了无效地址却先扣了库存这种严重业务事故。我在第一版就踩过这个坑,后来把校验逻辑拆成了一个独立的方法链,每一步都返回明确的错误码,才彻底解决。

5. 前端实现细节:Thymeleaf页面与商品的多规格联动

5.1 商品详情页的多规格选择逻辑

前端层面,我用了Thymeleaf模板加少量的jQuery来实现交互。整个页面开发难度最大的是商品详情页的多规格选择。用户进来看到的是一个商品,要选“颜色”和“尺码”,选完之后价格和库存要联动变化。

思路是这样的:后端返回当前商品下的所有SKU列表,前端把SKU数据序列化后存在一个JavaScript对象里:

var skuMap = { "绯红|S": {id: 1, price: 399, stock: 12}, "绯红|M": {id: 2, price: 399, stock: 0}, "豆绿|S": {id: 3, price: 429, stock: 8} };

用户选择颜色或尺码后,通过颜色 + "|" + 尺码拼接key去查SKU对象,动态更新价格和库存展示。如果某个组合已经无货,对应的尺码按钮直接置灰不可选。

这套逻辑虽然简单,但业务上很实用。汉服店铺最怕的就是用户在详情页里选中了一个颜色之后,发现想要的尺码没货,需要来回切换确认。联动的体验极大降低了这种信息不透明带来的操作成本。

5.2 后台管理页面的表格化设计与操作流

后台管理端包含商品管理、分类管理、订单管理、用户管理和轮播图管理五个模块。每个模块的页面风格是统一的:顶部是查询条件,中间是数据表格,表格行尾是操作按钮。

商品管理里我实现了“上下架”和“编辑”两个核心操作。下架操作通过修改status字段实现,同时会在SKU层面判断是否有未完成的订单,如果有未发货订单,系统会给出警告提示而不是直接硬删商品。这里也体现了一个重要的业务思路:电商系统里商品几乎都是逻辑删除,做物理删除会破坏历史订单的数据完整性。

后台接口我统一以/admin/开头,配合Spring Security的角色权限控制,管理员才能访问。表格数据的分页用MyBatis-Plus的Page插件实现,前端传页码和每页条数,后端返回封装好的分页对象。整个后台开发效率非常高,MyBatis-Plus确实是这类管理系统的开发利器。

6. 项目启动与部署:遇到过的问题和跑通的完整过程

6.1 从Git拉下代码到本地跑起来的完整步骤

我把项目结构按标准Maven工程组织好,拿到的同学不需要做复杂的前端构建,直接就能启动。启动步骤很简单,大致过程如下:

  1. 创建数据库hanfu_mall,导入项目里附带的sql/hanfu_mall.sql脚本,初始化基础数据。
  2. 修改application.yml里的数据库账号密码,以及JWT的密钥配置。
  3. Maven里clean + package打成jar包,或者直接在IDE里运行HanfuMallApplication.java的main方法。
  4. 启动成功后访问localhost:8080,默认管理员账号在初始化脚本里已经预置。

过程中最容易出问题的是数据库版本和时区配置。MySQL 8.x的驱动连接串最好显式加上serverTimezone=Asia/Shanghai,不然插入数据时可能会报时间相关的错误。还有一个容易被忽略的点是JDK版本,项目是基于Java 8写的,如果你本机装的是Java 17,部分老版本依赖会反射报错,需要把编译器级别调回Java 8。

6.2 我把项目放到云服务器上之后遇到的真实问题

本地跑通之后,我把它部署到了一台轻量云服务器上,使用Java 8环境加MySQL 8.0。部署时遇到两个值得记录的问题。

第一个是内存问题。小内存服务器上同时跑MySQL和Spring Boot应用,经常会出现内存不足导致Java进程被系统杀掉的情况。后来我改用java -jar -Xmx256m -Xms128m hanfu-mall.jar这种方式手动限制JVM堆内存,并且在服务器上配置了swap空间,系统才稳定下来。开发阶段用默认内存没问题,生产环境一定要评估内存用量。

第二个是端口安全。直接对外开放8080端口不太安全,我加了防火墙规则,只允许通过Nginx反向代理访问,同时把8080端口禁止了外网直接访问。实际访问路径是浏览器 -> Nginx 80端口 -> Java应用8080端口。同时针对这个项目配置了/api/路径的动静分离,静态资源由Nginx直接返回,动态请求转发到Spring Boot,访问响应速度提升明显。

7. 测试与性能优化:接口压测和并发场景下的真实表现

7.1 核心接口的压测数据与瓶颈定位

我使用JMeter对商品列表、商品详情、下单三个接口做了一轮简单的并发压测。测试环境是2核4G的云服务器,模拟100个线程同时循环请求,观察到的数据是:商品列表接口的QPS大约在380左右,详情接口QPS在220左右,下单接口由于涉及多表写入QPS较低,大约为80。

从压测结果里能明显看到下单接口是瓶颈。通过打印SQL日志发现,主要耗时在订单主表和明细表的插入操作,以及库存扣减语句上。为了减轻数据库压力,我给order表的主要查询字段添加了复合索引(user_id+create_time),给明细表的order_no加了唯一索引,同时把商品列表接口里原本查数据库的分类名称改成了Redis缓存。

在项目里引入spring-boot-starter-data-redis,商品列表接口先从Redis读,缓存未命中再去查数据库并回填缓存。加上一层缓存后,同样的压测条件下商品列表的QPS提升到了接近820,效果非常明显。如果只做一个优化点,我建议优先做这个,改动量不大但收益立竿见影。

7.2 SQL日志分析与慢查询排查

MyBatis-Plus在配置里开启了mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl,控制台能直接打印出完整SQL。排查慢查询时用上了MySQL自带的慢查询日志功能,定位到一条订单列表查询语句在订单量大了之后执行很慢。原因是最初的列表查询没有分页时就把所有字段查出来,再在内存里排序。后来改成只查需要展示的字段,并且配合Page插件做真正的数据库分页,慢查询问题就消失了。

这条经验可以提炼成一句通用原则:SQL不要select *,列表页永远走数据库分页而不是全量查出来内存截取。这个错误在初学者项目里极其常见,面试时主动把这个优化过程讲出来,会比单纯说“我用了分页”有说服力得多。

8. 这个项目能扩展成什么:简历上的一句话和真实业务逻辑的差距

8.1 给准备拿这个项目去面试的同学三个建议

第一个建议是项目描述不要只写“实现了商品管理、订单管理”,要突出业务复杂度和技术深度。比如你可以这样写:

  • 基于Spring Boot + MyBatis-Plus实现汉服商城,支持商品多SKU规格联动、预售定金/尾款切换、库存超卖防护(乐观锁)、JWT无状态认证
  • 订单模块采用多表事务处理,设计了包含普通订单/定金订单/尾款订单的完整状态机
  • 通过Redis缓存优化商品列表接口,QPS从380提升至820

第二是针对“为什么这么设计”的追问要提前准备。面试官大概率会问:为什么用JWT不用Session?为什么库存扣减用乐观锁不用悲观锁?定金订单取消后库存怎么返还?这些设计决策在文中都有对应的解释,你要能用自己的话讲出来,而不是背答案。

第三是提前想一想自己目录里的代码结构是否能讲清楚。我在项目里按controller / service / mapper / entity / common分包,每个包职责单一,没有出现一个类几千行的现象。面试官如果让你打开IDE讲项目,按这个结构讲下来思路会非常清晰。

8.2 更进一步的扩展方向

如果时间充裕,可以往这几个方向延伸:接入支付宝沙箱支付完成真实的支付流程;引入RabbitMQ做订单超时未支付自动关闭的延迟队列;做数据统计报表,分析用户画像和热门商品;将项目改造成前后端分离,用Vue3重写前端。这些扩展方向每一个都能单独写成项目亮点,在简历上会非常有竞争力。

我在部署和扩展这个项目的过程中最深的感受是:一个项目的价值不在于用了多么炫酷的技术,而在于你能否把一条完整的业务链路设计清楚、实现清楚、讲清楚。汉服商城这个选题正好把电商的通用能力包装到了一个具体且有趣的场景里,做一遍之后,你对Spring Boot、数据库设计、接口设计、部署上线的理解都会扎实很多。如果你正在为项目选题发愁,不妨就直接拿这套方案动手试试,从克隆代码跑起来开始,然后尝试自己改需求、加功能,这个从“运行别人代码”到“能自己改代码”的阶段,才是收获最大的过程。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询