文件
mashangyoujiu/Application/RiderApi/Controller/LocationController.class.php

511 行
18 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace RiderApi\Controller;
/**
* 骑手端位置控制器
*/
class LocationController extends BaseController {
/**
* 上报位置
*/
public function reportLocation() {
$riderId = $this->riderId;
$rider = $this->riderInfo;
// 检查Content-Type并解析参数
$contentType = '';
if (isset($_SERVER['CONTENT_TYPE'])) {
$contentType = $_SERVER['CONTENT_TYPE'];
}
// 使用统一的参数处理方法
// 兼容 lat/lng 和 latitude/longitude 两种参数名
$lat = floatval($this->getRequestParam('lat', $this->getRequestParam('latitude', 0)));
$lng = floatval($this->getRequestParam('lng', $this->getRequestParam('longitude', 0)));
$address = trim($this->getRequestParam('address', ''));
$accuracy = floatval($this->getRequestParam('accuracy', 0));
$speed = floatval($this->getRequestParam('speed', 0));
$direction = floatval($this->getRequestParam('direction', 0));
if (empty($lat) || empty($lng)) {
$this->ajaxReturn(array('code' => 0, 'msg' => '位置信息不能为空'));
}
if (!$rider) {
$this->ajaxReturn(array('code' => 0, 'msg' => '骑手不存在'));
}
if ($rider['work_status'] != 1) {
$this->ajaxReturn(array('code' => 0, 'msg' => '骑手未上线'));
}
// 记录位置轨迹
$locationModel = M('rider_location');
$locationData = array(
'rider_id' => $riderId,
'lat' => $lat,
'lng' => $lng,
'address' => $address,
'accuracy' => $accuracy,
'speed' => $speed,
'direction' => $direction,
'add_time' => date('Y-m-d H:i:s')
);
$result = $locationModel->add($locationData);
// 定期清理历史数据(每100次上报清理一次)
if (mt_rand(1, 100) === 1) {
$this->autoCleanHistoryLocation();
}
if ($result) {
$riderModel = M('rider_info');
// 更新骑手表中的最后位置
$riderModel->where(array('id' => $riderId))->save(array(
'last_location_lat' => $lat,
'last_location_lng' => $lng,
'last_location_time' => date('Y-m-d H:i:s')
));
$this->ajaxReturn(array(
'code' => 1,
'msg' => '位置上报成功'
));
} else {
$this->ajaxReturn(array('code' => 0, 'msg' => '位置上报失败'));
}
}
/**
* 逆地理编码 - 根据经纬度获取地址信息
*/
public function reverseGeocode() {
// 获取JSON格式的请求数据
$input = json_decode(file_get_contents('php://input'), true);
$lat = isset($input['lat']) ? $input['lat'] : null;
$lng = isset($input['lng']) ? $input['lng'] : null;
$type = isset($input['type']) ? $input['type'] : 1; // 坐标类型:1-GCJ02,2-BD09
if ($lat === null || $lng === null || $lat === '' || $lng === '') {
$this->ajaxReturn(array('code' => 0, 'msg' => '经纬度不能为空'));
}
$lat = floatval($lat);
$lng = floatval($lng);
if ($lat == 0 || $lng == 0) {
$this->ajaxReturn(array('code' => 0, 'msg' => '经纬度参数无效'));
}
// 调用公共函数进行逆地理编码
$result = getReverseGeocode($lat, $lng, $type);
if ($result['status'] == 1) {
$this->ajaxReturn(array(
'code' => 1,
'msg' => '获取成功',
'data' => $result['address']
));
} else {
$this->ajaxReturn(array(
'code' => 0,
'msg' => $result['msg'] ?: '获取地址信息失败'
));
}
}
/**
* 获取骑手当前位置
*/
public function getCurrentLocation() {
$riderId = $this->riderId;
$riderModel = M('rider_info');
$rider = $riderModel->field('last_location_lat, last_location_lng, last_location_time')->find($riderId);
if (!$rider) {
$this->ajaxReturn(array('code' => 0, 'msg' => '骑手不存在'));
}
$this->ajaxReturn(array(
'code' => 1,
'msg' => '获取成功',
'data' => array(
'lat' => floatval($rider['last_location_lat']),
'lng' => floatval($rider['last_location_lng']),
'update_time' => $rider['last_location_time']
)
));
}
/**
* 获取位置轨迹
*/
public function getLocationTrack() {
$riderId = $this->riderId;
$startTime = $this->getRequestParam('start_time', '');
$endTime = $this->getRequestParam('end_time', '');
$orderId = $this->getRequestParam('order_id', 0);
$where = array('rider_id' => $riderId);
if ($startTime && $endTime) {
$where['add_time'] = array('between', array($startTime, $endTime));
} elseif ($orderId) {
// 根据订单时间获取轨迹
$orderModel = M('rider_order');
$order = $orderModel->field('accept_time, delivery_time')->find($orderId);
if ($order && $order['accept_time']) {
$where['add_time'] = array('egt', $order['accept_time']);
if ($order['delivery_time']) {
$where['add_time'] = array('between', array($order['accept_time'], $order['delivery_time']));
}
}
} else {
// 默认获取今天的轨迹
$today = date('Y-m-d');
$where['add_time'] = array('between', array($today . ' 00:00:00', $today . ' 23:59:59'));
}
$locationModel = M('rider_location');
$locations = $locationModel->where($where)
->order('add_time ASC')
->select();
// 格式化数据
foreach ($locations as &$location) {
$location['lat'] = floatval($location['lat']);
$location['lng'] = floatval($location['lng']);
$location['accuracy'] = floatval($location['accuracy']);
$location['speed'] = floatval($location['speed']);
$location['direction'] = floatval($location['direction']);
}
$this->ajaxReturn(array(
'code' => 1,
'msg' => '获取成功',
'data' => $locations
));
}
/**
* 计算配送距离
*/
public function calculateDeliveryDistance() {
$riderId = $this->riderId;
$orderId = $this->getRequestParam('order_id', 0);
if (empty($orderId)) {
$this->ajaxReturn(array('code' => 0, 'msg' => '订单ID不能为空'));
}
// 获取订单信息
$orderModel = M('rider_order');
$order = $orderModel->where(array('id' => $orderId, 'rider_id' => $riderId))->find();
if (!$order || $order['rider_id'] != $riderId) {
$this->ajaxReturn(array('code' => 0, 'msg' => '订单不存在或无权限'));
}
if ($order['order_status'] < 2) {
$this->ajaxReturn(array('code' => 0, 'msg' => '订单未完成'));
}
// 获取配送期间的位置轨迹
$locationModel = M('rider_location');
$locations = $locationModel->where(array(
'rider_id' => $riderId,
'add_time' => array('between', array($order['accept_time'], $order['delivery_time']))
))->order('add_time ASC')->select();
if (count($locations) < 2) {
$this->ajaxReturn(array(
'code' => 1,
'msg' => '轨迹点不足',
'data' => array(
'distance' => 0,
'duration' => 0
)
));
}
// 计算总距离
$totalDistance = 0;
for ($i = 1; $i < count($locations); $i++) {
$distance = $this->calculateDistance(
$locations[$i-1]['lat'],
$locations[$i-1]['lng'],
$locations[$i]['lat'],
$locations[$i]['lng']
);
$totalDistance += $distance;
}
// 计算配送时长
$startTime = strtotime($order['accept_time']);
$endTime = strtotime($order['delivery_time']);
$duration = $endTime - $startTime;
$this->ajaxReturn(array(
'code' => 1,
'msg' => '计算成功',
'data' => array(
'distance' => round($totalDistance, 2), // 公里
'duration' => $duration, // 秒
'duration_format' => $this->formatDuration($duration),
'track_points' => count($locations)
)
));
}
/**
* 清理历史位置数据
*/
public function cleanHistoryLocation() {
$days = $this->getRequestParam('days', 30); // 保留天数
$cutoffDate = date('Y-m-d H:i:s', strtotime("-{$days} days"));
$locationModel = M('rider_location');
$result = $locationModel->where(array('add_time' => array('lt', $cutoffDate)))->delete();
$this->ajaxReturn(array(
'code' => 1,
'msg' => '清理完成',
'data' => array(
'deleted_count' => $result
)
));
}
/**
* 自动清理历史位置数据(内部方法)
*/
private function autoCleanHistoryLocation() {
try {
$days = 7; // 自动清理时保留7天的数据
$cutoffDate = date('Y-m-d H:i:s', strtotime("-{$days} days"));
$locationModel = M('rider_location');
// 分批删除,避免一次删除太多数据影响性能
$batchSize = 1000;
$deletedTotal = 0;
do {
$result = $locationModel->where(array('add_time' => array('lt', $cutoffDate)))
->limit($batchSize)
->delete();
$deletedTotal += $result;
// 如果删除的记录少于批次大小,说明已经清理完成
if ($result < $batchSize) {
break;
}
// 避免长时间占用数据库,稍作休息
usleep(100000); // 休息0.1秒
} while ($result > 0);
// 记录清理日志(可选)
if ($deletedTotal > 0) {
\Think\Log::write("自动清理位置数据:删除了 {$deletedTotal} 条记录", 'INFO');
}
} catch (\Exception $e) {
// 清理失败不影响主流程,只记录日志
\Think\Log::write("自动清理位置数据失败:" . $e->getMessage(), 'ERROR');
}
}
/**
* 计算两点间距离
*/
private function calculateDistance($lat1, $lng1, $lat2, $lng2) {
$earthRadius = 6371; // 地球半径,单位:公里
$lat1Rad = deg2rad($lat1);
$lng1Rad = deg2rad($lng1);
$lat2Rad = deg2rad($lat2);
$lng2Rad = deg2rad($lng2);
$deltaLat = $lat2Rad - $lat1Rad;
$deltaLng = $lng2Rad - $lng1Rad;
$a = sin($deltaLat / 2) * sin($deltaLat / 2) +
cos($lat1Rad) * cos($lat2Rad) *
sin($deltaLng / 2) * sin($deltaLng / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
return $earthRadius * $c;
}
/**
* 格式化时长
*/
private function formatDuration($seconds) {
$hours = floor($seconds / 3600);
$minutes = floor(($seconds % 3600) / 60);
$seconds = $seconds % 60;
if ($hours > 0) {
return sprintf('%d小时%d分钟%d秒', $hours, $minutes, $seconds);
} elseif ($minutes > 0) {
return sprintf('%d分钟%d秒', $minutes, $seconds);
} else {
return sprintf('%d秒', $seconds);
}
}
/**
* 批量上传位置数据(专为前端位置服务设计)
*/
public function upload() {
try {
$riderId = $this->riderId;
$rider = $this->riderInfo;
if (!$rider) {
$this->ajaxReturn(array('code' => 0, 'msg' => '骑手不存在'));
}
// 获取位置数据 - 只需要locations参数,rider_id从token中获取
$locations = $this->getRequestParam('locations', array());
if (empty($locations) || !is_array($locations)) {
$this->ajaxReturn(array('code' => 0, 'msg' => '位置数据不能为空'));
}
$locationModel = M('rider_location');
$locationDataList = array();
$latestLocation = null;
$latestTime = 0;
foreach ($locations as $location) {
// 验证必要字段
if (empty($location['latitude']) || empty($location['longitude']) || empty($location['timestamp'])) {
continue;
}
// 验证坐标范围
if ($location['latitude'] < -90 || $location['latitude'] > 90 ||
$location['longitude'] < -180 || $location['longitude'] > 180) {
continue;
}
$timestamp = $location['timestamp'];
$recordTime = date('Y-m-d H:i:s', $timestamp / 1000);
if ($timestamp > $latestTime) {
$latestTime = $timestamp;
$latestLocation = $location;
}
$locationDataList[] = array(
'rider_id' => $riderId,
'lat' => $location['latitude'],
'lng' => $location['longitude'],
'address' => isset($location['address']) ? $location['address'] : '',
'accuracy' => isset($location['accuracy']) ? $location['accuracy'] : 0,
'speed' => isset($location['speed']) ? $location['speed'] : 0,
'direction' => isset($location['heading']) ? $location['heading'] : 0,
'add_time' => $recordTime
);
}
if (empty($locationDataList)) {
$this->ajaxReturn(array('code' => 0, 'msg' => '没有有效的位置数据'));
}
try {
// 批量插入位置记录
$result = $locationModel->addAll($locationDataList);
if ($result) {
// 更新最新位置到骑手信息表
if ($latestLocation) {
$riderModel = M('rider_info');
$riderModel->where(array('id' => $riderId))->save(array(
'last_location_lat' => $latestLocation['latitude'],
'last_location_lng' => $latestLocation['longitude'],
'last_location_time' => date('Y-m-d H:i:s', $latestTime / 1000)
));
}
$this->ajaxReturn(array(
'code' => 1,
'msg' => '位置数据上传成功',
'data' => array(
'uploaded_count' => count($locationDataList),
'latest_location' => $latestLocation
)
));
} else {
$this->ajaxReturn(array('code' => 0, 'msg' => '位置数据上传失败'));
}
} catch (\Exception $e) {
$this->ajaxReturn(array('code' => 0, 'msg' => '位置数据上传异常: ' . $e->getMessage()));
}
} catch (\Exception $e) {
$this->ajaxReturn(array('code' => 0, 'msg' => $e->getMessage()));
}
}
/**
* 开始位置追踪
*/
public function startTracking() {
$riderId = $this->riderId;
$rider = $this->riderInfo;
if (!$rider) {
$this->ajaxReturn(array('code' => 0, 'msg' => '骑手不存在'));
}
$riderModel = M('rider_info');
// 更新追踪状态
$result = $riderModel->where(array('id' => $riderId))->save(array(
'location_tracking' => 1,
'tracking_start_time' => date('Y-m-d H:i:s')
));
if ($result !== false) {
$this->ajaxReturn(array('code' => 1, 'msg' => '开始位置追踪成功'));
} else {
$this->ajaxReturn(array('code' => 0, 'msg' => '开始位置追踪失败'));
}
}
/**
* 停止位置追踪
*/
public function stopTracking() {
$riderId = $this->riderId;
$rider = $this->riderInfo;
if (!$rider) {
$this->ajaxReturn(array('code' => 0, 'msg' => '骑手不存在'));
}
$riderModel = M('rider_info');
// 更新追踪状态
$result = $riderModel->where(array('id' => $riderId))->save(array(
'location_tracking' => 0,
'tracking_end_time' => date('Y-m-d H:i:s')
));
if ($result !== false) {
$this->ajaxReturn(array('code' => 1, 'msg' => '停止位置追踪成功'));
} else {
$this->ajaxReturn(array('code' => 0, 'msg' => '停止位置追踪失败'));
}
}
}