LikeShop 上门家政 Java 版师傅派单:区域匹配、订单推送与改派逻辑

简介: LikeShop家政系统Java版聚焦派单这一核心环节,通过区域分层匹配、多通道推送、超时自动改派及全程状态留痕,解决跨区派单、接单率低、改派混乱等痛点。架构清晰、逻辑严谨、可配置可审计,助力商家提升运营效率与用户体验

上门家政的核心运营环节是派单。和实物电商的“发货”不同,上门服务的派单涉及地理围栏、师傅运力、服务时段、改派兜底等一系列复杂逻辑。很多做家政系统的团队,一开始把派单做成“简单分配”,结果上线后问题百出:跨区域派单、师傅接不到订单、改派混乱、用户投诉。

LikeShop 上门家政 Java 版的师傅派单模块,是作为核心运营链路设计的:区域分层匹配、多通道订单推送、超时自动改派、全程状态留痕。这篇文章从架构、区域匹配、推送机制、改派逻辑四个层面拆解实现思路,附核心代码片段。

一、派单整体架构

派单不是单点操作,是一条完整的状态链路。上门家政场景下,派单分为两种模式:自动派单(系统默认,按规则匹配推送)和手动改派(运营人工介入,异常场景兜底)。

1.1 完整派单链路

用户下单支付
    ↓
地址解析 → 区域校验
    ↓
匹配可用师傅池(区域+类目+状态+负载)
    ↓
生成派单任务 → 多通道推送师傅
    ↓
┌─ 师傅接单 → 更新状态 → 上门服务 → 完成确认
│
└─ 拒单/超时未接 → 自动改派 → 重新匹配 → 推送下一位

1.2 订单派单状态机

public enum DispatchStatus {
    PENDING(10, "待派单"),
    PUSHED(20, "已推送"),
    ACCEPTED(30, "师傅已接单"),
    SERVICING(40, "服务中"),
    FINISHED(50, "已完成"),
    REDISPATCH(60, "改派中"),
    CANCELLED(70, "已取消");
}

整个派单流程围绕这个状态机流转,所有操作前置校验状态,不允许非法跳转。LikeShop 上门家政版的设计思路是:能自动的不人工,能规则化的不例外,把绝大多数常规订单交给系统自动处理,运营只处理异常场景。

二、区域匹配:派单精准的基础

派单准不准,第一步就在区域。家政服务的半径属性很强,跨街道、跨商圈派单,师傅通勤成本高、用户等待时间长,投诉率会直线上升。

2.1 服务区域的层级设计

采用行政区域 + 自定义电子围栏双层设计:

  • 行政区域层:省-市-区-街道四级,基础匹配用,适合标准化城市服务;
  • 电子围栏层:自定义多边形围栏(小区、商圈、产业园),精细匹配用,适合高密度区域。

师傅可以绑定多个服务区域,同时设置最远服务半径,双重校验避免超远派单。

2.2 核心数据库表

-- 服务区域表
CREATE TABLE ls_service_area (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  city_id BIGINT NOT NULL,
  area_name VARCHAR(64) NOT NULL,
  area_type TINYINT NOT NULL COMMENT '1行政区域 2电子围栏',
  boundary TEXT COMMENT '电子围栏多边形坐标JSON',
  center_lng DECIMAL(10,6),
  center_lat DECIMAL(10,6),
  status TINYINT NOT NULL DEFAULT 1,
  create_time DATETIME NOT NULL
);

-- 师傅服务区域绑定
CREATE TABLE ls_worker_area (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  worker_id BIGINT NOT NULL,
  area_id BIGINT NOT NULL,
  priority INT NOT NULL DEFAULT 0 COMMENT '区域优先级',
  create_time DATETIME NOT NULL,
  UNIQUE KEY uk_worker_area(worker_id, area_id)
);

-- 师傅服务状态
CREATE TABLE ls_worker_status (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  worker_id BIGINT NOT NULL UNIQUE,
  online_status TINYINT NOT NULL COMMENT '1在线 2忙碌 3离线',
  current_area_id BIGINT COMMENT '当前所在区域',
  today_order_count INT NOT NULL DEFAULT 0,
  last_active_time DATETIME,
  update_time DATETIME NOT NULL
);

