1. 系统全局规划与模块边界
做这套系统之前,我先把之前做商城、做物流SaaS的旧经验全推翻了。市面上大多数包裹管理系统要么只盯着快递员端,要么只做用户端查询,真正能跑通“下单—分拣—装车—配送—签收—异常退回”全链路闭环的非常少。这次设计“springboot智能包裹配送服务管理系统”,我的核心思路是:把它当成一个真实的同城配送公司的基础设施来做,而不是做一个毕业设计式的CRUD后台。
整个系统我按业务域切成了六个核心微模块:用户与权限域、订单域、分拣域、运力域(司机/快递员)、配送域(路线与签收)、数据分析域。每个域内部独立演进,域与域之间通过清晰的接口交互,底层共用一套Spring Boot基础能力,比如统一鉴权、统一异常处理、统一日志埋点、分布式任务调度。
为什么要这样拆?我踩过最大的坑就是单体应用里所有业务糊在一起,看起来开发快,实际上后面每加一个功能都要在几百个Mapper里面找对应SQL,改一个字段要担心影响另外五个模块。拆域之后,至少有个好处:订单域调整价格计算逻辑时,配送域的签收状态机完全不受影响。
模块边界划分好之后,还要解决一个关键问题:数据模型怎么设计。我所有业务表的主键都用雪花ID,不用数据库自增主键,原因有两个:一是后续要做分库分表,自增主键在分布式场景下就是灾难;二是雪花ID自带时间戳信息,排查数据问题时能直接看出这条记录大概是什么时候创建的,非常方便。状态字段全部用tinyint,1、2、3这种数字枚举,不用字符串,查询性能和存储空间都更优。这是很多新手容易忽略的一点,字符串状态值虽然在代码里可读性好,但数据库索引和统计查询时会被字符串比较拖慢,数据量一上来差距非常明显。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与Spring Boot核心配置
技术栈这块我最终确定:Spring Boot 2.7.x + MyBatis-Plus + MySQL 8.0 + Redis + RabbitMQ + XXL-Job + Vue 3 + Element Plus。这里每个选型都有明确的理由,不是拿着招聘要求随便拼的。
首先Spring Boot版本这块要特别强调,建议直接用2.7.x,别一上来就上3.x。为什么?Spring Boot 3.0以后是基于Jakarta EE 9的,javax包全部换成jakarta,很多老版本的第三方库和新版本不兼容,比如一些工作流引擎、报表组件在3.x下跑不起来。而且Spring Boot 3.x最低要求JDK 17,如果公司生产环境还在JDK 8,那就只能老老实实用2.7.x。Spring Boot 2.7是2.x系列的最后一个大版本,官方维护时间也最长,是目前国内生产环境最主流的选择。这个选型决策直接影响后面所有依赖的版本,千万不能拍脑袋。
其次是MyBatis-Plus,我用了它的LambdaQueryWrapper做条件构造,代码里不会出现硬编码的数据库字段名,重构表结构时IDE能直接提示。分页用内置的分页插件,一行配置搞定。这里有个性能小坑要注意:MyBatis-Plus的逻辑删除是全局配置的,一旦开启,所有查询都会自动追加deleted=0条件,如果某张表压根没有这个字段,启动时会直接报错。所以配置逻辑删除时,要么全局统一所有表都带该字段,要么通过注解只对指定表生效。
Redis在这里承担的角色很多:用户Token存储、热点数据缓存(比如用户经常查询的订单状态)、分布式锁(防止重复下单)、以及配送轨迹的临时存储。Redis配置里我特别设置了lettuce.pool.max-active=50,这个参数如果太小,高峰期会出现连接不够用的情况;太大会浪费资源。50是一个相对中庸的值,可以根据实际QPS再调。
RabbitMQ用来处理异步消息,比如下单成功后发送通知、分拣完成后的运力调度。为什么不直接用线程池异步?因为MQ有重试机制和消息确认机制,消息丢了还能捞回来,线程池一重启就全没了。我用的是直连交换机(direct exchange),路由键和队列一一对应,逻辑非常清晰,不用像topic那样去配模糊匹配规则。
以下是核心配置文件的核心部分,我直接贴出来参考:
yaml复制spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/parcel_delivery?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&rewriteBatchedStatements=true
username: root
password: xxxxxx
hikari:
minimum-idle: 5
maximum-pool-size: 20
connection-timeout: 30000
redis:
host: 127.0.0.1
port: 6379
lettuce:
pool:
max-active: 50
max-idle: 10
min-idle: 2
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
publisher-confirm-type: correlated
publisher-returns: true
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
rewriteBatchedStatements=true这个参数非常关键,批量插入订单明细时,如果没有这个参数,JDBC驱动会把批量操作拆成单条执行,性能差好几倍。加了之后MySQL才会真正走批量写入路径。
顺便说一下banner的事情,网上有在线banner生成器,可以生成好看的启动图案,这个无伤大雅,开发阶段有个性化banner挺有意思的,但生产环境建议关掉,减少无意义的日志打印。在application.yml里加一行spring.main.banner-mode=off就行。
3. 业务模块详解与重点难点攻克
整个系统的业务链路是:用户在小程序/APP下单(寄件信息+收件信息+物品信息)→ 系统分配订单号并生成运单 → 包裹到达网点后进入分拣区 → 分拣员扫码把包裹分配到对应路区 → 配送员APP接收配送任务 → 按路线逐个签收 → 异常包裹进入异常处理流程。接下来我逐块说设计和实现过程中的难点。
3.1 订单模块:防重复下单与分布式锁
订单模块是入口,也是并发压力最大的地方。用户可能因为网络抖动或者手滑点了两次提交按钮,系统必须保证同一个用户在短时间内不能生成两笔相同订单。这里我用Redis分布式锁来解决:以order:lock:{userId}:{hash}作为锁的key,hash是对寄件信息、收件信息、物品信息拼接后做的MD5。请求进来先尝试获取锁,如果拿不到说明之前已经有相同订单在创建中,直接返回“请勿重复提交”。
锁的实现我用的RedisTemplate配合setIfAbsent方法,设置超时时间3秒,保证极端情况下锁能自动释放,避免死锁。释放锁的时候要注意,不能用先删后判断的方式,要使用Lua脚本保证原子性,否则可能删掉别人的锁。这也是面试中常说的“Redis分布式锁的坑”。
订单号生成我用的规则是:时间戳(到秒)+ 业务线编码 + 6位随机数,再加上前面的雪花ID主键,双保险。业务单号要方便客服在电话里口述,所以不能用太长太复杂的编码。
3.2 分拣模块:扫码绑定与批量操作
分拣是很容易被忽略但实际最耗人力的环节。系统里我实现了两种分拣模式:单件分拣和批量分拣。单件分拣就是扫描一个包裹条码,然后扫描目标格口号,系统将两者绑定;批量分拣适合一个路区有几十个包裹的情况,用扫码枪连续扫入,最后一次性提交。
这里有个技术细节:扫码枪本质上就是一个键盘输入设备,它扫出来的条码会模拟键盘逐个字符输入,所以前端Input框需要自动聚焦并且支持回车触发查询。我专门写了扫码输入组件,防抖300毫秒,避免多扫或者漏扫。组件在扫码结束后自动清空输入框并聚焦,保持下次扫码即扫即用。
分拣绑定表sorting_record的设计我放入核心字段:parcel_id(包裹ID)、route_id(路区ID)、grid_no(格口号)、operator_id(分拣员ID)、sorting_time(分拣时间)、status(状态)。这个表是后续配送路线规划的数据基础,分拣数据越准确,后面的配送任务就越合理。
3.3 配送模块:状态机设计与签收闭环
配送状态我设计成一个严格的状态机:待取件→已取件→运输中→派送中→已签收/异常退回。每个状态之间的流转都有合法性校验,不允许跳状态或者逆状态流转。比如一个包裹还在“运输中”,就绝对不能直接变成“已签收”,必须经过“派送中”这个中间态。
状态机的实现我参考了Spring StateMachine的思路,但没有引入完整框架,因为业务复杂度还没到需要状态机框架的地步。我用一个枚举类加上Map配置状态流转表,每个状态定义允许到达的下一个状态集合,代码清晰且容易测试:
java复制public enum ParcelStatus {
PENDING(0, "待取件"),
PICKED(1, "已取件"),
IN_TRANSIT(2, "运输中"),
DELIVERING(3, "派送中"),
SIGNED(4, "已签收"),
EXCEPTION(5, "异常退回");
public boolean canTransferTo(ParcelStatus target) {
return TRANSITION_MAP.get(this).contains(target);
}
}
签收环节支持两种方式:一种是配送员APP扫码签收,需要用户出示收件码;另一种是用户不在场时,配送员拍照留证后标记为“代收点签收”。这里面需要做比较严的权限控制,代收签收必须上传照片,否则接口直接拒绝,防止配送员乱签收导致客诉。
3.4 轨迹模块:经纬度采集与路径回放
配送轨迹是“智能”两个字的重要体现。配送员APP每隔15秒上报一次经纬度坐标,服务端将坐标追加到Redis的有序集合(ZSet)里,score就是时间戳。这样既方便按时间范围查询,又可以利用ZSet天然按时间排序的特性做轨迹回放。
每5分钟将Redis中的轨迹数据批量落库到delivery_track表,同时清理已落库的Redis数据,避免Redis内存持续膨胀。这里有一个问题要注意:订单完成后,轨迹数据还需要保留一段时间供客服查询,而MySQL单表数据量过大会导致查询变慢。所以我的方案是:轨迹表按月做分区,历史数据定期归档到冷存储,查询时就按分区裁剪,性能非常可观。
4. 权限体系与接口安全设计
整个系统涉及的角色有:用户、网点管理员、分拣员、配送员、系统超级管理员。权限模型我选用RBAC(基于角色的访问控制),不允许用户直接绑定权限,必须通过角色间接绑定,这样管理起来才灵活。
Spring Security + JWT + Redis实现无状态鉴权。登录成功后服务端生成JWT,并把Token存入Redis,设置过期时间(用户端2小时,配送员端12小时)。请求进来时由过滤器解析Token,并将用户信息放入ThreadLocal。这里有个关键点:ThreadLocal用完必须remove,否则Tomcat线程池复用时会串号,我之前就踩过这个坑,用户A的请求在他自己的线程里能拿到数据,但下线后线程归还给线程池,下个用户B复用了这个线程,ThreadLocal里还是有A的信息,导致越权访问。这个问题非常隐蔽,排查起来极其痛苦,所以写完过滤器一定要做多用户并发测试。
密码存储用BCrypt加密,千万不能用MD5,MD5被彩虹表破解太容易了。BCrypt每次加密结果都不一样,但matches方法可以验证,安全性远强于MD5。数据库里禁止明文密码,这是底线。
接口安全方面,除了JWT之外,还要做接口签名校验。特别是用户端下单和支付回调相关的接口,一定要做验签,防止数据被中间人篡改。签名算法用简单的HMAC-SHA256,前后端约定好AppSecret,将所有请求参数按字典序拼接,加上时间戳和nonce,计算HMAC值放在Header里。服务端拿到请求后先校验时间戳(超过5分钟直接拒绝),再校验nonce是否用过(防重放),最后验签。这套东西在公司内部叫“API安全三件套”,能挡住绝大多数脚本攻击。
这里额外提一下API Key的对接场景,如果是第三方系统要接入我们的配送服务,不可能让第三方拿用户的JWT来调接口。正确做法是:给第三方生成独立的API Key和Secret,通过/auth/apikey/token接口换取临时访问凭证。这个临时凭证有效期只有30分钟,失效后自动用API Key续期,最大程度降低泄露风险。
5. 数据一致性与事务处理策略
发红包、扣库存、订单创建这类强一致性的操作,事务必须走数据库本地事务。但配送系统里有很多操作是跨模块的,比如用户下单后要扣减优惠券还要生成运单,这时候如果只在一个事务里处理,性能会比较差。我的策略是:核心的、不能出错的操作用@Transactional,非核心操作通过MQ异步化,并用消息确认机制保证不丢消息。
这里必须说一个Spring事务最容易翻车的点:同一个类内部方法调用,@Transactional会失效。原因很简单:Spring事务是通过AOP代理实现的,内部调用不会经过代理对象。我习惯把所有事务方法写在Service实现类里,控制器只做参数校验和结果封装,不在控制器上写事务注解。另外还要注意事务中不要做耗时操作,比如调用外部接口、发短信、上传图片,这些应该放到事务提交后再执行,否则会长时间占用数据库连接,把连接池拖垮。
另一个容易踩的坑是事务的传播行为。配送员APP上报签收结果时,需要同时更新包裹状态和配送员当日统计。如果默认的REQUIRED传播级别下,一个异常会使整个事务回滚,配送员的统计也会被回滚。这往往不是我们想要的,配送员统计的失败不应该影响签收成功。我的做法是:签收主流程用REQUIRED,统计更新用REQUIRES_NEW独立事务,即使统计失败也不影响主流程。这种大小事务拆分的思想在复杂的业务系统里非常重要。
关于循环依赖,Spring Boot 2.6版本以后默认禁止循环依赖,启动时直接报错。以前老项目里经常有A依赖B、B依赖A的情况,用@Lazy注解或者@Autowired放到setter上能绕过去,但这治标不治本。我遇到循环依赖的第一反应永远是重构代码,把公共逻辑抽到第三方的Service里,让依赖方向变成单向的,而不是想办法绕过启动检查。长期维护的项目里,循环依赖就是一颗定时炸弹,早期强行绕过去,后面一旦改动就可能引入不可预知的问题。
6. 数据查询优化与MyBatis-Plus实践
数据量大概到几十万单的时候,如果不做任何优化,列表查询就会明显变慢。我的优化思路是分三层:
第一层是SQL级别优化。所有查询SQL必须走索引,订单表在user_id和create_time上建联合索引,包裹表在tracking_no上建唯一索引。这里要特别强调,不要在MySQL索引列上使用函数,比如WHERE DATE(create_time) = '2025-01-01',这种写法会导致索引失效,全表扫描。正确写法是WHERE create_time >= '2025-01-01 00:00:00' AND create_time < '2025-01-02 00:00:00'。
第二层是缓存优化。以用户查询订单列表为例,先从Redis缓存中查订单ID列表,缓存未命中才去数据库查,查完回填缓存,缓存过期时间设为10分钟。订单状态变更时主动删除该用户的订单缓存,保证数据一致性。
第三层是查询优化。列表查询永远不做全表字段查询,只查列表页需要的字段。MyBatis-Plus中直接指定select列名,避免SELECT *。另外分页查询千万别用LIMIT 100000, 20这种深分页写法,MySQL会先扫出前十万条再丢弃,性能极差。我改用“上一页最后一条记录的ID + LIMIT 20”方案,虽然实现起来多一点点逻辑,但大数据量下性能提升是数量级的。
6.1 动态数据源与读写分离
系统发展到一定阶段,单库读写压力都会上来。我的规划是引入读写分离,主库负责写入,从库负责查询。通过配置多个数据源和@DS注解实现读写切换,查询方法默认走从库,写方法强制走主库。
这里有个问题需要注意:主从同步是有延迟的,如果用户刚下单成功立刻查订单详情,数据可能还没同步到从库,给用户造成“订单消失”的错觉。解决方案是给查询加一个“强制主库”的标识:订单创建后30秒内的查询强制走主库,过期后走从库。这个方案虽然不优雅,但非常实用,而且能完美解决数据一致性体验问题。
7. 报表统计与数据分析应用
前面做了那么多业务功能,数据都沉淀下来了,接下来就是发挥价值的时候了。数据分析我主要做了三个方向:配送效率分析、包裹流量分析、异常预警。
配送效率分析的核心指标是“妥投率”和“平均配送时长”。妥投率 = 当日签收单量 / 当日应配送单量。平均配送时长 = 从取件到签收的总时长 / 签收单量。这两个指标按网点、按路区、按配送员多个维度拆解,能看到哪个路区效率低、哪个配送员需要培训,非常直观。
异常预警部分,我用XXL-Job写定时任务,每天凌晨扫描前一天所有签收时间超过承诺时限的订单,自动生成异常报告并推送给网点管理员。这里要注意,定时任务里如果循环分批处理数据,一定要控制每批的大小,否则一次处理几万条记录,会把服务器内存打爆。
报表数据因为计算量比较大,我没有让用户实时查数据库,而是每天早上由定时任务把前一天的汇总数据计算好,写入独立的report_daily_summary汇总表。查询报表时直接查汇总表,毫秒级返回。这也是后端性能优化的一个经典手段:用空间换时间,预计算好,避免实时聚合大表。
数据分析的接口我用ECharts在前端做可视化展示,Spring Boot后端只负责提供聚合好的JSON数据。这里分享一个经验:接口返回的JSON字段名要提前定义好,前后端共同维护一份接口文档,不然前端等人联调的时候一脸懵。我用的是Apifox来做接口管理和Mock数据,比Postman更适合团队协作。
8. 单元测试与环境部署经验
这个项目我写了不少单元测试,主要集中在核心的Service层和工具类上。为什么强调单元测试?因为配送系统的状态流转逻辑一旦出错,影响的是线上真实包裹,客诉会直接打到客服那边,代价非常大。凡是涉及金额计算、状态流转、权限校验的,我都要求必须有单元测试覆盖。
Spring Boot的单元测试其实很成熟,核心就是@SpringBootTest加上MockMvc测试接口。但要注意,单元测试的目标是快速反馈,不是真的连数据库跑全套。所以MySQL和Redis都要用内嵌的测试替代方案,比如H2数据库和MockRedis。编写测试用例时遵守一条原则:不依赖外部真实环境,测试用例之间独立,不共享数据。
java复制@SpringBootTest
@AutoConfigureMockMvc
class ParcelOrderServiceTest {
@Autowired
private MockMvc mockMvc;
@Test
void testCreateOrderWithDuplicateRequest() throws Exception {
// 模拟用户重复提交订单请求, 期望返回错误码
mockMvc.perform(post("/api/order/create")
.contentType(MediaType.APPLICATION_JSON)
.content("{...}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(50001));
}
}
部署方面,我使用的是Docker Desktop运行容器,把Spring Boot应用打成jar包后构建Docker镜像。需要注意JDK版本和Docker基础镜像的匹配问题,比如JDK 8打包的应用,基础镜像应该用openjdk:8-jdk-alpine,不能用openjdk:17-jdk-alpine,否则运行时可能抛UnsupportedClassVersionError。Dockerfile里我用多阶段构建,先用Maven镜像编译,再把构建产物拷贝到运行镜像,这样能大幅减小镜像体积。
code复制FROM maven:3.8.6-openjdk-8 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
FROM openjdk:8-jdk-alpine
WORKDIR /app
COPY --from=builder /app/target/parcel-delivery.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Xms512m", "-Xmx1024m", "-jar", "app.jar"]
这里插一句,很多人在IDEA里创建Spring Boot项目时选了太高的Spring Boot版本,后面整合MyBatis-Plus或者其他中间件就各种报错。Spring Boot版本不是越高越好,而是要和生态配套。如果选4.0,那很多老注解位置变了,比如AOP相关的自动配置找不到了,网上资料又少,排查起来特别痛苦。我自己的策略是:新技术版本至少发布半年以上、有足够社区案例,才考虑引入到生产项目,稳字当头。
9. 常用配置白名单与前端联调细节
项目开发阶段,前后端联调容易出问题的地方,大部分都集中在跨域和接口路径上。Spring Boot后端配置跨域有个很简洁的方案,实现WebMvcConfigurer接口的addCorsMappings方法,允许本地前端的地址跨域访问。
但生产环境我不建议开CORS,因为同源策略本身就是一道安全防线。生产环境我习惯用Nginx做反向代理,前端请求/api路径,Nginx转发到后端服务,前后端同源,既解决跨域问题又增加了Nginx这一层负载均衡和缓存能力。
前端Vue这边也要注意一个细节:打包后的前端静态资源部署在Nginx上,刷新页面时可能出现404。原因是Vue是单页应用,路由用的是history模式,浏览器把路由路径发给Nginx,Nginx找不到对应文件就直接返回404。解决办法是在Nginx里配置try_files $uri $uri/ /index.html;,让所有路径都回退到index.html,由前端路由接管。
资源映射的问题也值得一提。配送员上传的签收照片、运单凭证,我存在服务器磁盘上,通过一个/files/**路径映射到磁盘目录。Spring Boot里配置一下就行:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/files/**")
.addResourceLocations("file:/data/parcel-files/");
}
}
大文件上传下载这块,我用的是分片上传方案。前端把文件切成2MB的小块,逐个上传,后端每收到一片就先存临时目录,全部上传完成后前端调合并接口,后端把分片合并成完整文件。这样即使网络中途断开,也只需要重传失败的分片,不用整个文件重新传。下载大文件时,后端用InputStream流式输出到HttpServletResponse,避免把整个文件加载到内存里导致OOM。
10. 常见问题与排查技巧实录
整理一下这几个月做这个项目过程中遇到频率最高的问题,希望对后来者有用:
10.1 Spring Boot启动报错版本不对
这是新手必踩的坑。spring-boot-starter-parent版本和JDK版本不匹配,或者Spring Boot版本和Spring Cloud版本不匹配。检查方式很简单,先确认JDK版本:java -version,再看pom.xml里Spring Boot版本要求。一般来说Spring Boot 2.7.x要求JDK 8及以上,Spring Boot 3.x要求JDK 17及以上。另外还要检查有没有引入版本冲突的依赖,用mvn dependency:tree命令查看依赖树,找出重复或冲突的jar包。
10.2 application.yml不自动提示
IDEA里新建Spring Boot项目后,application.yml文件没有自动补全和提示,十有八九是没引入配置处理器依赖。在pom.xml里加上:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
然后重新加载Maven项目,再把IDEA的Settings → Editor → File Types里确认YAML类型关联.yml扩展名,提示就出来了。这个问题不影响功能,但严重影响开发效率。
10.3 事务失效问题
前面提过内部方法调用导致事务失效,还有一种情况是方法被final修饰,CGLIB代理无法重写final方法,也会导致事务失效。所以事务方法不要加final。另外@Transactional默认只回滚RuntimeException和Error,如果方法里抛出了受检异常(比如SQLException),默认是不会回滚的,需要指定rollbackFor = Exception.class。这是非常经典的事务失效场景,面试中也经常被问到。
10.4 MyBatis-Plus自动填充不生效
设置了create_time和update_time自动填充,但插入数据时就是不生效。原因是实体类里缺少@TableField(fill = FieldFill.INSERT)注解,同时还要写一个MetaObjectHandler的实现类并注册为Spring组件。两个条件缺一不可。这个坑很隐蔽,因为代码不会报错,只是默默插入空值。
10.5 内存溢出排查
系统运行一段时间后内存持续往上走,最后OOM。排查方法:用jstat -gcutil观察GC情况,用jmap -dump:format=b,file=heap.hprof导出堆转储文件,再用VisualVM或者MAT分析。常见原因是静态集合一直往里面塞数据,或者Redis存储的缓存对象没有设置过期时间。在写代码时就要有意识:凡是往集合里放数据的,都要想清楚什么时候移除;凡是写入Redis的,都要设置合理的过期时间。
11. 踩坑清单与配置参考
最后用表格整理一下开发过程中整理的核心参数和关键配置,方便各位直接对照使用:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| Spring Boot版本 | 2.7.x | 生产环境最稳定,JDK8兼容 |
| Redis连接池max-active | 50 | 连接数过高浪费资源,过低高峰期抛异常 |
| MySQL连接池maximum-pool-size | 20 | 配合HikariCP默认配置,够用 |
| 事务超时时间 | 默认-1即可 | 事务中不要做外部调用,否则要手动设置超时 |
| JWT过期时间 | 用户端2小时/配送员12小时 | 太短影响体验,太长有安全风险 |
| RabbitMQ消息重试次数 | 3次 | 超过后进入死信队列人工处理 |
| XXL-Job调度间隔 | 按业务需求 | 分拣统计每10分钟一次,日报每天凌晨2点 |
这里再单独说一个数据库连接池的问题。Spring Boot 2.x默认的连接池是HikariCP,性能非常好。但如果项目里同时引入了Druid,一定要确保只生效一个,否则数据源初始化会冲突。一般做法是排除Druid的自动配置类,或者直接不用Druid,HikariCP完全满足中小型项目需求,不必为了“监控功能”去额外引入一个组件。
在开发这套系统时,我最大的感受是:Spring Boot的生态已经非常成熟,真正拉开差距的不是框架本身,而是对业务的理解和系统的建模能力。技术选型、模块划分、事务策略、缓存设计、排查手段,这些才是让一个系统从“能跑”到“好用”的关键。从最初的项目搭建,到中间的各种报错排查,再到现在能够稳定运行、支撑每天上千单的配送业务,每一步踩坑都是实打实的经验积累。希望这篇文章能帮你少走一些弯路,尤其是那些在网上搜不到答案的细节问题。
