init: import production code mss.nahaichuan.com (exclude Runtime/Uploads/backups/logs)
这个提交包含在:
@@ -0,0 +1,475 @@
|
||||
<?php
|
||||
namespace Api\Controller;
|
||||
|
||||
use Api\Controller\BaseController;
|
||||
|
||||
/**
|
||||
* 骑手位置API控制器
|
||||
* Class RiderLocationController
|
||||
* @package Api\Controller
|
||||
*/
|
||||
class RiderLocationController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 更新骑手位置
|
||||
* @return void
|
||||
*/
|
||||
public function updateLocation()
|
||||
{
|
||||
$param = I('post.');
|
||||
|
||||
// 验证骑手身份
|
||||
$riderInfo = $this->checkRiderAuth();
|
||||
if ($riderInfo['status'] != 1) {
|
||||
$this->ajaxReturn($riderInfo);
|
||||
}
|
||||
|
||||
$riderId = $riderInfo['data']['id'];
|
||||
|
||||
// 验证参数
|
||||
if (empty($param['lat']) || empty($param['lng'])) {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '位置信息不能为空'));
|
||||
}
|
||||
|
||||
// 验证坐标范围
|
||||
if ($param['lat'] < -90 || $param['lat'] > 90) {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '纬度范围错误'));
|
||||
}
|
||||
|
||||
if ($param['lng'] < -180 || $param['lng'] > 180) {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '经度范围错误'));
|
||||
}
|
||||
|
||||
$riderInfoModel = D('RiderInfo');
|
||||
$riderLocationModel = D('RiderLocation');
|
||||
|
||||
// 更新骑手信息表中的最新位置
|
||||
$updateData = [
|
||||
'last_location_lat' => $param['lat'],
|
||||
'last_location_lng' => $param['lng'],
|
||||
'last_location_time' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
$result = $riderInfoModel->updateRider(['id' => $riderId], $updateData);
|
||||
|
||||
if ($result) {
|
||||
// 记录位置轨迹
|
||||
$locationData = [
|
||||
'rider_id' => $riderId,
|
||||
'lat' => $param['lat'],
|
||||
'lng' => $param['lng'],
|
||||
'accuracy' => $param['accuracy'] ?? 0, // GPS精度
|
||||
'speed' => $param['speed'] ?? 0, // 速度
|
||||
'bearing' => $param['bearing'] ?? 0, // 方向角
|
||||
'altitude' => $param['altitude'] ?? 0, // 海拔
|
||||
'record_time' => date('Y-m-d H:i:s'),
|
||||
'add_time' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
$riderLocationModel->addLocation($locationData);
|
||||
|
||||
// 检查是否有需要更新的订单
|
||||
$this->checkOrderLocationUpdate($riderId, $param['lat'], $param['lng']);
|
||||
|
||||
$this->ajaxReturn(callBack(statusCode()['success'], '位置更新成功'));
|
||||
} else {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '位置更新失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新位置
|
||||
* @return void
|
||||
*/
|
||||
public function batchUpdateLocation()
|
||||
{
|
||||
$param = I('post.');
|
||||
|
||||
// 验证骑手身份
|
||||
$riderInfo = $this->checkRiderAuth();
|
||||
if ($riderInfo['status'] != 1) {
|
||||
$this->ajaxReturn($riderInfo);
|
||||
}
|
||||
|
||||
$riderId = $riderInfo['data']['id'];
|
||||
|
||||
if (empty($param['locations']) || !is_array($param['locations'])) {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '位置数据不能为空'));
|
||||
}
|
||||
|
||||
$riderLocationModel = D('RiderLocation');
|
||||
$locationDataList = [];
|
||||
$latestLocation = null;
|
||||
$latestTime = 0;
|
||||
|
||||
foreach ($param['locations'] as $location) {
|
||||
if (empty($location['lat']) || empty($location['lng']) || empty($location['record_time'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$recordTime = strtotime($location['record_time']);
|
||||
if ($recordTime > $latestTime) {
|
||||
$latestTime = $recordTime;
|
||||
$latestLocation = $location;
|
||||
}
|
||||
|
||||
$locationDataList[] = [
|
||||
'rider_id' => $riderId,
|
||||
'lat' => $location['lat'],
|
||||
'lng' => $location['lng'],
|
||||
'accuracy' => $location['accuracy'] ?? 0,
|
||||
'speed' => $location['speed'] ?? 0,
|
||||
'bearing' => $location['bearing'] ?? 0,
|
||||
'altitude' => $location['altitude'] ?? 0,
|
||||
'record_time' => $location['record_time'],
|
||||
'add_time' => date('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
|
||||
if (empty($locationDataList)) {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '没有有效的位置数据'));
|
||||
}
|
||||
|
||||
// 批量插入位置记录
|
||||
$result = $riderLocationModel->addLocationBatch($locationDataList);
|
||||
|
||||
if ($result) {
|
||||
// 更新最新位置到骑手信息表
|
||||
if ($latestLocation) {
|
||||
$riderInfoModel = D('RiderInfo');
|
||||
$riderInfoModel->updateRider(['id' => $riderId], [
|
||||
'last_location_lat' => $latestLocation['lat'],
|
||||
'last_location_lng' => $latestLocation['lng'],
|
||||
'last_location_time' => $latestLocation['record_time'],
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
|
||||
$this->ajaxReturn(callBack(statusCode()['success'], '批量位置更新成功', [
|
||||
'updated_count' => count($locationDataList)
|
||||
]));
|
||||
} else {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '批量位置更新失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取骑手位置轨迹
|
||||
* @return void
|
||||
*/
|
||||
public function getLocationTrack()
|
||||
{
|
||||
$param = I('post.');
|
||||
|
||||
// 验证骑手身份
|
||||
$riderInfo = $this->checkRiderAuth();
|
||||
if ($riderInfo['status'] != 1) {
|
||||
$this->ajaxReturn($riderInfo);
|
||||
}
|
||||
|
||||
$riderId = $riderInfo['data']['id'];
|
||||
|
||||
$startTime = $param['start_time'] ?? date('Y-m-d 00:00:00');
|
||||
$endTime = $param['end_time'] ?? date('Y-m-d 23:59:59');
|
||||
$limit = isset($param['limit']) ? intval($param['limit']) : 100;
|
||||
|
||||
$riderLocationModel = D('RiderLocation');
|
||||
|
||||
$track = $riderLocationModel->getLocationTrack($riderId, $startTime, $endTime, $limit);
|
||||
|
||||
// 计算轨迹统计信息
|
||||
$stats = $riderLocationModel->getRiderActivityStats($riderId, date('Y-m-d', strtotime($startTime)));
|
||||
|
||||
$this->ajaxReturn(callBack(statusCode()['success'], '获取位置轨迹成功', [
|
||||
'track' => $track,
|
||||
'stats' => $stats
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取附近的骑手
|
||||
* @return void
|
||||
*/
|
||||
public function getNearbyRiders()
|
||||
{
|
||||
$param = I('post.');
|
||||
|
||||
// 验证骑手身份
|
||||
$riderInfo = $this->checkRiderAuth();
|
||||
if ($riderInfo['status'] != 1) {
|
||||
$this->ajaxReturn($riderInfo);
|
||||
}
|
||||
|
||||
if (empty($param['lat']) || empty($param['lng'])) {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '位置信息不能为空'));
|
||||
}
|
||||
|
||||
$radius = isset($param['radius']) ? floatval($param['radius']) : 5; // 默认5公里
|
||||
|
||||
$riderLocationModel = D('RiderLocation');
|
||||
|
||||
// 获取附近骑手
|
||||
$nearbyRiders = $riderLocationModel->getRidersInRange($param['lat'], $param['lng'], $radius);
|
||||
|
||||
// 获取骑手详细信息
|
||||
if (!empty($nearbyRiders)) {
|
||||
$riderIds = array_column($nearbyRiders, 'rider_id');
|
||||
$riderInfoModel = D('RiderInfo');
|
||||
$riderInfos = $riderInfoModel->getList(['id' => ['in', $riderIds]], 'id,real_name,phone,work_status,rating');
|
||||
|
||||
$riderInfoMap = [];
|
||||
foreach ($riderInfos as $info) {
|
||||
$riderInfoMap[$info['id']] = $info;
|
||||
}
|
||||
|
||||
// 合并骑手信息
|
||||
foreach ($nearbyRiders as &$rider) {
|
||||
if (isset($riderInfoMap[$rider['rider_id']])) {
|
||||
$rider = array_merge($rider, $riderInfoMap[$rider['rider_id']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->ajaxReturn(callBack(statusCode()['success'], '获取附近骑手成功', [
|
||||
'riders' => $nearbyRiders,
|
||||
'total' => count($nearbyRiders),
|
||||
'radius' => $radius
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取区域骑手密度
|
||||
* @return void
|
||||
*/
|
||||
public function getRiderDensity()
|
||||
{
|
||||
$param = I('post.');
|
||||
|
||||
if (empty($param['lat']) || empty($param['lng'])) {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '位置信息不能为空'));
|
||||
}
|
||||
|
||||
$radius = isset($param['radius']) ? floatval($param['radius']) : 3; // 默认3公里
|
||||
$timeRange = isset($param['time_range']) ? intval($param['time_range']) : 30; // 默认30分钟
|
||||
|
||||
$riderLocationModel = D('RiderLocation');
|
||||
|
||||
$density = $riderLocationModel->getRiderDensity($param['lat'], $param['lng'], $radius, $timeRange);
|
||||
|
||||
$this->ajaxReturn(callBack(statusCode()['success'], '获取骑手密度成功', $density));
|
||||
}
|
||||
|
||||
/**
|
||||
* 预测到达时间
|
||||
* @return void
|
||||
*/
|
||||
public function predictArrivalTime()
|
||||
{
|
||||
$param = I('post.');
|
||||
|
||||
// 验证骑手身份
|
||||
$riderInfo = $this->checkRiderAuth();
|
||||
if ($riderInfo['status'] != 1) {
|
||||
$this->ajaxReturn($riderInfo);
|
||||
}
|
||||
|
||||
$riderId = $riderInfo['data']['id'];
|
||||
|
||||
if (empty($param['dest_lat']) || empty($param['dest_lng'])) {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '目标位置不能为空'));
|
||||
}
|
||||
|
||||
$avgSpeed = isset($param['avg_speed']) ? floatval($param['avg_speed']) : 20; // 默认20公里/小时
|
||||
|
||||
$riderLocationModel = D('RiderLocation');
|
||||
|
||||
$prediction = $riderLocationModel->predictArrivalTime($riderId, $param['dest_lat'], $param['dest_lng'], $avgSpeed);
|
||||
|
||||
$this->ajaxReturn(callBack(statusCode()['success'], '预测到达时间成功', $prediction));
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始位置追踪
|
||||
* @return void
|
||||
*/
|
||||
public function startTracking()
|
||||
{
|
||||
$param = I('post.');
|
||||
|
||||
// 验证骑手身份
|
||||
$riderInfo = $this->checkRiderAuth();
|
||||
if ($riderInfo['status'] != 1) {
|
||||
$this->ajaxReturn($riderInfo);
|
||||
}
|
||||
|
||||
$riderId = $riderInfo['data']['id'];
|
||||
|
||||
$riderInfoModel = D('RiderInfo');
|
||||
|
||||
// 更新追踪状态
|
||||
$result = $riderInfoModel->updateRider(['id' => $riderId], [
|
||||
'location_tracking' => 1,
|
||||
'tracking_start_time' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
if ($result) {
|
||||
$this->ajaxReturn(callBack(statusCode()['success'], '开始位置追踪成功'));
|
||||
} else {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '开始位置追踪失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止位置追踪
|
||||
* @return void
|
||||
*/
|
||||
public function stopTracking()
|
||||
{
|
||||
$param = I('post.');
|
||||
|
||||
// 验证骑手身份
|
||||
$riderInfo = $this->checkRiderAuth();
|
||||
if ($riderInfo['status'] != 1) {
|
||||
$this->ajaxReturn($riderInfo);
|
||||
}
|
||||
|
||||
$riderId = $riderInfo['data']['id'];
|
||||
|
||||
$riderInfoModel = D('RiderInfo');
|
||||
|
||||
// 更新追踪状态
|
||||
$result = $riderInfoModel->updateRider(['id' => $riderId], [
|
||||
'location_tracking' => 0,
|
||||
'tracking_end_time' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
if ($result) {
|
||||
$this->ajaxReturn(callBack(statusCode()['success'], '停止位置追踪成功'));
|
||||
} else {
|
||||
$this->ajaxReturn(callBack(statusCode()['error'], '停止位置追踪失败'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证骑手身份
|
||||
* @return array
|
||||
*/
|
||||
private function checkRiderAuth()
|
||||
{
|
||||
$token = I('post.token', '');
|
||||
if (empty($token)) {
|
||||
return callBack(statusCode()['error'], '请先登录');
|
||||
}
|
||||
|
||||
$riderTokenModel = D('RiderToken');
|
||||
$tokenInfo = $riderTokenModel->getFind(['token' => $token]);
|
||||
|
||||
if (empty($tokenInfo)) {
|
||||
return callBack(statusCode()['error'], '登录已过期,请重新登录');
|
||||
}
|
||||
|
||||
// 检查token是否过期
|
||||
if (strtotime($tokenInfo['expire_time']) < time()) {
|
||||
return callBack(statusCode()['error'], '登录已过期,请重新登录');
|
||||
}
|
||||
|
||||
// 获取骑手信息
|
||||
$riderInfoModel = D('RiderInfo');
|
||||
$riderInfo = $riderInfoModel->getFind(['id' => $tokenInfo['rider_id']]);
|
||||
|
||||
if (empty($riderInfo)) {
|
||||
return callBack(statusCode()['error'], '骑手信息不存在');
|
||||
}
|
||||
|
||||
if ($riderInfo['status'] != 1) {
|
||||
return callBack(statusCode()['error'], '账号已被禁用');
|
||||
}
|
||||
|
||||
return callBack(statusCode()['success'], '验证成功', $riderInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查订单位置更新
|
||||
* @param int $riderId 骑手ID
|
||||
* @param float $lat 纬度
|
||||
* @param float $lng 经度
|
||||
* @return void
|
||||
*/
|
||||
private function checkOrderLocationUpdate($riderId, $lat, $lng)
|
||||
{
|
||||
// 获取骑手当前进行中的订单(统一集合)
|
||||
import('@.Common.Business.OrderStatusUtil');
|
||||
$inProgress = \Common\Business\OrderStatusUtil::getInProgressStatuses();
|
||||
$riderOrderModel = D('RiderOrder');
|
||||
$activeOrders = $riderOrderModel->getList([
|
||||
'rider_id' => $riderId,
|
||||
'order_status' => ['in', implode(',', $inProgress)]
|
||||
]);
|
||||
|
||||
if (empty($activeOrders)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$riderLocationModel = D('RiderLocation');
|
||||
|
||||
foreach ($activeOrders as $order) {
|
||||
// 检查是否到达门店
|
||||
if ($order['order_status'] == 1 && !empty($order['store_lat']) && !empty($order['store_lng'])) {
|
||||
$storeDistance = $riderLocationModel->calculateDistance($lat, $lng, $order['store_lat'], $order['store_lng']);
|
||||
|
||||
// 如果距离门店小于100米,自动更新为已取货状态
|
||||
if ($storeDistance <= 0.1) {
|
||||
$riderOrderModel->updateOrder(['id' => $order['id']], [
|
||||
'order_status' => 2,
|
||||
'pickup_time' => date('Y-m-d H:i:s'),
|
||||
'updated_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
// 同步状态
|
||||
$orderStatusSync = new \Common\Business\OrderStatusSync();
|
||||
$orderStatusSync->syncOrderStatus([
|
||||
'rider_order_id' => $order['id'],
|
||||
'bill_id' => $order['bill_id'],
|
||||
'action' => 'pickup',
|
||||
'extra_data' => []
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否到达客户地址
|
||||
if ($order['order_status'] == 3 && !empty($order['customer_lat']) && !empty($order['customer_lng'])) {
|
||||
$customerDistance = $riderLocationModel->calculateDistance($lat, $lng, $order['customer_lat'], $order['customer_lng']);
|
||||
|
||||
// 如果距离客户小于50米,发送到达提醒
|
||||
if ($customerDistance <= 0.05) {
|
||||
// 这里可以发送推送通知给客户
|
||||
$this->sendArrivalNotification($order['bill_id'], $riderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送到达通知
|
||||
* @param int $billId 订单ID
|
||||
* @param int $riderId 骑手ID
|
||||
* @return void
|
||||
*/
|
||||
private function sendArrivalNotification($billId, $riderId)
|
||||
{
|
||||
// 这里可以实现推送通知功能
|
||||
// 暂时只记录日志
|
||||
$orderSyncLogModel = D('OrderSyncLog');
|
||||
$orderSyncLogModel->addLog([
|
||||
'rider_order_id' => 0,
|
||||
'bill_id' => $billId,
|
||||
'action' => 'arrival_notification',
|
||||
'description' => "骑手{$riderId}即将到达",
|
||||
'sync_time' => date('Y-m-d H:i:s'),
|
||||
'add_time' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户