2.3 区域匹配核心逻辑

匹配采用层级降级 + 多条件筛选:优先精细围栏匹配,没有则逐步降级到行政区域;同时校验师傅的在线状态、服务类目、当日负载。

@Service
public class WorkerMatchService {

    @Autowired
    private ServiceAreaMapper areaMapper;
    @Autowired
    private WorkerStatusMapper statusMapper;

    /**
     * 匹配可用师傅列表(按优先级排序)
     */
    public List<Long> matchWorkers(ServiceOrder order) {
        // 1. 解析订单地址,匹配所属区域
        Long areaId = matchArea(order.getAddress(), order.getLng(), order.getLat());
        if (areaId == null) {
            throw new BizException("该地址暂不提供服务");
        }

        // 2. 查询区域内所有绑定的师傅
        List<Long> workerIds = workerAreaMapper.getWorkerIdsByArea(areaId);
        if (CollectionUtils.isEmpty(workerIds)) {
            // 降级:向上级区域匹配
            areaId = getParentArea(areaId);
            workerIds = workerAreaMapper.getWorkerIdsByArea(areaId);
        }
        if (CollectionUtils.isEmpty(workerIds)) {
            throw new BizException("当前区域暂无可用师傅");
        }

        // 3. 多条件筛选:在线、服务类目匹配、当日负载
        List<WorkerStatus> availableWorkers = statusMapper.selectAvailable(
                workerIds, order.getServiceId());

        // 4. 排序:评分优先 > 今日单量少优先 > 距离优先
        availableWorkers.sort((a, b) -> {
            int scoreCompare = b.getScore().compareTo(a.getScore());
            if (scoreCompare != 0) return scoreCompare;
            int countCompare = a.getTodayOrderCount() - b.getTodayOrderCount();
            if (countCompare != 0) return countCompare;
            return getDistance(order, a).compareTo(getDistance(order, b));
        });

        return availableWorkers.stream()
                .map(WorkerStatus::getWorkerId)
                .collect(Collectors.toList());
    }

    private Long matchArea(String address, BigDecimal lng, BigDecimal lat) {
        // 优先电子围栏匹配(点在多边形内判断)
        List<ServiceArea> fences = areaMapper.selectFences();
        for (ServiceArea fence : fences) {
            if (GeoUtils.isPointInPolygon(lng, lat, fence.getBoundary())) {
                return fence.getId();
            }
        }
        // 兜底行政区域匹配
        return areaMapper.getByAddress(address);
    }
}

匹配结果是一个有序的师傅列表,派单时按顺序推送,保证最优质的师傅优先接到订单。

三、订单推送:多通道与状态回执

匹配到师傅池之后,就是推送环节。推送的核心要求是:不丢单、不重复、有回执、可超时。

3.1 两种推送策略

策略 逻辑 适用场景
顺序派单 按优先级逐个推送,一位超时未接则推下一位 订单充足、师傅多,保证优质师傅优先
群派模式 同时推送给多位师傅,先抢先得 订单稀缺、高峰期,提高接单率

默认采用顺序派单,兼顾公平和效率;高峰期可配置切换成群派。

3.2 多通道推送矩阵

不是只靠 APP 推送,要做分层通道,保证触达率:

  • APP 推送:师傅在线时优先,实时性最高,免费;
  • 短信通知:师傅离线时兜底,带接单链接;
  • 语音电话:高价值订单、紧急订单,强提醒。

3.3 推送核心实现

@Service
public class OrderDispatchService {

    @Autowired
    private WorkerMatchService matchService;
    @Autowired
    private DispatchTaskMapper taskMapper;
    @Autowired
    private PushService pushService;
    @Autowired
    private AlertService alertService;

    /**
     * 订单支付成功,触发自动派单
     */
    @EventListener
    @Transactional(rollbackFor = Exception.class)
    public void onOrderPaid(ServiceOrderPaidEvent event) {
        ServiceOrder order = event.getOrder();

        // 1. 匹配师傅列表
        List<Long> workerIds = matchService.matchWorkers(order);

        // 2. 生成派单任务
        DispatchTask task = new DispatchTask();
        task.setOrderId(order.getId());
        task.setWorkerIds(JSON.toJSONString(workerIds));
        task.setCurrentIndex(0);
        task.setStatus(DispatchStatus.PENDING.getCode());
        taskMapper.insert(task);

        // 3. 推送第一位师傅
        pushToWorker(task, workerIds.get(0));
    }

