☰
Spring Boot+Vue社区团购系统Java全栈毕设源码解析
2026/10/18 21:27:48 网站建设 项目流程

简介:一套基于 Java 的社区团购系统毕业设计资源,面向计算机相关专业毕业生和课程设计学生,提供可直接运行的完整项目源码,并配套毕业论文与开题报告,用于解决毕业设计选题、系统开发与论文撰写同步推进的问题。系统采用前后端分离架构,前端以 Vue 组件和 JavaScript 实现页面交互,后端由 Java 源码构成核心业务逻辑,辅以 HTML、CSS、XML、SQL 等文件完成静态页面、权限配置与数据库脚本,包内共 810 个文件,压缩包整体约 15MB,结构清晰便于按模块查阅。功能涵盖用户注册登录、商品浏览与详情、购物车管理、订单管理、团购查看以及管理员后台等典型模块,并附带安装、启动、打包等辅助脚本,可快速搭建本地环境。所有源码和论文文档统一整理,既有工程实现也有文字材料,已有 84 人学习,适合需要一套完整案例进行参考、复用或二次开发的 Java 学习者。

1. 社区团购毕设:一套能直接跑的 Java 全栈源码

先说个场景:毕设拿到“社区团购系统”这个题目的人不少,但多数人卡在同一个地方——前端套个模板,后端自己写接口,两边对接不上,下周就中期检查了项目还没跑起来。这套 Java 毕业设计源码不一样,它把选题、开题、实现、论文整条链路打包好了:Spring Boot 后端、Vue 前端、MySQL 数据库,配合注册登录、商品浏览、购物车、团购下单、后台管理这些完整功能,外加毕业论文和开题报告。你不用再东拼西凑,下载后按脚本装依赖、导入数据库、启动两个服务,浏览器一开就是成品。适合三类人:拿同题做课程设计的学生、想快速搞懂社区团购业务闭环的初学者、以及想要一套规范代码做二次开发的开发者。

2. 技术栈与项目结构:Spring Boot + Vue 前后端分离的完整档案

2.1 技术选型:为什么毕设几乎都用这套组合

这套项目的技术栈,从文件列表里就能读出七七八八。后端有 mvnw.cmd,说明是 Maven Wrapper 管理的 Java 项目;而社区团购这种带商品、购物车、订单的系统,用 Spring Boot 几乎是默认选择——它把 Tomcat 内嵌进去了,java -jar 就能起服务,不用像 SSH 时代那样手动装 Tomcat、配 server.xml。持久层最常见的做法是配合 MyBatis 或 MyBatis-Plus:前者 SQL 可控、好讲原理,后者写 CRUD 极快。答辩场景下我一般更推荐 MyBatis,因为可以把 SQL 单独拎出来讲,显得你对数据层有掌控力。

前端部分,那些 .vue.bak 文件已经暴露了用的是 Vue。Vue 2 + Element UI 是这套代码里几乎必然会出现的组合,Element UI 的表单、表格、弹窗组件涵盖了管理系统 90% 的需求。页面跳转用 Vue Router,请求后端用 axios。用户看到的商品列表、团购详情、购物车加减,全是 Vue 组件在浏览器里渲染,和后端通过 JSON 交互。

数据库是 MySQL。社区团购的业务模型——用户、商品、团购、购物车、订单——用关系型数据库表达非常自然,外键、索引、事务都能直接派上用场,这几个点也正是答辩时老师最爱问的。

选这套组合的最大理由是“对口”:技术栈主流、代码分层清晰、前后端分离,这些在课程设计评分标准里全是加分项。你要是用 JSP + Servlet 写同样功能,工作量翻倍,且看起来不像这个时代的产物。

2.2 逐个文件拆解:.classpath、.vue.bak、三个 .bat 分别是什么

先把这套资源里的关键文件按作用分个类,你看一眼就知道哪些要留、哪些可以无视。

