http://api.yourdomain.com跨城改派功能升级,司机可改派的范围从"仅同批次同时间司机"扩大为:
1. 同批次同时间司机(无需确认,直接改派)
2. 其他同公司司机(需要确认,Socket推送,60秒超时)
司机A发起改派 → 选择同批次司机B → 支付 → 直接改派成功 ✅
司机A发起改派 → 选择其他司机C → 支付 →
Socket推送给司机C(60秒倒计时)→
司机C点击同意/拒绝 →
同意:创建临时排班,改派成功 ✅
拒绝:通知司机A被拒绝 ❌
超时:通知司机A超时 ❌
接口: POST /api/orderCrossCity/queryDriver
变更内容:
- 响应数据中新增 needConfirm 字段
- needConfirm=1: 不需要确认(同批次),可直接改派
- needConfirm=2: 需要确认(其他司机),需Socket推送
接口: POST /api/orderCrossCity/respondReassign
用途: 司机收到改派推送后,点击"同意"或"拒绝"
接口: POST /api/orderCrossCity/queryPendingReassigns
用途: 查询当前司机待确认的改派申请列表
REASSIGN_CONFIRM_REQUEST - 改派确认请求REASSIGN_SUCCESS - 改派成功通知REASSIGN_REJECTED - 改派被拒绝通知REASSIGN_TIMEOUT - 改派超时通知POST /api/orderCrossCity/queryDriverneedConfirm 字段| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| 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 -> {
// 显示"需要确认"标识
// 点击后提示"已发送请求,等待对方确认"
}
}
}
POST /api/orderCrossCity/respondReassignAuthorization: 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()
}
})
}
POST /api/orderCrossCity/queryPendingReassignsAuthorization: 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"
}
]
}
┌─────────────────────────────────────┐
│ 待确认改派 (2) │
├─────────────────────────────────────┤
│ 改派申请 #12345 │
│ 张师傅 (138****1234) │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ 🚗 北京站 → 天津站 │
│ 👥 3人 💰 ¥150.00 │
│ ⏰ 2026-06-25 08:00出发 │
│ ⏱ 剩余时间: 00:45 │
│ │
│ [同意] [拒绝] │
├─────────────────────────────────────┤
│ 改派申请 #12346 │
│ ... │
└─────────────────────────────────────┘
ws://api.yourdomain.com/socket当其他司机发起改派,选择你作为目标司机时
{
"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()
}
}
┌─────────────────────────────────────┐
│ │
│ 🚕 改派确认请求 │
│ │
│ 有司机希望将订单改派给您 │
│ │
│ 起点:北京站 │
│ 终点:天津站 │
│ 人数:3人 │
│ 金额:¥150.00 │
│ 发车时间:2026-06-25 08:00 │
│ │
│ ⏱ 剩余时间: 00:58 │
│ │
│ [同意接单] [拒绝] │
│ │
└─────────────────────────────────────┘
你发起的改派请求被对方同意时
{
"type": "REASSIGN_SUCCESS",
"reassignId": 12345,
"orderId": 456789
}
when (message.type) {
"REASSIGN_SUCCESS" -> {
// 显示成功提示
Toast.makeText(context, "改派成功", Toast.LENGTH_SHORT).show()
// 刷新订单列表
refreshOrderList()
// 关闭改派申请页面
finish()
}
}
你发起的改派请求被对方拒绝时
{
"type": "REASSIGN_REJECTED",
"reassignId": 12345,
"orderId": 456789
}
when (message.type) {
"REASSIGN_REJECTED" -> {
// 显示拒绝提示
showDialog(
title = "改派被拒绝",
message = "对方拒绝了您的改派申请,请重新选择其他司机",
positiveButton = "重新选择" to {
// 返回司机选择页面
navigateToDriverSelection()
}
)
}
}
你发起的改派请求超过60秒未响应时
{
"type": "REASSIGN_TIMEOUT",
"reassignId": 12345,
"orderId": 456789
}
when (message.type) {
"REASSIGN_TIMEOUT" -> {
// 显示超时提示
showDialog(
title = "改派超时",
message = "改派申请已超时,对方未在60秒内响应,请重新选择其他司机",
positiveButton = "重新选择" to {
navigateToDriverSelection()
}
)
}
}
// ViewHolder
when (driver.needConfirm) {
1 -> {
// 不需要确认
badge.text = "同批次"
badge.setBackgroundColor(Color.GREEN)
clickHint.text = "点击直接改派"
}
2 -> {
// 需要确认
badge.text = "需确认"
badge.setBackgroundColor(Color.ORANGE)
clickHint.text = "需对方同意"
}
}
// 倒计时格式
"剩余时间: 00:58" // 58秒
"剩余时间: 00:30" // 30秒
"剩余时间: 00:05" // 5秒(红色显示)
"已超时" // 超时后
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" -> {
// 显示超时通知
}
}
}
}
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()
}
}
// 使用 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
// 处理错误
}
})
}
A: 通过 needConfirm 字段判断:
- needConfirm=1: 同批次司机,点击后直接改派
- needConfirm=2: 其他司机,点击后发送推送等待确认
A:
- 同时显示多个对话框(不推荐)
- 或者显示列表,司机选择处理哪个(推荐)
- 查询待确认列表接口可获取所有待确认改派
A:
- 后端返回 expireTime 绝对时间
- 前端计算 expireTime - 当前时间 = 剩余秒数
- 使用 CountDownTimer 每秒更新UI
A:
- 实现自动重连机制
- 重连后调用"查询待确认列表"接口补偿
- 避免漏掉改派请求
A:
- 后端已实现幂等性保护
- 前端重复点击会返回友好提示,不会重复操作
- 建议前端也做2秒防抖
如有疑问,请联系:
- 后端负责人: XXX
- 项目经理: XXX
- 技术文档: 查看 documents/ 目录下的详细文档
文档版本: v2.0
最后更新: 2026-06-25
适用版本: 司机端 v2.0+