    private void pushToWorker(DispatchTask task, Long workerId) {
        task.setStatus(DispatchStatus.PUSHED.getCode());
        task.setCurrentWorkerId(workerId);
        task.setPushTime(new Date());
        taskMapper.updateById(task);

        // 多通道推送
        boolean pushOk = pushService.pushWorker(workerId, 
                PushType.NEW_ORDER, task.getOrderId());

        if (!pushOk) {
            // APP推送失败,短信兜底
            pushService.sendSms(workerId, PushType.NEW_ORDER, task.getOrderId());
        }

        // 注册超时监听:15分钟未接单自动改派
        DispatchTimeoutJob.register(task.getId(), 15, TimeUnit.MINUTES);
    }

    /**
     * 师傅接单回调
     */
    public void acceptOrder(Long taskId, Long workerId) {
        DispatchTask task = taskMapper.selectById(taskId);
        if (task.getStatus() != DispatchStatus.PUSHED.getCode()) {
            throw new BizException("订单状态不可接单");
        }
        if (!task.getCurrentWorkerId().equals(workerId)) {
            throw new BizException("非当前派单师傅");
        }

        task.setStatus(DispatchStatus.ACCEPTED.getCode());
        task.setAcceptTime(new Date());
        taskMapper.updateById(task);

        // 更新师傅状态、今日单量
        workerStatusService.incOrderCount(workerId);

        // 通知用户:师傅已接单
        notifyService.notifyUserOrderAccept(task.getOrderId(), workerId);
    }
}

3.4 推送回执与幂等

所有推送都要有回执,不能“发出去就当成功了”。师傅端收到推送后,要回传接收确认;接单操作要做幂等校验,避免重复点击导致状态错乱。

四、改派逻辑:异常场景的兜底

派单不可能 100% 一次成功,拒单、超时、师傅临时有事、用户改约,都会触发改派。改派是派单系统的兜底能力,也是最容易出乱子的环节。

4.1 改派触发场景

  • 师傅主动拒单:师傅原因无法服务,填写拒单原因;
  • 超时未接单:推送后 15 分钟未操作,系统自动改派;
  • 运营人工改派:用户改时间/改地址、特殊需求,运营介入调整;
  • 师傅报备休息:师傅临时下线,未完成订单自动改派。

4.2 改派核心流程

  1. 回收原任务:标记原派单任务为改派中,原师傅释放订单;
  2. 校验改派次数:单订单最多改派 2~3 次,避免无限循环;
  3. 重新匹配:排除原师傅,重新匹配师傅池;
  4. 二次推送:按顺序推送给下一位师傅;
  5. 通知用户:改派后同步通知用户新师傅信息,避免投诉。

4.3 改派核心代码

@Service
public class OrderRedispatchService {

    @Autowired
    private DispatchTaskMapper taskMapper;
    @Autowired
    private WorkerMatchService matchService;
    @Autowired
    private DispatchLogMapper logMapper;

