settingService = $settingService; } /** * 获取技师的订单列表 * * @param int $userId 技师用户ID * @param array $params 请求参数 * @return \Illuminate\Pagination\LengthAwarePaginator */ public function getOrderList(int $userId, array $params) { return DB::transaction(function () use ($userId, $params) { try { // 加载技师用户信息 $user = MemberUser::findOrFail($userId); $coach = $user->coach; abort_if(! $coach, 404, '技师不存在'); // 构建订单查询 $query = $coach->orders()->whereNotIn('state', [ OrderStatus::CREATED->value, OrderStatus::ASSIGNED->value, ]) ->orderBy('created_at', 'desc'); // 分页获取数据 $paginator = $query->paginate( $params['per_page'] ?? 10, ['*'], 'page', $params['page'] ?? 1 ); // 需要加载关联数据 $items = $paginator->items(); $query->with(['project']); // 加载项目信息以便返回project_name等字段 return [ 'items' => $items, 'total' => $paginator->total(), ]; } catch (\Exception $e) { Log::error('获取技师订单列表失败', [ 'user_id' => $userId, 'error_message' => $e->getMessage(), 'error_trace' => $e->getTraceAsString(), ]); throw $e; } }); } /** * 获取可抢订单列表 * * @param int $userId 技师用户ID * @param array $params 请求参数 * @return \Illuminate\Pagination\LengthAwarePaginator */ public function getGrabList(int $userId, array $params) { try { // 加载用户和技师信息 $user = MemberUser::with([ 'coach', 'coach.info', 'coach.real', 'coach.qual', 'coach.locations', 'coach.projects', ])->findOrFail($userId); // 验证技师信息 [$coach, $location] = $this->validateCoach($user); // 获取技师项目信息 $coachProjects = $coach->projects; $coachProjectIds = $coachProjects->pluck('project_id')->toArray(); // 获取系统配置 $settings = $this->getSystemSettings(); // 获取附近订单 $orderDistances = $this->getNearbyOrders($location); // 构建订单查询 $query = $this->buildOrderQuery($coachProjectIds, $orderDistances); // 处理订单数据 $this->processOrderData($query, $orderDistances, $coachProjects, $settings); // 分页获取数据 return $query->paginate( $params['per_page'] ?? 10, ['*'], 'page', $params['page'] ?? 1 ); } catch (\Exception $e) { Log::error('获取可抢订单列表失败', [ 'user_id' => $userId, 'params' => $params, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); throw $e; } } /** * 验证技师信息 */ private function validateCoach(MemberUser $user): array { $coach = $user->coach; abort_if(! $coach, 404, '技师不存在'); abort_if(! $coach->info, 404, '技师信息不存在'); abort_if($coach->info->state != TechnicianStatus::ACTIVE->value, 404, '技师状态异常'); abort_if($coach->real->state != TechnicianAuthStatus::PASSED->value, 404, '技师实名认证未通过'); abort_if($coach->qual->state != TechnicianAuthStatus::PASSED->value, 404, '技师资质认证未通过'); $location = $coach->locations() ->where('type', TechnicianLocationType::COMMON->value) ->first(); abort_if(! $location, 404, '技师定位地址不存在'); return [$coach, $location]; } /** * 获取系统配置 */ private function getSystemSettings(): array { return [ 'coach_income' => $this->settingService->getItemDetail('coach_income')->default_value ?? 0, 'min_distance' => $this->settingService->getItemDetail('qibugongli')->default_value ?? 0, 'base_price' => $this->settingService->getItemDetail('qibujia')->default_value ?? 0, 'per_km_price' => $this->settingService->getItemDetail('meigonglijiage')->default_value ?? 0, ]; } /** * 获取附近订单 */ private function getNearbyOrders($location, float $maxDistance = 40): array { $nearbyOrders = Redis::georadius( 'order_locations', $location->longitude, $location->latitude, $maxDistance, 'km', ['WITHCOORD', 'WITHDIST'] ); if (! $nearbyOrders) { Log::warning('获取附近订单失败', [ 'location' => [ 'longitude' => $location->longitude, 'latitude' => $location->latitude, ], ]); return []; } $orderDistances = []; foreach ($nearbyOrders as $order) { $orderId = str_replace('order:', '', $order[0]); $distance = round($order[1], 1); $orderDistances[$orderId] = $distance; } return $orderDistances; } /** * 构建订单查询 */ private function buildOrderQuery(array $coachProjectIds, array $orderDistances) { return Order::query() ->select([ 'id', 'order_no', 'project_id', 'location', 'address', 'latitude', 'longitude', 'service_time', 'created_at', 'agent_id', DB::raw('0 as distance'), ]) ->with(['project', 'agent.projects']) ->where('state', OrderStatus::CREATED) ->whereIn('project_id', $coachProjectIds) ->whereIn('id', array_keys($orderDistances)) ->orderBy('created_at', 'desc'); } /** * 处理订单数据 */ private function processOrderData($query, array $orderDistances, $coachProjects, array $settings): void { $query->get()->each(function ($order) use ($orderDistances, $coachProjects, $settings) { // 设置距离 $order->distance = $orderDistances[$order->id]; // 处理代理商项目价格 $this->processAgentPrice($order); // 处理技师项目信息 $this->processCoachProject($order, $coachProjects, $settings); }); } /** * 处理代理商价格 */ private function processAgentPrice($order): void { if ($order->agent_id && $order->agent && $order->agent->projects) { $agentProject = $order->agent->projects ->where('id', $order->project_id) ->where('state', 1) ->first(); if ($agentProject) { $order->project->price = $agentProject->agent_price; $order->project->duration = $agentProject->duration; } } } /** * 处理技师项目信息 */ private function processCoachProject($order, $coachProjects, array $settings): void { $coachProject = $coachProjects->where('project_id', $order->project_id)->first(); if (! $coachProject) { return; } // 计算技师佣金 $order->project->coach_income = round($order->project->price * $settings['coach_income'], 2); // 处理优惠金额 if ($coachProject->discount_amount > 0) { $order->project->discount_amount = $coachProject->discount_amount; $order->project->final_price = $order->project->price - $coachProject->discount_amount; } // 处理路费 if ($coachProject->traffic_fee > 0) { $this->calculateTrafficFee($order, $coachProject, $settings); } } /** * 计算路费 */ private function calculateTrafficFee($order, $coachProject, array $settings): void { $trafficFee = $settings['base_price']; if ($order->distance > $settings['min_distance']) { $trafficFee += ($order->distance - $settings['min_distance']) * $settings['per_km_price']; } $order->project->traffic_fee = round($trafficFee, 2); // 双程收费 if ($coachProject->is_round_trip) { $order->project->traffic_fee *= 2; } // 计算最终价格 $order->project->final_price = ($order->project->final_price ?? $order->project->price) + $order->project->traffic_fee; } /** * 技师抢单 * * @param int $userId 技师用户ID * @param int $orderId 订单ID * @return array */ public function grabOrder(int $userId, int $orderId) { return DB::transaction(function () use ($userId, $orderId) { try { // 加载用户和技师信息 $user = MemberUser::with([ 'coach', 'coach.info', 'coach.real', 'coach.qual', 'coach.locations', 'coach.projects', ])->findOrFail($userId); // 验证技师信息 [$coach, $location] = $this->validateCoach($user); // 获取订单信息 $order = Order::lockForUpdate()->findOrFail($orderId); // 验证订单状态 abort_if($order->state !== OrderStatus::CREATED->value, 400, '订单状态异常,无法抢单'); // 验证订单类型 abort_if($order->type !== OrderType::GRAB->value, 400, '该订单不是抢单类型'); // 检查技师是否已参与抢单 $existingGrab = $coach->grabRecords() ->where('order_id', $orderId) ->first(); abort_if($existingGrab, 400, '您已参与抢单,请勿重复操作'); // 验证订单是否在技师服务范围内 // 通过Redis GEO计算订单与技师的距离 $distance = Redis::geodist( 'order_locations', 'order:'.$order->id, $location->longitude.','.$location->latitude, 'km' ) ?? PHP_FLOAT_MAX; abort_if($distance > self::MAX_DISTANCE, 400, '订单超出服务范围'); // 验证技师是否具备该项目服务资格 $coachProject = $coach->projects() ->where('project_id', $order->project_id) ->where('state', ProjectStatus::OPEN->value) ->first(); abort_if(! $coachProject, 400, '未开通该项目服务资格'); // 添加抢单记录 OrderGrabRecord::create([ 'order_id' => $order->id, 'coach_id' => $coach->id, 'distance' => $distance, 'state' => OrderGrabRecordStatus::JOINED->value, 'created_at' => now(), ]); // 记录日志 Log::info('技师参与抢单', [ 'user_id' => $userId, 'coach_id' => $coach->id, 'order_id' => $orderId, ]); return [ 'message' => '已参与抢单', 'order_id' => $orderId, ]; } catch (\Exception $e) { Log::error('技师参与抢单失败', [ 'user_id' => $userId, 'order_id' => $orderId, 'error_message' => $e->getMessage(), 'error_trace' => $e->getTraceAsString(), ]); throw $e; } }); } /** * 技师接单 * * @param int $userId 技师用户ID * @param int $orderId 订单ID */ public function acceptOrder(int $userId, int $orderId): array { return DB::transaction(function () use ($userId, $orderId) { try { // 加载用户和技师信息 $user = MemberUser::with([ 'coach', 'coach.info', 'coach.real', 'coach.qual', ])->findOrFail($userId); // 验证技师信息 [$coach, $location] = $this->validateCoach($user); // 获取订单信息并加锁 $order = Order::lockForUpdate()->findOrFail($orderId); // 验证订单状态 abort_if($order->state !== OrderStatus::ASSIGNED->value, 400, '订单状态异常,无法接单'); // 验证订单是否分配给该技师 abort_if($order->coach_id !== $coach->id, 403, '该订单未分配给您'); // 更新订单状态 $order->update([ 'state' => OrderStatus::ACCEPTED->value, 'accepted_at' => now(), ]); // 记录日志 Log::info('技师接单成功', [ 'user_id' => $userId, 'coach_id' => $coach->id, 'order_id' => $orderId, 'order_no' => $order->order_no, ]); return [ 'message' => '接单成功', 'order_id' => $orderId, 'order_no' => $order->order_no, ]; } catch (\Exception $e) { Log::error('技师接单失败', [ 'user_id' => $userId, 'order_id' => $orderId, 'error' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), ]); throw $e; } }); } }