首页/新闻资讯/正文详情

基于SpringBoot+Vue的前后端分离在线家具商城项目实战解析

发布时间:2026/9/26 6:58:42 来源:云帆数科 栏目:资讯中心
基于SpringBoot+Vue的前后端分离在线家具商城项目实战解析
前后端分离的在线家具商城这类项目我这两年带不少人跑通过。先说结论这是一个非常典型的Java全栈练手项目麻雀虽小五脏俱全——用户注册登录、商品分类浏览、购物车、下单结算、后台商品和订单管理全都有而且SpringBoot Vue MyBatis MySQL这套组合是现阶段国内中小型团队做Web系统最主流的选型之一你把这个项目吃透出去面试或者接手外包项目底子已经比大多数简历写“熟悉SpringBoot”的人扎实了。这篇博文我不想只丢一个源码包让你自己看而是直接把整个项目从需求拆解、技术选型、核心实现到部署上线的完整链路给你捋一遍。下面所有截图级的细节、配置文件和踩坑点都是我在实际部署和改造这个项目的过程中整理出来的适合正在做毕业设计、想入门全栈开发、或者准备做一套基础商城系统二次开发的读者参考。1. 项目整体拆解在线家具商城到底在做什么1.1 为什么选“家具”这个品类做前后端分离项目很多人一开始看到“家具商城”会觉得奇怪做商城系统用“图书”、“数码”不是更常见吗我当时选家具这个品类恰恰是因为它比图书、数码更有代表性。家具类商品有几个显著特点SKU库存量单位不算太多但属性复杂——一张沙发有材质、颜色、尺寸、风格、品牌五个维度主图要求高——需要多角度展示图、详情轮播图客单价高、订单状态流转长——从下单到发货再到收货确认中间还可能要经历定制工期。这些东西会倒逼你把数据库设计得足够规范把订单模块做完整而不是像图书商城那样简单“下单-发货”两步走完。从教学和毕设角度来看家具商城还天然带一个亮点后端管理端可以做得很有内容比如商品多图上传、分类层级管理、库存预警、订单多条件筛选。这些功能拆出来都是面试喜欢问的高频点写在简历上比“实现了用户登录注册”好看得多。1.2 需求范围与功能模块清单这个系统的整体角色分成两端前台商城用户端面向C端消费者核心使命是“让用户逛得爽、买得顺”。后台管理管理端面向运营和管理员核心使命是“让商品和订单管得清”。我们对照着来看模块清单端模块功能说明前台用户模块注册、登录JWT鉴权、个人信息维护、收货地址管理前台商品模块分类导航、商品列表价格/销量排序、关键词搜索、商品详情前台购物车加入购物车、修改数量、删除、选中结算前台订单模块订单创建、订单列表、订单详情、取消订单后台商品管理商品CRUD、多图上传本地存储、上下架、库存修改后台分类管理一级/二级分类维护、排序后台订单管理订单列表查询、发货状态更新、订单备注后台统计看板商品总数、订单总数、近7日订单趋势简单ECharts图表这套功能范围我建议你拿到源码后第一件事就是对照着跑一遍跑通之后你自然会对整个项目的数据流转有个整体的感知用户在前台点下“提交订单”的那一刻后端发生了什么数据库里几张表的数据怎么联动。带着这个感知去读源码效率会高很多。2. 技术选型SpringBoot Vue MyBatis MySQL为什么能打2.1 后端框架选型SpringBoot的取舍后端框架备选方案其实不少SSHStruts2 Spring Hibernate、SSMSpring SpringMVC MyBatis、SpringBoot都有人用。但放在2024年这个时间节点SpringBoot已经是事实上的Java后端标准没有太多悬念。选SpringBoot的理由很实在内置服务器自带的Tomcat让开发期零配置启动“写好接口直接Run”不用像传统SSM那样部署到外部Tomcat再启动迭代效率提升是肉眼可见的。自动配置数据库连接池、ORM框架、JSON序列化这些基础设施SpringBoot用spring-boot-starter-xxx一拉就全有了省掉大量XML配置。生态无缝衔接SpringSecurity、Redis、RabbitMQ、文件存储这些后续扩展组件全都是在SpringBoot上做starter集成你现在用SpringBoot打底后面不管加入什么功能都有现成方案。从学习角度我也建议你跳过传统的SSM阶段直接上SpringBoot。XML配置那套是老一辈程序员的历史包袱你花两周死磕applicationContext.xml的bean定义对理解现代Web开发几乎没有增量收益不如把这时间花在接口设计、数据库建模、权限方案上。2.2 数据持久层MyBatis凭什么替代JPA持久层框架的选择是个争论不休的话题我的态度很明确国范围内你想找一套最快上手、SQL最可控的方案MyBatis依然是首选。对比一下你就懂了对比维度MyBatisSpring Data JPASQL可控性手写SQL慢查询一眼就能定位动态生成的SQL复杂查询需要查日志上手门槛会SQL就会用XML直接写需要理解ORM思想、懒加载、持久化上下文复杂查询多表联查、动态条件都很好写关联查询容易踩N1坑国内普及度绝大多数公司Java岗都在用外企和部分新项目用得多家具商城这种系统有个很典型的场景商品列表需要多条件动态查询分类价格区间排序方式条件可能有也可能没有。在MyBatis里用where加if标签写动态SQL逻辑清晰、执行SQL一眼看懂在JPA里你得靠Specification或者Query拼接写出来又长又绕。所以我坚持用MyBatis本质上是为了让“SQL”这件事回归直觉——你写出什么SQL数据库就执行什么SQL性能全部在掌控之中。2.3 前端方案Vue 2还是Vue 3这个项目我用的是Vue 2 Element UI如果在2024年新开项目我会建议你直接上Vue 3 Element Plus。但这里有个现实考量现在市面上大量存量项目、毕设源码、外包系统都是Vue 2你想要快速跑通这套源码Vue 2是绕不开的学习成本。而且Vue 2的核心思想组件化、数据响应式、路由、状态管理在Vue 3里基本一脉相承你先把Vue 2的项目啃明白迁移到Vue 3只需要学Composition API和新的生态组件即可。Vue在前端分离这个架构里承担的角色很清晰组件化首页的导航栏、商品卡片、轮播图、购物车列表全部拆成独立组件相互不干扰这就是前后端分离里前端部分的自洽与模块化——后端按接口切分前端按组件切分。组件化带来最大好处是可复用比如商品卡片组件首页推荐位、商品列表页、搜索结果页三处共用改一个地方三处生效。数据响应式数据变更自动更新视图你在购物车页面点“1”总价实时刷新不需要手动操作DOM这种开发体验比直接写jQuery舒服太多。生态配套VueRouter管页面跳转、Axios管HTTP请求、Vuex/Pinia管全局状态三件套把前端工程化拼图补齐了。2.4 前后端分离的架构优势与通信机制聊聊“前后端分离”本身。早年的JSP开发模式是前端写死在Java里一个页面既要写HTML又要嵌套Java代码改个样式可能要重新编译整个项目。前后端分离之后职责边界非常清晰前端只负责页面渲染、用户交互、调用接口渲染数据跑在浏览器里。后端只负责业务逻辑、数据持久化、权限校验跑在服务器上。通信载体是JSON格式的RESTful API前端发HTTP请求后端返回JSON谁也不关心对面内部怎么实现。开发模式上只要接口文档定义好前后端可以并行开发前端用Mock数据先渲染页面后端同步写接口最后联调时把Mock换成真实接口就行。部署模式上前端构建成静态文件扔Nginx后端打包Jar或War独立运行两边各部署各的扩容时甚至可以分别做负载均衡。这套架构能成为今天Web开发的主流靠的是实打实的工程效率提升不是概念炒作。通信的细节部分有个必须掌握的机制——跨域。你在开发环境访问localhost:8080的Vue页面却要向localhost:9090的SpringBoot接口发请求浏览器会因为协议、域名、端口不一致而阻止这就是跨域问题。经典解法是在SpringBoot里加一个CORS全局配置类允许指定来源访问。这个配置我在后面的实操部分会给出完整代码。3. 核心实现细节与实操要点3.1 数据库设计9张核心表的设计思路数据库是这套系统的地基出问题全是连锁反应。我按业务模块把核心表拆开讲讲设计思路表名核心字段设计要点userid, username, password, nickname, avatar, phone密码必须加密存储我用的是BCrypt加盐哈希绝对不能明文addressid, user_id, receiver, phone, province, city, detail一个用户对应多个地址所以单拎出来做表categoryid, name, parent_id, sortparent_id为0表示一级分类实现两级分类结构productid, category_id, name, subtitle, main_image, price, stock, sale_count, status, detail用逻辑删除status字段而不是物理删除便于订单历史数据追溯product_imageid, product_id, url, sort一张商品多张图片用子表存cart_itemid, user_id, product_id, quantity, checked联合约束user_id product_id唯一同一商品复用一条记录orderid, order_no, user_id, total_price, status, receiver_infoorder_no全局唯一收货人信息冗余存入订单表防止地址变更后订单查不到旧地址order_itemid, order_id, product_id, product_name, product_image, price, quantity商品快照字段名称/图片/价格必须冗余否则商品改价后订单金额对不上adminid, username, password管理端单独账号体系和用户端隔离几个容易被人忽略的细节金额字段用十进制类型decimal(10,2)而不是float/double。浮点数的精度问题在金额计算上会直接导致对不上账这是初学者常踩的坑。订单号和雪花算法不要用数据库自增ID当订单号因为会暴露销量、而且多表合并时容易冲突。我项目里用UUID截取加时间戳生成了个32位的order_no虽然没那么花哨但足够满足唯一性要求。逻辑删除的设计商品表用status字段0下架、1上架、2删除代替物理删除。因为订单详情里要展示历史商品信息物理删除会导致外键悬空。同理用户也是逻辑删除防止user_id在订单里查无此人。3.2 后端分层与接口设计Controller-Service-Mapper怎么组织后端代码结构我建议你直接照搬经典三层架构别玩花活com.example.mall ├── controller // 接收请求、参数校验、返回统一结果 ├── service // 业务逻辑编排、事务管理 ├── mapper // MyBatis数据访问接口 XML文件 ├── entity // 数据库实体类 ├── dto // 数据传输对象前端传入/返回 ├── config // 全局配置跨域、拦截器、静态资源映射 ├── common // 统一返回结果、异常处理、常量 ├── util // 工具类JWT工具、MD5工具 └── interceptor // JWT登录校验拦截器Controller层我强调两点统一返回结构。所有接口都返回Result.success(data)或Result.error(msg)结构固定为{code, message, data}。这样前端拦截器可以统一判断code是否为200不用每个接口单独写错误处理。三端联调前端、后端、测试只看code就能定位问题。RESTful风格。比如商品接口设计为GET /product/{id}、GET /product?categoryId1pageNum1pageSize10sortprice_desc。路径上的名词代表资源HTTP方法代表操作语义清晰自解释前端对接零沟通成本。Service层是业务逻辑的重头戏最核心的就是下单接口。它不是一个简单的INSERT而是一个涉及多张表的事务操作Transactional public Order createOrder(Long userId, ListCartItemDTO items, Long addressId) { // 1. 校验购物车数据计算总金额 // 2. 检查商品库存不满足则抛出异常 // 3. 生成订单主记录状态待付款 // 4. 批量生成订单明细商品名称/图片/价格快照 // 5. 扣减库存UPDATE product SET stock stock - #{quantity} WHERE id #{id} AND stock #{quantity} // 6. 清空购物车中已下单的商品 return order; }注意第5步的写法WHERE stock #{quantity}是防超卖的关键通过UPDATE语句受影响行数是否为0来判断库存是否足够这种“乐观锁扣减”在单体应用里已经够用不需要引入Redis分布式锁那么重。方法上的Transactional保证这6步要么全部成功、要么全部回滚——比如库存扣减成功但订单明细插入失败整个事务回滚库存不会白白扣掉。MyBatis的XML部分商品列表的动态查询是我最想让你看懂的案例select idselectProductList resultTypecom.example.mall.entity.Product SELECT * FROM product where if testcategoryId ! null AND category_id #{categoryId} /if if testkeyword ! null and keyword ! AND (name LIKE CONCAT(%, #{keyword}, %) OR subtitle LIKE CONCAT(%, #{keyword}, %)) /if if teststatus ! null AND status #{status} /if /where ORDER BY ${sort} -- 这里注意用${}排序字段属于白名单配置不能走预编译 /selectwhere标签会自动处理掉第一个条件的ANDif实现了条件动态拼接。这套写法就是MyBatis的看家本领面试十有八九会问到你源码里多找几个这种例子自己能默写出来就算真掌握了。3.3 前端页面与组件拆解从登录页到购物车的完整链路前端项目结构我建议这样组织src ├── api // 接口请求模块按业务模块拆product.js, order.js, user.js ├── assets // 静态资源图片、全局样式 ├── components // 公共组件Header, Footer, ProductCard, Pagination ├── router // 路由配置含守卫 ├── store // Vuex状态管理购物车角标、用户信息 ├── views // 页面组件 │ ├── home // 首页 │ ├── product // 商品列表、详情 │ ├── cart // 购物车 │ ├── order // 订单确认、订单列表 │ ├── user // 登录、注册、个人中心 │ └── admin // 后台管理页面 └── utils // 工具axios封装、token存储核心请求封装直接用Axios实例统一注入token和响应拦截// utils/request.js import axios from axios import router from /router const service axios.create({ baseURL: /api, // 开发环境走Vue代理 timeout: 10000 }) // 请求拦截带上token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token // 后端从Header取值 } return config }) // 响应拦截统一处理code service.interceptors.response.use( response { const res response.data if (res.code ! 200) { // 401跳登录页 if (res.code 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(error) } )购物车角标这个功能最能体现Vuex的价值用户在不同页面首页加到购物车、商品详情页看购物车、下单页清空购物车时角标数字是全局共享的状态放进Vuex维护一个commit触发所有相关组件自动更新。前端几个容易忽略的细节路由守卫router.beforeEach((to, from, next) {})里判断路由meta是否需要登录没token就跳转/login?redirect当前路径登录成功后跳回原页面这个交互细节很提升体验。图片别名Vue CLI默认的指向src目录配置好了别瞎改不然一堆import报错。组件通信父子组件用props和$emit跨页面用Vuex全局事件如购物车变更可以用一个简单的$busnew Vue()来做但别在项目里到处乱用容易把数据流搞乱。3.4 鉴权与安全JWT登录态方案前后端分离项目不能靠Session维护登录态因为浏览器和服务器跨域了、而且前端可能是多端Web/H5/小程序。我用的是JWTJSON Web Token方案流程如下用户输入用户名密码后端校验通过签发一个token返回给前端。token由三部分组成Header加密算法、Payload用户ID、过期时间、Signature服务端密钥签名。密钥在服务端私藏用HS256算法签名客户端无法伪造。前端把token存到localStorage后续所有请求在Header里带上Authorization: token。后端写一个拦截器拦截除了登录、注册、商品列表、商品详情以外的所有接口校验token、解析出用户ID放入ThreadLocal用户上下文供Controller直接使用。管理端单独用一套AdminInterceptor做校验防止普通用户token访问管理接口。核心代码Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行OPTIONS预检请求 if (OPTIONS.equals(request.getMethod())) return true; String token request.getHeader(Authorization); if (token null || token.isEmpty()) { throw new BizException(401, 未登录); } try { Claims claims Jwts.parser().setSigningKey(secretKey) .parseClaimsJws(token).getBody(); // 用户ID存入ThreadLocal UserContext.set(claims.get(userId, Long.class)); return true; } catch (ExpiredJwtException e) { throw new BizException(401, 登录已过期); } catch (JwtException e) { throw new BizException(401, 非法token); } } }这个方案有几个需要特别注意的坑登录过期处理前端拦截器拿到401要跳登录页但Ajax请求要避免无限循环跳转我没用window.location.href直接跳而是用router.push并带上redirect参数。敏感信息不进PayloadJWT的Payload部分只是Base64编码不是加密别把密码、手机号明文塞进去放个用户ID就够了。注销问题JWT是无状态的服务端不能主动使它失效。所以我的方案是前端删除本地token配合短过期时间2小时。真要严格注销控制得引入Redis黑名单这个属于进阶扩展了。4. 完整部署教程从源码到上线4.1 本地开发环境搭建先把环境准备好我用的是目前稳定且兼容性最好的版本组合工具版本建议备注JDK1.8 或 11SpringBoot 2.x最稳的是1.8装3.x才需要17Maven3.6.3配置阿里云镜像加速依赖下载Node.js14.x 或 16.xVue2项目别装Node 18容易报OpenSSL错误MySQL5.7 或 8.08.0注意serverTimezoneAsia/ShanghaiIDEIDEA 2023后端强烈建议IDEA前端用VS Code即可环境搭建三个高频报错先给你预防针Maven依赖下载慢或者失败在~/.m2/settings.xml配阿里云镜像别用默认中央仓库。Node版本太高导致的OpenSSL问题Vue2老项目的webpack版本和Node18不兼容启动报error:0308010C:digital envelope routines::unsupported三个解法任选换Node16、或者用NODE_OPTIONS--openssl-legacy-provider环境变量硬撑。MySQL8认证插件问题JDBC连接报Unable to load authentication plugin caching_sha2_password把连接驱动换成mysql-connector-java最新版即可。数据库初始化我直接给你完整流程# 1. 登录MySQL mysql -u root -p # 2. 创建数据库utf8mb4比utf8更好兼容emoji和生僻字 CREATE DATABASE furniture_mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 3. 导入SQL脚本 use furniture_mall; source /path/to/furniture_mall.sql; # 4. 查看导入是否成功 SHOW TABLES;后端配置文件里的数据库连接和密钥照着这样的格式改spring: datasource: url: jdbc:mysql://localhost:3306/furniture_mall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver jwt: secret: 自定义一个至少32位的密钥字符串 expire: 7200000 # 过期时间毫秒数2小时前端环境搭建两条命令搞定npm install npm run serve如果npm install卡在某个包上先清缓存再换源npm config set registry https://registry.npmmirror.com rm -rf node_modules package-lock.json npm install4.2 后端打包jar包运行与Tomcat war包两种方案后端打包有两条路线我逐一说明适用场景。方案AJar包直接运行推荐SpringBoot内置Tomcat你最终交付的就是一个可执行的jar包服务器上只要装了JDK就能跑。步骤# 在项目根目录执行 mvn clean package -DskipTests # 得到 target/furniture-mall-0.0.1-SNAPSHOT.jar java -jar target/furniture-mall-0.0.1-SNAPSHOT.jar生产环境用nohup启动避免SSH断开导致进程退出nohup java -jar furniture-mall.jar --spring.profiles.activeprod logs/app.log 21 echo $! app.pid方案BWar包部署到外部Tomcat有些学校或公司的运维环境只允许把应用放进Tomcat的webapps目录这时候需要改造修改pom.xmlpackagingwar/packaging。启动类继承SpringBootServletInitializer并重写configure方法SpringBootApplication public class MallApplication extends SpringBootServletInitializer { Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(MallApplication.class); } }打包后把war放到Tomcat的webapps目录启动Tomcat自动解压部署。两种方案怎么选jar好比便携式炒锅自带加热源随处可用war好比电炉台面上的锅必须依托于外部容器。我强烈建议你学会jar方式因为这才是SpringBoot生态的默认姿势war是历史兼容产物。4.3 前端构建与Nginx部署这套前端300行配置你直接照抄核心就两个作用托管静态文件 反向代理后端接口server { listen 80; server_name your-domain.com; # 改成你的域名或服务器IP # 前端静态资源 location / { root /usr/share/nginx/html/furniture-mall; index index.html; try_files $uri $uri/ /index.html; # Vue history模式必须加这句 } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:9090/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 上传图片的访问映射 location /images/ { alias /data/images/; # 图片存在服务器的这个目录下 } }这里有两个必须理解的配置点try_files $uri $uri/ /index.html;Vue Router开了history模式URL不带#号时前端路由在服务端其实不存在对应文件。用户访问/product/1直接刷新Nginx找不到这个文件就会404这行配置把所有找不到的文件路径都回退到index.html由前端路由接管。没有这行刷新必挂。location /api/代理到后端前端请求/api/product/listNginx把请求转发到127.0.0.1:9090/product/list注意proxy_pass末尾的/会替换掉匹配的/api/前缀。这样浏览器访问的是同一域名不存在跨域问题生产环境根本不需要CORS配置。构建命令和执行过程# 前端项目目录 npm run build # 产物在 dist 目录 ls dist/ # dist/index.html dist/static/... # 部署到Nginx目录 sudo cp -r dist/* /usr/share/nginx/html/furniture-mall/ # 重新加载Nginx sudo nginx -s reload部署完成后访问http://服务器IP/看到首页就说明前端起来了点一个商品加入购物车如果数据正常返回说明前后端链路通了。4.4 前后端联调与生产环境配置联调阶段经常出现本地好好的、上线就坏的情况排查顺序很重要。我先说一个最容易翻车的点环境配置分环境。你别把开发环境的数据库密码、JWT密钥写死在application.yml里传到服务器。正确做法是# application-dev.yml spring: datasource: url: jdbc:mysql://localhost:3306/furniture_mall_dev password: dev_password # application-prod.yml spring: datasource: url: jdbc:mysql://你的生产库地址:3306/furniture_mall password: prod_password启动时指定环境java -jar app.jar --spring.profiles.activeprod另一个生产环境必踩的坑是上传图片的路径。本地开发随便存到项目resources下就行但jar包运行后往jar内部写文件要么不生效要么重启丢失。正确做法是在配置里指定外部路径upload: path: /data/images然后写一个静态资源映射配置类把/images/**映射到磁盘目录Configuration public class WebConfig implements WebMvcConfigurer { Value(${upload.path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/images/**) .addResourceLocations(file: uploadPath /); } }这样商品图片传到/data/images/xxx.jpg前端访问/images/xxx.jpgNginx再把这个路径映射到磁盘中间没有任何jar包内部写文件的坑。5. 常见问题与排查技巧实录5.1 跨域问题前后端联调第一道坎现象前端localhost:8080访问后端localhost:9090接口控制台报错Access to XMLHttpRequest at ... from origin ... has been blocked by CORS policy。原因浏览器同源策略拦截。不过有一个容易误解的点——跨域是浏览器的行为不是服务器的行为后端接口其实正常返回数据了但浏览器不让JS读取。我项目里给SpringBoot加一个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配了*前端请求带token时浏览器会先发一次OPTIONS预检请求这里allowCredentials(true)和allowedOriginPatterns(*)同时使用在浏览器中会被判定为非法配置因为*太宽泛需要把*换成具体的前端地址http://localhost:8080才不报错。这是很多新手照着教程配好之后依然报跨域的原因。5.2 PageHelper分页插件的两大坑分页用MyBatis最流行的插件PageHelper使用本来很简单PageHelper.startPage(pageNum, pageSize); ListProduct list productMapper.selectProductList(query); PageInfoProduct pageInfo new PageInfo(list); // pageInfo.getList() 就是分页数据getTotal()是总数但有两个坑我踩过好几次坑一startPage和查询必须紧邻。startPage开启一个线程本地变量如果不马上执行查询这个变量不会自动清除可能导致下一次查询被意外分页。所以在PageHelper.startPage()和mapper查询之间绝对不能插入任何其他DB操作否则分页条件就串了。坑二多表JOIN时分页不准。如果SQL里有LEFT JOINPageHelper会自动在SQL外面包一层SELECT COUNT(0) FROM (原SQL) tmp_count来统计总数但商品表关联图片表时一对多会产生重复行导致总数偏大。解决方法是在select语句里用DISTINCT关键字或者干脆子查询先聚合再关联比如商品主查询里不直接JOIN product_image而是把图片URL做成子查询。5.3 MyBatis缓存导致的幽灵数据现象后台修改了商品价格前台刷新还是旧价格重启项目就好了跑一段时间又复发。原因这是MyBatis二级缓存造成的。在单个Mapper的XML里配置了cache/开启二级缓存后同一Mapper的查询结果会被缓存到内存。如果你在管理端直接执行update修改了商品数据而查询缓存的key没有同步失效就会返回脏数据。排查方式看MyBatis日志里开了Cache Hit Ratio缓存命中率统计命中率高且数据不对基本就是缓存问题。这个项目我的建议是学习阶段直接去掉cache/配置不需要做缓存优化。等到系统真的出现性能瓶颈再去考虑Redis做热点数据缓存那是一个完整独立的优化专题。MyBatis缓存是个看着简单实际上容易踩坑的东西没必要在小项目里用它。5.4 Vue Router history模式刷新404现象本地npm run serve一切正常点击页面跳转也没事但部署到Nginx后在/product/1这种二级路径刷新页面404。原因/product/1在Nginx里找不到对应的静态文件Nginx直接返回404。解决办法前面已经提到了location /块里加try_files $uri $uri/ /index.html;。我再补充一个注意点如果前端项目不是部署在根路径而是部署在子路径比如/mall/那Nginx配置、Vue的base配置、路由的createWebHistory(process.env.BASE_URL)要三者保持统一否则资源路径全部错乱。5.5 数据库连接与中文乱码排查先给个快速自查表症状大概率原因解决页面表单提交中文乱码数据库连接URL没有characterEncodingutf8在JDBC URL加参数数据库表中查询乱码建表时字符集不是utf8mb4ALTER TABLE xxx CONVERT TO CHARACTER SET utf8mb4JSON返回乱码SpringBoot没有配置Jackson编码确保spring.http.encoding.forcetrue控制台中文乱码IDEA控制台编码问题IDEA设置里File Encodings全改成UTF-8还有一个特别隐蔽的坑MySQL的sql_mode里如果有ONLY_FULL_GROUP_BY某些GROUP BY查询会报错比如商品列表里想按分类聚合统计数量但SELECT字段包含非聚合列。换个思路改成子查询或者合理修改sql_mode别硬刚。5.6 前端启动常遇的Node版本和依赖安装问题前端项目clone下来npm install步骤经常出幺蛾子安装卡在node-sassnode-sass是Vue2老项目的经典痛点它需要下载二进制文件在国内网络经常超时。我建议你把样式方案降级把less/sass都换成纯CSS或者使用dart-sass替代node-sass。报错Maximum call stack size exceeded这是Node版本过高导致的node-sass等老依赖与高版本Node不兼容。换Node 16即可。.env环境变量没生效Vue CLI的.env.development只在npm run serve时加载.env.production只在npm run build时加载别把开发变量写到production文件里。6. 个人实操心得与拓展建议6.1 从这个项目往后延伸的三种方向先把基础版本跑通、能改、能部署然后你可以根据自己的目标做三种方向的延伸。方向一面试突击型。把高并发相关基础补上Redis缓存商品热点数据、秒杀场景用Redis预扣库存、消息队列削峰。核心不一定要真的实现但至少要在简历和面试里能讲清楚商城的哪个模块存在热点问题、你的方案思路是什么。这个项目就是你的“弹药库”所有场景都能落地到一个真实模块上。方向二商业接单型。把它改造成一个可交付的多商户商城把商品模型扩展成“店铺商品”订单增加售后流程后台加权限管理不同管理员不同权限支付接入微信/支付宝。说实话不少外包商城的原始版本就是我描述的这种结构你在上面做加法减轻了从零开始的巨大工作量。方向三技术深造型。把单体拆成微服务SpringCloud Gateway做网关、Nacos做注册中心、用户/商品/订单三个独立服务、分布式事务用Seata、统一日志用ELK。这个项目源码足够清晰拆起来会比较顺因为表结构和业务边界本来就划分得干净。6.2 最后几条实在的建议根据我反复跑这个项目的实际体验整理几条可能会帮你少走弯路的建议先跑通再读代码。别一开始就纠结某一行代码为什么这么写。先把环境搭起来、项目跑起来、走一遍“注册→登录→加购物车→下单→后台发货”的完整流程你对系统的体感会完全不一样。带着问题读代码效率翻倍。改代码前先备份数据库。这个项目的SQL脚本虽然可以反复执行但如果你已经手动改了表结构重跑脚本可能报错。养成习惯动手前mysqldump导出一份备份出问题随时回滚。接口调试用Postman或Apifox。前端页面没调通之前先用工具测后端接口。比如验证登录接口返回的token能不能通过拦截器、商品搜索接口的排序参数是否生效。工具先把后端验证完再联调前端出问题时定位范围小很多。保留一个全量SQL初始化文件。每次修改表结构或者新加测试数据都同步更新到SQL初始化脚本里。这样不管是换电脑、换服务器、给同学部署一条命令还原整个环境不用靠“经验”去手动补数据。最后再分享一个小技巧项目里所有的配置文件都尽量用url和密码分离的写法application.yml里不要写死具体环境变量统一通过--spring.profiles.activexxx和Value注入。这样代码在哪个环境部署都不用改源码只需要改启动参数和环境变量前后端分离项目的部署灵活性就是这么体现出来的。

