475 行
16 KiB
PHP
475 行
16 KiB
PHP
<?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')
|
|
]);
|
|
}
|
|
} |