做家具电商系统和做普通标准品电商完全是两码事这点我是在把第一版商品模块推倒重来之后才想明白的。如果你正准备接手或者启动一个SpringBootVueMyBatisMySQL架构的企业级在线家具商城项目这篇文章应该能帮你省掉不少我走过的弯路。我会从业务模型、表结构设计、订单闭环、前端工程组织一直聊到上线部署和实际踩过的坑把我自己落地这套管理系统源码时的完整思路拆给你看。这套系统本身解决的是家具零售行业里大件、低频、重服务这三个核心难题。通用电商平台擅长处理小件标品的快速交易但家具商品涉及多规格参数材质、尺寸、颜色、预约配送、安装服务、售后维保订单生命周期远比一件T恤复杂。如果直接套用通用电商模板开发到一半就会发现到处是坑——购物车参数对不上、库存减重混乱、订单状态难以跟踪物流和安装进度。所以这篇文章适合谁适合准备自己动手做商城类毕业设计或中小型商用系统的人也适合刚入行想搞懂企业级前后端分离项目是怎么组织的人。我会把系统拆成可落地的模块并给出具体表结构和核心流程的设计逻辑。1. 家具电商的企业级到底难在哪先想清楚业务边界再动手很多开发者的习惯是拿到需求直接建表写接口但在家具商城这个场景里业务边界的梳理比写代码重要得多。你没想清楚这个系统要为谁解决什么问题后面每一步都会被动。1.1 家具商品与传统电商SKU的核心差异家具商品有一个非常典型的特点一个沙发不是单品的概念它是一组规格参数的组合——三人位还是四人位、面料是布艺还是头层牛皮、颜色是深灰还是米白、是否需要贵妃位。不同参数组合对应不同的SKU价格、库存、物流体积完全不一样。我第一版系统直接照搬了普通电商的Spusku表结果发现一个问题家具的规格维度太多太杂三张表交叉产生的SKU数量会爆炸。一张2米的双人床光尺寸就有1.5米、1.8米、2.0米三个档位材质有实木、板材、皮艺三种选择颜色五六个组合下来就是几十个SKU。传统电商里一套规格模板通用的做法根本扛不住。所以在设计商品中心时我把规格拆成了两级销售属性影响SKU和价格比如尺寸、材质和描述属性不参与SKU组合只是商品详情展示时的参数比如环保等级、保修年限。这两者共用一张参数模板表但通过is_sale_attr字段区分。这样商品详情页能展示完整参数后台建品时又不会因为规格组合太多把SKU表撑爆。1.2 大件物流与安装服务对订单流程的约束家具是典型的大件商品物流不能走普通快递基本是干线物流同城配送预约上门。这意味着订单状态必须比普通电商多出几个节点——待确认配送时间、配送中预约制、安装完成。普通电商的发货→签收→完成三连在这个场景下是不够用的。订单表设计时我给订单状态字段预留了足够的扩展空间。下单、支付完成后订单并不像普通商品那样立即进入待发货而是进入待预约配送状态由客服或用户在后台预约送货时间之后才流转到配送中。整个状态机我放在后端用状态枚举加流转校验控制没用工作流引擎因为家具商城的订单流转环节虽然多但分支不复杂工作流引擎在这个体量下属于过度设计。1.3 系统模块划分六中心一后台最终我确定的企业级家具商城系统横向拆成六个中心每个中心负责一条完整的业务链路中心名称核心职责关键实体用户中心注册登录、收货地址、会员等级用户表、地址表商品中心分类、SPU/SKU、规格参数、上下架商品表、SKU表、参数表订单中心购物车、下单、支付回调、订单状态流转订单表、订单明细表、购物车表库存中心SKU库存、预占库存、出库入库库存表、库存流水表营销中心优惠券、满减、促销活动优惠券表、活动表售后中心退换货登记、维修工单售后表、工单表后台管理系统则单独拆出来给运营、客服、仓库三个角色使用运营管商品和营销客服管订单和预约配送仓库管库存和出库。前后端分离开发时前端按角色拆路由模块后面第五章详述后端用Spring Security做接口级权限控制。2. 技术选型复盘SpringBootVueMyBatis为什么到今天依然稳妥不是所有项目都需要微服务。家具商城这种业务体量单体应用配好缓存和索引完全能扛住中型商城的压力。选这套技术栈的核心理由是每一层都恰好落在省心和可控的交点上。2.1 后端三层选型的务实考量SpringBoot作为基础框架没有悬念。自动装配省掉大量XML配置内嵌Tomcat让打包部署变成一个jar包的事。企业级项目真正要花心思的不是框架选择而是项目分包和事务边界——这个我放在第四章展开讲。MyBatis和MyBatis-Plus的选择上我见过很多团队纠结。我的建议是直接用MyBatis-Plus但不要滥用。MyBatis-Plus提供了BaseMapper的通用CRUD单表操作不用写SQL效率很高复杂的多表关联查询和统计报表仍然手写XML里的SQL。这样既减少样板代码又能在性能敏感的查询上保持精准控制。一个典型的例子是商品列表页的按品牌材质价格区间联合筛选这种动态SQL手写在XML里用where标签组装条件比在业务代码里拼接字符串干净得多。Spring Security做登录认证和权限控制。家具商城后台分运营、客服、仓库三个角色接口的访问控制如果不用统一的安全框架散落在各个Controller里后期维护就是灾难。Spring Security的过滤器链机制天然适合做这种基于URL的权限过滤配合JWT做无状态登录前端在Token里带角色信息后端在拦截器里校验即可。2.2 前端Vue框架选型与配套方案Vue在前端框架里胜在上手曲线平缓、生态成熟。选用Vue 3配合Element Plus原因有二一是组合式API在处理复杂页面状态时更利于代码组织二是Element Plus的表格、表单、弹窗组件对后台管理系统的开发效率提升非常明显。电商前台和后台我用了两个工程但共享同一套API请求封装。前台面向消费者重视商品展示和下单流程的流畅度后台面向运营人员重视表格批量操作和数据可视化。拆开部署的好处是两者可以独立迭代、独立扩容前台流量再大也不会拖垮后台的运维操作体验。前端路由按角色做了动态加载。登录成功后后端返回该用户可访问的路由表前端用router.addRoute动态注册。这样做的实际价值在于客服账号在浏览器地址栏手动输入/admin/goods也只会被重定向到404因为路由根本不存在于他/她的Vue Router实例中。2.3 这套组合的边界什么时候该换这套技术栈不是万能药我明确说一下它的边界。如果业务量增长到需要多团队并行开发、需要独立部署订单服务那单体会成为瓶颈需要引入SpringCloud Alibaba那套微服务体系。如果SQL性能调优的需求变得极其频繁可以考虑将MyBatis替换为JOOQ后者在编译期就能检查SQL语法错误。但对于大多数企业级商城和毕业设计级别的项目SpringBootVueMyBatis是性价比极高的组合——开发速度快、排查问题顺手、招人容易。3. 数据库建模家具商品的SKU、库存与订单表设计细节数据库是企业级商城的底座表结构设计得好不好直接决定了系统能跑多远。我以MySQL 8.0为例把最核心的商品、库存、订单三块表设计思路完整过一遍。3.1 商品与SKU表分离销售属性与描述属性商品表t_goods保存的是SPU级别的公共信息商品名称、主图、详情描述、所属分类、品牌ID、上架状态。SKU表t_goods_sku保存具体可售卖单元规格值JSON串、销售价、划线价、库存ID、SKU编码、上下架状态。这里有个关键设计规格值用JSON字段存储。比如一个沙发的SKU规格值是{seatType:三人位, fabric:头层牛皮, color:深灰色}。很多人纠结要不要把每个规格维度单独建列我的答案是坚决不要——家具的规格维度既多又不固定单独建列意味着每加一种规格就要改表结构这在生产环境里代价极高。JSON字段配合MySQL的JSON_CONTAINS函数可以实现简单的规格筛选查询完全够用。SKU编码的生成规则我用了商品ID规格值hash的组合方式。这样同一个商品下即使有几十个SKU编码也能保持唯一且可读仓库发货时拿着编码就能快速定位商品信息。3.2 库存表与预占库存机制库存设计是家具商城最容易出事故的地方。我采用的是物理库存、预占库存、可用库存三字段模型CREATE TABLE t_sku_stock ( id BIGINT PRIMARY KEY AUTO_INCREMENT, sku_id BIGINT NOT NULL COMMENT SKU ID, physical_stock INT NOT NULL COMMENT 物理库存, locked_stock INT NOT NULL DEFAULT 0 COMMENT 预占库存, available_stock INT GENERATED ALWAYS AS (physical_stock - locked_stock) STORED COMMENT 可用库存, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, UNIQUE KEY uk_sku_id (sku_id) ) COMMENT SKU库存表;下单时扣减预占库存支付成功时将预占转为出库超时未支付则释放预占。这样设计的好处是用户下完单未支付的那段时间库存对其他人是不可见的能保证库存不超卖。扣减库存的SQL用乐观锁版本号控制并发UPDATE t_sku_stock SET locked_stock locked_stock 1, version version 1 WHERE sku_id #{skuId} AND available_stock 1 AND version #{version};影响行数为0时说明库存不足或版本冲突重新查询重试。这套机制在高并发秒杀场景下可能不够极致但对于家具这类低频高客单价商品性能和可靠性完全足够。3.3 订单主表与明细表快照的重要性订单表t_order我放了这些核心字段订单号、用户ID、订单状态、商品总金额、运费、优惠金额、实付金额、收货人信息快照、配送方式、预约配送时间、支付时间、发货时间、完成时间。订单中包含的收货人信息和价格信息必须是下单时刻的快照不能去关联用户表和商品表实时查询——否则用户改了地址、商品改了价格历史订单的数据就对不上了。订单明细表t_order_item记录每个SKU的购买信息同样价格和商品名称用快照存储。这里特别强调商品名称、SKU规格描述、成交单价一个都不能在订单明细表里省略。售后退款时要按明细计算财务对账时要按明细核对仓储备货时要按明细分拣这是一张高频查询且被多方依赖的核心表索引设计上必须把order_id作为聚簇索引之外的第一优先索引。订单号生成我采用了日期用户ID后四位随机序列的组合格式比如20240608123456789012345678。这个格式既方便财务按日对账又能保证并发下的唯一性。3.4 索引设计查询场景驱动不是全表加索引索引设计的原则是从查询语句出发。家具商城的高频查询场景主要就这几类商品列表页category_id is_on_sale created_time组合索引商品详情页sku_id唯一索引订单列表页user_id created_time组合索引后台订单管理order_status created_time组合索引我特别提醒一点不要在低区分度的字段上单独建索引。order_status字段只有五六个枚举值单独建索引时MySQL优化器大概率会放弃索引而走全表扫描因为回表成本比全扫还高。真正的高效做法是和其他筛选字段组成联合索引。4. 后端核心闭环购物车、下单、支付回调的完整流转后端服务的水平主要体现在订单闭环的严谨度上。购物车加车、下单、支付、退款这条链路每一步都有隐藏的细节我把我最终实现方案完整梳理一遍。4.1 购物车设计用户会话与DB的取舍购物车我用了RedisMySQL两级方案。用户在未登录状态下加购数据先存Redis以设备ID为key登录后合并入MySQL购物车表t_cart同步把Redis数据清掉。这样既保证了未登录用户的购物体验又能在换设备登录后恢复购物车数据。购物车表字段id, user_id, sku_id, quantity, checked_status, create_time, update_time加一个UNIQUE KEY uk_user_sku(user_id, sku_id)。同一SKU重复加购时走ON DUPLICATE KEY UPDATE把数量累加避免同一商品在购物车里出现多条记录这是购物车设计的常见坑。每次操作购物车时后端都实时校验SKU是否已下架、库存是否充足。下架商品在列表里置灰并提示已失效不直接清空让用户自己决定是否删除——直接清空会引发用户投诉。4.2 下单接口事务内的三步操作下单接口是整个系统事务控制的核心。我用了Transactional(rollbackFor Exception.class)包住三个关键操作生成订单主表记录和订单明细表记录订单状态设为待支付。扣减预占库存即执行第三章的乐观锁更新SQL。清空购物车中对应该订单的商品项。这里有一个极其重要的事务边界问题生成订单和扣减库存必须在一个事务里否则会出现订单创建成功但库存没扣到或者库存扣了但订单失败。Spring的声明式事务在方法内部自调用时容易失效this.method()调用不会被AOP代理拦截我实际开发中踩过这个坑解决办法是把下单逻辑拆到独立的Service类中通过注入代理对象调用。4.3 支付回调处理幂等与补偿支付回调是企业级商城绕不开的环节。我以模拟支付通道为例支付网关回调接口的设计必须满足两点验签和幂等。验签逻辑回调参数按字典序排列后拼接加上商户密钥做MD5签名后端用同样的算法重新计算签名比对不一致直接拒绝。防重逻辑先查订单表判断当前订单状态如果不是待支付状态说明回调已处理过直接返回成功响应不重复执行后续逻辑。Transactional public void handlePayCallback(String orderNo, String payNo, BigDecimal payAmount) { Order order orderMapper.selectByOrderNo(orderNo); if (!OrderStatus.WAIT_PAY.equals(order.getStatus())) { // 幂等处理订单已支付直接返回 return; } // 校验支付金额与订单金额一致 if (order.getPayAmount().compareTo(payAmount) ! 0) { throw new BizException(支付金额不一致); } // 更新订单状态为已支付记录支付流水号 order.setStatus(OrderStatus.PAID); order.setPayNo(payNo); order.setPayTime(LocalDateTime.now()); orderMapper.updateById(order); // 锁定库存转为实际出库 stockService.confirmOutput(orderNo); }这里我建议在更新订单状态时加入条件WHERE status 待支付实现数据库层的乐观锁控制防止并发回调下两个请求同时读到待支付状态、同时执行幂等分支而漏更新。4.4 超时未支付关单与库存释放用户下单后不支付订单不能永远挂在那里。我用定时任务扫描待支付超过30分钟的订单将订单状态改为已取消同时释放预占库存。这里存在一个典型的并发问题定时任务关单的同时用户刚好发起支付怎么办我的方案是状态机校验。关单操作的条件是UPDATE t_order SET status 已取消 WHERE order_no ? AND status 待支付支付操作也带同样的条件条件更新。两者的更新互相排斥只有一个能成功。被关单后用户再来支付后端会提示订单已超时关闭请重新下单同时把预占库存已经释放、用户支付失败这件事在日志里充分记录方便排查对账异常。5. 前端Vue工程商城前台与后台管理的模块化方案前端工程的组织方式直接影响多角色协作开发的效率。我把商城前台和运营后台分为两个Vue 3工程但公共部分做了统一封装。5.1 前端工程目录结构与路由设计前台工程的核心目录如下src/ ├── api/ # 按模块拆分的接口请求 │ ├── goods.js │ ├── order.js │ └── user.js ├── views/ # 页面级组件 ├── components/ # 公共业务组件商品卡片、价格展示、SKU选择器 ├── store/ # Pinia状态管理 ├── router/ └── utils/ # 请求封装、鉴权工具其中SKU选择器是家具商城前端的核心组件也是我花时间最多的部分。用户的交互过程是先看上图再选规格最后看价格和库存。SKU选择器的复杂之处在于不是所有规格组合都有货——某个颜色配某个尺寸可能没有库存这个规格按钮就必须置灰且不能被选中。实现上我用了可选规格矩阵算法后端返回当前SPU下的所有SKU及库存信息前端构建一个规格与SKU的映射表。每次用户选中一个规格项时遍历计算剩余规格项中哪些组合有可用库存有则高亮无则置灰。这个算法不复杂但交互细节直接影响商城的成交转化率值得反复打磨。5.2 后台管理系统表格驱动的角色权限页面后台管理端我统一采用了左侧菜单右侧表格页的布局。关键是根据角色动态渲染菜单运营看到商品管理、营销管理客服看到订单管理、售后管理仓库看到库存管理、出库管理。权限控制落到前端有两个动作一是路由注册时按角色过滤二是页面内的按钮用v-permission自定义指令控制。比如客服角色能看到订单详情页但审核商品按钮不会渲染出来。后端接口层面同样有权限校验前端控制只是体验优化真正的安全边界永远在后端。后台的订单管理列表页用Element Plus的el-table配合分页组件每一行提供查看详情修改地址预约配送时间标记发货等操作按钮。家具商城的订单操作链路比普通电商长我把高频操作做成了行内按钮而非下拉菜单减少了客服每天大量的点击次数——这种效率优化虽然不起眼但在企业级项目中价值非常高。5.3 首页性能优化首屏秒开的核心手段商城首页基本是流量入口性能优化必须到位。我重点做了三件事第一首页接口聚合。不要把轮播图、热销推荐、新品上架、品牌推荐拆成四五个接口串行请求而是做一个聚合接口一次返回。前端首屏只需要一个网络往返就能渲染完整页面。这在SpringBoot里实现非常简单Service层把多个查询结果包进一个Map或DTO返回即可。第二轮播图和推荐位数据做Redis缓存。这些数据变化频率极低每次让用户打到MySQL属于浪费。我用Spring Cache的Cacheable注解以home:banner为key缓存10分钟流量高峰期能挡住90%以上的重复查询。第三商品图片使用懒加载和WebP格式。家具商品图通常又大又多详情页加载十几张原图对移动端带宽压力很大。我用v-lazy指令做图片懒加载图片服务统一输出WebP格式肉眼几乎无差别体积能小一半以上。6. 前后端分离部署MySQL、Nginx、SpringBoot的生产级配置开发环境跑通只算完成了一半部署细节决定线上稳定性。我总结了生产部署的完整配置清单和几个容易忽略的坑。6.1 服务器基础环境配置我采用的部署架构是一台应用服务器部署SpringBoot jar包一台数据库服务器MySQL 8.0前端静态资源由Nginx托管并反向代理后端API。资源充足的情况下Nginx单独部署一台做负载均衡入口也是常见做法。MySQL生产配置里最关键的几个参数[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci innodb_buffer_pool_size 4G innodb_flush_log_at_trx_commit 1 max_connections 500 slow_query_log 1 long_query_time 1utf8mb4字符集是必须的因为用户昵称和收货地址里可能出现Emoji表情utf8存储会直接报错。innodb_buffer_pool_size设为物理内存的60%左右这个参数对InnoDB性能影响最大。innodb_flush_log_at_trx_commit 1保证每次事务提交都刷磁盘虽然性能有一点损耗但数据安全比性能重要——电商系统的订单数据丢一条都受不了。6.2 SpringBoot生产配置与JVM参数应用层面的application-prod.yml我重点配置这几块spring: datasource: url: jdbc:mysql://localhost:3306/furniture_mall?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai hikari: maximum-pool-size: 30 minimum-idle: 10 connection-timeout: 30000 redis: host: localhost port: 6379 timeout: 3000ms mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0Hikari连接池的maximum-pool-size不宜设置过大。MySQL默认连接数是151我见过有人把连接池调到100结果数据库直接被打死。连接池的大小应该基于应用并发量和数据库处理能力平衡30个连接配合合理的SQL性能完全够用。JVM启动参数我用了这样一组java -Xms2G -Xmx2G -XX:UseG1GC -XX:MaxGCPauseMillis100 -jar furniture-mall.jar --spring.profiles.activeprodXms和Xmx设成相同值避免运行期堆内存反复扩容收缩带来的性能抖动。G1垃圾回收器比传统的CMS更适合这类堆内存较大的应用能更好地控制停顿时间。6.3 Nginx配置静态资源缓存与API反向代理Nginx的核心配置点两个一个是对/api/路径的反向代理另一个是前端静态资源的缓存策略。server { listen 80; server_name your-domain.com; # 前端静态资源 root /var/www/furniture-mall/dist; index index.html; # 单页应用路由回退 location / { try_files $uri $uri/ /index.html; } # 静态资源长缓存文件名带hash location /assets/ { expires 30d; add_header Cache-Control public, immutable; } # API反向代理 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 $uri $uri/ /index.html这一行是Vue Router的history模式必须配置的。不配置的话用户直接访问/product/detail/123会得到404刷新页面就挂。location /assets/的缓存策略是建立在前端构建产物带内容hash的基础上——文件名变了才会重新拉取不存在缓存不更新的问题。6.4 线上日志排查上线第一天一定要做的三件事系统上线后日志排查效率决定了运维体验。我强烈建议上线前就做好三件事一是配置logback滚动日志按天滚动并保留30天日志文件按应用名-日期命名。二是对关键业务操作下单、支付回调、库存扣减、退款打印结构化日志包含订单号、用户ID、操作结果方便出问题时按订单号一条链路排查。三是开启SpringBoot的management.endpoints.web.exposure.includehealth,info,metrics用/actuator/health做健康检查探活接入告警系统CPU、内存、连接池指标异常时能第一时间收到通知。我实际排查过一个线上问题用户反馈下单后收不到支付回调依靠的就是订单号在整个日志链路中的检索从下单日志到支付网关请求日志10分钟内就定位到是网关回调超时重试机制的问题这个效率在没做结构化日志时是达不到的。7. 开发中踩过的坑与排查思路一次性给你说透最后这部分我把开发这套企业级家具商城管理系统时亲自踩过、也帮助别人解决的典型问题整理出来。每个都是真实场景包含完整排查链路而不是直接甩结论。7.1 MyBatis-Plus分页查询失效问题现象分页查询返回的总记录数正确但当前页只有10条且都是第一页的数据。根因MyBatis-Plus的分页插件没有正确注册。分页功能依赖PaginationInnerInterceptor这个拦截器漏掉它的话Page对象会被当成普通参数传给SQL查询结果不做分页处理。排查过程先看SQL日志发现执行的SQL没有LIMIT语句再看MyBatis-Plus配置类确认拦截器确实没加。补上配置后问题消失。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }同类坑多数据源配置下拦截器要加载到每个数据源的SqlSessionFactory里否则另一个数据源的分页也会失效。检查时别只看一个配置类。7.2 Vite生产构建后路由空白页现象本地开发一切正常npm run build后部署到Nginx访问首页白屏。根因Vite的构建配置中base选项默认是/如果静态资源部署在子路径下资源文件路径全错。另一个常见原因是路由模式问题——用了createWebHistory但Nginx没配置try_files回退。排查过程打开浏览器控制台看到JS资源404检查构建产物index.html里的script标签的src路径发现指向了根路径结合Nginx配置确认把base: ./写入vite.config.ts同时确保Nginx支持history模式回退。两个配置一起修复页面正常。7.3 事务中调用远程接口导致连接池耗尽现象系统运行一段时间后数据库连接池被占满SQL执行超时整个应用假死。根因在Transactional方法里调用了支付网关的远程接口且远程接口响应很慢。事务内连接一直持有不释放大量请求堆积后连接池耗尽。排查过程先看show processlist发现大量Sleep状态的连接再看日志确认这些连接的事务都卡在远程调用等待响应。修复方案是重构代码先本地完成事务提交再通过消息队列异步触发支付调用。这样连接在事务提交后立即释放远程调用的耗时不再占用数据库连接。这个坑是事务设计的经典反模式——事务内不要做RPC调用、不要做外部IO事务只做数据库操作。所有慢操作都放到事务外面或异步化处理。7.4 前后端联调时的跨域问题现象前端页面在http://localhost:5173访问http://localhost:8080/api接口浏览器报跨域错误。根因浏览器同源策略限制。前端开发服务器和生产域名不一致跨域请求默认被拦截。排查过程区分本地开发和线上环境分别处理。本地开发用Vite的proxy代理解决// vite.config.ts export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } });线上环境Nginx把/api路径反向代理到后端前端请求是相对路径不涉及跨域。我建议后端不需要额外配置CORS把跨域问题交给代理层解决减少后端的安全暴露面。7.5 商品搜索关键字被SQL注入的风险点现象安全测试时发现商品名搜索接口存在SQL注入风险。根因早期版本用${}直接拼接用户输入的关键字构造LIKE查询用户输入特殊字符可以拼接出恶意SQL。排查过程检查代码确认搜索SQL是动态拼接了用户关键字改用MyBatis的#{}参数占位符LIKE CONCAT(%, #{keyword}, %)方式构造模糊查询。这样用户输入永远被当作参数而非SQL语句执行从根上解决了注入风险。安全这块多说一句所有用户输入的地方不论前后端是否做了校验后端SQL一律使用参数绑定。安全是后端团队的底线不能依赖前端过滤。7.6 大数据量列表导出时的内存溢出现象后台订单导出功能导出几万条数据时后端报OutOfMemoryError。根因一次性把所有订单数据加载到内存再一次性写入Excel文件数据量大时堆内存扛不住。排查过程查看堆栈确认溢出点优化方案是分批查询流式写出。MyBatis-Plus提供StreamQuery或直接使用游标查询每批查5000条处理后写出处理完一批释放一批内存占用从几百MB降到了几十MB。这个优化思路对报表导出、批量通知等场景都适用。写在最后的一点体会这套家具商城系统的开发最让我有感触的一点是代码层面的难点反而是少的真正消耗精力的全在想清楚业务规则上。家具商品的规格到底怎么拆、订单状态每个节点由谁触发、预占库存什么时候释放、售后和退款怎么跟订单主流程联动——这些业务问题一旦想透写代码只是水到渠成的事。技术选型上SpringBootVueMyBatisMySQL这个组合可能不够新潮但它在稳定性、开发效率、社区资料丰富度上取得了很好的平衡尤其适合从单体起步的企业级系统。如果你按这篇文章的思路把业务边界梳理清楚、把表结构和订单闭环设计扎实把容易踩的坑提前避开我相信你也能交付一套跑得稳、改得动、对得上账的商城系统。最后再提一个建议项目验收或上线前一定记得把第三章的库存预占逻辑和第四章的支付幂等逻辑做成自动化测试用例这两个地方出问题都是线上事故级别的大问题。