
又是一年毕业季每到这个时候“XXX商城系统”绝对是最不缺的选题但说实话十个商城里面能让我觉得“这学生是真做过、不是抄的”的不到两三个。今年带学生做了一套“基于Java Web的银饰饰品商城系统”技术栈落在Spring Boot上从选题、建模、写代码到打包部署走完了一整遍。银饰饰品这个品类挺有意思它跟卖衣服、卖数码不一样的地方在于涉及纯度、克重、工费、定制等一堆额外维度做完这套系统电商项目里最麻烦的几个点基本都摸透了。这篇文章就把这套系统的设计思路、核心模块实现、踩过的坑完整拆开讲一遍给准备做同类毕业设计或者想练手电商项目的朋友一个能直接抄作业的参考。1. 表面是商城选题实际在考这几件事1.1 银饰品类给商城系统加了哪些额外要求很多人一听“商城系统”第一反应就是商品表、用户表、订单表然后开始搞CRUD。如果只是做一个普通杂货商城这么做确实够了导师也不会说你什么。但银饰饰品不一样这个品类本身的特性会逼着你把设计想得更细。银饰商品的信息维度比普通商品多得多。普通商品可能只需要一个标题、一张主图、一个价格、一个库存数。银饰呢材质925银、990足银、999足银、克重、工费、款式戒指、手镯、项链、耳环、吊坠、有无镶嵌锆石、翡翠、玉石、是否支持刻字、是否支持定制手围/圈号、是否有权威机构鉴定证书。这些维度如果全部平铺在商品表里那张表会变成一张超级宽表而且没法应对“同一款戒指有3号、4号、5号圈口有两个材质版本”这种典型的电商SKU场景。所以第一个考点就出来了你有没有能力把商品数据拆成SPU和SKU两层SPU是“一款戒指”SKU是“这款戒指的某一具体规格”比如“999足银素圈戒指-3号圈口-无刻字-重3.2克”。库存、价格、图片、SKU编码这些必须挂在SKU层级而商品的标题、详情、主图说明、工艺介绍则挂在SPU层级。另外银饰的价格计算也很有意思。纯银饰品通常不是一口价而是按“银价×克重 工费 搭配成本镶嵌/珐琅”来算的碰到活动还可能有折扣。银价还是浮动的今天一个价明天一个价。这就牵扯到一个电商系统里很重要的概念价格快照。下单那一刻算好的总价要原封不动存进订单里不能等发货了用户再去查商品页发现价格变了那样订单金额就说不清了。这些细节才是导师和评委真正想看到的“业务理解”。1.2 Spring Boot版本与配套依赖的选择思路技术选型这块我直接说结论如果你没有特别强烈的理由毕业设计请用Spring Boot 2.7.x不要盲目追3.x。原因特别现实。Spring Boot 3.x把javax命名空间迁到了jakarta很多老教程、老代码片段里的javax.servlet、javax.persistence直接报不存在MyBatis-Plus的早期版本兼容3.x也有问题。毕设这东西越往后越没时间折腾兼容性。2.7.x是javax时代的最后一个稳定大版本网上资料最全、踩坑的人最多、解决办法也最好找。配JDK 8或者JDK 11本地跑起来非常舒服Maven打包也顺利。配套依赖我建议这样搭ORM用MyBatis-Plus 3.5.x数据库用MySQL 8.05.7也完全没问题缓存用Redis如果不想加分可以不接但接了Redis做首页缓存和登录Token存储答辩时能多聊两句权限这块如果不想引入Spring Security那一大坨代码可以用拦截器JWT或者直接上Sa-TokenSa-Token对新手极其友好。前端的话如果你对前端不熟用Thymeleaf模板JQuery就够了如果想显得项目“现代”一点用Vue 3 Element Plus做单独的前端工程后端纯接口。这里要提醒一句毕设求的是稳不是新。你用Spring Boot 3 JDK 21确实听着新潮但万一环境有问题你连网上的报错信息都搜不齐。2. 功能模块设计与数据库建模2.1 用户端与后台管理端的功能清单这套系统我按标准的前后端分离结构来做。用户端面向普通消费者后台管理端面向运营管理员。用户端核心功能有注册登录、商品浏览、按分类筛选、关键词搜索、商品详情查看多图轮播、SKU选择、加入购物车、购物车管理、下单结算、订单列表与详情、发起售后状态跟踪、收货地址管理、个人资料修改。如果还要凑点亮点可以加一个“饰品搭配推荐”——根据用户最近浏览的款式推荐同材质或同风格的商品这个推荐逻辑不用多高级基于标签匹配就行。后台管理端就相对常规了管理员登录、仪表盘统计今日订单数、销售额、热门商品Top10、商品管理SPU的上下架、SKU的价格与库存管理支持批量上传图片、分类管理、订单管理订单列表、详情、发货操作、关闭订单、用户管理、轮播图管理。要是你们导师要求必须有“系统管理三件套”那就把用户管理、角色管理、菜单管理补齐加上一个简单的权限注解就能应付。要注意的是后台管理端不要做成和用户端混在一个页面里。最好是单独的路由前缀比如/users/**和/admin/**分开后端接口也分开方便做权限隔离。2.2 核心表结构设计与字段取舍数据库设计这部分我直接给出一版可落地的核心表结构。有一个原则订单相关的表和商品相关的表凡是涉及金额、数量、描述的地方一定要存快照字段而不是下单以后还去商品表里现查。user表id、username、passwordBCrypt加密、nickname、phone、avatar、create_time、status。category表id、parent_id、name、level、sort、is_show。银饰分类建议做成两级就够了一级比如“戒指”“手镯”“项链”“耳环”——这个字段名称需要注意不要叫name因为数据库里“name”容易和MySQL的关键字冲突实际建表时用category_name更稳。product表SPU层id、category_id、product_name、subtitle、main_image、detail_html、is_on_sale、create_time、update_time。detail_html存富文本详情页答辩时展示管理端的富文本编辑器会比较加分。product_sku表SKU层id、product_id、sku_code、material、purity925/990/999、weight_gram、craft_fee、extra_fee镶嵌/珐琅等附加费、price最终销售价、stock、sku_image、sale_count。这个price字段就是前台展示价后台也可以自动计算但必须允许管理员手动覆盖因为银价会波动系统不可能每次都自动去抓银交所行情。cart表id、user_id、sku_id、quantity、checked、create_time。checked表示是否在结算时勾选很多商城支持“购物车勾选部分商品下单”这个字段不能省。orders表id、order_no、user_id、total_amount、freight_amount、pay_amount、pay_type、pay_status、order_status、receiver_name、receiver_phone、receiver_address地址快照不要把地址ID存进去因为用户改地址后历史订单地址不能变、engrave_text刻字内容银饰定制常用、remark、pay_time、ship_time、create_time。order_item表id、order_id、sku_id、product_name、sku_spec_desc把“999足银-3号-无刻字-3.2g”拼成字符串存起来、price、quantity、weight_gram、craft_fee、extra_fee。订单详情页直接查这张表不关联商品表这样商品下架了订单一样能看清楚。另外强烈建议加一张silver_price表id、price_date、price_per_gram、update_time。每天可以后台手动录一次当日银价下单时用“price_per_gram * 克重 工费”作为计算基准。这张表的存在会让你有话可聊价格可追溯、价格波动可控、历史订单金额可解释。这在答辩里是很好的加分点。别小看这几张表的取舍很多人的订单金额写的是商品页面现查出来的价格等到演示的时候商品过期了、订单金额对不上直接露怯。2.3 电商系统里的金额计算到底怎么做银饰商品的最终售价计算逻辑我在后台管理端绑定了一个价格生成规则核心公式是最终售价 round(当日银价 × 克重 工费 附加费, 2)但注意这个公式只是“建议价”。实际操作中店铺可能会做满减、新客立减、会员折扣这些活动一叠加就不是一个简单公式能算完的了。我的做法是把折扣逻辑放到订单金额计算阶段而不是写到商品SKU里。SKU里只维护基准售价下单时再根据活动规则计算实际支付金额。活动规则如果毕设不想做太复杂可以做成一张activity表定义满多少减多少而且只作用在“商城首页推荐”的商品上。金额计算这块最容易被扣分的点是用了double存金额。千万千万别用doubleJava里0.10.2得不到0.3金额这种东西必须用BigDecimal数据库字段用decimal类型。我见过太多学生因为这个在答辩时被追问到哑口无言。所有涉及金额的入参和出参都用BigDecimal处理排序、除法和四舍五入严格遵守财务规则。2.4 项目目录结构与接口规划一个清晰的目录结构比写一百行注释都有用。我用的是常见的分层结构com.example.silvermall ├── common // 统一返回结果、异常处理、工具类 ├── config // 拦截器、跨域、Redis配置 ├── controller // Controller层按模块分包 │ ├── user // 用户端接口 │ └── admin // 管理端接口 ├── service │ ├── impl ├── mapper // MyBatis-Plus的Mapper接口 ├── entity // 数据库实体 ├── dto // 前端传入参数对象 ├── vo // 返回给前端的视图对象 └── task // 定时任务比如超时关单接口命名尽量遵守REST风格/api/user/cart、/api/user/order、/api/user/address管理端用/admin/goods、/admin/orders。不要在Controller里写大量业务代码Controller只负责接收参数和返回结果核心逻辑放Service层。一个小技巧所有接口返回统一的Result对象结构是{code:200, message:ok, data:...}。这样前后端联调时前端只要处理一种数据格式省掉一大堆重复判断。3. 核心流程的落地实现3.1 注册登录与权限处理从Session到JWT我最终选择了JWT 拦截器的方式不用Spring Security因为它对于毕设来说太重了配置半天还不一定搞得懂。登录流程很简单用户输入账号密码后端用BCrypt校验密码校验通过后生成一个tokentoken里带上用户id和角色然后返回给前端。前端每次请求在Header里带上Authorization: Bearer token后端拦截器解析token拿到用户信息放行或者拒绝。管理端接口单独走一个拦截规则校验管理员角色。这里有两个容易踩的坑。第一个是token过期问题JWT不像Session可以主动销毁所以退出登录时不要只依赖前端删token后端最好维护一个Redis黑名单把退出登录的token丢进去。第二个是密码不能明文存数据库用BCrypt加密MyBatis-Plus自带BCryptPasswordEncoder直接拿来用就行。答辩时导师问“密码存数据库被拖库怎么办”你能答出加密这一点就已经合格了。3.2 商品检索与SKU组合选择商品检索这块强烈建议不要上Elasticsearch。毕设项目里一个几百条商品的数据量用SQL的LIKE查询完全足够。搜索关键词拆成多个词分别匹配商品名称、副标题、材质字段就行。我用的是MyBatis-Plus的LambdaQueryWrapperLambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); if (StringUtils.isNotBlank(keyword)) { wrapper.and(w - w.like(Product::getProductName, keyword) .or().like(Product::getSubtitle, keyword) .or().like(Product::getMaterialKeyword, keyword)); }分类筛选同理传一个categoryId然后递归查出所有子分类的ID集合再IN查询。二级分类的场景这种写法足够。SKU选择是银饰商品页面的关键交互。用户选“手镯”→选“圈口”→选“材质”→选“是否刻字”每一步都会影响到底展示哪个SKU、什么价格、多少库存。前端做法是把当前商品的所有SKU一次性返回给前端前端根据用户已选的属性逐级过滤如果某个属性组合没有对应SKU就置灰不可选。后端只需要提供一个接口GET /api/product/detail/{productId} 返回商品SPU信息 所有SKU列表不要把“查询某个SKU是否有货”做成每次点击都请求后端那样做基本没法用用户体验特别差。3.3 购物车到订单的完整链路购物车的接口不多加购、修改数量、勾选/取消勾选、删除、查询列表全部围绕当前登录用户的user_id操作数据存Redis也可以但我建议还是落MySQL表因为毕设要展示数据持久化能力关系型表结构更直观。重头戏在下单流程。我先拆解一个完整的下单接口的步骤这部分基本是全网商城项目的通用答案从购物车取出勾选的SKU列表。循环校验每个SKU的状态和库存是否充足。重新计算总金额包括商品金额、运费这里是按订单金额满99包邮否则8元运费、活动扣减。生成订单主表和订单明细表状态为“待支付”。扣减库存扣减方式必须用乐观锁UPDATE product_sku SET stock stock - #{quantity} WHERE id #{skuId} AND stock #{quantity}受影响行数为0就说明库存被抢没了。删除购物车中对应的勾选项。上面所有步骤放在一个Transactional事务里任何一步失败全部回滚。关于第5步为什么用乐观锁这是一个必考知识点。如果直接用UPDATE ... SET stock stock - 1高并发下可能把库存扣成负数也就是超卖。用WHERE stock #{quantity}这个条件数据库层面的行锁会挡住超卖。到这里你的系统已经有了最基础但也最关键的并发安全处理。能在答辩现场说清楚这一条技术深度直接就上了一个台阶。订单支付环节我给的方案是“支付宝沙箱支付 本地轮询模拟成功”。真实对接支付宝需要商户号、密钥、回调地址对于毕设来说很容易在配置环节卡住。我的做法是下单时调用支付宝沙箱的统一下单API拿到支付链接用户跳转到沙箱收银台完成支付支付宝沙箱环境会自动回调我们的通知接口。如果实在没注册沙箱做一个模拟支付页面也行——用户点击“模拟支付成功”后端直接把订单状态改成已支付。答辩演示时用模拟支付最不容易出意外因为你永远不知道现场网络和支付宝环境会不会掉链子。3.4 超时未支付订单的处理下了单不付钱订单就一直挂在“待支付”状态库存还被占着这是有问题的。处理方式一般有三种定时任务轮询、延迟消息队列、Redis过期监听。对于毕设我用的是Spring自带的定时任务每分钟扫描一次“待支付”且创建时间超过30分钟的订单把它们改为“已取消”并回补库存。代码就一个注解加一个方法Component public class OrderTimeoutTask { Scheduled(fixedRate 60000) public void closeExpiredOrders() { // 查询超时订单 // 批量更新订单状态为已取消 // 同步回补库存 } }这个方法负责、逻辑简单而且在答辩时非常容易讲明白。如果要显得更高级可以提一句“生产环境可以用RocketMQ延迟消息”但不用真的去实现。3.5 后台订单管理与发货流程管理端订单列表我做了按订单状态Tab切换待支付、已支付、已发货、已完成、已取消、已退款。管理员在“已支付”列表里找到订单点击发货填写物流单号订单状态变为“已发货”用户端收到已发货状态后点击确认收货订单变为“已完成”。这里有一个小坑订单状态的流转不能随便跳。比如已取消的订单不能再发货已发货的订单不能取消。我的做法是在Service层写一个状态流转校验方法每个操作进来先校验当前状态是否允许目标状态不允许就抛异常返回提示。光这一个细节就能让你的项目在评委眼里的完成度高出一截。4. 部署、打包与答辩准备的实战经验4.1 本地开发环境配置的坑说几个我每次陪学生配环境都会遇到的“经典报错”。第一个Failed to configure a DataSource: url attribute is not specified。出现这个就是application.yml里数据源没配置或者配置没被加载。检查一下配置文件的位置Spring Boot会默认加载resources下的application.yml你把配置文件放别处了它当然找不到。第二个MySQL连接时区报错。连接串上一定要加这样一段jdbc:mysql://localhost:3306/silvermall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai不加serverTimezone高版本的MySQL驱动会抛The server time zone value \u00D6\u00D0\u00B9\u00EA\u00B1\u00EA is unrecognized。第三个是Maven依赖下载极慢的问题。直接在所有Maven项目的settings.xml里配阿里云或腾讯云的镜像源别去等Maven中央仓库的龟速下载。这个配好构建时间能从十几分钟降到几十秒。4.2 项目打包与上线部署后端打包用Maven很简单IDEA右边栏点package或者命令行执行mvn clean package -DskipTests找不到主类或者打包出来无法运行检查pom.xml里是不是漏了spring-boot-maven-plugin。这个插件负责把项目打成可执行jar包漏掉它打出来的jar是一个普通的jar直接java -jar是起不来的。前端如果是Vue项目执行npm run build生成dist目录。最后部署有两种路子第一种是前后端分离部署后端jar一台服务器dist里的静态文件丢Nginx或者直接放在后端jar同级的static目录里第二种是简化操作把dist目录里的文件复制到Spring Boot的resources/static目录下再重新打包这样只跑一个jar就能同时提供页面和接口。毕设演示环境我一般用第二种简单省事。4.3 答辩文档与现场演示的准备文档部分学校一般要求任务书、开题报告、中期检查、毕业论文、答辩PPT。除了答辩PPT我觉得论文里最重要的两个图是ER图和数据流图。别用网上找的破图自己用draw.io画把表关系画清楚各表的主外键、一对多关系标明白。评委看论文有时不细读正文但一定会看图。演示环节我建议准备一个固定的“演示脚本”按顺序走完整个流程避免现场慌乱前台注册一个账号登录。首页浏览推荐饰品进入商品详情页切换SKU。加入购物车再去挑一件勾选两件一起下单。用模拟支付完成支付订单变“已支付”。切回管理端看到这笔订单点击发货。回用户端看到“已发货”确认收货。最后展示一下后台的商品管理、订单统计图表。整个流程控制在8分钟以内讲清楚每一步背后的数据变化比如“刚支付时orders表的pay_status字段从0变成1”“发货后order_status从2变成3”。讲数据流转比讲界面操作更有说服力。5. 常见问题速查与排查实录5.1 启动与依赖类问题现象大概率原因解决办法Failed to configure a DataSourceapplication.yml 数据源未配置检查配置文件路径及内容端口被占用本机8080被其他进程占用server.port8081或杀掉占用进程打包后无法启动缺少spring-boot-maven-plugin在pom.xml中加入该插件lombok编译报错Lombok版本和JDK不匹配升级Lombok依赖到1.18.30Spring Boot 3.x下MyBatis-Plus报错jakarta与javax冲突换回Spring Boot 2.7.x5.2 前端联调与接口问题前后端分离最常遇到的就是跨域报错浏览器控制台报CORS policy之类。解决办法是在后端加一个CORS配置类千万别用浏览器插件糊弄过去因为答辩现场很可能用的不是同一台电脑。我用的配置是Spring Boot里最标准的那个WebMvcConfigurer跨域配置允许所有来源、所有头部、所有方法。第二个高频问题是接口返回时间格式不对前端显示2025-06-01T12:00:00很难看。在application.yml里配一下spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai第二天再查接口数据时间格式就正常了。这种细节很影响答辩观感因为评委一旦觉得“这个页面怎么怪怪的”印象分会掉。5.3 数据一致性相关问题曾经有个学生遇到一个很奇怪的问题订单创建成功了但是购物车里的商品还在。排查下来发现他的下单接口并不是一个事务方法扣库存是一个Mapper方法、删除购物车是另一个Mapper方法两个方法各自执行中间如果抛出异常前一步提交的事务就回不掉了。解决方式很简单在Service实现类的下单方法上加Transactional(rollbackFor Exception.class)然后需要注意的是同类中自调用方法事务注解会失效必须把下单逻辑拆到另一个Service或代理类里调用。另外一个经典问题是订单状态已经“已支付”但商品详情页的销量没增加。原因是销量更新直接写在支付回调前面支付回调失败导致订单状态没有更新而销量已经加了。正确的顺序应该是先更新订单状态并记录支付流水再异步更新销量统计。这类问题没有固定的代码模板核心是理清“哪个操作成功才允许执行下一个操作”。5.4 银价波动导致的金额不一致这部分是我自己实际遇到过、后来专门处理的。后台每天早上录入银价但如果当天银价变了用户浏览时的SKU价格显示还是昨天的。处理方式是用户端商品列表和详情页的展示价不直接从SKU表读而是通过一个ProductPriceService动态计算底层用Redis缓存今日银价缓存有效期设一小时也就够了。SKU表里保存的price字段我把它定位成“基准参考价”只在商品列表做排序和过滤用真正下单时走动态计算流程。后来接受导师建议把每次下单的计算参数包括当时银价、克重、工费、各种折扣原因全部存进订单明细表里。这样每一笔订单都能说清楚钱是怎么加出来的。这个设计在论文里单独拉了一小节写答辩时老师的反馈是“比纯CRUD项目有想法”。6. 从这套项目里能带走什么做完这套银饰商城我个人的体会是毕业设计最有价值的不是那个评分而是你完整走了一遍“业务分析—建模—编码—调试—写文档—答辩”的闭环。以后面试被问电商场景你可以直接说我做过一个带SKU拆分的商品系统用乐观锁防过超卖用事务保证过订单一致性用Redis缓存过热点商品。这些不比背一百道八股文更有说服力吗。最后分享一个经验如果你拿到的毕设题目是一个现成模板一定不要直接用。把表结构改一改、把字段名换一换、把业务逻辑里加上一两个自己真正理解的功能点。哪怕只是加一张“银价波动记录表”、加一个“手工改价需填写原因”的逻辑这个项目就长在你身上了。毕设答辩问的大多数问题其实都围绕一个点——这个东西到底是不是你自己做的。你只有在项目里埋下过只有你自己知道的细节才经得起追问。希望这篇拆解能帮你少走一点弯路做出一个真正能扛住答辩的银饰饰品商城。