ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue鲜花商城毕设全解析:从环境搭建到JWT登录鉴权与部署

SpringBoot+Vue鲜花商城毕设全解析:从环境搭建到JWT登录鉴权与部署 简介一份基于SpringBootVue的鲜花商城毕业设计源码包面向计算机相关专业学生及Java全栈开发者提供可运行的前后端分离项目作为参考。系统分为用户、店家、管理员三个角色涵盖注册登录、鲜花浏览与多条件检索、购物车、订单管理、评论、公告、轮播图配置等典型电商功能适合作为课程设计或毕业设计的蓝本。包内共90个文件约4.38MB以Java源码、Vue组件、JavaScript脚本、XML配置及HTML页面为主同时包含pom.xml依赖文件、数据库相关脚本和README说明便于本地部署与二次开发。当前已有86人学习下载。通过源码可学习SpringBootMyBatisVue项目的结构设计、接口调用与角色权限划分也可直接调整界面和业务逻辑形成自己的作品。资源目录清晰图片素材、前端页面与后端代码分层存放能帮助使用者快速定位模块并理解完整电商流程。1. 一份能跑通的毕业设计SpringBootVue 鲜花商城到底值不值得下如果你手里正压着一份 SpringBootVue 的鲜花商城源码包却还没把它从 .zip 变成能点的页面那这篇就是写给你看的。这类项目模块看着不多——商品浏览、购物车、下单、后台管理但真正跑通前后端接口时需要过的坑比想象中多数据库版本、跨域、token 失效、图片路径丢失。我拆过不少同款资源这套代码的业务链路是完整的后端 SpringBoot 负责接口与业务逻辑前端 Vue 负责页面交互MySQL 存数据登录鉴权用 JWT属于能直接拿去当毕业设计答辩底子、也能改造成个人项目的成熟结构。适合两类人一是想省时间把项目完整复现的在校生二是想拿现成代码做二次开发的从业者。2. 先把项目跑起来目录结构、环境版本与启动顺序2.1 源码包拆解后端、前端、数据库脚本各在哪把压缩包解压后你会看到三个核心目录后端工程、前端工程和 SQL 脚本。后端通常是标准的 Maven 工程入口是一个带SpringBootApplication注解的主类包名一般叫com.flower.shop或类似前端是 Vue CLI 或 Vite 搭建的 SPA 工程包含src、public、package.json。先别急着改代码我的习惯是先按目录把文件归属理清flower-shop/ ├── backend/ # SpringBoot 后端工程 │ ├── src/main/java/ # Java 源码 │ ├── src/main/resources/ │ │ ├── application.yml │ │ └── mapper/ # MyBatis 映射文件 │ └── pom.xml ├── frontend/ # Vue 前端工程 │ ├── src/ │ │ ├── api/ # axios 请求封装 │ │ ├── router/ # 前端路由 │ │ ├── views/ # 页面组件 │ │ └── main.js │ ├── package.json │ └── vue.config.js # 代理与构建配置 └── sql/ └── flower_shop.sql # 数据库初始化脚本这个结构在同类毕设里算是规范的了。数据库脚本是整套项目的地基拿到脚本后先别急着导入打开看一眼里面有没有CREATE DATABASE有些资源只有建表语句需要你自己建库有些直接连库带表一起建。我建议用 Navicat 或命令行先执行一遍确保脚本没报错再继续不然后端一启动就连不上库排查时容易误判成代码问题。2.2 环境版本陷阱JDK、Node、MySQL 怎么选很多人在第一步就被版本坑住。SpringBoot 2.x 配 JDK 8 或 11 都行1.8 最稳前端 Vue 如果是 2.x 项目Node 版本别超过 16Vue 3 配 Vite 的话 Node 14 以上即可。MySQL 用 5.7 或 8.0 都能跑但两者的驱动配置有差异。我给一份参考版本表组件推荐版本备注JDK1.8 / 11SpringBoot 2.x 建议 1.8避免高版本 JDK 出现反射告警MySQL5.7 / 8.08.0 需要配置时区参数Node14 / 16Vue 2.6 配套 Vue CLI 4/5Maven3.6后端构建工具Redis5.x若项目有缓存/秒杀模块才需要检查 Java 和 Node 环境可以用这两条命令确认版本后再继续不用盲目升级java -version node -v npm -v版本对齐是这类项目的第一道坎。Maven 仓库下载依赖慢的话在settings.xml里配阿里云镜像不然第一次mvn clean package能把耐心耗尽。后端启动前还有个常被忽略的点确认pom.xml里 SpringBoot 版本与 MySQL 驱动版本匹配比如mysql-connector-java8.x 对应的驱动类名是com.mysql.cj.jdbc.Driver5.x 用的是com.mysql.jdbc.Driver。2.3 后端启动三步走建库、改配置、跑主类后端启动顺序就三步建库、改配置、启动主类。先执行 SQL 脚本建库然后打开application.yml最需要改的就是数据源配置和 Redis 配置。下面这是最常见的配置模板server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/flower_shop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 redis: host: localhost port: 6379 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true这里几个参数要留意。serverTimezoneAsia/Shanghai必须加MySQL 8.0 下不写时区大概率启动报错password改成你自己数据库的密码mapper-locations指向 XML 文件位置如果启动时报找不到 mapper先看这个路径是否和实际目录一致。这些配置项在大多数 SpringBootVue 毕设里都是同一套逻辑掌握一次就能复用。配置改完后在 IDEA 里直接运行标注SpringBootApplication的主类或命令行执行mvn spring-boot:run看到Started Application in xxx seconds且没有红色报错后端就起来了。如果端口被占用把server.port改掉或找到占用进程处理掉这一步我后面踩坑章节再细说。2.4 前端启动npm install 与代理配置前端启动相对简单但npm install装依赖时经常因为网络或版本问题失败。建议用国内镜像源安装npm install --registryhttps://registry.npmmirror.com装完依赖后不是直接npm run serve先看vue.config.js里代理配置对不对。前后端分离开发的经典问题是跨域开发环境下最省事的方案是通过 webpack-dev-server 做代理把/api开头的请求转发到后端。这是这类项目里最常见的配置写法// vue.config.js module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, // 后端服务地址 changeOrigin: true, pathRewrite: { ^/api: } // 去掉 /api 前缀 } } } }代理的意义在于前端页面跑在 3000 端口浏览器访问的是http://localhost:3000/api/login代理把请求转发到http://localhost:8080/login这样前端代码里请求的 baseURL 就统一写成/api不用关心后端具体地址。如果接口路径里本身带了/api就要把pathRewrite去掉。配置完代理后执行npm run serve浏览器能打开登录页、输入账号能调通后端接口就算把环境跑通了。3. 登录鉴权的落地细节JWT 签发、拦截器与 Vue 路由守卫3.1 用户表设计与登录接口逻辑鲜花商城的用户体系分前台用户和后台管理员角色字段通常用role区分。用户表设计直接决定后续鉴权和订单关联的逻辑常见字段如下CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(255) NOT NULL COMMENT BCrypt加密后的密码, nickname VARCHAR(50) DEFAULT NULL COMMENT 显示昵称, role TINYINT DEFAULT 0 COMMENT 0-普通用户 1-管理员, phone VARCHAR(20) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像图片路径, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;密码字段用BCrypt加密存储而不是 MD5。MD5 加盐虽然比裸存好一些但 BCrypt 自带盐值且加密强度可调Spring Security 或spring-security-crypto工具类直接支持登录验证时用BCryptPasswordEncoder.matches(rawPassword, encodedPassword)。密码加密这件事在毕设答辩时经常被问到你答一句BCrypt 自动加盐防止彩虹表破解就能体现出不是纯抄代码。登录接口的逻辑其实很固定根据username查用户校验密码通过后生成 JWT 返回给前端。这一步不要自己拼接 JSON 返回用统一的Result对象包装前端解析更方便。3.2 JWT 工具类与后端拦截器JWT 的核心是服务端不保存登录态把用户信息加密进 token后续请求带上 token服务端只做验签。工具类里主要提供三个方法生成 token、解析 token、判断 token 是否过期。Component public class JwtUtil { // 注意生产环境密钥要放到配置中心不能硬编码 private static final String SECRET flower-shop-secret-key; private static final long EXPIRE_TIME 7 * 24 * 60 * 60 * 1000L; // 7天 public String generateToken(Integer userId, Integer role) { return Jwts.builder() .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }参数说明claim里放的是业务字段建议只放userId和role别把密码性别等敏感信息塞进去EXPIRE_TIME按项目需要调普通的商城系统 24 小时到 7 天都合理太短则用户频繁掉线太长又增加 token 泄露风险。有了工具类后需要一个拦截器对受保护接口做统一校验。常见做法是写一个HandlerInterceptor在preHandle里取请求头Authorization字段解析失败或过期直接返回 401Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) async throws Exception { // 放行预检请求 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { response.setStatus(401); return false; } try { Claims claims jwtUtil.parseToken(token.substring(7)); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { response.setStatus(401); return false; } } }注意两点拦截器放行的白名单必须包含登录接口和模拟支付回调接口否则用户还没登录就调登录接口会被自己的拦截器挡住OPTIONS预检请求必须放行否则前端浏览器跨域预检失败所有请求都进不了控制器。3.3 Vue 端请求封装axios 拦截器与 401 处理后端把 token 校验做严了前端就要把 token 的存储、携带、失效处理做好。常规做法是封装一个 axios 实例请求拦截器统一加Authorization头响应拦截器统一处理 401 跳转。// src/api/request.js import axios from axios import router from /router const request axios.create({ baseURL: /api, // 走 vue.config.js 里的代理 timeout: 10000 }) // 请求拦截器每个请求自动携带 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config }) // 响应拦截器token 失效时清理并跳登录页 request.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(error) } ) export default request这里有几个细节容易被忽略。baseURL写/api而不是http://localhost:8080/api是为了在开发时代理生效、在部署时不用改代码token 存localStorage还是sessionStorage跟业务相关商城系统一般用localStorage关闭浏览器后仍保持登录拦截器返回response.data而不是整个 response能省去每个页面里再取一次.data的动作。3.4 Vue 路由守卫登录态与角色控制前端路由守卫配合后端拦截器构成双保险。没登录的用户想跳进购物车或订单页路由层直接拦下。看这段router/index.js里的核心逻辑router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) return } // 管理员页面需要额外校验角色 if (to.meta.role admin) { const role localStorage.getItem(role) if (role ! 1) { next(/) return } } next() })meta.requiresAuth用来标记需要登录的页面meta.role标记管理员专属页面比如后台的商品管理、订单管理路由上加role: admin。这里手工判断角色就够用没必要引入 Vuex 管理用户信息把userId和role在登录成功后一并存进localStorage路由守卫直接读即可。如果之后想升级成动态路由权限可以在登录后根据role过滤路由表动态注册但毕设场景下静态路由加守卫已经足够。4. 商品与订单核心链路缓存查询、库存扣减与状态流转4.1 商品列表查询用缓存扛住首页压力商城的首页和商品列表页是访问量最大的接口如果每次都查数据库并发稍微上来一点数据库连接就会吃紧。这类项目里常用的优化手段是给商品列表加 Redis 缓存第一次查询走数据库并写入缓存后续查询直接命中缓存。Service public class ProductServiceImpl implements ProductService { Autowired private StringRedisTemplate redisTemplate; Autowired private ProductMapper productMapper; Override public ListProductVO getProductList(int page, int size) { String cacheKey product:list: page : size; String cached redisTemplate.opsForValue().get(cacheKey); if (cached ! null) { return JSON.parseArray(cached, ProductVO.class); } // 缓存未命中查数据库并回填缓存 ListProductVO list productMapper.selectPage(page, size); redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(list), 10, TimeUnit.MINUTES); return list; } }这里的缓存 key 设计要考虑参数维度页码和每页条数都拼进 key避免不同页面互相覆盖。过期时间设了 10 分钟商品数据不是实时强一致的可以接受短暂延迟。操作商品上下架或库存变动时应该主动删除对应缓存而不是让它自然过期否则会出现后台改了商品前台一直看不到的怪现象。缓存穿透和缓存雪崩这类进阶问题毕设答辩时被问到的概率不低。穿透的解法是缓存空值雪崩的解法是给过期时间加随机抖动这些可以在文档里补充进去但代码层面做到列表数据缓存 变更时删缓存已经能说明理解了。4.2 库存扣减防止超卖的正确写法秒杀或高并发下单场景下库存扣减是整套系统最脆弱的地方。最直观的翻车方式是先查库存判断大于 0再执行UPDATE扣减这段代码在并发下会出现超卖多个请求同时读到相同库存数全部通过判断结果库存被扣成负数。正确做法是把判断和扣减合成一条 SQL让数据库在原子层面保证库存不会为负UPDATE product SET stock stock - 1 WHERE id #{productId} AND stock 0执行这条 UPDATE 后检查返回值受影响行数为 1 表示扣减成功为 0 表示库存不足或商品不存在。这种写法属于乐观锁的思路通过WHERE stock 0条件让数据库帮我们把关。配合订单创建时用order_item表加唯一索引防止重复订单双保险就齐了。不过乐观锁在库存扣减失败时不能直接把整个下单事务回滚掉。常见做法是返回手慢了商品已抢光的提示同时把用户的待支付订单取消掉或者干脆在下单前先把库存预占支付成功再真正扣减。预占库存的写法是插入一条库存流水表记录状态为锁定支付成功后改为已扣减超时未支付则解锁归还库存这套思路比单纯依赖一条 UPDATE 更稳但代码量也更多。毕设场景下把乐观锁 UPDATE 写对再在文档里解释为什么不用悲观锁性能差、锁表影响面大就已经是一份能说的过去的答卷。4.3 订单状态机与模拟支付回调订单模块最重要的不是 CRUD而是状态流转。下游接口支付、物流和用户操作取消、确认收货都会改订单状态如果散落在各处随意赋值很快就分不清已发货和已完成谁在前谁在后了。正规做法是定义状态枚举把合法流转路径集中管理。以鲜花商城为例订单状态通常有以下几种待付款、已付款、待发货、待收货、已完成、已取消。状态枚举定义如下public enum OrderStatus { UNPAID(0, 待付款), PAID(1, 已付款待发货), SHIPPED(2, 已发货待收货), COMPLETED(3, 已完成), CANCELLED(4, 已取消); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } // 判断某个状态是否允许流转到目标状态 public boolean canTransitTo(OrderStatus target) { switch (this) { case UNPAID: return target PAID || target CANCELLED; case PAID: return target SHIPPED; case SHIPPED: return target COMPLETED; default: return false; } } }为什么要把状态流转收口到枚举里因为直接在 Service 层写if (status ! 2) { throw ... }也能拦但每处写法不一致时间长了就有人跳过判断直接赋值。枚举把谁能变成谁集中定义改一处全局生效出问题时排查也快。支付回调接口在开发环境里通常没有真实支付渠道这类项目的常见做法是用一个模拟接口代替前端点击模拟支付成功后后端直接把订单状态从待付款改为已付款。生产环境的支付回调必须验签防止伪造通知但本地演示用POST /api/pay/mock就能走通整条链路。5. 避坑清单跨域、时区、超卖与图片丢失5.1 前端能打开、接口全挂跨域与代理配置现象前端页面正常渲染但所有接口请求都报401或503浏览器控制台显示跨域错误。原因前后端分离项目里前端跑在 3000 端口后端在 8080 端口浏览器直接通过http://localhost:8080发请求属于跨域被浏览器 CORS 策略拦截或者是前端 axios 的 baseURL 直接写死了后端地址跳过了代理配置。解决开发环境优先用vue.config.js里的 proxy 代理把/api转发到后端这样前端请求同源不会触发跨域。后端同时配置 CORS 兜底处理特殊情况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); } }注意allowedOriginPatterns是 SpringBoot 2.4 的写法老版本用allowedOrigins(*)。开发环境两项都配上哪个环节出问题都能兜住。5.2 MySQL 连接报错时区与驱动版本现象后端启动时控制台弹出The server time zone value is unrecognized或Could not create connection to database server有时还会冒出一串乱码。原因MySQL 8.0 的连接驱动对时区要求严格URL 里没指定时区JDBC 驱动不知道用哪个时区计算时间乱码则是字符集没配对。解决URL 里显式加上serverTimezoneAsia/Shanghai并确保characterEncodingutf8。如果 pom 里引入的是 MySQL 5.x 驱动但连的是 8.0 数据库也会报Public Key Retrieval is not allowed这时能把allowPublicKeyRetrievaltrue加上或者直接升级驱动版本到 8.x。5.3 库存变成负数并发下单的超卖问题现象多人同时下单同一商品数据库里stock字段变成负值而代码里明明判断过stock 0。原因先查询后更新的写法存在竞态条件。两个请求同时读到库存为 1都通过了大于 0 的判断先后执行扣减结果库存变成 -1。解决用带条件的 UPDATE 原子扣减并把判断库存是否充足融入到WHERE stock 0条件里受影响行数为 0 则直接返回失败。同理生成订单号时也要保证唯一性不要在代码里用System.currentTimeMillis()裸拼并发下可能重复。5.4 图片上传成功但重启后打不开现象上传商品图片时明明提示成功页面也能显示但重启后端服务后所有图片都变成裂图。原因图片被保存到了项目的临时目录里比如src/main/resources/static/upload或 IDEA 工作目录下的临时路径重启后资源被清掉。解决把上传路径改成服务器上的绝对路径比如/data/flower-shop/images/并通过配置类做静态资源映射让/upload/**指向该目录Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file:/data/flower-shop/images/); } }前端图片的src写成/upload/xxx.jpg请求到达后端时会被映射到磁盘目录。注意 macOS 和 Windows 的绝对路径写法不同Windows 下是file:D:/flower-shop/images/路径末尾的斜杠不能丢。5.5 前端刷新白屏history 路由与部署回退现象前后端分离部署后用户从登录页跳转到首页一切正常但按 F5 刷新就变成 404 或白屏。原因Vue Router 默认用的 history 模式刷新时浏览器直接请求当前路径比如/product/3而后端没有对应的路由返回 404。解决如果前后端部署在同一台服务器用 Nginx 托管前端页面并配置try_files回退到index.htmllocation / { root /usr/share/nginx/html; # 前端 dist 目录 try_files $uri $uri/ /index.html; }如果要把前端打包进 SpringBoot 的static目录也需要在后端加一层 SPA 回退 Controller把所有非/api开头的未知路径转发到forward:/index.html。这个坑我在第 6 章详细讲。6. 二次开发与部署从 30 分钟验收用例到项目扩展6.1 前后端打包联调把 Vue 打包放进 SpringBoot 的注意点毕设答辩时很多同学选择把前端打包后放进 SpringBoot 一起运行这样只需要在答辩机器上启动一个 Jar 包。vue.config.js里把publicPath改成相对路径执行npm run build生成dist把dist里的文件拷贝到backend/src/main/resources/static/重新打包后端即可。注意这里有两个暗坑一是如果前端接口 baseURL 写的是/api后端转发到 SpringBoot 后也要有对应的接口前缀二是 history 模式刷新 404 的问题在 SpringBoot 里加一个 Controller 做回退Controller public class SpaController { RequestMapping(value {/{path:[^\\.]*}, /{path:^(?!api).*}/**/{path:[^\\.]*}}) public String forward() { return forward:/index.html; } }这段配置只对非api开头的路径做回退避免把接口请求也转发到页面。更省心的方案是用 Nginx 单独托管前端静态文件后端只管 API部署和排错都更清晰。6.2 30 分钟验收用例我从这套源码里验证了什么拿到任何一份源码我都不会直接改成自己的毕设而是先按下面的用例清单跑一遍全过了再动代码。这份清单是判断资源是否完整、有没有隐藏 bug 的最快方式用例操作步骤预期结果注册登录新用户注册使用账号密码登录登录成功跳转首页商品浏览打开首页和商品分类页商品列表正常展示图片不裂加购下单加入购物车结算生成订单库存扣减正确订单状态为待付款模拟支付订单页点击模拟支付状态变为已付款待发货后台发货管理员登录订单列表发货前端订单状态变为已发货确认收货用户操作确认收货状态变为已完成超时取消待付款订单触发取消逻辑状态变取消库存回补这 7 条用例覆盖了商城系统的主链路任何一环断了先定位是前端问题还是后端问题。我也是在第一次拆这类源码时上午部署、下午翻车反复折腾才总结出这套验收流程。从那以后我拿到任何一份 SpringBootVue 的资源都强制自己先完整跑一遍主链路再碰代码确认数据表、接口、页面闭环通畅才开始二次开发。这套习惯帮我避掉了不少隐藏雷区写在这里希望帮到你。本文还有配套的精品资源点击获取
返回列表