文件类型作用使用时注意
.classpathEclipse 工程文件记录项目类路径和依赖引用用 IDEA 打开时无视即可,不影响 Maven 识别
mvnw.cmdMaven Wrapper 脚本自动下载指定版本 Maven 并执行构建本机没装 Maven 也能用,首次运行会联网拉取
1-install.bat一键安装脚本安装后端依赖和前端 npm 依赖需先装好 JDK、Node、MySQL
2-run.bat一键启动脚本启动后端 jar 和前端开发服务器后端 8080,前端端口看脚本配置
3-build.bat打包脚本生产构建,生成可部署产物用于交付或上服务器部署
index.html.bak前端入口备份Vue 应用的 HTML 容器.bak 是备份,实际生效的是 index.html
update-password.vue.bak修改密码页面备份用户改密组件改密码要校验旧密码、确认新密码
IndexAsideStatic.vue.bak侧边栏菜单备份后台管理左侧菜单新增管理页面要同步改菜单配置
BreadCrumbs.vue.bak面包屑导航备份显示当前页面路径依赖路由 meta 里的 title 字段
IndexHeader.vue.bak顶部导航备份用户头像、退出登录等常配合下拉菜单做退出确认

这里有个值得说的细节:这几个 .vue.bak 文件名,是 vue-element-admin 这类后台模板里非常典型的布局组件——IndexAsideStatic 是静态侧边栏,BreadCrumbs 是面包屑,IndexHeader 是顶栏。改版时作者没有直接覆盖源文件,而是把旧的复制成 .bak 再改,这个习惯在真实项目里非常实用:改组件改到一半发现思路不对,把 .bak 改回来就是后悔药。

.vue.bak 是纯文本文件,去掉 .bak 后缀就能被 Vue 编译。如果你拿到源码后发现某个功能异常,而对应位置恰好存在 .bak 文件,打开对比新旧版本差异,通常能快速定位改哪儿改坏的。

2.3 项目目录结构:前后端分离的标准骨架

把 .classpath 和 .bat 放到一边,项目本体应该长这样(按 Maven + Vue 的标准布局补全,实际解压后大同小异):

community-group-buy ├── 1-install.bat ├── 2-run.bat ├── 3-build.bat ├── mvnw.cmd ├── backend │ ├── pom.xml │ ├── src/main/java/com/example/community │ │ ├── CommunityApplication.java # Spring Boot 启动类 │ │ ├── config │ │ │ ├── CorsConfig.java # 跨域配置 │ │ │ ├── LoginInterceptor.java # 登录拦截器 │ │ │ └── WebMvcConfig.java # 拦截器注册 │ │ ├── controller │ │ │ ├── UserController.java │ │ │ ├── GoodsController.java │ │ │ ├── GroupBuyController.java │ │ │ ├── CartController.java │ │ │ └── OrderController.java │ │ ├── service │ │ │ └── impl │ │ ├── mapper │ │ ├── entity │ │ └── common │ │ └── Result.java # 统一返回体 │ └── src/main/resources │ ├── application.yml │ ├── mapper # MyBatis XML 文件 │ └── static # 前端构建产物最终放这里 ├── frontend │ ├── package.json │ ├── vue.config.js │ └── src │ ├── main.js │ ├── router/index.js │ ├── api # axios 请求封装 │ └── views │ ├── Login.vue │ ├── Register.vue │ ├── Home.vue │ ├── GoodsDetail.vue │ ├── Cart.vue │ ├── OrderList.vue │ └── groupbuy/GroupBuyDetail.vue └── sql └── community.sql

这是典型的单体前后端分离:backend 单独打成 jar,frontend 单独构建成 dist。开发阶段两边分开跑,前端通过代理转发请求;部署阶段把 dist 里的静态文件拷进 backend 的 static 目录,打成一个 fat jar,实现单进程交付。论文里的“系统架构设计”章节,画一张这个结构的图,再配两三段文字解释,基本就稳了。

2.4 数据库设计:社区团购的核心表长什么样