相关推荐

OV5640数据手册深度解读:寄存器配置与硬件时序实战指南
OV5640数据手册深度解读:寄存器配置与硬件时序实战指南

1. 这份OV5640数据手册到底值不值得你花时间啃透?OV5640,这个在嵌入式视觉开发圈里被反复提起的名字,不是什么新锐网红芯片,而是实打实扛过十年以上工业与消费级项目考验的CMOS图像传感器老将。它出现在ESP32-S3开发板的摄像头模组… · 2026/9/26 6:58:42

黄浦区NETGEAR网件详细介绍 产品质量与实力怎么样
黄浦区NETGEAR网件详细介绍 产品质量与实力怎么样

当网络成为舞台的幕后主角深夜的会议室里,一场跨区域的视频会议正进行到关键节点,画面忽然卡顿,声音断断续续,屏幕那头的谈判对手表情耐人寻味;演出现场,灯光与音响的联动因为一次网络延迟乱了节拍,导演在耳… · 2026/9/26 6:58:42

硬件损伤下多跳无线传感器网络物理层安全路径选择仿真
硬件损伤下多跳无线传感器网络物理层安全路径选择仿真

做多跳收集-传输无线传感器网络(WSNs)物理层安全仿真时,我经常会遇到这样的场景:数据源节点要把采集结果通过若干中继一跳一跳送到汇聚节点,旁边还有一个窃听者在虎视眈眈;更麻烦的是,每个节点的… · 2026/9/26 6:58:42

