跨城改派功能升级 - 司机端接口文档

文档信息

  • 版本: v2.0
  • 更新日期: 2026-06-25
  • 适用端: 司机端(Android)
  • 基础URL: http://api.yourdomain.com

📋 需求概述

功能变更

跨城改派功能升级,司机可改派的范围从"仅同批次同时间司机"扩大为:
1. 同批次同时间司机(无需确认,直接改派)
2. 其他同公司司机(需要确认,Socket推送,60秒超时)

业务流程

流程1:同批次改派(原有逻辑保持)

司机A发起改派 → 选择同批次司机B → 支付 → 直接改派成功 ✅

流程2:其他司机改派(新增)

司机A发起改派 → 选择其他司机C → 支付 → 
Socket推送给司机C(60秒倒计时)→ 
司机C点击同意/拒绝 → 
同意:创建临时排班,改派成功 ✅
拒绝:通知司机A被拒绝 ❌
超时:通知司机A超时 ❌

🔄 接口变更说明

1. 查询可改派司机列表(已修改)

接口: POST /api/orderCrossCity/queryDriver

变更内容:
- 响应数据中新增 needConfirm 字段
- needConfirm=1: 不需要确认(同批次),可直接改派
- needConfirm=2: 需要确认(其他司机),需Socket推送


2. 司机响应改派接口(新增)⭐

接口: POST /api/orderCrossCity/respondReassign

用途: 司机收到改派推送后,点击"同意"或"拒绝"


3. 查询待确认改派列表(新增)⭐

接口: POST /api/orderCrossCity/queryPendingReassigns

用途: 查询当前司机待确认的改派申请列表


4. Socket消息(新增4种)⭐

  • REASSIGN_CONFIRM_REQUEST - 改派确认请求
  • REASSIGN_SUCCESS - 改派成功通知
  • REASSIGN_REJECTED - 改派被拒绝通知
  • REASSIGN_TIMEOUT - 改派超时通知

📡 接口详情

1. 查询可改派司机列表

基本信息

  • 接口: POST /api/orderCrossCity/queryDriver
  • 说明: 查询可以改派的司机列表
  • 变更: 响应中新增 needConfirm 字段

请求参数

参数名 类型 必填 说明
orderId String 是 订单ID

请求示例

{
  "orderId": "123456"
}

响应参数

参数名 类型 说明
code Integer 状态码,200=成功
msg String 提示信息
data Array 司机列表
├─ id Integer 班次ID
├─ lineShiftDriverId Integer 司机排班ID
├─ driverId Integer 司机ID
├─ name String 司机姓名
├─ headImgUrl String 司机头像
├─ carLicensePlate String 车牌号
├─ carColor String 车辆颜色
├─ brand String 车辆品牌型号
├─ totalSeat Integer 总座位数
├─ laveSeat Integer 剩余座位数
├─ evaluate Double 司机评分
├─ startTime String 班次时间(如"08:00-09:00")
├─ needConfirm Integer ⭐ 是否需要确认(1=不需要,2=需要)

响应示例

{
  "code": 200,
  "msg": "success",
  "data": [
    {
      "id": 101,
      "lineShiftDriverId": 1001,
      "driverId": 5001,
      "name": "李师傅",
      "headImgUrl": "http://example.com/avatar/5001.jpg",
      "carLicensePlate": "京A12345",
      "carColor": "白色",
      "brand": "大众帕萨特",
      "totalSeat": 5,
      "laveSeat": 3,
      "evaluate": 4.8,
      "startTime": "08:00-09:00",
      "needConfirm": 1  // ⭐ 不需要确认,同批次司机
    },
    {
      "id": 101,
      "lineShiftDriverId": null,
      "driverId": 5002,
      "name": "王师傅",
      "headImgUrl": "http://example.com/avatar/5002.jpg",
      "carLicensePlate": "京B67890",
      "carColor": "黑色",
      "brand": "本田雅阁",
      "totalSeat": 5,
      "laveSeat": 5,
      "evaluate": 4.9,
      "startTime": "08:00-09:00",
      "needConfirm": 2  // ⭐ 需要确认,其他司机
    }
  ]
}

前端处理逻辑

data.forEach { driver ->
    when (driver.needConfirm) {
        1 -> {
            // 显示"直接改派"标识
            // 点击后直接提交改派申请
        }
        2 -> {
            // 显示"需要确认"标识
            // 点击后提示"已发送请求,等待对方确认"
        }
    }
}

2. 司机响应改派接口(新增)⭐

基本信息

  • 接口: POST /api/orderCrossCity/respondReassign
  • 说明: 司机收到改派推送后,点击"同意"或"拒绝"调用此接口
  • 幂等性: ✅ 支持,重复点击返回友好提示

请求头

Authorization: Bearer {token}

请求参数

