# 跨城改派功能升级 - 司机端接口文档 ## 文档信息 - **版本**: 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 | #### 请求示例 ```json { "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=需要)** | #### 响应示例 ```json { "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 // ⭐ 需要确认,其他司机 } ] } ``` #### 前端处理逻辑 ```kotlin data.forEach { driver -> when (driver.needConfirm) { 1 -> { // 显示"直接改派"标识 // 点击后直接提交改派申请 } 2 -> { // 显示"需要确认"标识 // 点击后提示"已发送请求,等待对方确认" } } } ``` --- ### 2. 司机响应改派接口(新增)⭐ #### 基本信息 - **接口**: `POST /api/orderCrossCity/respondReassign` - **说明**: 司机收到改派推送后,点击"同意"或"拒绝"调用此接口 - **幂等性**: ✅ 支持,重复点击返回友好提示 #### 请求头 ``` Authorization: Bearer {token} ``` #### 请求参数 | 参数名 | 类型 | 必填 | 说明 | |-------|------|------|------| | reassignId | Integer | 是 | 改派申请ID | | responseType | Integer | 是 | 响应类型:1=同意,2=拒绝 | #### 请求示例 ```json { "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秒未响应 | | 该改派申请已被其他司机接受或已过期 | 并发场景,被其他司机抢先接受 | | 订单不存在 | 订单已取消或删除 | | 订单状态已变更,无法改派 | 订单已完成或取消 | #### 前端处理建议 ```kotlin fun respondReassign(reassignId: Int, isAccept: Boolean) { val responseType = if (isAccept) 1 else 2 api.respondReassign(reassignId, responseType) .enqueue(object : Callback { 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 | 原司机手机号(脱敏) | #### 响应示例 ```json { "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)⭐ #### 触发时机 当其他司机发起改派,选择你作为目标司机时 #### 消息格式 ```json { "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 } ``` #### 前端处理 ```kotlin 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) #### 触发时机 你发起的改派请求被对方同意时 #### 消息格式 ```json { "type": "REASSIGN_SUCCESS", "reassignId": 12345, "orderId": 456789 } ``` #### 前端处理 ```kotlin when (message.type) { "REASSIGN_SUCCESS" -> { // 显示成功提示 Toast.makeText(context, "改派成功", Toast.LENGTH_SHORT).show() // 刷新订单列表 refreshOrderList() // 关闭改派申请页面 finish() } } ``` --- ### 3. 改派被拒绝通知(REASSIGN_REJECTED) #### 触发时机 你发起的改派请求被对方拒绝时 #### 消息格式 ```json { "type": "REASSIGN_REJECTED", "reassignId": 12345, "orderId": 456789 } ``` #### 前端处理 ```kotlin when (message.type) { "REASSIGN_REJECTED" -> { // 显示拒绝提示 showDialog( title = "改派被拒绝", message = "对方拒绝了您的改派申请,请重新选择其他司机", positiveButton = "重新选择" to { // 返回司机选择页面 navigateToDriverSelection() } ) } } ``` --- ### 4. 改派超时通知(REASSIGN_TIMEOUT) #### 触发时机 你发起的改派请求超过60秒未响应时 #### 消息格式 ```json { "type": "REASSIGN_TIMEOUT", "reassignId": 12345, "orderId": 456789 } ``` #### 前端处理 ```kotlin when (message.type) { "REASSIGN_TIMEOUT" -> { // 显示超时提示 showDialog( title = "改派超时", message = "改派申请已超时,对方未在60秒内响应,请重新选择其他司机", positiveButton = "重新选择" to { navigateToDriverSelection() } ) } } ``` --- ## 🎨 UI/UX 建议 ### 1. 司机列表展示 ```kotlin // 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. 倒计时显示 ```kotlin // 倒计时格式 "剩余时间: 00:58" // 58秒 "剩余时间: 00:30" // 30秒 "剩余时间: 00:05" // 5秒(红色显示) "已超时" // 超时后 ``` --- ## 🔧 技术要点 ### 1. Socket 连接管理 ```kotlin 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. 倒计时实现 ```kotlin 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. 防重复点击 ```kotlin // 使用 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+