开源研究智能体OpenResearch实操指南:架构、选型与落地
开源研究智能体OpenResearch实操指南:架构、选型与落地

从零搭建一个属于你的 OpenResearch:开源研究智能体实操全记录先说结论:OpenResearch 不是那种只能跑 demo 的玩具项目,它是一整套把“人肉调研”变成“半自动研究流水线”的工程方案。我把它理解为一个面向研究场景的开源智能体框架&#xf… · 2026/9/26 7:24:38

Redis明明设置了过期时间,我的缓存怎么还没清除?
Redis明明设置了过期时间,我的缓存怎么还没清除?

上周排查一个线上广告投放系统的性能问题时,发现Redis内存占用居高不下——明明所有缓存Key都设置了24小时过期,但凌晨低峰期仍有60%的Key存活。你是不是也遇到过类似情况?今天我们就扒开Redis的过期策略,看看那些"你以为会过… · 2026/9/26 7:24:38

性工作社会语言解码·有罪论 → 日常论·拥有自身语言决定了自身制度
性工作社会语言解码·有罪论 → 日常论·拥有自身语言决定了自身制度

BSD Step 211 ★★★★ 性工作社会语言解码有罪论 → 日常论量级错觉显化拥有自身语言决定了自身制度L1 层:04_社会稳态治理学科 社会语言本体论治理范式解码 ​前承​:Step151(量级错觉公理认知学科第一公理) Step174&#xff0… · 2026/9/26 7:24:38

