☰
SpringBoot+Vue前后端分离小区疫情购物系统实战与部署
2026/10/14 4:17:54 网站建设 项目流程

说实话,第一眼看到“前后端分离小区疫情购物系统”这个标题,我第一时间想到的是两件事:一是这类系统在当时确实是刚需,二是这又是一个典型的“麻雀虽小五脏俱全”的练手项目。如果你正在找毕业设计或者想系统练一遍全栈技能,用它来切入再合适不过。SpringBoot + Vue + MyBatis + MySQL,这套组合基本就是当前中小型管理系统开发的主流配置,学完不愁没项目经验可写。

我身边有不少人拿到源码后跑来问我“怎么跑不起来”,绝大部分问题不在代码本身,而是前后端分离项目涉及的东西比想象中多——环境变量、跨域配置、鉴权逻辑、数据库初始化、前端构建,任何一个环节没对上,项目就起不来。这篇文章我就不客气了,把我实际部署和带着别人调试这类项目的经验全部拆开讲,从整体设计思路到后端核心实现,从前端页面联调到最终上线部署,每一步都尽量说透,顺带把那些文档里不会写的坑也一并列出来。

1. 项目整体解读与技术选型思路

这东西本质上是把线下小区的日常购物行为搬到线上,用户在小程序或网页上看商品、加购物车、下单支付,管理员在后台维护商品、处理订单、管理用户。业务不复杂,但链路长,从登录注册到商品展示,再到订单流转,几乎把Web开发最常见的场景都过了一遍。

1.1 这类系统的业务场景与功能定位

从业务上看,这个系统可以分成两条核心链路:用户端和管理端。

用户端关心的是“买得到、查得到”:商品列表能看到在售什么、库存还有多少,商品详情能看规格和说明,下单之后能查到订单状态,收货之后能确认收到货。整个流程和今天你在任何一个电商App上的体验没有本质区别,只不过业务规模小,规则更简单。

管理端关心的是“管得住、看得清”:管理员要能上架或下架商品、调整价格和库存,用户下单后需要审核或确认订单,订单完成之后要能看到这段时间的整体销售情况。这个系统的管理端通常是角色的核心价值所在,因为对小区物业或运营方来说,能不能高效完成日常运维,直接就决定了线下服务能不能跑起来。

这种分工决定了架构方式:用户端和管理端虽然功能差异大,但它们访问的是同一套数据、同一套业务逻辑,所以后端必须做成统一的能力中心,前端则根据角色拆成不同的界面。这也正是前后端分离在此时最大的意义——它不是赶时髦,而是业务天然要求两端独立演进、独立部署。

1.2 技术栈选型背后的逻辑

后端选SpringBoot,不用多说,它是当前Java领域做接口服务最主流的选择。它解决了传统SSH或Servlet开发里大量繁琐配置的问题,内嵌Tomcat,一个jar包直接跑起来,开发和部署成本都低。再加上它的生态极其完善,无论是权限控制、参数校验还是接口文档生成,都有现成方案能接进来。

MyBatis和SpringBoot的搭配在中小型管理系统中相当常见。它不像JPA那样把SQL完全封装起来,而是让你自己写SQL,对于业务复杂、SQL需要精细控制的地方反而更顺手。这个系统的订单统计、商品筛选、多表联查都很适合用MyBatis直接写Mapper来实现。

前端Vue.比如创建项目、安装依赖、准备数据库,每一件都要先做完后面的工作才有意义。后端常见的坑是端口被占用、命名的扫描包路径没对上,启动直接报错找不到Bean;前端常见的坑是Node版本太高或太低导致依赖装上以后跑不起来;数据库常见的坑是SQL脚本里的编码和排序规则与项目配置不一致,中文变成乱码。

我把这套流程拆成几个部分细说。

3.1 基于SpringBoot的工程搭建与分层结构

后端入口工程一般就叫后端项目名,包结构大概长这样:

服务启动类写在根包下,控制器、服务、Mapper、实体类各自分包。要注意扫描范围,启动类所在包要覆盖所有子包,不然Controller扫描不到。

工程结构里比较关键的是,在运行一个前后端分离项目时,当你用IDEA调试时,启动类名前的服务启动配置和虚拟机参数要配合好,不能让配置覆盖掉项目里自带的配置。目标目录下如果出现配置文件不一致的情况,一般是因为运行了几次不同的配置名称,刷新掉就恢复正常。

