ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于SpringBoot+Vue的鲜花商城系统:从下单到支付全链路实战

基于SpringBoot+Vue的鲜花商城系统:从下单到支付全链路实战 简介这是一套面向高校计算机专业毕业设计场景的鲜花商城全栈项目源码采用SpringBootMyBatis后端与Vue前端组合配套MySQL数据库适合正在准备毕设或需要电商类实战案例的开发者参考。系统按用户、店家、管理员三角色划分权限用户端覆盖注册登录、鲜花浏览与多条件检索花店、花名、用途、花语、评论互动、购物车、下单支付、收货地址与收藏管理店家端支持商品新增、评论查看与信息维护管理员端则负责用户与商家管理、鲜花分类、公告、轮播图及全量订单状态跟踪。压缩包共90个文件约4.38MB以28个Java源文件、12个XML配置、7个Vue组件、8个JS脚本及若干HTML、CSS、图片资源为主前后端结构与静态素材齐备。目前已有86人学习可作为毕设选题落地的完整参考方案帮助读者快速理解三角色电商系统的模块划分、接口组织与页面交互逻辑。1. 鲜花商城系统到底在做什么从一束花的订单链路说起情人节前一天晚上八点后台订单量突然翻了六倍客服在群里喊用户说下单后没收到取货码。我打开日志一看订单状态卡在已支付待发货但库存表里那批玫瑰已经被扣了三次。这就是鲜花商城系统最真实的战场——它不是把商品摆上网页那么简单而是要在节日洪峰里保证下单、扣库存、生成配送单、通知门店这条链路一次跑对。基于 SpringBoot Vue 的鲜花销售系统本质是一套前后端分离的电商骨架只是把通用商品换成了有保鲜期、有配送时效、有节日属性的鲜花。它要解决三件事让顾客在手机端快速挑花下单让门店实时看到待处理订单让运营能改价格、上下架、看销量。适合谁做计算机毕业设计选题卡在电商太烂大街、又不想做纯管理后台的同学以及想练一套完整 CRUD 状态机 定时任务的中级练手项目。下面我按真实落地顺序把选型、建表、接口、避坑一条条拆开讲。2. 技术选型与工程骨架为什么是 SpringBoot Vue 而不是别的2.1 后端选 SpringBoot 的三个现实理由毕业设计最怕的是环境配三天代码写两行。SpringBoot 的核心价值在于 starter 依赖把 Tomcat、Jackson、数据源全部自动装配一个spring-boot-starter-web就能起服务。对比传统 SSM你不需要再手写一堆 XML 配置DispatcherServlet和MapperScannerConfigurer。第二个理由是生态。鲜花商城绕不开支付回调、文件上传花束图片、定时任务超时未支付自动取消这些在 SpringBoot 里都有成熟 starterspring-boot-starter-quartz做定时、spring-boot-starter-validation做参数校验、mybatis-spring-boot-starter做持久层。第三个理由是调试友好application.yml里改个端口、换个数据库连接就是几行的事配合spring-boot-devtools热重启改完 Controller 不用手动重启。我一般给学生的建议是JDK 选 8 或 11别追最新。热词里springboot版本太高是高频翻车点——SpringBoot 3.x 强制 JDK 17 且把javax.*换成了jakarta.*很多老教程的代码直接编译不过。毕设求稳2.7.x 是甜点版本。2.2 前端选 Vue 而不是原生 jQuery 的理由鲜花商城的页面交互密度高购物车增减、规格切换11 朵 / 19 朵 / 99 朵、地址选择、订单状态轮询。用 jQuery 手写 DOM 操作代码会迅速失控。Vue 的响应式数据绑定让数据变、视图变自动完成v-for渲染花束列表、v-model绑定数量输入框逻辑清晰。Vue 2 和 Vue 3 怎么选如果跟着网上大多数毕设教程走Vue 2 Element UI 资料最多、踩坑最少如果想写得现代一点Vue 3 Element Plus Vite 启动更快。热词里vue安装及环境配置vue安装依赖说明很多人卡在环境这一步我的经验是Node 版本锁 16.x用npm install而不是cnpm镜像源配淘宝源能省掉一半的玄学报错。2.3 前后端分离的目录结构与联调约定一个能跑通的最小骨架长这样flower-mall/ ├── flower-backend/ # SpringBoot 后端 │ ├── src/main/java/com/flower/ │ │ ├── controller/ # 接口层 │ │ ├── service/ # 业务层 │ │ ├── mapper/ # MyBatis 接口 │ │ ├── entity/ # 实体类 │ │ └── config/ # 跨域、拦截器配置 │ └── src/main/resources/ │ ├── application.yml │ └── mapper/ # XML 映射文件 └── flower-frontend/ # Vue 前端 ├── src/api/ # axios 封装 ├── src/views/ # 页面 └── src/router/ # 路由联调的核心约定是统一返回体。后端所有接口返回{code, msg, data}三段式前端 axios 拦截器统一处理code ! 200的提示。这样前端不用每个接口都判断 HTTP 状态码后端也不用为每个接口写不同的错误格式。// 统一返回体放在 common 包下 public class ResultT { private Integer code; // 200 成功500 失败401 未登录 private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.msg 操作成功; r.data data; return r; } public static T ResultT fail(String msg) { ResultT r new Result(); r.code 500; r.msg msg; return r; } }这段代码的关键点是泛型T让data能装列表、对象、分页结果而不丢类型。code用整数而不是布尔是为了后续扩展 401、403 这类业务状态。参数上没什么可调的但要注意别把code和 HTTP 状态码混用HTTP 层永远返回 200业务成败看code这是前后端分离里最省心的约定。3. 数据库设计与核心表鲜花订单为什么不能照抄普通电商3.1 六张核心表与字段取舍鲜花商城的表结构比通用电商多两个特殊点一是商品有保鲜期和配送时段二是订单要绑定送达日期而不是下单日期。下面是我实际用过的核心表设计。表名作用关键字段设计要点user用户id, phone, password, nickname手机号唯一索引密码存 BCrypt 哈希flower鲜花商品id, name, price, stock, cover, category_id, statusstatus 控制上下架stock 用乐观锁category分类id, name, sort支持玫瑰/百合/混搭分类cart购物车id, user_id, flower_id, numuser_id flower_id 联合唯一orders订单主表id, order_no, user_id, total, status, delivery_date, addressorder_no 唯一delivery_date 是送达日order_item订单明细id, order_id, flower_id, num, priceprice 快照下单时价格order_item里存price快照是血泪经验。如果只存flower_id等运营改了价格历史订单金额就全乱了。同理orders里的total也是下单时算好的不能实时去 join 明细求和。3.2 库存扣减为什么不能简单 update节日洪峰下update flower set stock stock - 1 where id ?这种写法在并发时会超卖。正确做法是带条件的原子更新-- 扣库存只有库存足够时才扣返回影响行数判断是否成功 UPDATE flower SET stock stock - #{num} WHERE id #{flowerId} AND stock #{num};执行后检查affectedRows如果为 0 说明库存不足直接抛业务异常回滚。这比先 select 再 update少了一次查询也避免了查询和更新之间的竞态窗口。参数上num是购买数量flowerId是商品 ID两个都是必传。注意这条 SQL 必须在事务里执行且和订单插入在同一个事务否则扣了库存订单没生成数据就脏了。3.3 订单状态机五个状态怎么流转订单状态不能随便改要有明确的流转规则待支付0下单后初始状态30 分钟未支付自动取消已支付1支付回调后置为已支付等待门店接单配送中2门店确认后进入配送已完成3用户确认收货已取消4超时或用户主动取消状态流转用枚举 校验方法控制禁止从待支付直接跳到已完成。这个约束写在 Service 层每次改状态前先校验当前状态是否允许目标状态。4. 后端接口与前端页面打通从下单到支付的完整链路4.1 下单接口的完整实现下单是整个系统最复杂的接口它要在一个事务里完成校验库存、扣库存、生成订单、生成明细、清购物车。Service public class OrderServiceImpl implements OrderService { Autowired private FlowerMapper flowerMapper; Autowired private OrderMapper orderMapper; Autowired private OrderItemMapper orderItemMapper; Autowired private CartMapper cartMapper; Override Transactional(rollbackFor Exception.class) // 任何异常都回滚 public String createOrder(Long userId, ListCartItem items, String address, Date deliveryDate) { // 1. 生成订单号时间戳 用户ID后四位保证可读且不易撞 String orderNo System.currentTimeMillis() String.format(%04d, userId % 10000); BigDecimal total BigDecimal.ZERO; // 2. 逐个扣库存任一失败整体回滚 for (CartItem item : items) { int affected flowerMapper.deductStock(item.getFlowerId(), item.getNum()); if (affected 0) { throw new BizException(鲜花[ item.getName() ]库存不足); } total total.add(item.getPrice().multiply(new BigDecimal(item.getNum()))); } // 3. 插入订单主表 Orders order new Orders(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotal(total); order.setStatus(0); // 待支付 order.setAddress(address); order.setDeliveryDate(deliveryDate); orderMapper.insert(order); // 4. 插入明细price 存快照 for (CartItem item : items) { OrderItem oi new OrderItem(); oi.setOrderId(order.getId()); oi.setFlowerId(item.getFlowerId()); oi.setNum(item.getNum()); oi.setPrice(item.getPrice()); // 快照价 orderItemMapper.insert(oi); } // 5. 清空已下单的购物车项 cartMapper.deleteByUserAndFlowers(userId, items); return orderNo; } }逻辑说明Transactional保证五步要么全成要么全败rollbackFor Exception.class是因为默认只回滚运行时异常业务异常如果不显式声明可能不回滚。参数上deliveryDate是送达日期鲜花必须支持预约配送这是和普通电商最大的区别。orderNo用时间戳加用户 ID 后缀既保证趋势递增又便于人工排查。4.2 前端购物车与下单页的联动前端用 Vuex或 Pinia管理购物车状态加入购物车时先调后端接口持久化再更新本地 store。下单页从 store 读取选中项提交时把flowerId、num、price一起传给后端。// api/order.js —— axios 封装 import request from /utils/request export function createOrder(data) { return request({ url: /api/order/create, method: post, data // { items: [...], address: , deliveryDate: } }) } // 下单页提交逻辑 async submitOrder() { if (!this.address) { this.$message.warning(请先选择收货地址) return } const res await createOrder({ items: this.selectedItems, address: this.address, deliveryDate: this.deliveryDate }) if (res.code 200) { this.$router.push(/pay/${res.data}) // 跳支付页带订单号 } else { this.$message.error(res.msg) } }关键点是deliveryDate必须在下单时就确定不能等支付后再填。前端要做日期校验不允许选当天之前的日期。res.data返回的是订单号支付页用它去查订单详情。4.3 跨域与登录拦截的配置前后端分离必然遇到跨域。后端加一个全局配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) // 允许所有来源生产环境要收紧 .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) // 允许携带 cookie .maxAge(3600); } }注意allowedOriginPatterns和allowedOrigins的区别当allowCredentials(true)时allowedOrigins(*)会报错必须用allowedOriginPatterns。这是很多人卡半天的坑。登录拦截用拦截器实现放行登录、注册、商品列表等公开接口其余接口校验 token。5. 避坑与排查鲜花商城上线前必须过的五道坎5.1 现象节日下单接口大面积超时日志显示库存更新锁等待原因扣库存的UPDATE在高并发下对同一行加行锁大量请求排队。解决把热点商品的库存扣减改成 Redis 预扣 异步落库或者用消息队列削峰。毕设规模下至少给flower表的id加索引并缩短事务范围——扣库存和插订单分开提交用补偿机制兜底。5.2 现象用户支付成功但订单还是待支付原因支付回调接口没做幂等或者回调地址配错。解决回调接口先根据orderNo查订单如果已是已支付直接返回成功避免重复处理同时加一个定时任务每 5 分钟扫描待支付且超过 30 分钟的订单置为取消并回补库存。5.3 现象前端页面刷新后购物车清空原因购物车只存在 Vuex 内存里刷新即丢。解决登录用户的购物车存后端未登录用户存localStorage登录后合并。合并逻辑要处理同一商品数量叠加。5.4 现象上传的花束图片显示 404原因文件存到了本地磁盘但没配静态资源映射或者打包后路径变了。解决配置WebMvcConfigurer的addResourceHandlers把上传目录映射成/upload/**或者直接上对象存储。毕设里本地存储够用但路径要用绝对路径配置项别写死。5.5 现象Vue 打包后部署到服务器接口全部 404原因开发环境用代理解决跨域生产环境代理失效。解决生产环境用 Nginx 反向代理把/api转发到后端端口前端只请求相对路径。别在打包后的代码里写死localhost:8080。6. 让鲜花商城更像真项目的三个进阶技巧第一个技巧是给商品加节日标签和库存预警。鲜花销售有极强的节日属性母亲节、情人节、教师节的价格和库存策略完全不同。在flower表加一个festival_tag字段前端首页按标签做专题聚合运营后台能批量给商品打标。库存预警则是定时任务扫描stock 10的商品给管理员发站内信。这个功能代码量不大但答辩时特别加分因为它体现了业务理解而不是纯 CRUD。第二个技巧是把订单导出做成 Excel。运营需要每天导出待配送订单给门店。用 EasyExcel 写一个导出接口注意两点一是导出字段要和门店实际配送单对齐收货人、电话、地址、花束名称、数量、送达日期二是大数据量要分页查询避免 OOM。这个功能在热词里对应springboot 与报表整合那类需求是真实项目里高频出现的。第三个技巧是加一层简单的接口限流。用 Guava 的RateLimiter或者 Redis 计数器对下单接口做单用户每秒 1 次的限制。不是为了防攻击而是防止用户狂点提交按钮生成一堆重复订单。配合前端按钮的loading状态禁用双保险。// 基于 Guava 的简单限流放在下单接口入口 private final RateLimiter rateLimiter RateLimiter.create(1.0); // 每秒 1 个令牌 PostMapping(/create) public ResultString create(RequestBody OrderDTO dto) { if (!rateLimiter.tryAcquire()) { // 非阻塞获取令牌 return Result.fail(操作太频繁请稍后再试); } // ... 正常下单逻辑 }RateLimiter.create(1.0)的参数是每秒允许的请求数tryAcquire()立即返回布尔值不阻塞。注意这是单机限流分布式部署要换成 Redis。毕设单机够用但你要知道它的边界在哪。我自己做这类项目最大的教训是别一上来就追求功能多先把下单-支付-发货这条主链路跑通跑稳再往上加专题、加导出、加限流。主链路不稳功能越多越像纸糊的。希望帮到你。本文还有配套的精品资源点击获取
返回列表