Spring Boot 3 + LangChain4j 构建企业级RAG应用实战
Spring Boot 3 + LangChain4j 构建企业级RAG应用实战

1. 这不是“又一个Spring Boot教程”,而是企业级AI应用落地的实操切口我带过三支不同行业的AI工程团队,从金融风控到制造业知识管理,最常被问的问题不是“大模型怎么调参”,而是:“老板说下周要上线一个制度问答助手&a… · 2026/9/26 7:24:38

Isaac Gym高扭矩腿式机器人强化学习环境配置与训练避坑指南
Isaac Gym高扭矩腿式机器人强化学习环境配置与训练避坑指南

简介:基于Isaac Gym的HighTorque腿式机器人强化学习环境,面向机器人运动控制与深度强化学习研究者,解决高扭矩关节驱动腿式机器人在仿真环境中的训练与部署难题。资源包共包含63个文件,以Python脚本、STL三维模型和配置文件为核心… · 2026/9/26 7:24:32

小样本铁路轨道故障图像识别:从CNN到YOLOv5的分类实践
小样本铁路轨道故障图像识别:从CNN到YOLOv5的分类实践

简介:面向铁路轨道视觉检测场景的已标注图像分类数据集,围绕轨道表面损坏与未损坏的二分类任务构建,适合正在开展缺陷识别、智慧运维或图像分类相关课题的算法工程师、科研人员与学生使用。数据包含约380张轨道图像,标签分为损坏、… · 2026/9/26 7:24:32

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码