参数名 类型 必填 说明
reassignId Integer 是 改派申请ID
responseType Integer 是 响应类型:1=同意,2=拒绝

请求示例

{
  "reassignId": 12345,
  "responseType": 1  // 1=同意,2=拒绝
}

响应参数

参数名 类型 说明
code Integer 状态码,200=成功
msg String 提示信息
data Object 空

响应示例

成功(同意):
json { "code": 200, "msg": "改派成功", "data": null }

成功(拒绝):
json { "code": 200, "msg": "已拒绝改派", "data": null }

失败(已被其他司机接受):
json { "code": 500, "msg": "该改派申请已被其他司机接受或已过期", "data": null }

失败(已超时):
json { "code": 500, "msg": "该改派申请已超时", "data": null }

错误码说明

错误信息 说明
改派申请不存在 reassignId 无效
无权操作 当前司机不是目标司机
该改派申请已被接受 已经被处理(重复点击)
该改派申请已被拒绝 已经被处理
该改派申请已超时 超过60秒未响应
该改派申请已被其他司机接受或已过期 并发场景,被其他司机抢先接受
订单不存在 订单已取消或删除
订单状态已变更,无法改派 订单已完成或取消

前端处理建议

fun respondReassign(reassignId: Int, isAccept: Boolean) {
    val responseType = if (isAccept) 1 else 2
    
    api.respondReassign(reassignId, responseType)
        .enqueue(object : Callback<BaseResponse> {
            override fun onResponse(response: BaseResponse) {
                when (response.code) {
                    200 -> {
                        // 成功
                        Toast.makeText(context, response.msg, Toast.LENGTH_SHORT).show()
                        if (isAccept) {
                            // 跳转到订单详情或刷新订单列表
                            navigateToOrderDetail()
                        }
                    }
                    else -> {
                        // 失败,显示错误信息
                        Toast.makeText(context, response.msg, Toast.LENGTH_SHORT).show()
                    }
                }
            }
            
            override fun onFailure(t: Throwable) {
                Toast.makeText(context, "网络错误", Toast.LENGTH_SHORT).show()
            }
        })
}

3. 查询待确认改派列表(新增)⭐

基本信息

  • 接口: POST /api/orderCrossCity/queryPendingReassigns
  • 说明: 查询当前司机待确认的改派申请列表(可用于"改派通知"页面)

请求头

Authorization: Bearer {token}

请求参数

无(从token中获取司机ID)

响应参数

参数名 类型 说明
code Integer 状态码,200=成功
msg String 提示信息
data Array 待确认改派列表
├─ reassignId Integer 改派申请ID
├─ orderId Integer 订单ID
├─ orderType Integer 订单类型(3=跨城)
├─ startAddress String 起点地址
├─ endAddress String 终点地址
├─ peopleNumber Integer 乘客人数
├─ travelTime String 出行时间
├─ orderMoney Double 订单金额
├─ expireTime String 超时时间
├─ reason String 改派原因
├─ insertTime String 申请时间
├─ originalDriverName String 原司机姓名
├─ originalDriverPhone String 原司机手机号(脱敏)

响应示例

{
  "code": 200,
  "msg": "success",
  "data": [
    {
      "reassignId": 12345,
      "orderId": 456789,
      "orderType": 3,
      "startAddress": "北京站",
      "endAddress": "天津站",
      "peopleNumber": 3,
      "travelTime": "2026-06-25 08:00:00",
      "orderMoney": 150.00,
      "expireTime": "2026-06-25 07:31:00",
      "reason": "临时有事",
      "insertTime": "2026-06-25 07:30:00",
      "originalDriverName": "张师傅",
      "originalDriverPhone": "138****1234"
    },
    {
      "reassignId": 12346,
      "orderId": 456790,
      "orderType": 3,
      "startAddress": "北京南站",
      "endAddress": "保定站",
      "peopleNumber": 2,
      "travelTime": "2026-06-25 09:00:00",
      "orderMoney": 120.00,
      "expireTime": "2026-06-25 08:35:00",
      "reason": "身体不适",
      "insertTime": "2026-06-25 08:34:00",
      "originalDriverName": "李师傅",
      "originalDriverPhone": "139****5678"
    }
  ]
}

前端UI建议

┌─────────────────────────────────────┐
│  待确认改派 (2)                      │
├─────────────────────────────────────┤
│  改派申请 #12345                     │
│  张师傅 (138****1234)               │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  │
│  🚗 北京站 → 天津站                  │
│  👥 3人   💰 ¥150.00                │
│  ⏰ 2026-06-25 08:00出发             │
│  ⏱ 剩余时间: 00:45                  │
│                                      │
│  [同意] [拒绝]                       │
├─────────────────────────────────────┤
│  改派申请 #12346                     │
│  ...                                 │
└─────────────────────────────────────┘

🔔 Socket 消息

