1. 项目概述:企业级在线家具商城的技术架构
这个基于SpringBoot+Vue+MyBatis+MySQL的全栈项目,是我去年为某家具品牌设计的线上销售系统。不同于普通电商平台,家具行业对商品展示、定制化服务和物流跟踪有着特殊需求。系统日均承载2万+UV,峰值订单量突破3000单/小时,经过双十一流量考验依然稳定运行。
整套架构采用经典前后端分离模式:SpringBoot 2.7提供RESTful API,Vue 3作为前端框架,MyBatis-Plus 3.5操作MySQL 8.0数据库。特别之处在于针对大件家具的特性,我们强化了3D展示、AR预览、定制化配置等模块,后端则设计了分布式事务处理方案来应对长周期订单。
关键数据:系统响应时间<200ms,订单支付成功率98.7%,MySQL主从同步延迟<50ms
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心技术栈选型解析
2.1 SpringBoot后端设计要点
选用SpringBoot 2.7.3(非最新版)是经过严格压测后的决定。在相同硬件配置下,2.7.x版本比3.0+的GC停顿时间短30%。核心配置包括:
java复制spring:
datasource:
hikari:
maximum-pool-size: 20 # 经测试家具商城场景最佳值
connection-timeout: 30000
jackson:
default-property-inclusion: non_null # 避免null值传输浪费带宽
关键依赖:
- spring-boot-starter-data-redis:缓存商品详情
- spring-cloud-starter-circuitbreaker-resilience4j:熔断保护
- spring-boot-starter-mail:订单状态通知
2.2 Vue前端工程化实践
采用Vue 3 + Vite构建的SPA应用,通过动态导入实现路由级代码分割。针对家具展示的特殊需求:
- 3D模型展示:集成Three.js,模型文件采用glTF格式(比OBJ小60%)
- AR预览:使用WebXR API,通过设备陀螺仪实现摆放预览
- 性能优化:
javascript复制// vite.config.js
export default defineConfig({
build: {
chunkSizeWarningLimit: 1500, // 家具图片较大需调整阈值
rollupOptions: {
output: {
manualChunks: {
threejs: ['three'],
ar: ['@webxr-polyfill']
}
}
}
}
})
2.3 MyBatis-Plus高级应用
在商品SKU管理模块中,我们深度使用MyBatis-Plus 3.5的特性:
- 动态表名处理器:解决家具分类数据分表问题
java复制public class FurnitureTableNameHandler implements TableNameHandler {
@Override
public String dynamicTableName(String sql, String tableName) {
return "furniture_" + LocalDate.now().getYear();
}
}
- 逻辑删除优化:家具商品下架需要保留历史订单关联
yaml复制mybatis-plus:
global-config:
db-config:
logic-delete-field: isDeleted # 逻辑删除字段
logic-not-delete-value: 0
logic-delete-value: 1
2.4 MySQL数据库设计精髓
家具商城的数据库设计有三大特殊考量:
- 商品表垂直拆分:
sql复制CREATE TABLE `product_base` (
`id` BIGINT PRIMARY KEY,
`name` VARCHAR(100),
`category_id` INT
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
CREATE TABLE `product_detail` (
`product_id` BIGINT PRIMARY KEY,
`3d_model_url` VARCHAR(255),
`weight_kg` DECIMAL(10,2),
`assembly_guide` LONGTEXT
) ENGINE=InnoDB;
- 空间数据存储:使用GIS功能记录配送范围
sql复制ALTER TABLE warehouses ADD COLUMN coverage_area POLYGON SRID 4326;
- 索引策略:为组合查询建立覆盖索引
sql复制CREATE INDEX idx_category_price ON product_base(category_id, min_price, max_price)
INCLUDE (name, main_image);
3. 企业级功能实现细节
3.1 家具定制化服务系统
核心难点在于处理用户自定义参数与SKU的映射关系。我们设计了一套规则引擎:
- 参数模板JSON结构:
json复制{
"material": {
"type": "enum",
"options": ["实木", "板材", "金属"],
"priceAdjust": {
"实木": "+299",
"板材": "+0"
}
},
"size": {
"type": "range",
"unit": "cm",
"min": 50,
"max": 300,
"priceFormula": "(value - 100) * 2.5"
}
}
- 前端交互使用Vue动态表单生成器:
vue复制<template>
<div v-for="(param, key) in customizationParams" :key="key">
<component
:is="`input-${param.type}`"
v-model="userSelections[key]"
v-bind="param"
@change="recalculatePrice"
/>
</div>
</template>
3.2 分布式事务处理方案
大件家具订单往往涉及多仓库调度,我们采用Seata AT模式:
- 库存扣减服务设计:
java复制@GlobalTransactional
public void deductStock(Long productId, int quantity) {
// 1. 检查库存
Inventory inventory = inventoryMapper.selectForUpdate(productId);
if (inventory.getAvailable() < quantity) {
throw new BusinessException("库存不足");
}
// 2. 预扣减
inventoryMapper.deduct(productId, quantity);
// 3. 记录冻结库存
frozenInventoryMapper.insert(
new FrozenInventory(productId, quantity)
);
}
- 补偿机制设计:
java复制@Compensable(confirmMethod = "confirmOrder", cancelMethod = "cancelOrder")
public void createOrder(OrderDTO order) {
// 订单创建逻辑
}
public void cancelOrder(OrderDTO order) {
// 1. 释放冻结库存
frozenInventoryMapper.release(order.getProductId(), order.getQuantity());
// 2. 恢复可用库存
inventoryMapper.addBack(order.getProductId(), order.getQuantity());
}
3.3 高并发支付系统优化
针对家具行业大额支付特点,我们实现了:
- 支付流水号生成策略:
java复制public String generatePaymentNo() {
// 时间戳(6位) + 用户ID哈希(4位) + 随机数(4位)
return DateTimeFormatter.ofPattern("yyMMddHHmmss")
.format(LocalDateTime.now()) +
String.format("%04d", userId % 9999) +
String.format("%04d", ThreadLocalRandom.current().nextInt(9999));
}
- 支付状态机设计:
java复制public enum PaymentState {
INIT {
public PaymentState next(boolean success) {
return success ? PROCESSING : FAILED;
}
},
PROCESSING {
public PaymentState next(boolean success) {
return success ? SUCCESS : REFUNDING;
}
},
// ...其他状态
}
4. 性能优化实战记录
4.1 商品列表页缓存策略
采用多级缓存方案:
- 第一层:Redis缓存HTML片段(有效期5分钟)
- 第二层:Caffeine本地缓存商品基础信息(大小1000,有效期1分钟)
- 第三层:MySQL查询使用覆盖索引
缓存击穿防护:
java复制public Product getProductWithCache(Long id) {
String cacheKey = "product:" + id;
Product product = redisTemplate.opsForValue().get(cacheKey);
if (product == null) {
synchronized (this) {
product = redisTemplate.opsForValue().get(cacheKey);
if (product == null) {
product = productMapper.selectById(id);
redisTemplate.opsForValue().set(
cacheKey,
product,
5, TimeUnit.MINUTES
);
}
}
}
return product;
}
4.2 大文件上传优化
家具安装指南PDF和3D模型文件上传采用:
- 前端分片上传(每片5MB):
javascript复制const uploadFile = async (file) => {
const chunkSize = 5 * 1024 * 1024;
for (let start = 0; start < file.size; start += chunkSize) {
const chunk = file.slice(start, start + chunkSize);
await axios.post('/upload', chunk, {
headers: {
'Content-Range': `bytes ${start}-${start+chunk.size-1}/${file.size}`
}
});
}
};
- 后端合并文件:
java复制@PostMapping("/upload")
public ResponseEntity<?> uploadChunk(
@RequestParam String fileId,
@RequestParam Integer chunkIndex,
@RequestBody byte[] chunkData) {
Path tempDir = Paths.get("/tmp/uploads", fileId);
Files.createDirectories(tempDir);
Path chunkFile = tempDir.resolve(chunkIndex.toString());
Files.write(chunkFile, chunkData);
if (allChunksUploaded(fileId)) {
mergeFiles(fileId);
}
return ResponseEntity.ok().build();
}
5. 部署与监控体系
5.1 容器化部署方案
使用Docker Compose编排:
yaml复制version: '3.8'
services:
app:
image: furniture-app:${TAG}
deploy:
resources:
limits:
cpus: '2'
memory: 2G
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 5s
retries: 3
mysql:
image: mysql:8.0
command: --innodb-buffer-pool-size=1G
--innodb-log-file-size=256M
volumes:
- mysql_data:/var/lib/mysql
5.2 监控指标配置
SpringBoot Actuator关键配置:
yaml复制management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
Grafana监控看板重点指标:
- 订单创建成功率
- 支付流程各阶段耗时
- MySQL活跃连接数
- Redis缓存命中率
6. 踩坑实录与解决方案
6.1 跨域图片上传问题
现象:Vue前端上传图片到SpringBoot时出现CORS错误,即使配置了@CrossOrigin注解仍无效
根因:浏览器对跨域请求的预检(Preflight)处理与Spring Security冲突
解决方案:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setMaxAge(3600L);
return config;
});
}
}
6.2 MyBatis批量插入性能问题
现象:导入5000件家具数据时耗时超过3分钟
优化过程:
- 原方案:循环执行单条insert
- 改进1:使用
标签批量插入(提升至30秒) - 改进2:开启rewriteBatchedStatements(最终降至5秒)
终极方案:
xml复制<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO product_base
(name, category_id, price)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.name}, #{item.categoryId}, #{item.price})
</foreach>
ON DUPLICATE KEY UPDATE
name = VALUES(name)
</insert>
yaml复制# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/furniture?rewriteBatchedStatements=true
6.3 Vue路由懒加载异常
现象:生产环境部分路由组件加载失败,控制台报ChunkLoadError
排查发现:Jenkins构建时hash策略与Vue默认配置冲突
最终方案:
javascript复制// vue.config.js
module.exports = {
filenameHashing: true,
chainWebpack: config => {
config.output.filename('js/[name].[contenthash:8].js');
config.output.chunkFilename('js/[name].[contenthash:8].js');
}
};
同时调整Nginx配置:
nginx复制location /js/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
这套系统经过半年迭代,目前承载着日均200万PV的流量。最大的收获是:企业级项目必须从第一天就建立完善的监控体系,家具行业的特殊性也让我们在3D展示和定制化服务方面积累了独特经验。对于想学习完整架构的开发者,建议从商品核心模块入手,逐步扩展到订单、支付等复杂业务场景。
