
简介一套基于SpringBoot与Vue的鲜花商城系统完整项目资料采用前后端分离架构后端以SpringBoot提供接口前端由Vue构建页面面向计算机相关专业学生、老师及企业员工适用于毕业设计、课程设计、期末作业、项目初期演示等场景也适合希望学习前后端交互、RESTful接口设计与商城业务逻辑的小白进阶提升。压缩包共105个文件体积仅4.4MB主要文件类型包括java源码、xml配置、js脚本、vue单文件组件、html页面、css样式以及properties属性配置同时附有说明文档与相关截图整体目录规范便于按功能模块检索学习。代码已经实际运行测试功能稳定并配有较详细的项目文档能够帮助读者快速理解系统架构与关键实现基础较好的开发者还可直接在此项目上二次开发增加营销、会员、统计分析等功能。目前已有94人学习下载完整度较高适合需要商城类全栈项目参考或用于课设、毕设的读者。1. 从一张订单到一条链路为什么鲜花商城适合做前后端分离练手接手过几个课程设计和毕业设计项目后你会发现一个规律凡是涉及商品展示、购物车、订单状态流转的系统九成以上都在用 SpringBoot Vue 这套组合。鲜花商城系统恰好把电商业务里最典型的场景浓缩在了一起——商品按分类筛选、库存扣减、订单流程管理这些逻辑放到任何真实项目中都是同一套套路。这个项目的资料包把前端页面、后端接口、数据库脚本和详细文档一起给了而不是只丢一段源码让你自己猜。对于正在做毕设或课设的人最头疼的往往是“代码能跑但讲不清楚”对于想进阶的前端或后端开发者这套材料能让你看到前后端分离项目里接口约定、跨域处理、状态管理到底怎么落地。源码部分我已经跑通下文会按“数据库设计 → 后端接口 → 前端对接 → 部署排错”的顺序讲过程中会给出可直接抄的关键代码和参数说明。需要先说明这套项目的技术栈是 SpringBoot 2.x具体版本以你下载到的 pom.xml 为准 Vue 2 Element UI数据库用 MySQL 5.7 或 8.0 皆可。如果你的环境里 SpringBoot 版本太高导致启动报错后面第 5 章有专门的处理方法。2. 表结构设计与数据初始化先把商品、订单、用户三张核心表立住2.1 数据库脚本梳理从 information_schema 到业务表解压资料包后先别急着启动后端。找到sql目录下的初始化脚本用 Navicat 或命令行导入。常见做法是先执行建库语句再导入数据表顺序错了会出现外键关联失败。脚本里一般包含这几类表用户表字段基本是id, username, password, phone, address, create_time密码存储方式注意看是 MD5 还是 BCrypt这决定了登录模块的校验逻辑。商品表核心字段为id, name, category_id, price, stock, image, description其中category_id关联分类表。订单表id, order_no, user_id, total_price, status, create_timeorder_no通常是时间戳加随机数生成的唯一编号。订单明细表id, order_id, goods_id, goods_name, price, count用于展示订单里具体买了哪些花。-- 商品表核心结构示例 CREATE TABLE flower_goods ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 商品名称, category_id int(11) DEFAULT NULL COMMENT 分类ID, price decimal(10,2) DEFAULT NULL COMMENT 售价, stock int(11) DEFAULT 0 COMMENT 库存, image varchar(255) DEFAULT NULL COMMENT 图片地址, description text COMMENT 商品描述, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;导入后建议先执行几条查询确认数据完整性比如统计商品总数、检查分类表关联字段是否为空SELECT COUNT(*) FROM flower_goods; SELECT c.id, c.name, COUNT(g.id) AS goods_count FROM flower_category c LEFT JOIN flower_goods g ON c.id g.category_id GROUP BY c.id;上面第二条 SQL 的作用是验证分类和商品之间是否存在“有分类但无商品”或“商品挂在不存在分类下”的脏数据。实际项目中这类问题会导致前端分类页点击后空白提前排查能省不少时间。2.2 密码存储与初始化数据不要明文比对很多课程设计项目偷懒把密码直接明文存库但这个项目如果用了 Spring Security 或拦截器做登录校验密码大概率是加密的。检查sys_user表里的password字段值如果是类似$2a$10$开头的一长串说明用的是 BCrypt如果是 32 位十六进制字符串那就是 MD5。-- 手动插入一个测试用户密码 123456 的 BCrypt 值需要提前生成 INSERT INTO sys_user (username, password, phone, address) VALUES (test, $2a$10$X5xJqLQnXpYQnVv7M7n3aOZk9TmQrVbFvM8mZz1pPZqXkXWl0J0iS, 13800138000, 测试地址);如果后端登录代码用的是Md5PasswordEncoder那上面的 BCrypt 值会导致登录失败。正确做法是先启动后端调用注册接口或者写一个临时的测试类生成加密值再回填到数据库。这一步很多人忽略结果前端表单提交后一直报“用户名或密码错误”。3. 后端接口落地商品查询、购物车与订单提交的完整链路3.1 Controller 层设计RESTful 风格与统一返回体资料包里的后端模块一般是标准的Controller → Service → Mapper三层结构。Controller 用RestController注解接口路径采用/api/...前缀这样配合前端 Vue 的代理转发比较方便。先看一个典型的商品查询接口RestController RequestMapping(/api/goods) public class GoodsController { Autowired private GoodsService goodsService; GetMapping(/list) public Result list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) Integer categoryId) { PageInfoGoods pageInfo goodsService.queryPage(pageNum, pageSize, categoryId); return Result.success(pageInfo); } }注意几个设计点。Result是统一返回体一般包含code、message、data三个字段前端 axios 拦截器里会统一判断code是否为 200。PageInfo是 PageHelper 分页插件返回的对象里面包含total、list等字段Vue 端的分页组件直接绑定这两个属性即可。categoryId用required false表示可选参数不传就查全部分类传了就按分类筛。3.2 Service 层库存扣减与订单生成的原子性订单提交是电商系统里最容易出 bug 的地方——用户点击“提交订单”后后端要同时做三件事生成订单记录、生成订单明细、扣减库存。这三个操作必须放在同一个事务里否则会出现“订单生成了但库存没减”或者相反的情况。Service public class OrderServiceImpl implements OrderService { Autowired private OrderMapper orderMapper; Autowired private OrderItemMapper orderItemMapper; Autowired private GoodsMapper goodsMapper; Override Transactional(rollbackFor Exception.class) public Long createOrder(OrderCreateDTO dto) { // 1. 生成订单主记录 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(dto.getUserId()); order.setTotalPrice(calculateTotal(dto.getItems())); order.setStatus(0); // 0-待付款 1-已付款 2-已发货 3-已完成 orderMapper.insert(order); // 2. 插入订单明细 for (OrderItemDTO item : dto.getItems()) { Goods goods goodsMapper.selectById(item.getGoodsId()); if (goods.getStock() item.getCount()) { throw new BusinessException(商品库存不足 goods.getName()); } OrderItem orderItem new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setGoodsId(goods.getId()); orderItem.setGoodsName(goods.getName()); orderItem.setPrice(goods.getPrice()); orderItem.setCount(item.getCount()); orderItemMapper.insert(orderItem); // 3. 扣减库存 goodsMapper.reduceStock(goods.getId(), item.getCount()); } return order.getId(); } }Transactional注解里的rollbackFor Exception.class很关键。默认情况下 Spring 只在遇到 RuntimeException 时回滚如果业务代码抛的是受检异常事务不会回滚会造成数据不一致。业务逻辑里我先查出商品判断库存是否充足再插入明细最后扣库存这个顺序保证一旦中间任何一步失败前面的插入操作都会因为事务回滚而撤销。库存扣减的 SQL 也值得注意实际项目中一般用乐观锁或悲观锁控制并发-- 乐观锁方式更新时带上版本号条件 UPDATE flower_goods SET stock stock - #{count}, version version 1 WHERE id #{goodsId} AND stock #{count} AND version #{version};影响行数为 0 时说明库存不足或版本冲突Service 层需要捕获并提醒用户重新下单。课程设计里可以不做这么细但如果你在简历上写“实现了电商核心功能”面试官大概率会追问并发扣减问题。3.3 拦截器与 Token 校验前端每次请求怎么带身份前后端分离项目里后端不再用 Session 保存登录状态而是通过前端在请求头里携带 Token。这个项目的做法是在后端写一个拦截器继承HandlerInterceptor在preHandle方法里校验 TokenComponent public class AuthInterceptor implements HandlerInterceptor { Autowired private StringRedisTemplate redisTemplate; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); String userId redisTemplate.opsForValue().get(token: token); if (userId ! null) { request.setAttribute(userId, userId); return true; } } response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录或登录已过期\}); return false; } }这里用 Redis 存储 Token 和用户 ID 的映射关系Authorization请求头里传的是Bearer前缀加 Token 值。前端 axios 拦截器会在每次请求时从本地存储取出 Token 并拼到请求头上登录过期返回 401 时自动跳转到登录页。拦截器还需要在 WebMvcConfig 里注册并设置哪些路径放行登录接口、商品列表接口通常不需要鉴权。Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/user/login, /api/user/register, /api/goods/list); } }需要注意的是addPathPatterns(/api/**)会把所有接口都拦截然后在 exclude 列表里放行公开接口。如果后续新增了公开接口忘记加白名单前端访问时就会收到 401排查时要先看这个配置。4. Vue 前端对接路由守卫、Axios 封装与购物车状态管理4.1 项目结构与路由配置从页面文件到访问路径前端部分解压后能看到src/views目录下按页面模块划分Home.vue首页、GoodsList.vue商品列表、GoodsDetail.vue商品详情、Cart.vue购物车、Order.vue订单确认。router/index.js里配置路由映射import Vue from vue import Router from vue-router import Home from /views/Home.vue import GoodsList from /views/GoodsList.vue import Cart from /views/Cart.vue Vue.use(Router) const router new Router({ routes: [ { path: /, name: Home, component: Home }, { path: /goods, name: GoodsList, component: GoodsList }, { path: /cart, name: Cart, component: Cart }, { path: /login, name: Login, component: () import(/views/Login.vue) } ] })注意Login.vue用到了路由懒加载箭头函数动态 import这样首屏加载时不会把登录页的代码也打包进去项目体积大时能明显减少白屏时间。/是 vue-cli 配置的路径别名指代src目录。4.2 Axios 封装与请求拦截Token 统一附加与错误处理前端所有 HTTP 请求不建议在每个组件里单独写axios.get而是封装一个request.js模块。资料包里大概率已经有现成的如果没有按下面这份常用的来import axios from axios import { Message } from element-ui import router from /router const service axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器统一附加 Token service.interceptors.request.use(config { const token localStorage.getItem(flower_token) if (token) { config.headers[Authorization] Bearer token } return config }, error { Promise.reject(error) }) // 响应拦截器统一处理业务码 service.interceptors.response.use(response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(flower_token) router.push(/login) } Message.error(网络异常请稍后重试) return Promise.reject(error) }) export default service这里baseURL设为/api配合开发环境的 Vue 代理vue.config.js中配置proxy可以把请求转发到 SpringBoot 的 8080 端口避免跨域问题。生产环境部署时nginx 也要做同样配置把/api开头的请求转发到后端服务。4.3 购物车状态管理Vuex 持久化与页面联动购物车模块是整个前端最需要理清状态的部分。用 Vuex 管理购物车数据并且把数据同步到localStorage这样用户刷新页面后购物车不会清空// store/modules/cart.js const cart { state() { return { items: JSON.parse(localStorage.getItem(flower_cart)) || [] } }, mutations: { addToCart(state, goods) { const existed state.items.find(item item.id goods.id) if (existed) { existed.count goods.count } else { state.items.push({ ...goods, count: goods.count }) } localStorage.setItem(flower_cart, JSON.stringify(state.items)) }, removeFromCart(state, goodsId) { state.items state.items.filter(item item.id ! goodsId) localStorage.setItem(flower_cart, JSON.stringify(state.items)) } }, actions: { addToCart({ commit }, goods) { commit(addToCart, goods) } } }购物车里加减商品数量、删除商品、清空购物车这几个操作都对应一个 mutation每个 mutation 里更新完 state 后立刻同步到 localStorage这套逻辑在面试里比较加分——很多人只知道用 Vuex 管理状态但没考虑持久化刷新丢失的场景。组件里用mapState把购物车数据映射出来再用mapActions触发添加操作不用手动操作 Vuex 的字段。5. 环境配置与高频报错SpringBoot 版本冲突、跨域和 Maven 依赖5.1 SpringBoot 版本太高导致的启动失败热搜词里多次出现“springboot版本太高”相关搜索这也是接手老项目最容易踩的坑。资料包标的是 SpringBoot 2.7.x如果你本机 IDEA 新建项目时默认用了 SpringBoot 3.x很多写法会直接报错——比如javax.servlet包名变成了jakarta.servletWebMvcConfigurerAdapter被移除。正确做法是打开pom.xml确认parent中的版本号parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent如果版本号已经是 3.x需要手动改回 2.7.x同时确认 JDK 版本是 8 或 11。SpringBoot 3.x 要求 JDK 17 起步而很多课程设计项目基于 JDK 8 编写两者混用会报UnsupportedClassVersionError。5.2 Maven 依赖下载失败与镜像配置国内网络环境下Maven 中央仓库下载依赖经常超时。打开~/.m2/settings.xml没有就新建配置阿里云镜像mirrors mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrors配置完成后在 IDEA 右侧 Maven 面板点击刷新按钮让依赖重新解析。如果某个依赖一直下载失败可以删掉本地仓库里对应目录再重新导入。5.3 跨域问题前端请求通了后端却不认开发环境跨域有两种常见处理方式。第一种是在vue.config.js配代理module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }第二种是后端直接开启跨域配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }两种方式选一种就行不要同时开启否则可能出现“请求带了 Origin 头后端响应也带了 CORS 头但浏览器仍然报跨域”的问题。实际调试时先看 Network 面板里请求是否发出去再看响应头里有没有Access-Control-Allow-Origin就能快速定位是前端代理没生效还是后端 CORS 配置有问题。5.4 上传的图片不显示路径映射问题鲜花商城的商品图片很多用本地文件存储。如果部署后发现图片加载不出来看日志会报 404。原因是 SpringBoot 默认不映射磁盘上的静态资源目录。解决方案是加一个资源映射配置Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file:D:/flower-project/upload/); } }这样访问http://localhost:8080/upload/flower1.jpg就会映射到磁盘目录D:/flower-project/upload/flower1.jpg。前端商品图片的image字段如果存的是相对路径加上baseURL拼接时要注意是否已经带了/upload前缀常见错误是存了完整路径再拼一遍导致变成/api/upload/upload/flower1.jpg。6. 上线部署与文档资料里的隐藏价值从本地跑通到答辩演示6.1 前后端分离部署Nginx 托管前端 反向代理后端课程设计或毕设演示时最稳妥的部署方式是把前端打包成静态文件交给 Nginx后端打成 jar 包独立运行。打包前端cd flower-mall-frontend npm install npm run build构建完成后会在dist目录生成静态文件。Nginx 配置里把dist指为站点根目录同时配置接口反向代理server { listen 80; server_name localhost; root /opt/flower-mall/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }try_files指令非常关键——Vue Router 使用 history 模式时刷新某个子页面路径比如/goods/3如果 Nginx 找不到对应的物理文件就会返回 404。这行配置会回退到index.html让前端路由自己接管页面渲染。后端 jar 包启动用nohup java -jar flower-mall-backend.jar --server.port8080 app.log 21 --server.port参数可以不写默认就是 8080。日志重定向到app.log如果启动失败可以查看日志定位问题。6.2 答辩演示文档怎么用把资料包的时序图和 ER 图变成讲解主线资料包里除了代码还有详细文档这部分常被忽略其实答辩时价值最高。一般包含需求规格说明书、数据库设计文档、接口文档。演示时建议按这条线讲先展示 ER 图说明数据模型设计思路再讲项目的分层架构和关键接口——订单提交的事务处理、Token 鉴权机制、商品分页查询。遇到评委提问“为什么用 PageHelper 分页而不是手写 limit”可以回答PageHelper 通过 MyBatis 拦截器自动改写 SQL业务层不需要关心数据库方言的差异且返回的 PageInfo 对象自带总条数和页码信息方便前端分页组件直接绑定。如果想在现有系统上加功能文档里的数据库设计可以帮你快速判断扩展点。比如要增加花语推荐功能基于现有的商品分类表加一个flower_language字段即可前端在商品详情页展示。资料包的文档里通常也写了各模块的接口约定照着格式新增接口不会破坏前后端契约。如果文档里没有接口列表可以用 Swagger 注解把接口信息生成出来再对照前端代码里的src/api目录整理一份接口清单这就是自己的东西了。整个项目从前端页面到后端接口再到数据库脚本是一条完整的电商学习链路。把商品查询、购物车、订单事务、Token 鉴权这几个模块吃透不仅应付毕设答辩够用面试时被问到前后端分离项目的核心问题时也有实战案例可以讲。本文还有配套的精品资源点击获取