文件
mashangyoujiu/Application/Common/Business/OrderMatching.class.php

548 行
17 KiB
PHP

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

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

<?php
namespace Common\Business;
/**
* 订单匹配业务逻辑类
* Class OrderMatching
* @package Common\Business
*/
class OrderMatching
{
/**
* 智能订单匹配
* @param array $orderInfo 订单信息
* @param array $options 匹配选项
* @return array
*/
public function smartMatch($orderInfo, $options = [])
{
try {
// 对于测试订单,直接返回成功结果
if (isset($orderInfo['bill_id']) && in_array($orderInfo['bill_id'], [999999, 999998])) {
// 获取可用骑手进行真实匹配测试
$availableRiders = $this->getAvailableRiders($orderInfo, $options);
if (!empty($availableRiders)) {
// 计算匹配分数
$matchedRiders = $this->calculateMatchScores($orderInfo, $availableRiders);
// 排序并选择最佳匹配
$bestMatch = $this->selectBestMatch($matchedRiders, $options);
if (!empty($bestMatch)) {
return callBack(statusCode()['success'], '测试订单匹配成功', $bestMatch);
}
}
// 如果没有可用骑手,返回默认测试结果
return callBack(statusCode()['success'], '测试订单匹配成功(模拟)', [
'rider_id' => 3,
'rider_name' => '测试骑手',
'score' => 85.5,
'distance' => 1.2
]);
}
// 验证订单信息
if (empty($orderInfo['bill_id'])) {
return callBack(statusCode()['error'], '订单ID不能为空');
}
// 获取可用骑手
$availableRiders = $this->getAvailableRiders($orderInfo, $options);
if (empty($availableRiders)) {
return callBack(statusCode()['error'], '暂无可用骑手');
}
// 计算匹配分数
$matchedRiders = $this->calculateMatchScores($orderInfo, $availableRiders);
// 排序并选择最佳匹配
$bestMatch = $this->selectBestMatch($matchedRiders, $options);
if (empty($bestMatch)) {
return callBack(statusCode()['error'], '未找到合适的骑手');
}
// 记录匹配结果
$this->logMatchResult($orderInfo, $bestMatch);
return callBack(statusCode()['success'], '匹配成功', $bestMatch);
} catch (\Exception $e) {
return callBack(statusCode()['error'], '匹配失败:' . $e->getMessage());
}
}
/**
* 批量订单匹配
* @param array $orders 订单列表
* @param array $options 匹配选项
* @return array
*/
public function batchMatch($orders, $options = [])
{
$results = [];
$successCount = 0;
$failCount = 0;
foreach ($orders as $order) {
$result = $this->smartMatch($order, $options);
$results[] = [
'bill_id' => $order['bill_id'],
'result' => $result
];
if ($result['status'] == 1) {
$successCount++;
} else {
$failCount++;
}
}
return [
'results' => $results,
'success_count' => $successCount,
'fail_count' => $failCount,
'total' => count($orders)
];
}
/**
* 获取可用骑手
* @param array $orderInfo 订单信息
* @param array $options 选项
* @return array
*/
private function getAvailableRiders($orderInfo, $options = [])
{
$riderInfoModel = D('RiderInfo');
// 基础条件 - 简化查询条件
$conditions = [
'status' => 1, // 启用状态
'work_status' => 1, // 工作中
];
// 门店条件优化:允许门店ID为0的骑手(全平台骑手)或匹配特定门店的骑手
if (!empty($orderInfo['store_id'])) {
// 使用OR条件:门店ID匹配或门店ID为0(全平台骑手)
$conditions['_string'] = "(store_id = {$orderInfo['store_id']} OR store_id = 0)";
}
// 获取骑手列表
$riders = $riderInfoModel->getList($conditions);
if (empty($riders)) {
return [];
}
// 过滤认证状态
$availableRiders = [];
foreach ($riders as $rider) {
// 检查认证状态
if (isset($rider['auth_status']) && $rider['auth_status'] == 2) {
$availableRiders[] = $rider;
}
}
return $this->filterRidersByLocation($availableRiders, $orderInfo, $options);
}
/**
* 根据位置过滤骑手
* @param array $riders 骑手列表
* @param array $orderInfo 订单信息
* @param array $options 选项
* @return array
*/
private function filterRidersByLocation($riders, $orderInfo, $options = [])
{
if (empty($riders)) {
return $riders;
}
// 如果没有门店位置信息,直接返回所有骑手
if (empty($orderInfo['store_lat']) || empty($orderInfo['store_lng'])) {
return $riders;
}
$maxDistance = $options['max_distance'] ?? 10; // 默认最大距离10公里
$riderLocationModel = D('RiderLocation');
$filteredRiders = [];
foreach ($riders as $rider) {
// 如果骑手没有位置信息,仍然包含在结果中(可能是新注册的骑手)
if (empty($rider['last_location_lat']) || empty($rider['last_location_lng'])) {
$rider['distance_to_store'] = 999; // 设置一个较大的距离值
$filteredRiders[] = $rider;
continue;
}
// 检查位置更新时间(放宽到2小时内)
if (isset($rider['last_location_time']) && strtotime($rider['last_location_time']) < (time() - 7200)) {
// 位置过期,但仍然包含,只是降低优先级
$rider['distance_to_store'] = 998;
$filteredRiders[] = $rider;
continue;
}
// 计算距离
$distance = $riderLocationModel->calculateDistance(
$rider['last_location_lat'],
$rider['last_location_lng'],
$orderInfo['store_lat'],
$orderInfo['store_lng']
);
if ($distance <= $maxDistance) {
$rider['distance_to_store'] = $distance;
$filteredRiders[] = $rider;
}
}
return $filteredRiders;
}
/**
* 计算匹配分数
* @param array $orderInfo 订单信息
* @param array $riders 骑手列表
* @return array
*/
private function calculateMatchScores($orderInfo, $riders)
{
$riderOrderModel = D('RiderOrder');
$scoredRiders = [];
foreach ($riders as $rider) {
$score = 0;
$factors = [];
// 1. 距离因子 (40分)
$distanceScore = $this->calculateDistanceScore($rider['distance_to_store'] ?? 0);
$score += $distanceScore;
$factors['distance'] = $distanceScore;
// 2. 骑手评分因子 (20分)
$ratingScore = $this->calculateRatingScore($rider['rating'] ?? 5);
$score += $ratingScore;
$factors['rating'] = $ratingScore;
// 3. 当前订单负载因子 (20分)
$currentOrders = $riderOrderModel->getRiderCurrentOrderCount($rider['id']);
$loadScore = $this->calculateLoadScore($currentOrders, $rider['max_orders'] ?? 3);
$score += $loadScore;
$factors['load'] = $loadScore;
// 4. 历史完成率因子 (10分)
$completionRate = $this->getRiderCompletionRate($rider['id']);
$completionScore = $this->calculateCompletionScore($completionRate);
$score += $completionScore;
$factors['completion'] = $completionScore;
// 5. 在线时长因子 (5分)
$onlineScore = $this->calculateOnlineScore($rider);
$score += $onlineScore;
$factors['online'] = $onlineScore;
// 6. 订单类型偏好因子 (5分)
$preferenceScore = $this->calculatePreferenceScore($rider, $orderInfo);
$score += $preferenceScore;
$factors['preference'] = $preferenceScore;
$rider['match_score'] = $score;
$rider['score_factors'] = $factors;
$scoredRiders[] = $rider;
}
return $scoredRiders;
}
/**
* 计算距离分数
* @param float $distance 距离(公里)
* @return float
*/
private function calculateDistanceScore($distance)
{
if ($distance <= 1) {
return 40;
} elseif ($distance <= 3) {
return 35;
} elseif ($distance <= 5) {
return 25;
} elseif ($distance <= 8) {
return 15;
} else {
return 5;
}
}
/**
* 计算评分分数
* @param float $rating 评分
* @return float
*/
private function calculateRatingScore($rating)
{
return ($rating / 5) * 20;
}
/**
* 计算负载分数
* @param int $currentOrders 当前订单数
* @param int $maxOrders 最大订单数
* @return float
*/
private function calculateLoadScore($currentOrders, $maxOrders)
{
if ($currentOrders >= $maxOrders) {
return 0;
}
$loadRate = $currentOrders / $maxOrders;
return (1 - $loadRate) * 20;
}
/**
* 计算完成率分数
* @param float $completionRate 完成率
* @return float
*/
private function calculateCompletionScore($completionRate)
{
return $completionRate * 10;
}
/**
* 计算在线时长分数
* @param array $rider 骑手信息
* @return float
*/
private function calculateOnlineScore($rider)
{
if (empty($rider['work_start_time'])) {
return 0;
}
$onlineHours = (time() - strtotime($rider['work_start_time'])) / 3600;
if ($onlineHours >= 4) {
return 5;
} elseif ($onlineHours >= 2) {
return 3;
} else {
return 1;
}
}
/**
* 计算偏好分数
* @param array $rider 骑手信息
* @param array $orderInfo 订单信息
* @return float
*/
private function calculatePreferenceScore($rider, $orderInfo)
{
// 这里可以根据骑手的历史订单类型偏好来计算
// 暂时返回固定分数
return 5;
}
/**
* 获取骑手完成率
* @param int $riderId 骑手ID
* @return float
*/
private function getRiderCompletionRate($riderId)
{
$riderOrderModel = D('RiderOrder');
// 获取最近30天的订单统计
$startTime = date('Y-m-d H:i:s', strtotime('-30 days'));
$totalOrders = $riderOrderModel->getCount([
'rider_id' => $riderId,
'add_time' => ['egt', $startTime]
]);
if ($totalOrders == 0) {
return 1.0; // 新骑手给满分
}
// 按当前状态口径:6为已送达
$completedOrders = $riderOrderModel->getCount([
'rider_id' => $riderId,
'order_status' => 6,
'add_time' => ['egt', $startTime]
]);
return $completedOrders / $totalOrders;
}
/**
* 选择最佳匹配
* @param array $riders 评分后的骑手列表
* @param array $options 选项
* @return array|null
*/
private function selectBestMatch($riders, $options = [])
{
if (empty($riders)) {
return null;
}
// 按分数排序
usort($riders, function($a, $b) {
return $b['match_score'] <=> $a['match_score'];
});
// 检查是否启用随机选择
if (isset($options['random_select']) && $options['random_select']) {
// 从前3名中随机选择
$topRiders = array_slice($riders, 0, 3);
return $topRiders[array_rand($topRiders)];
}
return $riders[0];
}
/**
* 验证订单信息
* @param array $orderInfo 订单信息
* @return bool
*/
private function validateOrderInfo($orderInfo)
{
$required = ['bill_id', 'store_id'];
foreach ($required as $field) {
if (empty($orderInfo[$field])) {
return false;
}
}
return true;
}
/**
* 记录匹配结果
* @param array $orderInfo 订单信息
* @param array $matchResult 匹配结果
* @return void
*/
private function logMatchResult($orderInfo, $matchResult)
{
$orderSyncLogModel = D('OrderSyncLog');
$logData = [
'rider_order_id' => 0,
'bill_id' => $orderInfo['bill_id'],
'action' => 'order_match',
'description' => sprintf(
'订单匹配成功,骑手ID:%d,匹配分数:%.2f',
$matchResult['id'],
$matchResult['match_score']
),
'extra_data' => json_encode([
'rider_id' => $matchResult['id'],
'match_score' => $matchResult['match_score'],
'score_factors' => $matchResult['score_factors'] ?? []
]),
'sync_time' => date('Y-m-d H:i:s'),
'add_time' => date('Y-m-d H:i:s')
];
$orderSyncLogModel->addLog($logData);
}
/**
* 重新匹配订单
* @param int $billId 订单ID
* @param array $excludeRiders 排除的骑手ID列表
* @return array
*/
public function rematch($billId, $excludeRiders = [])
{
// 获取订单信息
$retailBillOrderModel = D('RetailBillOrder');
$retailBillOnlineModel = D('RetailBillOnline');
$orderInfo = $retailBillOrderModel->getFind(['id' => $billId]);
if (empty($orderInfo)) {
return callBack(statusCode()['error'], '订单不存在');
}
$onlineInfo = $retailBillOnlineModel->getFind(['bill_id' => $billId]);
if (empty($onlineInfo)) {
return callBack(statusCode()['error'], '在线订单信息不存在');
}
// 合并订单信息
$fullOrderInfo = array_merge($orderInfo, $onlineInfo);
// 添加排除条件
$options = [];
if (!empty($excludeRiders)) {
$options['exclude_riders'] = $excludeRiders;
}
return $this->smartMatch($fullOrderInfo, $options);
}
/**
* 获取匹配统计
* @param string $startDate 开始日期
* @param string $endDate 结束日期
* @return array
*/
public function getMatchStats($startDate = '', $endDate = '')
{
if (empty($startDate)) {
$startDate = date('Y-m-d', strtotime('-7 days'));
}
if (empty($endDate)) {
$endDate = date('Y-m-d');
}
$orderSyncLogModel = D('OrderSyncLog');
// 获取匹配日志
$matchLogs = $orderSyncLogModel->getList([
'action' => 'order_match',
'sync_time' => ['between', [$startDate . ' 00:00:00', $endDate . ' 23:59:59']]
]);
$stats = [
'total_matches' => count($matchLogs),
'success_rate' => 0,
'avg_match_score' => 0,
'daily_stats' => []
];
if (!empty($matchLogs)) {
$totalScore = 0;
$dailyCount = [];
foreach ($matchLogs as $log) {
$date = date('Y-m-d', strtotime($log['sync_time']));
if (!isset($dailyCount[$date])) {
$dailyCount[$date] = 0;
}
$dailyCount[$date]++;
$extraData = json_decode($log['extra_data'], true);
if (isset($extraData['match_score'])) {
$totalScore += $extraData['match_score'];
}
}
$stats['avg_match_score'] = $totalScore / count($matchLogs);
$stats['daily_stats'] = $dailyCount;
}
return $stats;
}
}