1. 选题思路与技术方案设计1.1 满大街的商城系统怎么做出辨识度计算机毕业设计做商城系统每年都是热门这句话估计计算机专业的同学都听腻了。但你要是真去了解一圈会发现大部分“商城毕设”只是几个页面拼出来的壳子商品数据是随便填的订单逻辑是硬凑的答辩老师问一句“库存和订单怎么保持一致”就直接卡壳。我自己做过一轮完整项目后最大的感受是商城系统的难点从来不在页面多好看而在业务链路能不能自洽。所以我当时做这个基于 Java Spring Boot 架构的南京特色美食小吃商城系统时第一件事不是急着写代码而是想清楚怎么让选题有辨识度。单纯叫《网上商城系统》这种题目已经烂大街了所有人都在抢同一套通用模板开题报告都不知道怎么写。给题目加上“南京特色美食小吃”这个前缀后整个项目的气质就不一样了商品分类有天然层次可以是秦淮名小吃、鸭类熟食、糕团点心、汤包面点每个商品除了价格还能带产地、老字号、推荐指数这些属性评论功能也有真实场景比如有人买完盐水鸭回来评价“切工不错但偏咸”后台的商品推荐位、轮播图也跟着有了实际运营价值。切这个角度不是为了标题党而是让整套系统从数据库到接口到前端都有业务支撑。游客到了南京想吃地道小吃不知道去哪家、点什么本地商户想把特色美食搬到线上销售平台要有后台去管理菜品、订单、用户和内容。这个需求闭环在答辩时非常容易讲清楚老师问“为什么做这个”你有足够扎实的答案而不是说“因为别人都做商城”。1.2 Spring Boot 分层架构为什么是一台稳定发动机技术选型上我选了 Java Spring Boot这在当时几乎是零风险的决定。Spring Boot 自带内嵌 Tomcat打包成一个 jar 文件就能直接跑省掉了传统 SSM 项目里单独配置 Servlet 容器的麻烦配合 Maven 管理依赖打开项目就能启动。省出来的是整理代码和写文档的时间而这些时间对毕业设计来说非常关键。代码组织用的是最经典的三层架构也是整个行业最普遍的分层方式Controller 层负责接收参数、调用 Service、统一返回Service 层放业务逻辑比如下单事务、库存扣减、状态流转Mapper 层用 MyBatis-Plus 做单表 CRUD复杂查询再写少量 SQL。当时也有人劝我说干脆上一套微服务架构把 Nacos、Gateway、OpenFeign 全塞进去显得技术含量高。还好我没听。毕业设计考察的是你对业务场景的完整把控能力不是拆装了多少中间件。单体架构不是落后它模块边界清楚、部署简单、代码可读性高答辩时你反而能把逻辑讲得更透。我项目里依然按用户、商品、购物车、订单、支付、评论、运营管理这些模块来划分包结构将来真要拆微服务这些模块就是现成的边界。2. 功能拆解与数据库设计2.1 用户端、管理端和三类角色的权限边界商城系统至少要分清三类使用对象普通游客、注册用户、后台管理员。游客能浏览商品和搜索但没法加购下单注册用户登录后可以加购物车、下单、评价、收藏管理员负责维护菜品、分类、订单、Banner 和用户状态。如果不区分这些后台管理页面谁都能访问接口安全性在答辩时完全站不住脚。用户端要做六个主功能首页展示、分类浏览、关键字搜索、商品详情、购物车、订单中心。首页要有轮播 Banner 和推荐位把热门小吃推到最前面分类页用左侧分类树右侧商品列表的布局方便按鸭血粉丝汤、梅花糕这些品类筛选商品详情页要展示图片、价格、库存、月销量和评价购物车支持选中、改数量、删项订单中心可以看到待支付、待发货、已完成等各类状态。管理端相对直接一点登录后能看到数据统计仪表盘比如订单总量、销售额、待发货数量商品管理用于上下架菜品和改价分类管理维护分类树订单管理可以发货、取消、查看详情评论管理能隐藏敏感评价Banner 管理负责首页运营位用户管理负责启用或禁用账号。把这两个端面的功能列一表项目的基本盘子就清晰了。模块用户端管理端商品搜索、分类筛选、详情新增、编辑、上下架、库存调整购物车加购、改数量、删除、选中结算无订单创建、支付、取消、确认收货发货、查看详情、取消订单评论对已完成订单评价删除或隐藏违规内容内容浏览 Banner、公告维护 Banner、轮播图用户注册、登录、地址管理查看列表、禁用账号表里的每一行都是后续数据库设计的一个依据。千万不要先建表再做功能更推荐先画功能结构再回来反推表这样字段不会缺。2.2 十张核心表把业务关系锁死数据库是整个项目的底盘也是我之前查漏补缺时间最长的地方。整理下来最重要的表有这些用户表 user、分类表 category、美食商品表 food、购物车表 cart_item、地址表 address、订单表 orders、订单明细表 order_item、评论表 comment、收藏表 favorite、轮播图表 banner。每张表的职责必须单一。比如 user 表里除了基本账号信息还必须有 role 字段区分普通用户和管理员status 字段控制账号是否可用。category 用 parent_id 支持多级分类方便做“南京小吃 汤包面点”这种层级。food 表是核心除了名称、描述、图片、价格之外一定要有库存 stock、销量 sales、上架状态 status、是否推荐 is_featured这些字段直接支撑列表排序、库存扣减和首页推荐。订单相关的两张表需要特别说明。orders 表记录订单主信息包括订单号 order_no、用户 ID、总金额、状态、收货人、联系电话、收货地址、下单时间和支付时间。但它不会把商品信息直接存进去因为一个订单对应多个商品必须拆出 order_item 明细表记录每个商品的 ID、名称、图片、购买时价格、数量和小计金额。为什么要在明细表里冗余一份商品名称和价格因为商品表以后可能改价、改名甚至删除但历史订单必须保持当时的快照不然用户查看历史订单时看到的是现在的新价格逻辑就乱套了。订单金额、明细金额都用 DECIMAL 类型而且统一保留两位小数数据库浮点类型容易出现精度问题这个地方省不了。收藏表 favorite 就三个核心字段用户 ID、商品 ID、收藏时间再加唯一约束防止用户重复收藏同一件商品。地址表除了联系人、电话、详细地址还要有 is_default 表示默认地址下单时优先取默认值。这十张表建完之后业务关系的骨架基本就定了。2.3 订单状态机和金额设计的细节订单状态是整个系统里最容易讲不清楚的地方也是答辩时老师比较爱抠的细节。我最终用的是五个状态0 待支付1 已支付待发货2 已发货3 已完成4 已取消。流程很直接用户下单生成待支付订单支付成功变成已支付管理员后台点发货变已发货用户确认收货变已完成下单后不支付可以取消变成已取消。如果做退款还需要再加一个退款状态但毕业设计先跑通主线流程更重要。状态流转一定要在代码里限制不能允许用户从已完成再跳回待支付。最简单的方式是 Service 层做一个状态校验每次更新订单状态之前先查询当前状态判断该状态下是否允许跳转到目标状态不是只靠前端按钮隐藏。订单号生成也有讲究别用简单的自增 ID用户下单后看到的订单号至少要有一定的随机性我用的方案是“yyyyMMddHHmmss 4 位随机数”后面发现并发高的情况下有可能重复又加了数据库唯一索引兜底。设计这块时宁可保守一点也不要让订单号出现重复否则支付回调时对不上单子是非常尴尬的事。3. Spring Boot 工程搭建与公共能力封装3.1 初始化项目和依赖清单工程初始化用 IDEA 的 Spring Initializr 就很方便几秒钟就能生成一个基础工程。需要注意版本一致性问题如果电脑装的是 JDK 8别去硬选 Spring Boot 3.x因为 Spring Boot 3 整套规范迁移到了 Jakarta很多旧教程的代码会直接报错稳妥的方案是 JDK 8 配 Spring Boot 2.7JDK 17 再考虑 Spring Boot 3.x。我的项目最终跑在 JDK 8 Spring Boot 2.7 上代码兼容性和教程资料都更丰富。pom.xml 里核心依赖大致是这些dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependencyMyBatis-Plus 一定要配合分页插件使用不然 Page 查询会失效这是很多人都会踩的坑。另外建议加上 spring-boot-starter-validation用来做参数校验比自己在代码里写一堆 if 判断要清爽得多。3.2 统一返回体和全局异常处理先把基本功打好工程骨架搭好后第一件事不是急着写登录业务而是先封装公共返回体和全局异常处理。这两样东西直接决定你后面的开发速度和接口规范性。统一返回体我定义了一个 Result 泛型类Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT ok(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT fail(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }有了这个类之后所有 Controller 返回的形状都一样前端处理逻辑也会统一。配合全局异常处理器把业务异常、参数异常、系统异常分别处理前端不会收到莫名其妙的堆栈信息。我自定义了一个 BizException 业务异常在 Service 层遇到库存不足、商品已下架这种情况时直接抛出由全局异常处理器统一转换成 Result.fail 返回给前端。这样代码可读性高答辩时说起“统一异常处理”也是一个务实亮点。3.3 基于 JWT 的登录鉴权怎么落地登录鉴权我用了 JWT流程并不复杂用户提交用户名和密码后端校验通过后生成一个包含用户 ID 和角色信息的 token 返回前端之后每次请求都在请求头里带着这个 token。后端写一个拦截器拦截需要登录的接口解析 token 并把用户信息放入 ThreadLocal这样 Service 层随时能拿到当前用户不用每个方法都传一遍用户 ID。要注意拦截器得配白名单。用户的登录注册、首页展示、商品列表和详情这些接口必须放行否则游客根本打不开页面。管理端接口则要额外校验角色只有管理员角色的 token 才能访问。刚开始做鉴权的时候我犯过把/**全部拦截掉的错误导致前端所有请求都 401花了不少时间排查。后来固定套路是先放行静态资源和公开接口再拦截剩余路径问题就少了很多。4. 核心业务实现商品、购物车、订单一条线4.1 商品列表与条件检索细节都在参数里商品列表页是最常见的入口需要同时支持关键字搜索、分类筛选、排序和分页。Controller 接收这些参数后Service 层用 MyBatis-Plus 的 LambdaQueryWrapper 动态拼接条件。Keyword 参数要处理空字符串和空格情况不能直接拼到 SQL 里所有用户输入先 trim再用like条件。搜索范围放在商品名称和描述上比如用户搜“鸭血粉丝汤”可以把名称或描述里包含这个关键词的菜都查出来。排序功能是个容易被忽视的安全隐患。如果排序字段直接接收前端参数然后拼进 SQL可能被传进来奇怪的字段名导致报错甚至注入风险。我的处理是定义一个排序字段白名单只允许sales、price、create_time这几个值其他一律走默认排序。分页使用 MyBatis-Plus 的分页插件返回给前端的数据结构统一为{ records: 商品列表, total: 总数, current: 当前页, size: 每页条数 }前端拿到直接就能渲染。4.2 购物车数量合并与价格以服务端为准购物车的核心逻辑在数量合并和价格重算。用户点“加入购物车”时后端先查这个用户的购物车表里有没有同一件商品如果已经存在就把数量加一而不是再插一条新记录如果之前没有才创建一条新记录。这个逻辑看似简单但实际操作时很多人会漏查直接插入结果购物车里出现同一商品多行数据结算时金额全部算错。价格重算更关键。前端传过来的商品 ID 和数量只能作为参考后端必须重新从商品表查出最新价格和库存。举个例子前端页面显示某套餐 15.8 元用户加入购物车后管理员把价格改成了 18 元如果后端不重新查价格用户还是按旧价格结算平台就要亏损。购物车列表接口也一样每次查询都要 join 商品表拿最新价格和上架状态已下架商品要标记为无效前端显示“已失效”而不是让用户继续提交。接收请求时价格安全原则是非常重要的一条实际经验。4.3 下单事务与库存扣减一致性要守住下单是整个系统里最需要把守的地方因为涉及多个表同时变更生成订单主记录、生成订单明细、扣减库存、清空购物车。这四步任意一步失败其他步骤都必须回滚所以 Service 方法上必须加Transactional注解由事务保证原子性。库存扣减这块有一个很多新手容易踩的比较致命的问题先查库存判断大于购买数量后再用 update 扣减。这在单用户测试时没问题但并发时就会超卖两个人同时看到库存 1 件都去下单结果库存被扣成负数。正确做法是直接用一条带条件 update 的 SQL 去扣库存在事务里完成boolean success foodMapper.updateStock(id, quantity);对应的 SQL 逻辑是UPDATE food SET stock stock - #{quantity} WHERE id #{foodId} AND stock #{quantity}。如果返回的影响行数为 0说明库存不足直接抛出业务异常让事务回滚。这样数据库层面就保证了不会超卖代码也不复杂。我之前在另一版项目里亲测过这个方案比“先查后更”要稳非常多答辩时把这条讲清楚就是加分项。创建订单时还有一个容易被忽略的点订单金额一定要在服务端汇总计算。我的做法是先从购物车查出选中商品及其数量逐项查询最新价格计算小计和总额再插入订单和明细。前端传过来的 totalAmount 我根本不会直接信任因为这个值可以被人为篡改。4.4 模拟支付与订单状态流转支付功能对毕业设计来说完整对接支付宝或微信支付反而是一个负担需要一个商户号和一堆资质材料。我采用的是模拟支付方案用户下单后进入待支付状态点击“模拟支付”按钮后端直接调用一个 mock 支付接口把订单状态从 0 改成 1同时记录支付时间。这个方案够用也是很多毕业设计的常见做法。订单取消和超时关闭这两个场景也要处理。用户可以在待支付状态下主动取消订单。超时关闭可以用 Spring 的定时任务每分钟扫描一次创建超过 30 分钟仍未支付的订单将它们状态改为已取消。管理员在后台可以把已支付订单标记为发货用户确认收货后订单变为已完成。每个状态变化都在 Service 层做前置校验并且更新时带上当前状态作为条件避免多个操作重复处理同一笔订单。5. 前端页面设计与接口联调5.1 前端形态的选择模板渲染还是前后端分离前端形态在毕业设计里有两种主流选择一种是用 Thymeleaf 模板加 Bootstrap 写服务端渲染页面另一种是 Vue Axios 做前后端分离管理端再用一个独立后台工程。两者都能完成项目主要看你自己更熟悉哪种技术栈。我采用的是前后端分离结构。用户端用 Vue 写单页应用管理端单独一套页面后端只负责提供 RESTful 接口。这样做前后端职责更清楚数据库、接口、前端代码在论文里也好分开描述。但如果你时间不多Thymeleaf 会更快服务端模板渲染不用处理跨域token 也可以放在 session 里。没有绝对好坏选自己熟悉的、能快速出东西的方案才是性价比最高的做法。5.2 用户端页面结构与核心交互用户端页面一般就这七个首页、商品列表页、商品详情页、购物车页、结算页、订单列表页、个人信息页。首页核心是 Banner 轮播和推荐位后面接热门小吃展示我直接把销量最高的前几个商品取出来放到“招牌推荐”区域这个接口很简单但很能撑场面。商品列表页根据用户点击的分类或者搜索结果展示商品卡片点击卡片进详情。购物车页和结算页是交互比较复杂的地方。购物车每一行有选中框、数量加减、删除按钮勾选结果要实时传给后端计算总金额但所有金额还是以后端计算结果为准。结算页显示收货地址、商品清单、金额明细提交订单后跳转到支付页点击“模拟支付”完成整个闭环。订单列表页按状态切换展示有一个醒目的倒计时提醒用户待支付订单会在 30 分钟后自动关闭。5.3 联调阶段容易翻车的四个问题前后端联调是把整个项目“最后一里路”走完的关键也是各种奇怪问题高发的时候。最常碰到的四类问题我整理了一下一是时间格式。后端返回的 LocalDateTime 默认是一长串带 T 的格式前端直接展示很丑。我在全局配置里加了 Jackson 的日期格式转换统一输出yyyy-MM-dd HH:mm:ss前端就不需要单独处理了。二是跨域请求。前后端分离时必须配置 CORS否则前端请求发不出去。我用一个 WebMvcConfigurer 统一配置跨域规则允许前端地址访问并放行指定请求头。三是 Token 传递。前端 Axios 请求拦截器要统一从 localStorage 取出 token加到请求头 Authorization 里还要处理 401 状态token 过期后自动跳转登录页。四是分页数据格式。MyBatis-Plus 的 Page 对象默认字段是 records、total、current、size但如果直接返回给前端最好在 Service 层转成统一结构保持接口风格一致。这四个坑提前规划好联调时会少掉很多睡眠时间。6. 常见问题排查与答辩经验6.1 问题速查表实践里踩过的坑项目开发过程中我整理过一份问题排查表这里直接放出来很多问题都属于“百度半天才发现是低级错误”的类型现象可能原因解决办法启动时报依赖版本冲突Spring Boot 3.x 用了 jakarta 包旧代码引用 javax统一使用 JDK8 Spring Boot 2.7或 Spring Boot 3 Jakarta 新语法MyBatis-Plus 分页不生效缺少分页拦截器配置新建 MybatisPlusConfig注入 PaginationInnerInterceptor数据库中文乱码连接 URL 缺少字符集参数JDBC URL 加useUnicodetruecharacterEncodingutf8启动端口被占用本地多个服务占用了 8080改server.port如 8088接口 404 但 Controller 存在包扫描路径不对或方法映射写错确认启动类在根包下RequestMapping 路径正确登录后所有请求 401拦截器没有放行 token 校验逻辑或 token 没带上检查前端请求头和拦截器白名单下单成功但库存没扣Service 方法没加事务或扣减逻辑没执行加 Transactional检查库存扣减语句订单号偶尔重复随机数生成碰撞订单号加时间戳到毫秒数据库加唯一索引6.2 论文和源码怎么整理才能从容答辩论文和源码是毕业设计的“交付物”很多同学代码写得还行但论文逻辑混乱导致答辩被抓着问。我的论文结构按学校模板要求走了最标准的六个章节绪论讲背景和意义相关技术介绍 Java、Spring Boot、MySQL需求分析画功能结构和用例图系统设计写总体架构和数据库设计系统实现按模块贴核心代码和截图最后系统测试列功能测试和性能测试结果。章节里只要有一张完整的数据库表结构清单、一张订单状态流转图、几张关键页面截图老师基本就能看出你是真做了。源码整理方面我在项目根目录放了一份 README写清楚环境要求、启动步骤、数据库初始化方法以及一个默认管理员账号。SQL 文件里要包含建库建表语句和基础测试数据测试数据特意插入了盐水鸭、鸭血粉丝汤、梅花糕、牛肉锅贴这些南京特色菜品演示的时候首页不至于空荡荡。源码注释至少保证核心 Service 层有清晰的逻辑说明不是为了好看是为了你自己答辩时对照代码讲得出来。答辩的时候我会建议你把精力集中在四个点上订单状态流转是怎么控制的、JWT 登录鉴权是怎么实现的、下单事务和库存扣减怎么保证一致、数据库表怎么设计才避免数据冗余。把这四个点每个准备两分钟左右的讲述再准备两三个踩坑经历答辩基本就问不倒你。7. 项目完成后的一些真实体会整个项目从选题到交付我的感觉是商城系统确实不难但要做完整并讲清楚工作量比想象中大得多。最大的收获并不是学会 Spring Boot 的某个注解而是完整走通了一条从需求分析到数据库设计、从后端接口到前端联调、从代码实现到文档写作的链路。这条链路比任何单点知识都珍贵因为以后做实际项目时你面对的问题形态大体也是如此。最后再分享两个小建议。第一不要害怕用“老技术”Spring Boot 2.7 加 MyBatis-Plus 的组合已经被验证过很多年毕业设计的核心是把业务跑通、把逻辑讲透而不是追新。第二代码里一定要让人一眼看出你的设计思路比如订单状态枚举、分组的事务处理、数据库的唯一索引这些在答辩时都是比“我会用 Spring Boot”更能打动人的细节。希望这份经验能帮你少折腾几个夜晚把更多时间留给真正该打磨的部分。
阅读完成 · 觉得有帮助?