3.2 角色控制与登录态的常见实现方案对比

系统里有普通用户和管理员两种角色,登录后要做的事完全不一样,权限控制就非常关键。常见实现有三种:

  • 用拦截器或过滤器判断session(传统做法)
  • 用JWT拦截器模式(目前中小型项目主流做法)
  • 用SpringSecurity或Sa-Token这类框架(适合安全要求较高的场景)

对于这个项目,用JWT拦截器模式比较合适。SpringSecurity虽然强大,但对初中级开发者来说配置门槛偏高,而且这类系统的安全需求并没有复杂到需要引入完整安全框架的程度。引入更多的是学习成本。JWT配合拦截器足够应付角色判断的需求,代码少量轻量又好理解。

登录成功之后,后端生成一个包含用户信息(用户ID、角色标志、过期时间)的Token返回给前端;前端把Token存在本地存储里,每次请求带上;后端拦截器确认Token合法,再从Token里取出角色信息,按照预定的规则决定是否放行。

3.3 订单流程与库存处理的核心细节

现在可以放大来看订单这个环节,因为它是整个后端代码里最容易出问题的地方。

订单流程通常包含三步:先生成基础信息(单号、用户ID、商品明细、总价、订单状态),再扣减库存,然后返回前端用于入库的订单ID。

先扣减库存再下单,时在极端并发下容易出现超卖;先下单再扣库存,可能出现库存为负自己却没发现。比较稳妥的做法是:生成订单基本信息时先查一次库存,确认足够,然后插入订单,再执行存量扣减SQL,最后提交事务。也就是说,下订单和扣库存要在同一个事务里,扣库存SQL要带上“库存大于等于购买数量”的条件,返回影响行数,影响行数为0就说明库存不够,主动抛异常回滚事务。

还有一个容易忽略的问题——重复提交。用户在网页上网络卡顿,手快点了好几次提交按钮,就可能产生多个订单。解决方案是前端按钮提交后变灰色不可点击;后端可以增加幂等校验,比如要求前端生成一个唯一的请求标识,后端记录标识,重复标识直接拦截。

3.4 MyBatis的Mapper编写与动态SQL实战

MyBatis里动态SQL是使用频率最高的功能之一,尤其是在商品列表,因为筛选条件是动态的。用户可能只按分类过滤,也可能只按关键词搜索,也可能同时要求按价格排序。如果用JDBC拼字符串,稍微不注意就出SQL注入问题,而且代码丑到没法维护。

动态SQL的一个典型写法长下面这个样子:

<select id="selectProductList" resultType="商品实体类全限定名"> SELECT id, name, price, stock, category, image, description, status, create_time, update_time FROM product <where> <if test="keyword != null and keyword != ''"> AND name LIKE CONCAT('%', #{keyword}, '%') </if> <if test="category != null and category != ''"> AND category = #{category} </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY id DESC </select>

这样写的优势是,条件满足时自动拼接,条件不满足时自动去掉WHERE之后的对应项,非常贴合业务里筛选组合多变的情况。注意LIKE查询不要用“${keyword}”的方式拼接,用户输入直接拼进SQL里,极其容易被注入攻击,时间一长数据库里的数据都保不住。

多表联查在订单列表里也很常见。订单基本表只存用户ID和商品ID,要显示用户名和商品名时就需要关联用户表和商品表。用MySQL左连接查即可,注意别在老版本里出现ON条件和WHERE条件混在一起影响可读性的写法。

3.5 订单状态变更与异步通知的隐患

系统允许管理员修改订单状态,如把“已支付”改成“配送中”或“已完成”。比较常见的开发方式是管理员在页面上操作一个下拉框或按钮,直接修改订单状态字段。

这里面有个考虑点:直接暴露状态修改接口,如果前端不校验角色,任何人伪造请求都能把别人的订单改掉。所以后端修改订单状态的接口一定得加角色控制,只允许管理员访问。

另外,当订单状态变更时,是否要通知用户?如果系统里没有短信或即时通知推送能力,那至少在业务上要留下变更的时间记录,方便日后查看。简单做法是订单表里加两个字段:状态和状态变更时间,每次改状态时同步更新。别小看这一步,没有时间记录的订单列表,售后时根本没法追踪。

4. 前端Vue项目与页面交互实现

接下来看前端。Vue在这类项目里承担的是界面渲染、用户交互和接口对接的角色。

4.1 Vue工程的目录结构与路由设计

Vue2项目用脚手架创建之后,具体的代码目录一般是这个结构:

src/ api/ // 所有接口调用统一封装 assets/ // 静态资源 components/ // 公共组件 router/ // 路由配置 store/ // 全局状态管理 views/ // 页面组件 utils/ // 工具函数 App.vue // 根组件 main.js // 入口文件

路由配置方面,比较偷懒的做法是直接把所有页面铺开,不加任何守卫。但稍微负责一点的做法是配置两个基础布局:一个给用户端的首页、购物车、订单页,一个给管理端的商品管理、订单管理、可视化数据页。两种布局的界面骨架不同,顶部导航栏和侧边栏的差异较大,分开配置更合理。

4.2 axios请求封装与Token注入方式

前端请求后端的标准做法是用axios统一封装。好的封装本质上解决的是“每个页面写请求时不带重复逻辑”的问题。

封装层的标准动作包括:

  • 设置基础URL,代理模式下直接写相对路径即可
  • 请求拦截器:每次请求自动把本地存储中的Token取出放进HTTP头里
  • 响应拦截器:统一处理响应数据,拿不到数据时统一弹提示信息
  • 统一处理错误码,如Token过期时自动跳转登录页

伪代码大致是这样:

import axios from 'axios' const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { // 统一错误提示 return Promise.reject(new Error(res.message || '请求出错')) } return res }, error => { // 网络错误或超时统一处理 return Promise.reject(error) } ) export default service

注意一个小坑:Token如果在前端只存在本地存储里,页面刷新后路由守卫读取不到登录状态,用户会被强制回登录页。如果项目里没有做持久化登录的需求,这种实现没问题;但如果你希望用户刷新页面还能保持登录状态,那就要在Vuex.Action文件里做初始化时从本地存储恢复登录态的处理。

4.3 登录态管理与角色路由守卫的实现思路

路由守卫在Vue里叫“前置守卫”,设置起来比较直接:在跳转每个路由前检查是否登录。未登录用户访问需要登录的页面时直接跳转登录页;已登录用户访问登录页时跳转到首页。

角色差异主要靠路由meta信息控制。管理员页面在meta里标记“需要管理员权限”,用户页面标记“需要登录”。跳转时除了检查登录,还要判断当前用户角色是否匹配。

实现上要注意一个细节:角色信息从哪里拿?如果存在本地存储里,页面被篡改的话前端就能看到管理员页面,但是后端接口还有一层角色校验兜底。前面说过,前端控制隐藏按钮,后端控制数据安全。两层都不缺才稳妥。

4.4 商品展示与购物车交互的常见实现方式

商品列表页用卡片网格排版,每张卡片包含商品图、名称、价格、库存、加购按钮。图片字段在前端可以通过URL或Base64存进数据库,开发环境下常用静态图片链接指向本地资源目录。注意打包上线的时候,图片路径要以站点上线后的可访问路径为准。

购物车在数据库里单独建表,记录用户ID、商品ID、数量。前端购物车列表从后端拉取,这样一来换设备登录账号购物车也还在。如果不建表,用本地存储也能实现“购物车”功能,但换设备就丢了。做在线购物系统建议建表,代码量增加不多,体验却差很多。

数量加减按钮每次点击都要调接口更新购物车数量,还是只在结算时统一提交?两种做法都能跑。调接口更新数量更实时,但请求频繁;统一提交操作量少但需要在结算页再核对一次数量。规模小的项目用实时更新,体验更好,代码也不复杂。

4.5 订单提交与待支付状态的联动逻辑

订单提交的核心是:结算时把购物车里勾选的商品数据发给后端,后端生成订单,前端拿到订单ID后,再把购物车中被选购的商品删掉。

注意商品价格一定要以后端数据库为准,不能信任前端传来的价格。理论上页面显示的价格如果被篡改,用户用改过的伪造价格下单,后端不校验就直接入账,资金和库存都对不上。所以下单接口的入参通常只有商品ID和数量,价格由后端实时从数据库读取计算。

这个细节在校验支付逻辑时非常关键,也就是“前后端分离系统里,前端只负责展示,真正的业务规则永远在后端执行”。

5. 数据库初始化和后端部署流程

到这一步,代码层面基本讲完了。现在要把项目真正跑起来,这也是几乎所有拿到源码的人最容易卡住的地方。

5.1 数据库初始化与环境版本注意事项

数据库初始化时,先创建数据库实例,然后再执行SQL脚本。

执行SQL脚本时碰到的最典型问题就是编码问题。如果数据库实例创建时没有指定utf8mb4字符集,执行建表语句后插入中文大概率出现乱码或直接报错。比较稳妥的建库语句长这样:

CREATE DATABASE IF NOT EXISTS 数据库名 DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;

注意utf8mb4和utf8的区别。utf8在MySQL里最多支持3字节编码,而很多生僻字和emoji需要4字节,用utf8mb4可以保证所有字符集都能正常存储。互联网项目默认推荐utf8mb4,这已经是共识。

版本方面:MySQL 5.7和8.0都兼容这个项目。8.0之后默认认证插件是caching_sha2_password,JDBC驱动要使用MySQL Connector/J 8.0及以上版本,否则连接会报认证失败。这是很多新人容易踩的坑——数据库密码没问题,但驱动版本太老。

5.2 后端项目的打包配置与启动命令

后端打包常用Maven。编辑pom.xml,确认打包方式为jar,插件用spring-boot-maven-plugin。打包命令是:

mvn clean package -DskipTests

打包完成后,target目录下会生成可执行Jar包,然后用命令启动:

java -jar 项目名-0.0.1-SNAPSHOT.jar

启动之前,必须把配置文件里的数据库地址、账号、密码改成本地实际值。尤其是密码,很多人打包后忘记改配置文件里的测试环境密码,导致本地启动一直报连接失败。生产环境建议通过外部配置文件或环境变量传入,开发环境直接改文件问题也不大。

还有一点:如果项目里配置了外部静态资源路径,如商品图片上传目录,注意打包时资源目录不会带上外面路径的内容,启动后图片访问404的话,优先检查路径配置。

5.3 前端构建与Nginx部署方式详解

前端构建命令:

npm run build

构建产物在dist目录下。如果不想自己搞服务器,最简单的方式是把dist目录放进Nginx的html目录,然后配置反向代理,把接口请求转发给后端服务。

本机开发场景下的Nginx配置实例:

server { listen 8088; server_name localhost; location / { root C:/web/dist; index index.html; 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; } }

配置要点有两个:

  • location /里的try_files配置是为了解决Vue路由在刷新页面时出现404的问题。Vue是单页应用,路由由前端控制,刷新时Nginx要把请求重写到index.html,交给前端路由再渲染。
  • location /api/里的proxy_pass配置是为了把前端发出的API请求转发到后端端口,解决跨域问题。后端地址写127.0.0.1:8080,端口要和SpringBoot运行端口一致。

5.4 Windows和Linux环境下的部署差异

开发时大多在Windows上跑,直接双击Jar包或者命令行运行都可以。如果你有条件用Linux服务器部署,注意以下几点:

  • 在Linux上运行Jar包推荐用nohup方式后台启动:
nohup java -jar 项目名.jar > app.log 2>&1 &
  • Linux上配置文件里的数据库地址如果也是本机,直接写127.0.0.1即可。如果数据库在另一台机器,务必写内网IP。
  • Nginx的路径写法在Linux下用正斜杠,Windows下路径可能会涉及权限和盘符问题,配置不一致时先检查这块。
  • 服务器防火墙要放开Nginx监听端口和后端端口,不然外部访问不了,前后端联调接口时本地正常,线上却怎么都调不通。

5.5 启动失败的常见原因与排查操作

这里把启动项目最常见的四种失败原因和排查方法列一下:

现象可能原因排查方式
后端启动报端口被占用8080端口已被其他进程使用命令行执行netstat -ano,找到占用端口的PID并结束进程,或改配置文件换端口
前端请求接口一直pending后端没启动或代理配置错误先直接访问后端接口看是否通,再检查前端代理配置的target地址
数据库连接失败数据库没启动、密码错误、URL写错用数据库连接工具直接测试连接,排查是哪一层的问题
页面能开但接口404Nginx代理路径配置不对确认前端接口前缀和后端Controller的@RequestMapping前缀一致

遇到启动报错别急着瞎改代码,先看控制台输出的第一行报错信息。绝大多数启动失败都是环境问题,与业务代码无关。

6. 常见踩坑记录与调试经验

代码跑通只是第一步,真正让你成长的是实际调试和修bug的过程。这部分把我亲自带人做这类项目时遇到的典型问题整理一遍。

6.1 开发过程中的高频问题速查表

问题描述出现原因解决方案
Vue项目npm install安装依赖失败Node版本与依赖不兼容,node-sass等旧依赖在Node高版本上装不上用Node 14或16配合Vue2项目;或换用sass替代node-sass
表格数据加载出来但图片不显示图片字段存的是本地路径,后端返回相对路径,前端拼接地址错误确认后端返回的是完整可访问URL,或者前端在基础路径上拼接图片路径
登录成功后刷新页面又回到登录页前端路由守卫重新判断时读不到登录状态在Vuex初始化时从本地存储恢复用户状态,再更新路由守卫里的校验逻辑
接口报错403或401Token缺失、过期、或角色权限不足查看请求头里是否带Token,检查后端拦截器和角色规则
中文乱码数据库字符集不是utf8mb4,或后端响应头未指定UTF-8建库时指定utf8mb4;在SpringBoot配置文件里加响应编码配置
删除商品后订单明细级联问题外键约束导致删除失败,或者没有外键导致删除后明细引用不到明确关联关系,选择合适策略(逻辑删除或外键级联)

6.2 两个印象深刻的调试案例

案例一:某位朋友部署时Nginx页面能打开,但登录接口一直报404。排查一圈发现,前端代码里的登录请求地址是“/api/user/login”,而SpringBoot控制器的请求地址是“/user/login”,Nginx没做链路重写,导致跟代理里的“/api/”前缀对不上。解决方法是把后端的服务器上下文路径配置成“/api”,或者在Nginx配置里用“rewrite”对路径做修改。这类问题有一个核心规律:前端、Nginx、后端三者之间的路径必须完全对齐,想象成水管对接,一个口径不对水就漏。

案例二:系统上线测试时,某管理员更新商品库存后,用户端刷新页面却发现库存没变化。一开始怀疑缓存问题,翻代码发现商品的查询接口上没有加动态刷新配置,而管理员端和后端代码里两个服务查的不同的表字段(一个同步一个不同步),实际是改库时漏掉了同步字段。这类问题在“复制代码改项目”时格外常见,尤其当一个人同时维护前后端两套代码的时候。我的习惯是改完数据库字段,马上全局搜索所有涉及该表的Mapper和查询语句,确保没有遗漏。

6.3 给新手的实战建议

如果你打算把这个项目作为学习素材,给你几条实在的建议:

  • 不要照着一行行敲代码,先跑起来,再改着玩。把系统跑起来之后,把某个接口的数据改掉看前端如何响应,把某个表加一个字段看要动哪些代码,这种“改坏再修好”的过程比单纯阅读源码有效得多。
  • 把自己的想法加进去。比如给商品增加一个“今日推荐”字段,或者给订单增加“备注”字段,只看代码很难学到解决问题的能力,动手加需求能逼你把整条链路走通。
  • 日志是你的好朋友。后端所有接口调用都尽量打印日志,特别是订单流程和登录状态这两个环节。前端主要在开发工具里看Network面板和Console报错,大多数联调问题在这里就能看出来。
  • 注意备份数据库脚本。每次改表结构后,记得导出一份新的SQL脚本,不然团队协作时别人本地还是旧表结构,又会出现字段不对齐的问题。

这套系统做完,能覆盖的不仅是这一套业务,对“一个可以上线的全栈Web应用”的理解会完整很多。前后端怎么配合、权限怎么设、事务怎么控制、跨域怎么解决、部署怎么搞,这些问题放在任何商业项目里都一样遇到。

7. 总结与个人体会

我自己带人做这套系统时最大的感慨是,技术上每一块拿出来都不难,但把SpringBoot、Vue、MyBatis、MySQL完整串起来,还要让它在别人的电脑上也能一键跑起来,这个“让项目可运行、可交付”的过程反而比写功能代码复杂得多。

如果你是从零学起,我会建议你先别管Nginx,也不要一上来就研究JWT的源码实现。先把项目跑起来,把购物流程完整走一遍,然后加一个自己喜欢的字段或模块,再断点调试一遍订单生成的代码。

等你亲手把这个系统的每个接口都调通过,每张表都看得明明白白,再去看生产环境的复杂业务,你会发现很多高大上的概念其实就是在这套基础能力上垒起来的。这种从零到一建立起来的信心,比任何教程都值钱。

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

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

立即咨询