连接信息

  • 协议: WebSocket
  • URL: ws://api.yourdomain.com/socket
  • 认证: 连接时传递token

1. 改派确认请求(REASSIGN_CONFIRM_REQUEST)⭐

触发时机

当其他司机发起改派,选择你作为目标司机时

消息格式

{
  "type": "REASSIGN_CONFIRM_REQUEST",
  "reassignId": 12345,
  "orderId": 456789,
  "startAddress": "北京站",
  "endAddress": "天津站",
  "peopleNumber": 3,
  "travelTime": "2026-06-25 08:00:00",
  "expireTime": "2026-06-25 07:31:00",
  "money": 150.00
}

前端处理

when (message.type) {
    "REASSIGN_CONFIRM_REQUEST" -> {
        // 弹出全屏确认对话框
        showReassignConfirmDialog(
            reassignId = message.reassignId,
            startAddress = message.startAddress,
            endAddress = message.endAddress,
            peopleNumber = message.peopleNumber,
            travelTime = message.travelTime,
            orderMoney = message.money,
            expireTime = message.expireTime
        )
        
        // 开始倒计时
        startCountdown(message.expireTime)
        
        // 播放提示音
        playNotificationSound()
        
        // 震动提醒
        vibrateDevice()
    }
}

UI建议

┌─────────────────────────────────────┐
│                                      │
│         🚕 改派确认请求               │
│                                      │
│  有司机希望将订单改派给您             │
│                                      │
│  起点:北京站                         │
│  终点:天津站                         │
│  人数:3人                            │
│  金额:¥150.00                       │
│  发车时间:2026-06-25 08:00          │
│                                      │
│  ⏱ 剩余时间: 00:58                  │
│                                      │
│  [同意接单] [拒绝]                   │
│                                      │
└─────────────────────────────────────┘

2. 改派成功通知(REASSIGN_SUCCESS)

触发时机

你发起的改派请求被对方同意时

消息格式

{
  "type": "REASSIGN_SUCCESS",
  "reassignId": 12345,
  "orderId": 456789
}

前端处理

when (message.type) {
    "REASSIGN_SUCCESS" -> {
        // 显示成功提示
        Toast.makeText(context, "改派成功", Toast.LENGTH_SHORT).show()
        
        // 刷新订单列表
        refreshOrderList()
        
        // 关闭改派申请页面
        finish()
    }
}

3. 改派被拒绝通知(REASSIGN_REJECTED)

触发时机

你发起的改派请求被对方拒绝时

消息格式

{
  "type": "REASSIGN_REJECTED",
  "reassignId": 12345,
  "orderId": 456789
}

前端处理

when (message.type) {
    "REASSIGN_REJECTED" -> {
        // 显示拒绝提示
        showDialog(
            title = "改派被拒绝",
            message = "对方拒绝了您的改派申请,请重新选择其他司机",
            positiveButton = "重新选择" to {
                // 返回司机选择页面
                navigateToDriverSelection()
            }
        )
    }
}

4. 改派超时通知(REASSIGN_TIMEOUT)

触发时机

你发起的改派请求超过60秒未响应时

消息格式

{
  "type": "REASSIGN_TIMEOUT",
  "reassignId": 12345,
  "orderId": 456789
}

前端处理

when (message.type) {
    "REASSIGN_TIMEOUT" -> {
        // 显示超时提示
        showDialog(
            title = "改派超时",
            message = "改派申请已超时,对方未在60秒内响应,请重新选择其他司机",
            positiveButton = "重新选择" to {
                navigateToDriverSelection()
            }
        )
    }
}

🎨 UI/UX 建议

1. 司机列表展示

// ViewHolder
when (driver.needConfirm) {
    1 -> {
        // 不需要确认
        badge.text = "同批次"
        badge.setBackgroundColor(Color.GREEN)
        clickHint.text = "点击直接改派"
    }
    2 -> {
        // 需要确认
        badge.text = "需确认"
        badge.setBackgroundColor(Color.ORANGE)
        clickHint.text = "需对方同意"
    }
}

2. 改派确认对话框

  • 全屏对话框,不可点击外部关闭
  • 60秒倒计时,醒目显示
  • 订单信息完整展示
  • 按钮大而明显:"同意接单"(绿色)、"拒绝"(灰色)

3. 提示音和震动

  • 收到改派请求:播放提示音 + 震动
  • 改派成功:短促震动
  • 改派被拒绝:提示音
  • 改派超时:提示音

4. 倒计时显示

// 倒计时格式
"剩余时间: 00:58"  // 58秒
"剩余时间: 00:30"  // 30秒
"剩余时间: 00:05"  // 5秒(红色显示)
"已超时"           // 超时后

🔧 技术要点

1. Socket 连接管理

class ReassignSocketManager {
    private var socket: WebSocket? = null
    