    /**
     * 改派订单
     */
    @Transactional(rollbackFor = Exception.class)
    public void redispatch(Long taskId, String reason, Long operatorId) {
        DispatchTask task = taskMapper.selectById(taskId);

        // 1. 状态校验
        if (task.getStatus() != DispatchStatus.PUSHED
                && task.getStatus() != DispatchStatus.ACCEPTED) {
            throw new BizException("当前状态不可改派");
        }

        // 2. 校验改派次数
        if (task.getRedispatchCount() >= 2) {
            throw new BizException("订单改派次数已达上限,请人工处理");
        }

        // 3. 释放原师傅
        Long oldWorkerId = task.getCurrentWorkerId();
        workerStatusService.decOrderCount(oldWorkerId);

        // 4. 重新匹配师傅(排除原师傅)
        List<Long> workerIds = matchService.matchWorkers(
                getOrderByTask(taskId));
        workerIds.remove(oldWorkerId);
        if (CollectionUtils.isEmpty(workerIds)) {
            // 无可用师傅,转人工处理
            task.setStatus(DispatchStatus.PENDING);
            alertService.send("派单异常", "订单" + task.getOrderId() + " 改派无可用师傅");
            return;
        }

        // 5. 更新任务,推送给下一位
        task.setRedispatchCount(task.getRedispatchCount() + 1);
        task.setWorkerIds(JSON.toJSONString(workerIds));
        task.setCurrentIndex(0);
        taskMapper.updateById(task);

        // 6. 写改派日志
        DispatchLog log = new DispatchLog();
        log.setTaskId(taskId);
        log.setFromWorker(oldWorkerId);
        log.setToWorker(workerIds.get(0));
        log.setReason(reason);
        log.setOperatorType(operatorId == null ? 1 : 2);
        log.setOperatorId(operatorId);
        log.setCreateTime(new Date());
        logMapper.insert(log);

        // 7. 推送新师傅
        dispatchService.pushToWorker(task, workerIds.get(0));

        // 8. 通知用户
        notifyService.notifyUserRedispatch(task.getOrderId());
    }

    /**
     * 超时未接单自动改派
     */
    public void onTimeout(Long taskId) {
        DispatchTask task = taskMapper.selectById(taskId);
        if (task.getStatus() == DispatchStatus.PUSHED) {
            redispatch(taskId, "超时未接单", null);
        }
    }
}

每次改派都留痕,谁发起的、什么原因、从哪个师傅改到哪个师傅,全部可追溯。

五、常见坑与最佳实践

  1. 区域重叠导致重复派单
    坑:电子围栏和行政区域重叠,同一个师傅匹配到两次,重复推送。
    最佳实践:匹配结果按师傅 ID 去重,同一订单同一师傅只推一次。
  2. 师傅在线状态失真
    坑:师傅 APP 后台挂着,人其实不在,订单推送过去石沉大海。
    最佳实践:加心跳机制,5 分钟无动作自动置为离线,匹配时只选在线师傅。
  3. 推送无回执,重复派单
    坑:推送没收到回执,系统以为没发成功,重复推送,师傅收到好几次。
    最佳实践:推送有回执,接单操作幂等,按任务维度去重。
  4. 改派不通知用户
    坑:师傅换了用户不知道,上门了才发现换人,体验很差。
    最佳实践:改派成功立刻推送用户通知,附新师傅信息和联系电话。
  5. 无负载均衡
    坑:评分高的师傅单量爆炸,新师傅没单接,运力不均。
    最佳实践:排序加入当日单量因子,单量少的师傅优先级提升,均衡负载。

结语

上门家政的派单,看起来是个简单的分配功能,本质是地理、运力、时效的综合调度。区域准不准、推送快不快、改派顺不顺,直接影响师傅效率和用户体验。

LikeShop 上门家政 Java 版把这套逻辑做成了标准化模块,区域围栏可配置、推送策略可调、改派有兜底、全程留痕可审计,商家不用从零踩坑。对做上门服务的商家来说,派单这条链路跑顺了,整体运营效率和服务质量都能上一个台阶。

目录
相关文章
|
7天前
|
云安全 人工智能 运维
当攻击全面Agent化,你的安全防御能否跟上AI Speed?
多起客户侧AK泄露事件应急的真实观察
1309 6
|
5天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
|
10天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
1月前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9963 33
|
5天前
|
人工智能 开发者
Qoder CN 大使计划正式开启|无限创造,等你一起
全年免费积分、优先内测、活动支持与更多共创机会,邀请用 Qoder 把想法变成现实的你。
523 6
|
16天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
7天前
|
Web App开发 人工智能 JavaScript
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
REA(Reverse Engineer Anything)是近期爆火的开源AI逆向工具,4月发布,1个月内斩获15万+ Star。它让AI Agent无需源码即可分析二进制、APK、JS/Electron、HAR包等17类目标,自动反编译、生成可运行代码,并强制返回带证据与局限的结论。全本地执行,MIT协议,支持Hopper/Ghidra/IDA等引擎,一行命令即可上手。
747 2
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
|
15天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
1069 1