
手头攒了个选题是很多同学问过的“生鲜销售商城”。这类风格的项目网上模板不少但大多要么只是一个光秃秃的CRUD外壳要么是前后端耦合得厉害的主力代码拿来改都不好改。今天这篇我就以“基于SpringBoot Vue的生鲜销售商城系统”为标题把这类毕设项目从设计思路到落地实现完整拆开讲一遍。篇幅会比较长不光聊功能还会把生鲜行业的特殊业务逻辑、数据库设计、订单跟库存的链路以及答辩时容易被追问的细节一起理清楚。先说明一下这个项目本身是我带着某高校同学做模拟项目X时整理的一套可运行前后端分离源码技术栈适配主流毕设场景。如果你正在找毕设方向或者已经选了商城类题目但苦于“怎么把生鲜行业特点做出来”这篇文章可以作为一份比较完整的参考资料。1. 整体设计与技术选型为什么是SpringBoot Vue生鲜商城的核心难点在哪1.1 技术栈选型的逻辑前后端分离是“默认答案”但不该止步于“默认”先聊聊技术栈。SpringBoot Vue是现在毕设里最常见的组合原因其实很务实SpringBoot的自动配置和Starter生态能让你在最短时间内把后端核心服务搭起来不用像SSH框架时代那样配一堆XML。Vue的组件化开发配Element UI这类现成组件库前台页面和后台管理界面都能快速成型。前后端分离之后部署和联调思路很清晰也符合当前企业的基本开发模式。但正因为是“默认答案”很多同学做出来是同一个样用户表、商品表、订单表、购物车四个表怼上去然后拼一堆国能优秀的接口。这不能算错却很难在答辩时讲出亮点。所以我建议选这个题目的人要着重思考一个问题生鲜销售系统和普通商城系统相比差别到底在哪1.2 生鲜商城的业务特殊性保质期、批次库存和时效才是灵魂普通商城卖的是数码产品、图书库存是一个单纯的数量字段下单后扣减就可以了。生鲜商品不一样有几个非常典型的行业特征第一是保质期短、批次敏感。今天的猪肉和明天的猪肉虽然是同一商品ID但入库批次不同、过期时间不同。系统不能只记录“猪肉还有50份”必须知道这50份分别属于哪个批次、什么时候到期。否则就会出现前端显示有货实际拣货时才发现全部过期的尴尬。第二是库存时效性强。生鲜走量快客户在购物车页面看到的库存和下单支付后的库存往往是两回事。这里就需要考虑锁定库存和超时释放的问题。第三是重量/计价特殊。很多生鲜商品按“份”卖但也有一部分按重量浮动计价比如活鱼、散称水果。这类商品到结算环节的价格可能和列表页不一致系统设计时要给“商品快照下单称重”留出空间。明白了这三点你再去写模块划分时就有的放矢了后面答辩被问“你的系统有什么特色”你也不愁没话说。1.3 系统模块全景前台后台数据库三块各司其职我采用的系统结构分为三个层面前台用户端Vue登录注册、商品分类浏览、商品关键字搜索、购物车、下单结算、微信支付模拟、订单管理、地址管理、个人中心。后台管理端Vue管理员登录、商品分类管理、商品管理含商品批次、订单管理、用户管理、轮播图管理、数据概览。后端服务SpringBoot负责向前端提供RESTful API包含认证授权、商品服务、订单服务、购物车服务、用户服务、文件上传等模块。数据库层面核心表我建议至少包含用户表、商品分类表、商品信息表、商品批次库存表、购物车表、订单主表、订单明细表、收货地址表、管理员表、轮播图表。具体的字段设计我在第三部分展开讲。提示这里特意把“商品表”和“批次库存表”拆成了两张表这是生鲜系统区别于普通商城的关键设计。如果你在答辩时主动讲清楚这一点比你在自述里重复十遍“我用了SpringBootVue”更加分。2. 数据库与核心业务模型生鲜系统最不能省的一张表2.1 建表思想从“商品”到“批次”的建模转变很多同学建表时习惯性地在一张商品表里直接加个stock字段。这样做不是不行但生鲜系统会出问题——商品过期了怎么办是按批次退回供应商还是整批报废批次的价格可能在一天内有波动库存表不跟批次关联日后统计毛利都费劲。所以我在这套毕设里设计了一张商品批次库存表我给它起的逻辑名是“商品批次表”物理表名生鲜商城常见命名是product_batch_stock。核心字段大致是这样的字段说明id主键product_id关联商品表batch_no批次编号如日期流水号quantity当前可用量lock_quantity锁定中数量production_date生产日期expire_date过期日期status批次状态可售/售罄/过期入库的时候同一个商品可以有多条批次记录。用户下单时优先扣“最早过期”的批次符合生鲜行业常说的FEFOFirst Expire First Out原则。2.2 主订单与订单明细快照机制不能省再强调一个非常容易忽略的细节订单明细中除了关联商品ID之外必须冗余记录商品名称、商品图片、单价、购买数量。为什么因为商品可能下架、改名、调价如果订单明细只存一个product_id你去后台翻历史订单时看到的可能会是一堆“已删除商品”而用户拿到的却是下单当时的信息。这叫订单快照几乎所有电商系统都会这么做属于基本常识级别。订单主表里需要记录订单号、用户ID、总金额、实付金额、订单状态、收货信息快照、下单时间、支付时间、发货时间等。注意收货信息同样要做快照不能只存地址ID。地址信息后续可能被用户编辑不能影响历史订单的发货。2.3 购物车与结算链路锁定库存的时机要知道购物车表相对普通无非是用户ID、商品ID、数量、勾选状态这些字段。但“何时锁定库存”这个问题是区分系统设计水平的关键。很多商城把库存扣减放在“加入购物车”这一步这样太粗暴用户加了不买会大量占用库存。也有系统把扣减放在“提交订单”时但用户可能一直不支付所以还需要“锁定库存 超时释放”的机制。我的方案是这样的用户点击“提交订单”后端先对购物车里的商品执行“预扣库存”——把对应批次的可售量减掉同时增加锁定数量此时订单状态为“待支付”。用户支付时再把锁定量真正扣减掉订单状态变为“已支付”。如果用户超时未支付比如30分钟后台定时任务将锁定库存回补订单自动取消。如果用户手动取消未支付订单同样回补库存。这个机制不算复杂但很贴合真实场景。答辩时你可以画一条结算时序或者直接口述这个完整流程比只讲“我用Redis做了缓存”这类空话要具体得多。2.4 为什么用MySQL就行毕设慎用分布式方案有些同学为了显得高大上非要把订单号用什么雪花算法、把库存扣减搞成分布式锁、把系统拆成微服务。说句实在话对毕设来说这些方案大多是负优化。过重的架构会带来部署成本、联调成本和排错成本而你的答辩时间只有那么几分钟面试官更关注的是你是否理解业务核心并且选择了恰当的复杂度。本项目的订单号我直接采用“时间戳用户ID尾号随机数”保证不重复即可。库存扣减使用数据库乐观锁/事务控制单机部署完全够用。Redis在这个系统里主要用于缓存轮播图、首页推荐商品和会话存储后面第四部分我会再说什么时候值得用Lua脚本。3. 系统模块路由与功能清单前台、后台、数据库一张图理清3.1 前台用户端核心功能路由设计前台页面我按照电商常见路径来组织Vue Router里的路由设计大致是路由路径对应页面功能说明/首页轮播图、今日推荐、分类入口/product/detail/:id商品详情展示批次剩余量、加入购物车、立即购买/cart购物车增删改选、结算跳转/order/confirm确认订单页地址选择、金额结算、提交订单/order/list订单列表待付款、待发货、待收货、已完成分类/order/detail/:id订单详情展示订单快照信息、物流信息/user/profile个人中心头像、昵称、订单数据概览这里有一个细节值得讲购物车里的数据不要一直存在前端localStorage里。很多在线代码教程为了省事把购物车做成纯前端存储刷新不丢是做到了但换台电脑购物车就没了而且后端完全不知道用户在加什么商品无法做库存预判。我建议购物车表放到后端这样即使前端重新登录购物车数据也能从接口恢复这才是真实电商的做法。3.2 后台管理端功能模块解析后台管理端我采用的是经典Admin布局左侧菜单、顶部用户区、右侧内容区。核心模块有数据概览展示今日销售额、订单量、用户数、库存预警商品。数据不需要很复杂用几个统计SQL或者聚合接口就能搞定。商品管理商品上下架、编辑价格、维护分类、上传图片。批次库存管理新增入库批次、查看剩余量和过期时间、标错过期批次。订单管理查看全部订单、发货操作、查看订单明细、处理退款。分类管理一级分类的增删改查前端首页按分类展示。后台端最容易忽略的是“权限控制”。毕设阶段可以简化但最简单的RBAC基于角色的访问控制一定要有哪怕只是管理员表里加一个role字段然后在后端写一个拦截器判断一下角色。不要所有接口都是“裸奔”的否则答辩时老师随便问一句“你的后台接口安全吗”你容易当场尴尬。3.3 核心接口清单与请求链路示例后端接口我尽量采用RESTful风格路径其实很直接模块请求方式与路径说明用户登录POST /api/user/login返回JWT Token商品搜索GET /api/product/search?keyword支持模糊搜索商品详情GET /api/product/detail/{id}返回规格、批次汇总购物车列表GET /api/cart/list按用户ID查询添加购物车POST /api/cart/add增加商品数量提交订单POST /api/order/create预扣库存返回订单号模拟支付POST /api/order/pay将待支付订单置为已支付取消订单POST /api/order/cancel回补库存后台批次入库POST /api/admin/batch/add管理员新增批次接口设计上有一个容易踩的坑前后端字段命名要一致。后端如果用驼峰命名字段前端JSON解析起来没问题但如果数据库字段是下划线风格后端返回前最好统一成驼峰格式否则前端写死了下划线属性名改起来相当麻烦。这套源码里我统一让返回对象为驼峰风格避免团队里有人再单独做一层字段转换。4. 实操核心业务代码与实现链路拆解4.1 登录鉴权JWT方案的前后端协作逻辑登录模块我采用JWTJSON Web Token生成令牌后端SpringBoot用拦截器统一校验前端在Vue Router里设置路由守卫。这里给出一个后端拦截器的核心伪代码方便你理解“校验Token”的完整流程public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equalsIgnoreCase(OPTIONS)) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { return error(response, 未登录或登录已过期); } String realToken token.substring(7); try { MapString, Object claims JwtUtil.parseToken(realToken); request.setAttribute(userId, claims.get(userId)); return true; } catch (Exception e) { return error(response, 无效登录凭证); } } }前端路由守卫的做法大致是每次路由跳转之前检查本地是否有Token如果没有强制回到登录页如果有放行并在请求拦截器里统一加上Authorization头。这里的核心逻辑是“后端负责校验前端负责跳转”各司其职。4.2 库存设计落地Redis 数据库事务的双保险前文我提了“锁定库存”的业务流程这里说下具体怎么实现。基本思路是“Redis预减库存 数据库最终入账”用户提交订单时先通过Lua脚本检查并扣减Redis中的可用库存防止并发超卖扣减成功后向订单表插入一条“待支付订单”并通过事件延时释放锁库存。这里要特别注意Redis和数据库之间最终一定要对得上不要让Redis扣了但订单没建成功。-- 库存预减Lua脚本 if redis.call(get, KEYS[1]) false then return -1 end local stock tonumber(redis.call(get, KEYS[1])) if stock tonumber(ARGV[1]) then return -2 end redis.call(decrby, KEYS[1], ARGV[1]) return 1如果只是小学生级别的毕设不引入Redis也是可以的直接在Service层加事务用UPDATE product_batch_stock SET quantity quantity - #{num} WHERE id #{batchId} AND quantity #{num}这种SQL天然防超卖的方式实现但那样很难展示你对并发场景的理解。以“稳”为主的话用数据库乐观锁足够想有点亮点可以引入RedisLua方案。我在项目源码里两种都写了代码里保留切换开关。4.3 Vue前端页面搭建细节别被UI框架绑架前端页面我花时间比较多的是首页和商品详情页。首页用轮播图展示主推商品再按分类拉取推荐商品商品详情页要展示商品信息和批次剩余量、过期倒计时。这里提醒一下Vue项目构建时要注意路由懒加载。在路由配置文件里使用component: () import(/views/Home.vue)这样首屏加载速度会明显提升。很多同学打包完只有一个巨大的app.js加载非常慢多数情况下就是因为所有页面都被打进一个包了。另外前端请求接口时封装一个统一的axios实例很有必要。把基准地址、超时时间、请求拦截器、响应拦截器都集中在一个文件里别在每个页面都写一遍axios.get(http://localhost:8080/api/...)不然代码量上去之后改一次后端地址会让你崩溃。4.4 联调与上线前后端分离项目的运行流程本地联调阶段前端通过Vite或Webpack DevServer代理到后端端口避免跨域问题。生产环境我的做法是将后端打包为jar前端执行npm run build后将dist目录交给后端Spring Boot的静态资源目录统一托管。这一步的好处非常明显部署时只需要运行一个Java进程不需要单独配置Nginx去反代两个服务。对毕设演示环境、甚至放在服务器上跑给老师看都特别方便。如果你自己对Nginx很熟那就另说但为了保证“一键启动”的体验我还是建议走静态资源合一的方式。启动顺序要注意一下先启动MySQL和Redis再启动SpringBoot后端最后启动前端。前端联调时如果你改了后端端口记得同步修改前端代理配置和axios基准地址这个坑我踩过好几次。5. 常见问题与排查技巧实录实操翻车现场总结5.1 前端联调时的跨域问题前后端分离模式下最常见的报错是“blocked by CORS policy”。很多同学第一反应是装跨域插件但正确的做法是在后端统一处理。比较稳妥的方案是写一个WebMvcConfigurer配置跨域映射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预检请求这个我上面的拦截器代码里已经处理了。如果你把这段代码直接保存到项目里大部分跨域问题都能解决。5.2 数据类型和日期的序列化问题本地调试时可能会发现后端LocalDateTime类型的字段传给前端变成了“2025-06-01T10:30:00”非常不友好。建议在配置文件中统一设置日期格式比如spring.jackson.date-formatyyyy-MM-dd HH:mm:ss spring.jackson.time-zoneGMT8同时前端拿到的日期字段可以直接展示不需要再解析。这个细节虽然不起眼但很影响演示效果老师看后台订单列表如果全是“T”样式的日期心里多少会犯嘀咕。5.3 图片上传失败与静态资源映射商品图片上传是本系统一个比较常见的坑。默认情况下SpringBoot的静态资源路径是classpath:/static/上传的图片文件如果放在项目运行目录之外访问不到。我的做法是在配置文件中额外映射了一个本地绝对路径到/images/**虚拟路径spring.web.resources.static-locationsclasspath:/static/,file:${upload.path}上传接口里再把upload.path指定为一个项目外的目录比如E:/fresh-mall/upload/。这样图片既能落盘保存也能被前端通过URL直接访问重启服务后图片也不会消失。5.4 订单超时未支付别做复杂的状态机有同学特别喜欢给订单设计状态机把订单状态拆得特别细比如“已创建—已确认—已备货—出库中—配送中—已完成—已取消”。做电商毕设时如果状态细分到这种程度前端交互要做到位非常耗时而且容易在状态流转上出BUG。我建议用最精简的一组状态待支付、已支付、已发货、已完成、已取消、退款中。这套状态在业务上完全够用也便于你实现后续的“超时自动取消”。超时逻辑我用一个简单的定时任务每分钟扫描一次待支付订单超30分钟的订单自动取消并回补库存。Scheduled(cron 0 * * * * ?) public void cancelTimeoutOrders() { ListOrder list orderMapper.selectTimeoutOrders(LocalDateTime.now().minusMinutes(30)); list.forEach(order - orderService.cancelOrder(order.getId())); }5.5 数据库SQL性能索引能省则省毕设项目数据量不大很多人觉得索引无用但答辩时老师很可能翻你的数据库设计查看索引。三个地方建议加索引用户表手机号登录检索、订单表用户ID按用户查订单、商品表名称搜索场景。加上这些索引后后台联调的速度明显提升也不会被问到“你的订单查询是全表扫描吗”这种尴尬问题。6. 答辩准备与扩展思路如何把普普通通的商城讲出亮点6.1 讲解项目时的三条主线如果你的PPT或演示被压缩到只有十分钟我建议你把讲解梳理成三条主线第一业务主线用户从注册登录、浏览商品、加入购物车、提交订单、支付、查看订单把这条线完整串下来配合页面截图。第二技术主线讲清楚前后端分离结构、认证方式、数据库设计里的批次库存模型、订单超时释放库存的实现。让老师知道不是完成CRUD了事而是对业务有思考。第三生鲜特色主线FEFO先出原则、保质期预警、批次库存。这三点是你和其他做普通商城同学拉开差距的关键。6.2 可扩展的方向和“如果继续做”的建议如果时间允许你还可以在这个基础上加一点亮点增加定时统计报表用ECharts做一个简单的按日销售趋势分析前后端都有现成库工作量不大。增加Excel导出订单功能使用EasyExcel或者POI在后台一键导出订单列表。很多毕设辅导老师对这个功能很有好感因为它看起来很“企业级”。增加菜品营养标签这类生鲜行业特色字段也可以提升系统辨识度。如果你熟悉消息队列还可以将超时关单改为延迟消息方案不过这就看个人精力了。以我的实际经验来看毕设项目的成果不在于功能越来越多而在于每个已实现功能能讲清“为什么这么做”。比如你用批次库存而不用单一库存你就要准备好回答“如果用户同时下单两个批次的同一商品怎么办我的方案是在购买明细里按批次拆行展示。”想明白这类边界情况你答辩的底气会大不一样。6.3 最后分享一个细节源码里我建议你亲手改的部分很多人拿到源码第一件事是改个系统名称然后交差。我的建议是至少自己动手改造以下几个小点既不会太累又能在答辩时坦然说出“我做了一些个性化调整”去后台放一个“过期商品提醒”列表把近三天临期批次用醒目颜色标出来。这个功能只需要对批次表做一次条件查询前端表格加一列状态渲染工作量很小但非常贴合生鲜主题。商品详情页加一个“保质期倒计时”展示用前端时间计算和过期日期字段即可实现。下单前“再次确认收货地址”的二次弹窗这对订单准确度很有用。经历过几次带学生做完项目的沟通后我的体会是生鲜商城这个题目最大的优势在于业务不那么复杂但又有足够多的“行业特色点”让你讲出差异化。只要把批次库存这条逻辑吃透把订单状态流转串顺这个项目不论从完成度还是答辩表现来看都算是非常拿得出手的。后续你想在这个基础上继续扩展成多商户平台、增加优惠券系统也不是难事。希望这篇拆解能帮你少走点弯路动手去敲代码的时候多留意那些“数据库字段和前端字段对不对得上”的小细节项目的完成度一定会有质的提升。