最近两年同城货运市场增长迅猛,身边不少朋友都在咨询如何快速搭建一个轻量级的搬家货运平台。正好去年我主导开发过一个类似货拉拉的SAAS系统,今天就把其中可复用的核心模块抽离出来,做个技术拆解。
这个项目采用经典的SpringBoot+MyBatis技术栈,去掉了商业系统中复杂的计费规则和调度算法,保留了最核心的订单状态机、费用预估和接单逻辑。特别适合想要快速验证商业模式的小团队,或者需要毕业设计的同学参考。整个系统从零搭建到上线,我用周末时间实测大概需要8-10个小时。
提示:项目源码已经过脱敏处理,去掉了企业级项目中的权限校验和风控模块,建议在实际商用前补充相关功能
先看最核心的业务闭环(图1):
code复制用户下单 → 系统派单/司机抢单 → 开始服务 → 到达目的地 → 完成支付 → 双方评价
这个流程中有三个关键状态转换点:
采用标准的四层架构,但做了些实用化调整:
code复制表现层:RESTful API + 小程序端
业务层:SpringBoot + 自定义状态机
数据层:MyBatis + 多数据源(订单库单独拆分)
基础设施:Redis(缓存+分布式锁) + RabbitMQ(削峰填谷)
特别说明数据库设计:
状态流转是系统的核心,我们采用枚举+策略模式实现:
java复制// 状态枚举定义(带流转规则)
public enum OrderStatus {
WAIT_PAY(0, "待支付", next(WAIT_RECEIVE)),
WAIT_RECEIVE(1, "待接单", next(RECEIVED, CANCELLED)),
RECEIVED(2, "已接单", next(TRANSPORTING, CANCELLED)),
TRANSPORTING(3, "运输中", next(COMPLETED)),
COMPLETED(4, "已完成", terminal()),
CANCELLED(5, "已取消", terminal());
// 状态流转校验逻辑
public boolean canTransferTo(OrderStatus next) {
return allowedTransfers.contains(next);
}
}
在Service层实现状态变更的统一入口:
java复制@Transactional
public void changeStatus(Long orderId, OrderStatus newStatus) {
Order order = getOrder(orderId);
if (!order.getStatus().canTransferTo(newStatus)) {
throw new BizException("状态转换非法");
}
// 记录状态变更日志
logStatusChange(order, newStatus);
// 触发相关事件(如接单后通知用户)
eventPublisher.publish(new OrderStatusEvent(order, newStatus));
}
费用计算要考虑三个维度:
实现时采用策略模式+规则引擎:
java复制public interface FeeRule {
BigDecimal calculate(OrderContext context);
}
// 示例规则:夜间服务费
public class NightFeeRule implements FeeRule {
private static final Time NIGHT_START = Time.valueOf("21:00:00");
private static final Time NIGHT_END = Time.valueOf("06:00:00");
@Override
public BigDecimal calculate(OrderContext ctx) {
if (isNightTime(ctx.getOrderTime())) {
return new BigDecimal("15.00");
}
return BigDecimal.ZERO;
}
private boolean isNightTime(LocalDateTime time) {
// 时间判断逻辑...
}
}
接单环节有两个技术难点:
核心代码片段:
java复制public List<Order> findNearbyOrders(Driver driver, int radiusKm) {
// 1. 获取司机当前位置
Point position = driver.getCurrentPosition();
// 2. 查询半径内的订单
GeoRadiusCommandArgs args = new GeoRadiusCommandArgs()
.includeCoordinates()
.includeDistance()
.sortAscending();
return redisTemplate.opsForGeo()
.radius("orders:geo",
new Circle(position, new Distance(radiusKm, Metrics.KILOMETERS)),
args);
}
@Transactional
public boolean acceptOrder(Long driverId, Long orderId) {
// 加分布式锁防止重复接单
String lockKey = "order:accept:" + orderId;
try {
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (!locked) {
throw new BizException("订单正在被其他司机处理");
}
// 核心接单逻辑...
} finally {
redisTemplate.delete(lockKey);
}
}
采用多级缓存方案:
缓存键设计示例:
code复制用户信息:user:{userId}:info
司机位置:driver:{driverId}:location
待接单订单:orders:geo(GEO类型)
几个关键优化点:
sql复制-- 示例分表配置
CREATE TABLE orders_202307 (
id BIGINT PRIMARY KEY,
...其他字段...
) ENGINE=InnoDB PARTITION BY RANGE (MONTH(create_time)) (
PARTITION p7 VALUES LESS THAN (8),
PARTITION p8 VALUES LESS THAN (9)
);
在订单状态变更和司机余额变更时,最初直接用@Transactional导致数据不一致。后来改用最终一致性方案:
发现司机位置有时会异常跳跃(GPS信号问题),解决方案:
遇到0.01元差价引发的客诉,改进措施:
java复制// 正确的金额计算示例
BigDecimal fee = baseFee.add(extraFee)
.divide(step, 2, RoundingMode.HALF_UP)
.setScale(1, RoundingMode.HALF_UP);
如果想进一步提升系统,可以考虑:
接入高德地图API实现:
增加智能调度:
python复制# 简易版派单算法伪代码
def dispatch(order):
drivers = get_available_drivers(order.start_point)
scores = []
for driver in drivers:
score = 0.6 * distance_score + 0.3 * rating_score + 0.1 * response_score
scores.append((driver, score))
return max(scores, key=lambda x: x[1])
增加风控模块:
这个项目最让我有成就感的,是看到状态机设计在实际运行中完美处理了各种边界情况。建议大家在实现时,一定要先画好状态流转图,把每个转换的条件和副作用都明确标注出来。