1. 为什么做服装定制系统:行业需求与项目定位
1.1 定制行业的现状与数字化缺口
服装定制这个赛道,这几年其实挺有意思的。传统裁缝店接了订单靠手工登记,量体数据记在本子上,款式改来改去全靠电话沟通,生产效率极低。稍微做起来一点的定制品牌,想要接线上订单、让客户自助选款、在线看效果,又发现市面上现成的系统要么贵得离谱,要么功能根本不符合定制场景——它们大多是为标准尺码服装设计的,哪有多规格面料、多定制项、多部位量体数据这些玩法。
我当时接手这个项目的时候,客户方的诉求很具体:他们要一个能把"量体数据管理、款式定制、报价、下单、生产排期"串起来的系统。这就不是一个普通的商城系统能搞定的。经过一番调研和需求梳理,我判断这个项目必须自己做,技术方案锁定在 java springboot 服装定制系统 vue 这个组合上。
1.2 这个系统到底要解决什么问题
很多开发者在拿到"服装定制系统"这种需求时,第一反应是"这不就是个电商系统加两个表"。实际上完全不是这么回事。我把核心痛点拆成了五个维度,这五个维度直接影响后面所有的技术决策:
- 量体数据复杂:一套定制西装涉及颈围、胸围、腰围、臀围、肩宽、袖长、衣长、裤长等几十个身体部位数据,且每个部位的数据精度要求不一样,有的需要精确到0.5cm。
- 款式规格多样化:同一件衬衫,客户可以选择领型、袖型、门襟样式、口袋款式、刺绣文字、面料编号,每个选项还可能互相约束(比如选择了某款面料后,部分领型不可用)。
- 报价规则动态:基础价格 + 面料差价 + 定制项差价 + 加急费用,每一档都需要灵活的规则配置,不是写死在代码里的。
- 订单流转链路长:定制订单不是付款就结束了,后面还有量体确认、生产排期、质检、发货、售后服务,每个环节都有状态变化。
- 部分定制与全定制的混合:有的客户只改袖长,属于"半定制";有的从面料到每一个细节都自定义,属于"全定制"。两者的后台处理流程完全不同。
搞清楚了这五件事,我才开始动手做架构设计。如果一上来就建表和写接口,后面百分之百要返工。
2. 技术选型:Spring Boot + Vue是如何敲定的
2.1 后端框架选择的真实考量
后端这块,我在Spring Boot、传统SSM、Python Django三者之间反复对比过。最终选Spring Boot 2.7.x,不是因为"大家都用它所以我也用",而是有三个实打实的理由。
第一,项目方后续要求系统能对接企业微信通知、对接物流API、对接ERP系统,这些领域成熟的SDK几乎都以Java生态为第一优先。Spring Boot在这个场景下的集成成本最低。第二,定制系统的业务规则复杂,涉及大量事务操作——下单扣库存、订单状态流转、支付回调处理,Spring的声明式事务管理用起来最顺手。第三,团队里现有的开发人员Java基础最扎实,招人也最容易。
版本上我特意选了2.7.x而不是3.x,这里有个细节值得说道说道。Spring Boot 3.x强制要求JDK 17以上,而且javax命名空间换成了jakarta,如果项目要部署在客户现有的老服务器上(可能只装了JDK 8),3.x版本就是灾难。选2.7.x配合JDK 8,兼容性是最稳的。
2.2 前端框架的权衡
前端选型其实比后端更纠结。项目现场的操作员普遍年龄偏大,之前用惯的是传统的多页面应用,突然换成前后端分离架构,很多人担心"页面会不会变慢""浏览器兼容性好不好"。
我最终选了Vue 3 + Element Plus。原因有这几个:Vue的模板语法对于后端转前端的开发人员来说学习曲线最平缓;Element Plus的表单组件、表格组件、弹窗组件正好覆盖管理后台80%以上的界面需求;更重要的是Vue对IE11的兼容性(通过polyfill)比React舒服,客户现场确实还有两三台老电脑装的是老版本浏览器。
还有一个私心:我当时评估过团队里前端能力最弱的那个人,他之前只会写jQuery,但看了一周Vue教程就能上手开发业务页面了。这在项目工期排得满满的情况下,是巨大的优势。
2.3 为什么不上微服务
我见过太多人一接项目就上微服务,搞得特别"高大上"。这个项目我坚持用单体应用 + 模块化拆分。
我做了个简单的估算:这个系统的核心业务接口大概60到80个,用户规模初期也就几百人,峰值并发能有50就已经很了不起了。这种量级上微服务,纯粹是给自己找麻烦——服务拆分、分布式事务、链路追踪、配置中心,每一块都在增加不必要的复杂度。当然了,为了后续扩展,我把代码结构按照模块划分得比较清晰,将来真有需要,项目模块之间可以比较平滑地拆成服务,这是后话。
3. 系统架构设计:从前端页面到数据存储的完整链路
3.1 前后端分离的整体架构
整个系统的请求链路是这样的:Vue前端通过Axios发起HTTP请求,经过Nginx反向代理,转发到Spring Boot后端服务;后端通过Spring Security + JWT做身份认证,Controller层接收请求,Service层处理业务逻辑,Mapper层通过MyBatis-Plus操作MySQL数据库;Redis用来存储验证码、临时购物车数据、热点商品的缓存信息;文件服务器存储量体照片、面料实拍图、设计稿等图片资源。
这套架构本身不稀奇,但有几个细节我在实际落地时做了针对性调整。
对于接口设计,所有返回结果统一封装成一个 Result 对象,包含 code、message、data 三个字段。这样前端拦截器可以统一处理错误状态码,不用每个接口单独写try-catch。对于JWT令牌,accessToken有效期设置为2小时,refreshToken设置为7天,避免频繁登录的困扰;用户每次请求时,后端用一个拦截器自动刷新令牌。这算是一个提升体验的小优化。
用表格说明核心的请求流程会比较清楚:
| 步骤 | 操作 | 技术组件 |
|---|---|---|
| 1 | 用户登录,提交账号密码 | Vue + Axios |
| 2 | 后端验证身份,签发JWT | Spring Security |
| 3 | 后续请求携带JWT访问接口 | Nginx转发 |
| 4 | 后端校验令牌,返回业务数据 | Spring Boot拦截器 |
| 5 | 敏感数据写入Redis缓存 | Redis + Spring Cache |
3.2 后端目录结构的规划
项目后端基础包名是 com.garment.custom,下面按模块分包,而不是按技术层次分包。这样做的好处是业务内聚性强,改动一个功能的时候不需要跨多个包跳来跳去。我的结构大致是:
com.garment.custom ├── common // 通用类:统一返回、异常处理、工具类 ├── config // 配置类:Security配置、Redis配置、MyBatis配置 ├── controller // 控制层 ├── service // 业务层接口 ├── service.impl // 业务层实现 ├── mapper // 数据访问层 ├── entity // 数据库实体 ├── dto // 数据传输对象 ├── vo // 视图对象 ├── utils // JWT工具、文件上传工具等 └── task // 定时任务:订单超时自动关闭、生产排期提醒实际上在开发过程中,我慢慢发现,按业务模块分包更合理。比如把 order、user、product、customize 这些业务模块作为顶层包,然后在每个模块内部再放 controller、service、mapper。这个项目做到中期,我进行了调整,之后新功能的开发效率明显提升了。
3.3 版本控制与团队协作
开发时用的是 Git + GitFlow 分支模型:master分支保持稳定版本,develop分支是日常开发的主战场,每个功能从develop拉出feature分支,开发完再合并回去。这个流程极大地减少了代码冲突。
不过我一个人在项目中踩过一个"分支管理"的坑:有次一个功能在feature分支改到一半,临时去修复线上bug,直接从feature分支切到了master分支,结果代码还没提交,现场的生产代码混进了开发中的文件。那天晚上光清理这个就花了两个小时。后来我给自己定了一个规矩:切换分支之前,必须先git stash或提交当前进度,绝不带着未提交的代码切分支。
4. 数据库设计:多规格多款式带来的建模挑战
4.1 核心业务表结构梳理
数据库是这个项目的重头戏。定制系统的数据模型比普通商城复杂得多,核心表我一共设计了15张,再加上关联表、日志表,接近30张。挑几张最有代表性的详细说一下。
用户表(sys_user):除了常规的账号、密码、手机号字段,我特意加了一个 user_type 字段,区分"客户"和"门店操作员"。定制系统里,经常出现客户在线上自助下单,但线下门店的人员需要代为录入的情况。加了这个字段,一个账号体系就能同时支撑两种角色。
款式表(product_style):这是定制系统的核心。跟普通商品的SPU概念类似,一张表记录了款式名称、基础价格、缩略图、适用性别、所属季节。但特别注意,这张表不存具体的规格参数,因为一件定制衬衫可能有接近十种可自定义的部位,规格组合是指数级增长的,不可能用传统SKU表去穷举。
量体数据表(measurement_data):每个客户的量体结果单独存一张表,关联用户ID。这张表的字段非常细:胸围、腰围、臀围、肩宽、袖长、衣长、裤长、颈围、臂围、腕围、大腿围、小腿围、前腰节高、后腰节高、背宽、胸宽,足足16个字段。我把数据精度统一设为 DECIMAL(5,1),避免出现一位小数和整数混存导致的问题。每个字段旁边还设置了单位字段,因为有些老裁缝习惯用"寸",系统输入时统一转成厘米存储。
订单表(order_info):订单主表,关联用户ID和款式ID。包含订单编号、订单金额、优惠金额、实付金额、订单状态、量体数据ID、加急标志、收货地址、备注等字段。
在订单表的设计上我踩了个小坑。最初我直接把所有的定制选项存成了一个TEXT长字段,用JSON格式存储,想着"反正前端传什么我存什么,查询时再解析"。后来发现,运营想要按"领型"或者"面料"维度做销售统计时,SQL根本没法直接在JSON字段上做分组查询。最后我单独拆了一张订单定制项表(order_customize_item),每条记录存一个定制项的名称和值,例如"领型:温莎领""面料编号:C0012"。这样既保留了查询能力,又避免了表字段爆炸。
我以表格展示一下几个关键表的关联关系:
| 表名 | 说明 | 关键外键/关联字段 |
|---|---|---|
| sys_user | 用户表 | id |
| product_style | 款式表 | id |
| product_customize_item | 款式定制项模板 | style_id → product_style.id |
| measurement_data | 量体数据表 | user_id → sys_user.id |
| order_info | 订单主表 | user_id, style_id, measure_id |
| order_customize_item | 订单定制项表 | order_id → order_info.id |
| order_status_log | 订单状态日志 | order_id → order_info.id |
4.2 款式定制项与SKU的取舍思路
普通电商系统习惯用SKU(库存量单位)来管理商品规格,比如"红色 / L码 / 棉质"对应一个SKU,库存扣减按SKU走。但定制系统的选项维度经常是十几二十个,如果再叠加面料、尺寸、刺绣文字,SKU数量会爆炸。
我的做法是用"模板 + 实例"的模式:product_customize_item 表定义"这个款式支持哪些定制项",例如:
- 领型(标准领、温莎领、小方领、戗驳领)
- 袖型(单扣、双扣、法式袖)
- 门襟(明门襟、暗门襟)
- 口袋(无袋、贴袋、双嵌线袋)
- 面料(纯棉C001、真丝S002、羊毛W003)
- 刺绣文字(最多20个字符)
实际下单时,用户在 order_customize_item 表里为每个定制项选择或填写具体的值。这样设计,新增一个定制项只需要在模板表里加一条配置记录,不需要改动任何Java代码和数据库结构,运营人员自己就能维护。
这种设计有个额外的优点:订单历史数据是快照式的。客户下单时选的"温莎领 / 纯棉C001",即便后来商品后台把"温莎领"改成了"温莎领(窄)"甚至删掉了这个选项,订单里存的还是当时的完整定义,不会跟着后台配置变动。
4.3 库存与面料的特殊处理
定制系统不搞"按尺码库存",但面料是有库存概念的。面料按卷采购,每卷有长度(米),系统里按"剩余可裁剪米数"来管理。
这带来一个问题:多个订单同时使用同一卷面料时,如何避免超卖?我用了MySQL的乐观锁来解决。面料表里加了一个 stock_version 字段,每次扣减库存时先查出来,更新时在 WHERE 条件里带上 stock_version = 查出来的旧值,如果更新影响行数为0,说明别人已经改过了,重新查询并再次尝试。同时在更新语句中设置条件stock_remaining >= 需要的米数,这样数据库层面就能拦住超卖,不用依赖分布式锁。
提示:这种用"版本号 + 条件更新"的方式,在订单量不大(每秒几十笔)的定制场景下完全够用。不要一听到并发就上Redis分布式锁,无谓增加复杂度。
5. 后端核心模块实现:从下单到生产排期的业务闭环
5.1 登录认证与权限控制
用户认证用的是 Spring Security + JWT。登录接口接收账号和密码,校验通过后生成JWT返回前端,前端存在 localStorage 里,每次请求通过拦截器放到请求头的 Authorization 字段里。
这里特别处理了两个细节:
第一是密码加密。存储密码使用 BCryptPasswordEncoder,这是Spring Security自带的加密器,每次加密时自动加盐,比MD5安全得多。哪怕数据库泄露了,密码也是不可逆的。
第二是多角色权限。系统里有三种角色:普通客户、门店操作员、管理员。虽然角色不多,但权限差异很明显:客户只能看自己的订单,操作员可以代客下单,管理员能看所有订单并且能修改订单状态。我在后端接口上使用注解@PreAuthorize("hasRole('ADMIN')")做细粒度控制,前端路由上也做了菜单拦截,双管齐下。
5.2 报价计算模块的关键实现
定制系统的报价计算是业务核心中的核心。它的规则是:基础价 + 每个定制项的加价 + 加急费用。我设计了一个报价引擎接口,用策略模式来应对不同款式类型的计价规则。
public interface PriceCalculator { BigDecimal calculate(QuoteRequest request); } @Component("shirtPriceCalculator") public class ShirtPriceCalculator implements PriceCalculator { @Override public BigDecimal calculate(QuoteRequest request) { // 基础价 BigDecimal total = request.getBasePrice(); // 遍历每个选中的定制项,累加差价 for (OrderCustomizeItem item : request.getCustomizeItems()) { total = total.add(calculateExtraPrice(item)); } // 加急费 if (request.getUrgent()) { total = total.add(new BigDecimal("50")); } return total; } }定制项的差价数据存在数据库里,运营人员可以在后台修改"温莎领加30元""法式袖加45元"。这样改价不用改代码,上线后运营有完全的自主权。
这里我要强调一个经验:报价计算一定要在后端做,绝不允许前端把计算好的金额传给后端保存。前端传来的金额只能作为展示参考,后端必须根据定制项重新计算一次真实价格。否则客户抓包改价格,几百块的定制款就能被改成一分钱。
5.3 订单状态流转的设计
定制订单的状态远比普通电商复杂,我设计了这样一条链路:
待支付 → 已支付(待量体) → 量体完成 → 待确认 → 生产排期 → 制作中 → 质检 → 已发货 → 已完成
另有三个终止态:已取消、已退款、已退货。
每个状态转换都有前置条件和后置动作。比如"待支付 → 已支付"要扣减面料库存、生成定制工单;"生产排期 → 制作中"要通知生产部门,并给客户发短信;"制作中 → 质检"要关联质检人员。
状态的记录和流转我用了状态机模式。下面是简化版的核心逻辑:
public class OrderStateMachine { // key: 当前状态 + 操作事件 private static final Map<String, OrderStatus> TRANSITIONS = new HashMap<>(); static { TRANSITIONS.put("PENDING_PAYMENT" + "_" + "PAY", OrderStatus.PAID); TRANSITIONS.put("PAID" + "_" + "SUBMIT_MEASURE", OrderStatus.MEASURED); TRANSITIONS.put("MEASURED" + "_" + "CONFIRM", OrderStatus.TO_BE_CONFIRMED); TRANSITIONS.put("TO_BE_CONFIRMED" + "_" + "SCHEDULE", OrderStatus.PRODUCTION); TRANSITIONS.put("PRODUCTION" + "_" + "FINISH", OrderStatus.QUALITY_CHECK); TRANSITIONS.put("QUALITY_CHECK" + "_" + "PASS", OrderStatus.SHIPPED); TRANSITIONS.put("SHIPPED" + "_" + "RECEIVE", OrderStatus.COMPLETED); } public static OrderStatus next(OrderStatus current, OrderEvent event) { OrderStatus next = TRANSITIONS.get(current.name() + "_" + event.name()); if (next == null) { throw new IllegalStateException("非法的状态转换: " + current + " -> " + event); } return next; } }这样做的好处是,状态转换逻辑集中在一处,避免了Service层到处写 if-else 导致状态混乱。每次状态变化时,同时往 order_status_log 表里插入一条日志,记录操作人、操作时间、变更前后的状态。客户端的订单详情页能完整展示"订单旅程",体验好了很多。
5.4 订单超时自动关闭
很多客户下单后不付款,如果不做处理,这些"幽灵订单"会一直占用系统资源,还会干扰库存统计。我用 Spring 自带的 @Scheduled 定时任务,每五分钟扫描一次超过30分钟未支付的订单,自动把状态置为已取消。
@Component public class OrderTimeoutTask { @Scheduled(cron = "0 */5 * * * ?") public void cancelExpiredOrders() { LocalDateTime deadline = LocalDateTime.now().minusMinutes(30); List<OrderInfo> orders = orderMapper.findPendingOrdersBefore(deadline); for (OrderInfo order : orders) { // 恢复面料库存、修改订单状态、记录日志 orderService.cancelOrder(order.getId(), "系统超时自动取消"); } } }这里有个细节:定时任务执行时要加上分布式锁,防止多个实例同时跑导致重复处理。虽然当前部署是单机,但代码层面提前留了个基于Redis的SETNX锁实现,后续集群部署时直接用。
6. Vue前端关键交互实现
6.1 定制选项的动态渲染
前端最核心的页面就是"定制下单页"。用户选择一个款式后,页面上显示的定制项完全由后端返回的模板配置决定,代码里不写死任何一个选项组。
接口返回的数据结构大致是:
{ "styleId": 101, "styleName": "经典商务定制衬衫", "basePrice": 399.00, "customizeItems": [ { "itemCode": "COLLAR_TYPE", "itemName": "领型", "type": "radio", "required": true, "options": [ { "label": "标准领", "value": "STANDARD", "extraPrice": 0 }, { "label": "温莎领", "value": "WINDSOR", "extraPrice": 30 }, { "label": "小方领", "value": "BUTTON_DOWN", "extraPrice": 20 } ] }, { "itemCode": "EMBROIDERY_TEXT", "itemName": "刺绣文字", "type": "input", "maxLength": 20, "extraPrice": 15 } ] }Vue组件渲染时,根据 type 字段动态选择渲染 radio 选项组、select 下拉、input 输入框还是 textarea。选完之后,实时调用后端报价接口,把当前选中的定制项传过去,拿到最新价格后展示在页面上。
一开始我把报价逻辑放在了前端,选了定制项后直接前端累加差价显示,然后提交时后端再算。后来发现"展示价"和"后端算出来的价"偶尔对不上,排查后原因是有些隐藏规则(比如满减、老客户折扣)只在后端生效。这之后我改成每次选项变化都用防抖(300ms)调用后端报价接口,虽然多了一些请求,但保证用户看到的价格一定准确。
6.2 购物车与下单流程的融合
购物车在定制系统里有点特殊。普通的电商购物车是"把商品加入购物车再统一结算",但定制商品在加入购物车时,定制项就已经确定了,相当于一个"定制方案"存到了购物车里。
前端用 Vuex 管理购物车状态,后端用 Redis 存储临时购物车数据,key 为cart:{userId},value 是定制项JSON数组。用户确认下单后,前端把购物车中的定制方案POST到后端,后端生成正式订单并清空Redis购物车数据。
有个交互细节需要注意:如果用户在购物车停留了很久,后台的定制项配置可能已经变了(比如某款面料下架了)。前端下单接口返回时要做校验,如果某个定制项已失效,必须提示用户"某某选项已下架,请重新选择",而不是直接下单失败。我在前端封装了一个专门的错误码处理函数来应对这种情况。
6.3 量体数据录入与校验
量体数据录入页面是给门店操作员用的。16个数值输入框,全部用数字输入控件,限制小数点后一位。这里我把校验做得很严格,因为一个数据录错了,做出来的衣服可能完全穿不了。
录入时要做两个层面校验:
- 基础范围校验:比如胸围最小50cm,最大200cm,超出范围直接禁止提交。
- 关联逻辑校验:比如腰围跟臀围的差值不能太离谱(差值过大说明录错了单位);袖长和衣长的比例关系也做了合理性检查。
前端校验之外,后端同样做了一套一致的校验逻辑。前端防的是用户误操作,后端防的是接口被绕过或者前端校验被篡改。前后端双重校验在这个场景不是过度设计,而是必需品。
6.4 订单跟踪页面的时间线组件
订单状态多,而且每个状态都有关键时间点,我直接用 Element Plus 的 Timeline 时间线组件来渲染。后端返回订单状态日志列表,前端按时间倒序排列,当前状态高亮,已经走过的流程打勾。
页面上还做了一个"预计完成时间"的展示:根据生产排期的流水线节奏,下单后大概14天内发货。这个数据不是写死的,是后端根据当前排期队列动态计算的。逻辑是:统计当前排了多少件同款式的定制单,乘上单件平均生产时长,再加上缓冲天数,就是预计完成时间。
这个功能其实不复杂,但客户方非常满意。因为它把"等待"变成了"可预期",客户黏性高了不少。
7. 部署上线与性能优化
7.1 生产环境的部署方案
生产环境部署用的是经典的"前后端分离 + Nginx反向代理"方案:
- 前端:npm run build 打包成 dist 目录,放到 Nginx 的 html/custom-frontend 目录。
- 后端:mvn package 打成 jar 包,放在 /opt/garment 目录,用
nohup java -jar garment.jar启动。 - Nginx 配置了两个 location:
/api/请求代理到后端的8080端口,其他请求指向前端的静态文件。
server { listen 80; server_name garment.example.com; # 前端静态资源 location / { root /opt/garment-frontend/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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }try_files $uri $uri/ /index.html这行特别重要,少了它,Vue Router 开启 history 模式后,用户刷新页面会出现404。我第一次部署时就忘了,结果用户从订单详情页按F5,直接白屏。这是前端路由部署的经典坑。
7.2 数据库连接池与参数调优
系统上线初期用的数据库连接池是默认配置,没过多久就出现了高峰期数据库连接不够用的问题。HikariCP 默认最大连接数是10,但这个系统的业务场景里有一个特点:订单状态流转涉及大量事务操作,每个事务占用连接的时间较长。
我把连接池参数调整成下面这样:
spring: datasource: hikari: maximum-pool-size: 30 minimum-idle: 10 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000具体参数可以根据服务器配置微调,但核心思路是:连接池大小不是越大越好,因为每个连接都有内存开销。在MySQL默认配置下,连接数超过50反而会因为线程切换开销导致性能下降。我这个项目部署在4核8G的服务器上,峰值并发约50,连接池设成30是合适的。
7.3 常见异常与处理经验
上线三个月,我记录了三个最有代表性的问题,每一个都值得分享。
第一个是接口超时导致前端重复提交。用户在下单接口上连点两次"提交订单",结果同一个定制方案生成了两笔订单。后来我在后端下单接口上加了幂等性校验,使用Redis的SETNX创建一个"下单令牌",前端进入下单页时先向后端申请一个 token,提交订单时必须携带 token,后端处理完一个单子就把 token 删掉。这样重复提交最多成功一次。
第二个是图片上传导致请求体过大。量体照片和面料实拍图动辄几MB,前端通过Base64编码传到后端,一个请求可能超过10MB。Spring Boot 默认的请求体大小限制是1MB,直接报错。我把配置调成了20MB,并且在前端做了图片压缩处理(用 canvas 把图片最长边限制在2000像素内再上传),实际效果还不错。
第三个是数据库连接泄漏。有一次排查慢接口,发现部分请求半天不返回,查看日志发现是数据库连接池被耗尽。定位到原因:某个事务方法里调用了外部短信接口的HTTP请求,而且没有设置连接超时时间,短信服务一旦不可用,事务就一直挂着不释放连接。修复方案是给 HTTP 客户端设置了3秒超时,同时把外部调用移出事务,改成事件异步触发。
7.4 一些小而有效的优化
最后说几个效果明显但改动很小的优化点:
- 首页热销款的列表接口:因为数据不常变,加了 Redis 缓存,缓存时间10分钟,数据库压力降低了一大截。
- 订单列表分页查询:每次只查20条,不查全量。但要注意 MySQL 的深分页问题,
LIMIT 100000, 20这种写法在数据量大时性能极差,我优化成了基于上次查询的最大ID做 cursor 分页。 - 静态资源CDN加速:Vue 打包后的 JS、CSS、图片放在 CDN 上,首屏加载时间从2.8秒降到了0.9秒。
- 日志分级:业务日志和错误日志分开输出,错误日志单独存一个文件,出了问题能第一时间定位。
注意:Cursor分页不适合列表频繁跳转页码的场景,比如用户想从第1页跳到第5页,就没办法用简单的方式实现。如果是后台管理系统的列表筛选,可以用传统的 pageNum/pageSize 方案配合复合索引来优化,产品体验和数据量需要权衡。
从需求梳理到上线,这个项目前后花了大约四个月。中间踩过的坑,不管是数据库设计时的反复推翻、前端动态渲染的性能优化,还是部署时那些让人抓狂的404和白屏,事后看都是宝贵的实战积累。特别是"定制选项模板化"和"订单状态机"这两个设计决策,直接决定了系统后续可扩展性的上限。
如果你也准备做类似的项目,我的建议是:先花时间把定制项的灵活性和订单状态的复杂度想清楚,再动手写代码。数据库建模阶段多花一周,后面能少加一个月的班。技术选型上,Spring Boot + Vue 这个组合在这个体量的业务系统里,确实是一个让人省心的选择。