    fun connect(token: String) {
        val request = Request.Builder()
            .url("ws://api.yourdomain.com/socket?token=$token")
            .build()
        
        socket = client.newWebSocket(request, object : WebSocketListener() {
            override fun onMessage(webSocket: WebSocket, text: String) {
                val message = gson.fromJson(text, SocketMessage::class.java)
                handleMessage(message)
            }
        })
    }
    
    private fun handleMessage(message: SocketMessage) {
        when (message.type) {
            "REASSIGN_CONFIRM_REQUEST" -> {
                // 显示确认对话框
                EventBus.getDefault().post(ReassignConfirmEvent(message))
            }
            "REASSIGN_SUCCESS" -> {
                // 显示成功通知
            }
            "REASSIGN_REJECTED" -> {
                // 显示拒绝通知
            }
            "REASSIGN_TIMEOUT" -> {
                // 显示超时通知
            }
        }
    }
}

2. 倒计时实现

class CountdownTimer(
    private val expireTime: String,
    private val onTick: (remainingSeconds: Int) -> Unit,
    private val onFinish: () -> Unit
) {
    private var timer: Timer? = null
    
    fun start() {
        val expireMillis = parseTime(expireTime)
        val nowMillis = System.currentTimeMillis()
        val remainingMillis = expireMillis - nowMillis
        
        if (remainingMillis <= 0) {
            onFinish()
            return
        }
        
        timer = Timer().apply {
            scheduleAtFixedRate(object : TimerTask() {
                override fun run() {
                    val remaining = (expireMillis - System.currentTimeMillis()) / 1000
                    if (remaining <= 0) {
                        onFinish()
                        cancel()
                    } else {
                        onTick(remaining.toInt())
                    }
                }
            }, 0, 1000)
        }
    }
    
    fun cancel() {
        timer?.cancel()
    }
}

3. 防重复点击

// 使用 RxJava 防抖动
button.setOnClickListener {
    Observable.just(Unit)
        .throttleFirst(2, TimeUnit.SECONDS)  // 2秒内只能点击一次
        .subscribe {
            respondReassign(reassignId, true)
        }
}

// 或使用简单的 flag
private var isSubmitting = false

button.setOnClickListener {
    if (isSubmitting) return@setOnClickListener
    isSubmitting = true
    
    api.respondReassign(reassignId, responseType)
        .enqueue(object : Callback {
            override fun onResponse(response: Response) {
                isSubmitting = false
                // 处理响应
            }
            
            override fun onFailure(t: Throwable) {
                isSubmitting = false
                // 处理错误
            }
        })
}

📋 测试检查清单

接口测试

  • [ ] 查询司机列表,验证 needConfirm 字段
  • [ ] 同意改派接口调用成功
  • [ ] 拒绝改派接口调用成功
  • [ ] 查询待确认列表正常返回
  • [ ] 重复点击同意,返回友好提示
  • [ ] 超时后点击,返回超时提示

Socket 测试

  • [ ] 收到改派确认请求推送
  • [ ] 收到改派成功通知
  • [ ] 收到改派拒绝通知
  • [ ] 收到改派超时通知
  • [ ] Socket 断线重连正常

UI 测试

  • [ ] 司机列表正确显示 needConfirm 标识
  • [ ] 改派确认对话框正确展示
  • [ ] 倒计时正确显示
  • [ ] 超时后对话框自动关闭
  • [ ] 提示音和震动正常

异常测试

  • [ ] 网络异常处理
  • [ ] Token 过期处理
  • [ ] 并发点击处理
  • [ ] 应用后台时收到推送

🆘 常见问题

Q1: 如何区分同批次司机和其他司机?

A: 通过 needConfirm 字段判断:
- needConfirm=1: 同批次司机,点击后直接改派
- needConfirm=2: 其他司机,点击后发送推送等待确认

Q2: 收到多个改派请求怎么处理?

A:
- 同时显示多个对话框(不推荐)
- 或者显示列表,司机选择处理哪个(推荐)
- 查询待确认列表接口可获取所有待确认改派

Q3: 60秒倒计时如何实现?

A:
- 后端返回 expireTime 绝对时间
- 前端计算 expireTime - 当前时间 = 剩余秒数
- 使用 CountDownTimer 每秒更新UI

Q4: Socket 断线怎么办?

A:
- 实现自动重连机制
- 重连后调用"查询待确认列表"接口补偿
- 避免漏掉改派请求

Q5: 幂等性如何保证?

A:
- 后端已实现幂等性保护
- 前端重复点击会返回友好提示,不会重复操作
- 建议前端也做2秒防抖


📞 技术支持

如有疑问,请联系:
- 后端负责人: XXX
- 项目经理: XXX
- 技术文档: 查看 documents/ 目录下的详细文档


文档版本: v2.0
最后更新: 2026-06-25
适用版本: 司机端 v2.0+