社区团购和普通电商的区别在于“团购”这条业务线。用户表、商品表、购物车、订单是通用电商的标配,团购表是这套系统的特色,五张表串成完整业务链。

核心表设计大致如下:

user(用户表)

  • id 主键
  • username 登录名
  • password 加密后的密码
  • user_type 区分普通用户和管理员
  • phone、address 收货信息

goods(商品表)

  • id 主键
  • goods_name 商品名称
  • price 单价
  • stock 库存
  • img 商品图片
  • detail 图文描述

group_buy(团购活动表)

  • id 主键
  • goods_id 关联商品
  • group_price 团购价
  • group_size 成团人数
  • start_time 活动开始时间
  • end_time 活动结束时间
  • status 活动状态

cart(购物车表)

  • id 主键
  • user_id 哪个用户
  • goods_id 哪个商品
  • quantity 数量

orders(订单表)

  • id 主键
  • order_no 订单号
  • user_id 下单人
  • goods_id 商品
  • group_buy_id 走团购时关联活动
  • quantity 数量
  • total_price 总价
  • status 待付款/已付款/已发货/已完成
  • create_time 下单时间

表之间的约束值得展开:购物车表的 user_id + goods_id 应该建唯一索引,防止同一用户把同一件商品插成多条记录;订单表的 order_no 必须唯一且尽量用业务号而不是自增 ID,方便查询和对账;订单表里加 group_buy_id 字段,是为了区分普通购买和团购购买,后续统计“这个团购带来了多少订单”直接查这个字段就行。这些细节写进论文的数据表设计章节,属于加分项。

3. 核心功能拆解:一个请求从登录到加购下单的完整链路

3.1 注册登录:user_type 字段区分用户类型

摘要里描述了注册、登录界面。在社区团购系统里,登录用户分为普通用户和管理员两种,登录界面会让你选登录类型。这个 user_type 字段贯穿了整个权限体系。

后端登录接口的常见实现:

@PostMapping("/login") public Result login(@RequestBody LoginRequest req) { // 1. 按用户名查询用户,这一步走 MyBatis 的 selectOne User user = userMapper.selectByUsername(req.getUsername()); // 2. 用户不存在或密码不匹配,统一返回同一句话,不暴露哪个错误 if (user == null) { return Result.error("用户名或密码错误"); } // 实际 password 字段存的是 BCrypt 加密串,这是简写演示 if (!passwordEncoder.matches(req.getPassword(), user.getPassword())) { return Result.error("用户名或密码错误"); } // 3. 登录时选的类型必须和库里一致,管理员不能走用户端入口 if (!req.getUserType().equals(user.getUserType())) { return Result.error("登录类型不匹配"); } // 4. 登录成功,把用户 id 和 user_type 放进 session request.getSession().setAttribute("userId", user.getId()); request.getSession().setAttribute("userType", user.getUserType()); return Result.success(user); }

参数说明:LoginRequest 是前端传来的请求体,包含 username、password、userType 三个字段;passwordEncoder 是 BCryptPasswordEncoder,matches 方法负责比对明文和加密串,绝不能把密码解密出来做字符串比较;session 是登录状态的载体,后续接口全部通过 LoginInterceptor 拦截未登录请求。

有同学问,为什么不用 JWT?JWT 也行,返回 token 让前端存 localStorage。但毕设场景下用 session 更符合教科书要求,论文里能写“基于 Session 的登录状态保持机制”,老师也容易理解。

登录拦截器的注册是关键步骤:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录、注册接口 if (request.getRequestURI().contains("/user/login") || request.getRequestURI().contains("/user/register")) { return true; } // 校验 session 里有没有 userId Object userId = request.getSession().getAttribute("userId"); if (userId == null) { response.setStatus(401); // 未登录返回 401,前端收到后跳回登录页 return false; } return true; } }

拦截器必须注册到 WebMvcConfig 里,并指定排除路径。这套机制是后端保护接口的第一道防线——前端路由守卫只是 UI 层面的跳转控制,真正的接口防裸调靠的是拦截器。很多毕设翻车就翻在只做了前端路由守卫,后端接口不带任何校验,答辩时打开开发者工具直接调接口就能绕过登录,这个漏洞必须堵上。

前端登录页的 Vue 代码不复杂,重点在表单校验和登录后跳转:

<script> export default { data() { return { loginForm: { username: '', password: '', userType: 'user' }, rules: { username: [{ required: true, message: '请输入用户名', trigger: 'blur' }], password: [{ required: true, message: '请输入密码', trigger: 'blur' }] } } }, methods: { handleLogin() { this.$refs.loginForm.validate(valid => { if (!valid) return // userType 决定登录成功后跳前台还是跳后台 const redirect = this.loginForm.userType === 'admin' ? '/admin' : '/home' this.$router.push(redirect) }) } } } </script>

注册页的逻辑和登录页几乎对称:表单增加确认密码、手机号字段,提交前做前端格式校验,提交后调 /user/register,成功就跳登录页。这里提醒一句:注册接口后端必须做用户名重复检查,否则库里出现两个同名账号,后面登录时 selectOne 会直接报异常,这是很容易踩的暗坑。

3.2 商品详情与团购:一个详情页怎么同时展示两种价格

商品详情页是这套系统里信息密度最高的页面,因为它要同时处理两套数据:商品本身的信息(名称、图片、单价、库存、描述),以及当前是否有进行中的团购活动(团购价、成团人数、剩余时间)。

后端组装详情数据的逻辑:

public GoodsDetailVO getGoodsDetail(Long goodsId) { // 查商品基本信息 Goods goods = goodsMapper.selectById(goodsId); if (goods == null) { throw new BizException("商品不存在"); } // 查当前正在进行中的团购活动 GroupBuy activeGroupBuy = groupBuyMapper.findActiveByGoodsId(goodsId); GoodsDetailVO vo = new GoodsDetailVO(); BeanUtils.copyProperties(goods, vo); if (activeGroupBuy != null) { vo.setGroupPrice(activeGroupBuy.getGroupPrice()); vo.setGroupSize(activeGroupBuy.getGroupSize()); vo.setGroupEndTime(activeGroupBuy.getEndTime()); } return vo; }

逻辑说明:findActiveByGoodsId 对应 SQL 里带 status = 1 且 end_time > now 的条件,保证查出来的一定是当前有效的团购,过期活动不会冒出来;用 GoodsDetailVO 而不是直接把 Goods 实体返回给前端,可以避免把库存、状态等内部字段暴露到页面上,也方便后续扩展团购字段;BeanUtils.copyProperties 做属性拷贝,省去一大段 setter 代码。

前端展示规则是:有团购活动就显示团购价(红色字号加大),并多出一个“立即参团”按钮;没有团购只显示普通价和“加入购物车”。这套逻辑写进论文可以叫“商品详情联动团购活动的动态展示策略”,实际代码就一个 if 判断的事。

加入购物车的接口逻辑前面提过,这里补一个收藏的细节。收藏表通常单独建一张 favorite,字段就 user_id + goods_id + create_time。点收藏前先判断是否已收藏,重复点击应该是“取消收藏”而不是报错,这个交互细节前端容易漏,漏了会出现点第二次收藏按钮报错或者生成重复收藏记录的问题。

3.3 购物车与下单:事务和库存扣减的配合

购物车界面按摘要描述支持修改数量、删除商品和购买。修改数量是前端组件和后端接口的配合:前端用 Element UI 的数字输入框,每次 change 事件调一次 /cart/updateQuantity,后端更新 quantity 后重新计算小计金额返回给前端展示。

关键在后端的下单接口,这是整个系统最容易出 bug 的地方,必须加事务:

@Transactional(rollbackFor = Exception.class) public String createOrder(OrderCreateRequest req) { // 1. 生成业务订单号:前缀 CG + 时间戳 + 随机数 String orderNo = "CG" + System.currentTimeMillis() + (int)(Math.random() * 1000); BigDecimal totalPrice = BigDecimal.ZERO; // 2. 遍历购物车中勾选的商品,逐项校验库存 for (CartItemDTO item : req.getCheckedItems()) { Goods goods = goodsMapper.selectById(item.getGoodsId()); if (goods == null || goods.getStock() < item.getQuantity()) { throw new BizException("商品库存不足:" + goods.getGoodsName()); } // 3. 扣减库存:用 update 语句直接扣,而不是查出来再算 int rows = goodsMapper.decreaseStock(item.getGoodsId(), item.getQuantity()); if (rows == 0) { throw new BizException("库存扣减失败,请稍后重试"); } // 4. 计算总价 BigDecimal itemPrice = goods.getPrice() .multiply(BigDecimal.valueOf(item.getQuantity())); totalPrice = totalPrice.add(itemPrice); // 5. 写订单主表和订单明细表 orderMapper.insertOrder(...); orderItemMapper.insert(...); } // 6. 下单成功后删除购物车中已购买的条目 cartMapper.deleteByIds(req.getCartItemIds()); return orderNo; }

这段代码值得逐行看:@Transactional 保证整个下单过程是一个原子操作——第 3 步扣了库存,第 5 步写订单失败,事务回滚会把库存补回来,不会出现“库存扣了订单没生成”的灵异事件;decreaseStock 的 SQL 是 update goods set stock = stock - #{quantity} where id = #{id} and stock >= #{quantity},用数据库行锁保证并发安全,先 select 再 update 的写法在并发场景下会超卖,这是电商系统的经典坑;订单号用时间戳拼随机数,避免并发下单时主键冲突;全部成功后再清空购物车,避免用户下单失败购物车被误删。

前端下单流程是:购物车页勾选要买的商品,点“去结算”进入确认页,确认页展示商品清单和总价,点“提交订单”调 createOrder 接口,拿到 orderNo 后跳转订单列表页。这里有个交互细节:提交按钮要做防重复点击,否则手抖点两下会生成两个一模一样的订单,常规做法是点击后把按钮置为 loading 状态,请求结束再恢复。

订单列表页按摘要描述展示个人订单信息,常见实现是分页查询 orders 表关联 goods 表,按 create_time 倒序,订单状态用 tag 组件显示不同颜色。如果要做取消订单,后端需要反向操作:订单状态改成已取消,同时把扣掉的库存加回来,这个操作同样要放在事务里。

4. 从零跑通:安装依赖、导入数据库到一键启动

4.1 环境准备:JDK、MySQL、Node 一个都不能少

先把环境清单列出来,版本不需要最新,稳定最重要:

  • JDK 1.8,Spring Boot 2.x 用 JDK 8 最稳,高版本 JDK 有时会遇到 Lombok 兼容问题
  • MySQL 5.7 或 8.0,本地装一个 Navicat,或者直接用命令行导入 SQL
  • Node.js 14 或 16,Vue 2 项目对 Node 版本敏感,装 Node 18+ 容易在 npm install 阶段翻车
  • Maven 可以不装,项目里有 mvnw.cmd 会自动下载

检查命令:

java -version node -v npm -v mysql --version

如果 java 或 node 命令找不到,去官网装对应版本并配置环境变量。配置完要新开一个终端窗口才生效——这是经典坑:配完了还在旧窗口敲命令,当然报“不是内部或外部命令”。

这里额外说一句:如果你本机已经装了高版本 JDK(比如 JDK 17),跑 Spring Boot 2.x 大概率会遇到 java.lang.reflect.InaccessibleObjectException 这类反射报错。最省事的办法不是改代码,而是把 JDK 切回 8,或者升级 Spring Boot 版本。毕设项目,稳定优先,别折腾。

4.2 初始化数据库:导入 SQL 脚本

解压源码后,在 sql 目录下找到 community.sql(或类似命名),建库导入:

CREATE DATABASE community DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE community; SOURCE community.sql的绝对路径;

核对两件事:第一,建库时指定 utf8mb4 字符集,避免中文乱码,很多翻车现场是数据库默认用了 latin1,页面显示一堆问号;第二,导入后打开数据表确认数据条数,商品表和用户表应该有初始数据,管理员账号能直接在 user 表里看到。

然后打开后端配置文件:

spring: datasource: url: jdbc:mysql://localhost:3306/community?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的数据库密码

application.yml 里的账号密码必须改成你自己本机的。如果密码里有特殊字符,在 URL 里可能需要转义,这是一个很容易被忽略的细节。改完记得保存,然后重启后端才生效。

4.3 一键脚本:1-install.bat、2-run.bat 背后做了什么

来看看这三个 .bat 脚本通常做的事情。

1-install.bat 核心内容:

@echo off echo [1/2] 安装后端 Maven 依赖... call mvnw.cmd clean install -DskipTests echo [2/2] 安装前端 npm 依赖... cd frontend call npm install --registry=https://registry.npmmirror.com cd .. echo 依赖安装完成! pause

说明一下:mvnw.cmd 是 Maven Wrapper,它第一次运行时会通过网络下载指定版本的 Maven,所以第一次会比平时慢一些,那是正常的。-DskipTests 是跳过单元测试,避免测试代码环境不干净导致构建失败。npm install 后面跟 registry 参数,是让 npm 走国内镜像源,速度会明显快不少。如果不配镜像,第一次 npm install 可能要下载几百兆依赖,卡十分钟以上不稀奇。

2-run.bat 核心内容:

@echo off echo 启动后端服务... start "community-backend" cmd /k "call mvnw.cmd spring-boot:run" echo 启动前端开发服务器... cd frontend start "community-frontend" cmd /k "npm run serve" echo 两个窗口启动完成! pause

两个 start 会各弹一个新的命令行窗口,分别跑后端和前端。后端默认监听 8080,前端开发服务器默认也是 8080,但被占用时会自动加 1 变成 8081。打开浏览器访问前端地址就能看到登录页。

前端 8081 怎么访问后端 8080?靠 vue.config.js 里的代理:

module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }

参数说明:/api 开头的请求会被代理转发到后端 8080,前端代码里 axios 的 baseURL 只需写成 /api,就能完美避开跨域问题;changeOrigin 设为 true,后端看到的请求来源就是 8080 而不是 8081。如果不配代理,也可以在后端写 CORS 跨域配置,但开发阶段代理更省事——改一行配置就行,不用动 Java 代码。

注意,如果某个版本的 Vue CLI 启动端口不是 8081 而是 8082,那是端口被别的进程占了,代理配置不用改,浏览器访问对应端口即可。

4.4 3-build.bat:生产构建和交付验证

开发模式跑通只是第一步,毕设交付和答辩环境还需要构建产物。3-build.bat 做的事情:

@echo off cd backend call mvnw.cmd clean package -DskipTests cd ..\frontend call npm run build echo 后端 jar 包:backend\target\*.jar echo 前端静态文件:frontend\dist\ pause

构建完成后有两个产物。常见做法是:把 frontend/dist 里的文件复制到后端 src/main/resources/static 目录下,再次打包后端,这样最终只需要一个 jar 就能同时提供页面和接口。部署到服务器上执行:

java -jar community-0.0.1-SNAPSHOT.jar

浏览器访问 http://服务器IP:8080 就是完整系统,不需要再启动 Node。这个“前后端合并单 jar”的部署方式,在论文的部署章节里写出来,比“需要同时开两个服务”要显得更专业,也更好演示。

4.5 跑通验证:三步确认系统正常

后端窗口出现 Started CommunityApplication in XX seconds 说明启动成功。接下来三步验证:

  1. 打开浏览器访问前端地址,看到登录页
  2. 用普通用户账号登录,能进首页看到商品列表
  3. 点一个商品看详情,加购物车下单,订单列表里能看到新订单

如果三步全通,说明前后端、数据库、事务整条链路没问题,论文里截这些图就够了。然后换管理员账号登录,进入后台把用户管理、商品管理、团购管理逐一点一遍,确认管理功能也正常。这里建议尽快用管理员账号把后台全点一遍,因为后台接口如果没测过,答辩现场才第一次打开,容易碰到没初始化的页面报错。

5. 避坑指南:跑这种毕设项目最常见的五个翻车现场

5.1 npm install 报 ERESOLVE 错误,装到一半就挂了

现象:执行 1-install.bat 时,npm install 跑一会儿就报 ERESOLVE unable to resolve dependency tree,前端依赖装不上,后面的 2-run 直接无从谈起。

原因:Node 版本太新。Vue 2 时代的依赖树和 Node 17+ 的依赖解析规则冲突,尤其是 node-sass 这类原生模块,在 Node 18 上几乎必挂。报错信息里通常能找到 node-sass 或 node-gyp 的字样,看到这几个关键字基本就可以判定是版本问题。

解决:用 nvm 切回 Node 14 或 16,删掉 node_modules 和 package-lock.json,重新执行 npm install。如果项目里用的是 node-sass,可以把它换成 sass(dart-sass),能省去一堆编译麻烦,但要注意语法差异。排查命令:

node -v nvm list nvm use 14.21.3 npm install

切换版本后如果 npm install 还是失败,重点看报错里第一个依赖包的名字,去 package.json 里搜,把版本号放宽到兼容范围即可。

5.2 后端启动报端口被占用

现象:2-run.bat 启动后端,窗口里出现 Web server failed to start. Port 8080 was already in use.,然后进程退出。

原因:之前残留了一个没关掉的 Java 进程,或者你自己开了别的服务占了 8080。

解决:先查端口,再杀进程:

netstat -ano | findstr 8080 taskkill /F /PID 这里填查到的PID

注意,杀进程要确认 PID 对得上,别把别的程序杀了。如果不想杀进程,也可以改后端的 server.port,但前端代理 target 也要跟着改,所以最简单的还是清掉占用端口的进程。

提示:改端口必须前后端一起改——后端 server.port 改了 8081,前端 proxy target 也要改成 http://localhost:8081,只改一边会出现前端页面能开、接口全挂的情况。

5.3 页面全中文乱码,数据库里存进去的就是问号

现象:页面显示的商品名称、用户昵称全是 ??,打开数据库看,存进去的字段本身就是问号。

原因:建库时字符集没指定 utf8mb4,或者后端 JDBC URL 没带 characterEncoding=utf8。很多本地 MySQL 默认字符集是 latin1,中文根本存不进去。检查方法:

mysql> show variables like '%character%';

看到 character_set_database 是 latin1,基本就是它了。

解决:删库重建,建库时用 utf8mb4;同时检查 JDBC URL 是否带 characterEncoding=utf8 和 serverTimezone=Asia/Shanghai。改完配置重启后端再试。乱码问题一旦数据已经写坏,光改配置救不回旧数据,必须重建数据库重新导入 SQL。

5.4 前端页面能打开,但所有接口请求报 404 或 401

现象:浏览器里前端页面正常渲染,登录后一调接口就报错。打开 F12 看网络请求,/api 开头的请求要么 404,要么被拦成 401。

原因:404 大都是代理没生效或者后端没启动;401 大都是登录拦截器生效了,但请求没带上 session 的 Cookie。前端 axios 默认不开 withCredentials,Cookie 不会随请求发送,后端拿不到登录态。

解决分两步:第一步,确认 vue.config.js 里 proxy 配置正确,后端窗口里能看到请求日志;第二步,在 axios 请求封装处加一行:

// 请求封装文件,通常是 src/api/request.js const service = axios.create({ baseURL: '/api', timeout: 10000, withCredentials: true // 关键:跨域请求携带 Cookie })

排查顺序建议是:先看后端窗口有没有收到请求,再检查代理,最后看 Cookie。直接猜原因大概率浪费时间。用 curl 先打一下后端接口:

curl http://localhost:8080/api/user/login

后端返回 JSON 说明服务正常,问题在代理或前端;连不上说明后端本身没起来。

5.5 登录后刷新页面,又跳回登录页

现象:登录成功进入首页,按 F5 刷新页面,跳回了登录页。

原因:Vue 是单页应用,刷新后内存里的数据全没了。如果登录状态只存在 Vuex 里,没写 sessionStorage 或 localStorage,路由守卫一刷新就判断未登录。

解决:登录时把用户信息同步存一份 sessionStorage,路由守卫里优先读 sessionStorage,刷新时先取本地存储恢复登录状态再渲染。退出登录时清理本地存储。示例:

// 路由守卫,常见于 router/index.js router.beforeEach((to, from, next) => { const userInfo = sessionStorage.getItem('userInfo') if (to.path === '/login') { next() } else if (!userInfo) { next('/login') } else { next() } })

这也是答辩时老师常点的问题:“为什么刷新一下就掉线?”提前解决掉,能避免现场尴尬。

以上五个问题,前四个出现在跑通阶段,第五个出现在二次开发阶段,都和“环境、状态、配置”相关,源码本身基本不会有问题。记住排查顺序:先看日志,再看网络请求,最后才改代码。日志是黑匣子的钥匙,很多看似玄学的问题,日志里一行就写明白了。

6. 二次开发:把固定角色改成动态权限,并给团购加上预扣库存

拿到这套源码,第一个值得做的改造是权限部分。原系统的用户类型是写死的 user 和 admin 两个值,你可以在 user 表加一个 role 字段,改成标准的 RBAC 模型,这样以后加“团长”“配送员”这种角色,不用改表结构。

具体改动分三步:第一步,user 表增加 role 字段,保留原 user_type 用于兼容,登录接口返回时带上 role,前端把角色信息存到 sessionStorage。第二步,后端加一个角色校验注解:

// 自定义角色校验注解,标注在需要管理员权限的接口上 @RequireRole("admin") @GetMapping("/admin/user/list") public Result listUsers() { // 只有 admin 角色能访问 }

前端路由守卫同步判断角色,非 admin 访问后台路由跳 403 页面。第三步,把 IndexAsideStatic.vue.bak 对应的侧边栏改成根据角色动态渲染,普通用户登录后看不到管理菜单。

第二个值得做的改造在团购模块。现在的团购下单如果是先查库存再 update,并发高了会超卖。我一般会把抢购逻辑改成 Redis 预扣库存:

// 团购开始前,把库存预加载到 Redis stringRedisTemplate.opsForValue() .set("groupbuy:stock:" + groupBuyId, String.valueOf(stock)); // 用户参团时,用 Redis 原子递减 Long remain = stringRedisTemplate.opsForValue() .decrement("groupbuy:stock:" + groupBuyId); if (remain == null || remain < 0) { // 库存不足,回滚这个 key stringRedisTemplate.opsForValue() .increment("groupbuy:stock:" + groupBuyId); return Result.error("手慢了,拼团名额已满"); }

然后再走数据库下单和库存扣减。用 Redis 的原子操作把判断库存和扣库存合并成一步,天然避免并发超卖,这个设计在论文里可以单独写一小节。

改造完这两处,系统就从“能跑”升级成了“能讲”:权限设计用 RBAC 模型讲,高并发库存用 Redis 原子操作讲。论文里每一个改造点都能对应一个小节,导师问起来你也有话说。

拿到这套源码后,我的习惯是先花一个晚上把数据库表和接口对一遍,理清楚每个页面对应哪几个接口,再动手改。改之前给要动的文件复制一个 .bak 备份——这套源码的作者就是这么干的。从那以后我每次拿到新项目,都会强制先梳理一遍数据表关系再改代码,这个习惯帮我避开了好几次改到一半回不去的大坑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询