1. 项目背景与技术选型
在线装修管理系统作为家居行业的数字化解决方案,需要同时满足企业高效管理和用户便捷体验的双重需求。这套基于Java SpringBoot+Vue3+MyBatis的技术栈组合,在当前企业级应用开发中具有典型代表性。
技术栈的合理性分析:
- SpringBoot 2.7.x:作为后端基础框架,其自动配置特性大幅减少了XML配置,内嵌Tomcat服务器简化部署流程。实测启动时间在3秒内(空载),适合需要快速迭代的装修业务场景
- Vue3 + Composition API:相比Vue2选项式API,组合式API更适合装修系统这种需要频繁复用业务逻辑(如报价计算、材料选择)的场景
- MyBatis-Plus 3.5.x:在传统MyBatis基础上增强,提供Lambda表达式查询,避免SQL字符串拼写错误。在装修材料多表关联查询时,其Wrapper条件构造器可减少30%的样板代码
- MySQL 8.0:选用InnoDB集群方案,支持JSON字段存储装修方案的非结构化数据,与GIS空间索引结合可实现基于地理位置的服务推荐
提示:实际开发中发现SpringBoot 2.7.x与Vue3的axios 1.x版本存在跨域兼容问题,建议锁定axios版本为0.27.2
2. 系统架构设计与核心模块
2.1 前后端分离架构实现
采用标准的RESTful API交互模式,通过JWT进行身份验证。特殊处理点在于装修行业的文件上传需求:
java复制// 文件上传接口示例(SpringBoot)
@PostMapping("/uploadDesign")
public Result uploadDesign(@RequestParam MultipartFile file,
@RequestHeader("Authorization") String token) {
// JWT解析用户ID
String userId = JwtUtil.parseToken(token).getSubject();
// 限制文件类型为CAD/图片
String[] allowedTypes = {"image/jpeg", "image/png", "application/dwg"};
if (!Arrays.asList(allowedTypes).contains(file.getContentType())) {
throw new BusinessException(ErrorCode.FILE_TYPE_ERROR);
}
return storageService.upload(file, userId);
}
对应的Vue3前端采用axios拦截器统一处理Token:
javascript复制// axios请求拦截
instance.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
}, error => {
return Promise.reject(error)
})
2.2 核心业务模块划分
-
用户中心模块
- 装修公司/设计师双角色权限体系
- 基于RBAC的动态菜单生成
- 短信验证码登录(阿里云SDK集成)
-
项目管理模块
- 甘特图展示装修进度
- 施工节点状态机设计
java复制public enum ProjectStatus { DESIGNING(1), MATERIAL_SELECTION(2), CONSTRUCTION(3), ACCEPTANCE(4); // 状态校验逻辑... } -
材料管理模块
- 建材SKU管理系统
- 三维模型预览(集成Three.js)
- 供应商比价功能
-
财务模块
- 装修分期付款计算
- 微信/支付宝双渠道支付
- 施工队工资结算
3. 数据库设计与性能优化
3.1 关键表结构设计
sql复制CREATE TABLE `design_plan` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT NOT NULL COMMENT '业主ID',
`designer_id` BIGINT COMMENT '设计师ID',
`style_type` ENUM('modern','chinese','european') NOT NULL,
`total_area` DECIMAL(10,2) UNSIGNED,
`budget` DECIMAL(12,2) UNSIGNED,
`status` TINYINT DEFAULT 0,
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME ON UPDATE CURRENT_TIMESTAMP,
FULLTEXT INDEX `idx_style` (`style_type`) WITH PARSER ngram
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
索引优化策略:
- 为高频查询的
project_status字段添加普通索引 - 材料表的
category_id建立联合索引(category_id, price) - 使用MySQL 8.0的窗口函数优化分页查询
3.2 事务处理难点
装修订单创建涉及多表操作,采用Spring声明式事务管理:
java复制@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderDTO dto) {
// 1. 创建订单主记录
orderMapper.insert(dto);
// 2. 扣减材料库存
materialMapper.batchUpdateStock(dto.getMaterials());
// 3. 生成支付流水
paymentService.createPayment(dto);
// 4. 分配设计师(可选)
if(dto.getDesignerId()!=null){
designerMapper.updateWorkload(dto.getDesignerId());
}
}
注意:MySQL默认RR隔离级别可能导致死锁,建议在application.yml中配置:
yaml复制spring: datasource: hikari: isolation-level: READ_COMMITTED
4. 典型业务场景实现
4.1 装修报价计算
前端采用Vue3的Composition API封装计算逻辑:
javascript复制// 报价计算Hook
export function useQuoteCalculator() {
const materials = ref([])
const laborCost = ref(0)
const total = computed(() => {
return materials.value.reduce((sum, item) => {
return sum + (item.price * item.quantity)
}, 0) + laborCost.value
})
function addMaterial(item) {
const exist = materials.value.find(m => m.id === item.id)
exist ? exist.quantity += item.quantity : materials.value.push(item)
}
return { materials, laborCost, total, addMaterial }
}
后端对应提供材料单价API:
java复制@GetMapping("/material/price")
public Result<List<MaterialPriceVO>> getMaterialPrices(
@RequestParam List<Long> ids) {
// 批量查询避免N+1问题
return Result.success(materialService.listPrices(ids));
}
4.2 施工进度同步
采用WebSocket实现实时进度更新:
java复制@ServerEndpoint("/progress/{projectId}")
@Component
public class ProgressWebSocket {
private static final Map<Long, Session> sessions = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session,
@PathParam("projectId") Long projectId) {
sessions.put(projectId, session);
}
public static void sendProgress(Long projectId, Integer percent) {
Session session = sessions.get(projectId);
if(session != null) {
session.getAsyncRemote().sendText(percent.toString());
}
}
}
前端通过Vue3自定义Hook管理连接:
javascript复制export function useProgressSocket(projectId) {
const progress = ref(0)
const socket = ref(null)
onMounted(() => {
socket.value = new WebSocket(`ws://yourdomain/progress/${projectId}`)
socket.value.onmessage = (e) => {
progress.value = parseInt(e.data)
}
})
onUnmounted(() => {
socket.value?.close()
})
return { progress }
}
5. 部署与运维实践
5.1 容器化部署方案
Docker Compose编排文件示例:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/decoration?useSSL=false
frontend:
build: ./frontend
ports:
- "80:80"
volumes:
- ./frontend/dist:/usr/share/nginx/html
5.2 性能监控配置
SpringBoot Actuator集成Prometheus:
yaml复制management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
metrics:
export:
prometheus:
enabled: true
对应的Grafana监控看板应关注:
- API平均响应时间(<500ms为优)
- MySQL连接池使用率(警戒线80%)
- JVM内存波动(Full GC频率)
6. 开发经验与避坑指南
-
MyBatis批量插入优化
- 错误做法:在循环中执行单条insert
- 正确方案:使用
<foreach>标签批量插入
xml复制<insert id="batchInsert"> INSERT INTO material_usage (project_id, material_id, quantity) VALUES <foreach collection="list" item="item" separator=","> (#{item.projectId}, #{item.materialId}, #{item.quantity}) </foreach> </insert>- 实测:插入1000条记录从12s降至0.8s
-
Vue3组件性能优化
- 使用
<KeepAlive>缓存材料选择组件 - 复杂表单采用
v-memo避免不必要的渲染
vue复制<div v-memo="[formData.styleType]"> <!-- 只在装修风格变化时重新渲染 --> </div> - 使用
-
SpringBoot多环境配置
- 通过
spring.profiles.active指定环境 - 建议的配置文件结构:
code复制application.yml # 公共配置 application-dev.yml # 开发环境 application-test.yml # 测试环境 application-prod.yml # 生产环境 - 通过
-
跨域解决方案对比
方案 适用场景 优缺点 @CrossOrigin注解 简单测试环境 配置简单但无法精细控制 WebMvcConfigurer 生产环境 可配置允许的HTTP方法/头部 Nginx反向代理 前后端分离部署 性能最好但需要运维配合
在实际装修项目管理系统开发中,当遇到材料分类树加载缓慢时,可采用以下优化方案:
java复制// 使用Redis缓存材料分类树
public List<MaterialCategory> getCategoryTree() {
String key = "material:category:tree";
String json = redisTemplate.opsForValue().get(key);
if(StringUtils.isNotBlank(json)) {
return JSON.parseArray(json, MaterialCategory.class);
}
List<MaterialCategory> tree = buildCategoryTree();
redisTemplate.opsForValue().set(key,
JSON.toJSONString(tree),
1, TimeUnit.HOURS);
return tree;
}
对于前端图片预览性能问题,建议:
- 使用
<img loading="lazy">实现懒加载 - 对施工图进行WebP格式转换(体积减少30%)
- 采用CDN分发静态资源
