12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082 |
- <?php
- namespace App\Services\Coach;
- use App\Enums\OrderGrabRecordStatus;
- use App\Enums\OrderRecordStatus;
- use App\Enums\OrderStatus;
- use App\Enums\OrderType;
- use App\Enums\ProjectStatus;
- use App\Enums\TechnicianAuthStatus;
- use App\Enums\TechnicianLocationType;
- use App\Enums\TechnicianStatus;
- use App\Jobs\AutoFinishOrder;
- use App\Models\CoachUser;
- use App\Models\MemberUser;
- use App\Models\Order;
- use App\Models\OrderGrabRecord;
- use App\Models\OrderRecord;
- use App\Models\SettingGroup;
- use App\Services\SettingItemService;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Log;
- use Illuminate\Support\Facades\Redis;
- class OrderService
- {
- private const DEFAULT_PER_PAGE = 10;
- private const MAX_DISTANCE = 40; // 最大距离限制(公里)
- private SettingItemService $settingService;
- private CommissionService $commissionService;
- public function __construct(SettingItemService $settingService, CommissionService $commissionService)
- {
- $this->settingService = $settingService;
- $this->commissionService = $commissionService;
- }
- /**
- * 获取技师的订单列表
- *
- * @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 = collect($paginator->items())->map(function ($order) {
- $order->type_text = OrderType::from($order->type)->label();
- $order->state_text = OrderStatus::from($order->state)->label();
- return $order;
- });
- $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::PAID->value, 400, '订单状态异常,无法接单');
- // 验证订单是否分配给该技师
- abort_if($order->coach_id !== $coach->id, 403, '该订单未分配给您');
- // 更新订单状态
- $order->state = OrderStatus::ACCEPTED->value;
- $order->save();
- // 清理技师相关缓存
- $this->clearCoachCache($coach->id, $order->service_time);
- // 记录日志
- 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;
- }
- });
- }
- /**
- * 技师拒单
- *
- * @param int $userId 用户ID
- * @param int $orderId 订单ID
- * @param string $reason 拒单原因
- */
- public function rejectOrder(int $userId, int $orderId, string $reason): array
- {
- return DB::transaction(function () use ($userId, $orderId, $reason) {
- 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(! in_array($order->state, [
- OrderStatus::ASSIGNED->value,
- OrderStatus::PAID->value,
- ]), 400, '订单状态异常,无法拒单');
- // 验证订单是否分配给该技师
- abort_if($order->coach_id !== $coach->id, 403, '该订单未分配给您');
- // 检查拒单次数限制
- $rejectCount = OrderRecord::where('object_id', $coach->id)
- ->where('object_type', CoachUser::class)
- ->where('state', OrderRecordStatus::REJECTED->value)
- ->whereDate('created_at', today())
- ->count();
- // 更新订单状态
- $order->update([
- 'state' => OrderStatus::REJECTED->value,
- ]);
- // 创建订单记录
- OrderRecord::create([
- 'order_id' => $order->id,
- 'object_id' => $coach->id,
- 'object_type' => CoachUser::class,
- 'state' => OrderRecordStatus::REJECTED->value,
- 'remark' => $reason,
- ]);
- // 发送消息通知
- try {
- // event(new OrderRejectedEvent($order, $coach, $reason));
- } catch (\Exception $e) {
- Log::error('发送拒单通知失败', [
- 'order_id' => $orderId,
- 'coach_id' => $coach->id,
- 'error' => $e->getMessage(),
- ]);
- }
- // 记录日志
- Log::info('技师拒单成功', [
- 'user_id' => $userId,
- 'coach_id' => $coach->id,
- 'order_id' => $orderId,
- 'order_no' => $order->order_no,
- 'reason' => $reason,
- 'reject_count' => $rejectCount + 1,
- ]);
- return [
- 'message' => '拒单成功',
- 'order_id' => $orderId,
- 'order_no' => $order->order_no,
- 'reject_count' => $rejectCount + 1,
- 'max_reject_count' => 5,
- ];
- } catch (\Exception $e) {
- Log::error('技师拒单失败', [
- 'user_id' => $userId,
- 'order_id' => $orderId,
- 'reason' => $reason,
- 'error' => $e->getMessage(),
- 'file' => $e->getFile(),
- 'line' => $e->getLine(),
- ]);
- throw $e;
- }
- });
- }
- /**
- * 技师出发
- *
- * @param int $userId 技师用户ID
- * @param int $orderId 订单ID
- */
- public function depart(int $userId, int $orderId): array
- {
- try {
- return DB::transaction(function () use ($userId, $orderId) {
- // 获取技师信息
- $user = MemberUser::with(['coach'])->findOrFail($userId);
- // 获取订单信息
- $order = Order::query()->where('id', $orderId)->lockForUpdate()->first();
- // 检查订单是否存在
- abort_if(! $order, 404, '订单不存在');
- // 检查是否是该技师的订单
- abort_if($order->coach_id !== $user->coach->id, 403, '无权操作此订单');
- // 根据订单类型判断订单状态
- if ($order->type == OrderType::VISIT->value) {
- abort_if($order->state != OrderStatus::ACCEPTED->value, 400, '订单状态不正确');
- } elseif ($order->type == OrderType::GRAB->value) {
- abort_if($order->state != OrderStatus::PAID->value, 400, '订单状态不正确');
- }
- // 更新订单状态为技师出发
- $order->state = OrderStatus::DEPARTED->value;
- $order->save();
- // 记录订单状态变更日志
- OrderRecord::create([
- 'order_id' => $orderId,
- 'state' => OrderRecordStatus::DEPARTED->value,
- 'object_id' => $user->coach->id,
- 'object_type' => CoachUser::class,
- 'remark' => '技师已出发',
- ]);
- // 发送通知给用户
- // TODO: 发送通知
- // event(new TechnicianDepartedEvent($order));
- return [
- 'status' => true,
- 'message' => '操作成功',
- 'data' => [
- 'order_id' => $orderId,
- 'status' => $order->state,
- 'created_at' => $order->created_at,
- ],
- ];
- });
- } catch (\Exception $e) {
- \Log::error('技师出发失败', [
- 'user_id' => $userId,
- 'order_id' => $orderId,
- 'error' => $e->getMessage(),
- ]);
- throw $e;
- }
- }
- /**
- * 技师到达
- *
- * @param int $userId 技师用户ID
- * @param int $orderId 订单ID
- */
- public function arrive(int $userId, int $orderId): array
- {
- return DB::transaction(function () use ($userId, $orderId) {
- try {
- // 获取技师信息
- $user = MemberUser::with(['coach'])->findOrFail($userId);
- $coach = $user->coach;
- abort_if(! $coach, 404, '技师信息不存在');
- // 获取订单信息
- $order = Order::query()->where('id', $orderId)->lockForUpdate()->first();
- abort_if(! $order, 404, '订单不存在');
- // 检查是否是该技师的订单
- abort_if($order->coach_id !== $coach->id, 403, '无权操作此订单');
- // 检查订单状态
- abort_if(! in_array($order->state, [
- OrderStatus::DEPARTED->value,
- ]), 400, '订单状态不正确');
- $now = now();
- // 更新订单状态为技师到达
- $order->state = OrderStatus::ARRIVED->value;
- $order->save();
- // 记录订单状态变更日志
- OrderRecord::create([
- 'order_id' => $orderId,
- 'state' => OrderRecordStatus::ARRIVED->value,
- 'object_id' => $coach->id,
- 'object_type' => CoachUser::class,
- 'remark' => '技师已到达',
- ]);
- // 更新技师当前位置到Redis GEO
- try {
- Redis::geoadd(
- 'coach_locations',
- $order->longitude,
- $order->latitude,
- $coach->id.'_'.TechnicianLocationType::CURRENT->value
- );
- } catch (\Exception $e) {
- Log::error('更新技师位置失败', [
- 'coach_id' => $coach->id,
- 'order_id' => $orderId,
- 'error' => $e->getMessage(),
- ]);
- }
- // TODO: 发送通知给用户
- // event(new TechnicianArrivedEvent($order));
- Log::info('技师到达成功', [
- 'coach_id' => $coach->id,
- 'order_id' => $orderId,
- 'arrived_at' => $now,
- ]);
- return [
- 'status' => true,
- 'message' => '操作成功',
- 'data' => [
- 'order_id' => $orderId,
- 'status' => $order->state,
- 'arrived_at' => $now,
- ],
- ];
- } catch (\Exception $e) {
- Log::error('技师到达失败', [
- 'user_id' => $userId,
- 'order_id' => $orderId,
- 'error' => $e->getMessage(),
- 'trace' => $e->getTraceAsString(),
- ]);
- throw $e;
- }
- });
- }
- /**
- * 技师扫码开始服务
- *
- * @param int $userId 技师用户ID
- * @param int $orderId 订单ID
- * @param string $qrCode 客户二维码
- */
- public function startService(int $userId, int $orderId, string $qrCode): array
- {
- return DB::transaction(function () use ($userId, $orderId, $qrCode) {
- try {
- // 获取技师信息
- $user = MemberUser::with(['coach'])->findOrFail($userId);
- $coach = $user->coach;
- abort_if(! $coach, 404, '技师信息不存在');
- // 获取订单信息
- $order = Order::query()
- ->where('id', $orderId)
- ->lockForUpdate()
- ->first();
- abort_if(! $order, 404, '订单不存在');
- // 检查是否是该技师的订单
- abort_if($order->coach_id !== $coach->id, 403, '无权操作此订单');
- // 检查订单状态
- abort_if(! in_array($order->state, [
- OrderStatus::ARRIVED->value,
- OrderStatus::PAID->value,
- ]), 400, '订单状态不正确');
- // 验证二维码
- $this->validateQrCode($order, $qrCode);
- $now = now();
- // 更新订单状态为服务中
- $order->state = OrderStatus::SERVING->value;
- $order->save();
- // 记录订单状态变更日志
- OrderRecord::create([
- 'order_id' => $orderId,
- 'state' => OrderRecordStatus::STARTED->value,
- 'object_id' => $coach->id,
- 'object_type' => CoachUser::class,
- 'remark' => '开始服务',
- ]);
- // 获取项目服务时长(分钟)
- $duration = $order->project->duration ?? 60;
- // 派发延迟任务,在服务时长到期后自动完成订单
- AutoFinishOrder::dispatch($order)
- ->delay(now()->addMinutes($duration));
- // TODO: 发送通知给用户
- // event(new ServiceStartedEvent($order));
- Log::info('技师开始服务', [
- 'coach_id' => $coach->id,
- 'order_id' => $orderId,
- 'service_start_time' => $now,
- ]);
- return [
- 'status' => true,
- 'message' => '开始服务成功',
- 'data' => [
- 'order_id' => $orderId,
- 'status' => $order->state,
- 'service_start_time' => $now,
- ],
- ];
- } catch (\Exception $e) {
- Log::error('开始服务失败', [
- 'user_id' => $userId,
- 'order_id' => $orderId,
- 'qr_code' => $qrCode,
- 'error' => $e->getMessage(),
- 'trace' => $e->getTraceAsString(),
- ]);
- throw $e;
- }
- });
- }
- /**
- * 验证客户二维码
- *
- * @param Order $order 订单对象
- * @param string $qrCode 扫描的二维码
- */
- private function validateQrCode(Order $order, string $qrCode): void
- {
- // 二维码格式: order_{order_id}_{timestamp}_{sign}
- $parts = explode('_', $qrCode);
- abort_if(count($parts) !== 4, 400, '二维码格式错误');
- [$prefix, $scanOrderId, $timestamp, $sign] = $parts;
- // 验证前缀
- abort_if($prefix !== 'order', 400, '无效的二维码');
- // 验证订单ID
- abort_if((int) $scanOrderId !== $order->id, 400, '二维码与订单不匹配');
- // 验证时间戳(二维码5分钟内有效)
- $qrTimestamp = (int) $timestamp;
- $now = time();
- abort_if($now - $qrTimestamp > 300, 400, '二维码已过期');
- // 验证签名
- $correctSign = md5("order_{$order->id}_{$timestamp}_".config('app.key'));
- abort_if($sign !== $correctSign, 400, '二维码签名错误');
- }
- /**
- * 技师撤离
- *
- * @param int $userId 技师户ID
- * @param int $orderId 订单ID
- *
- * @throws \Exception
- */
- public function leave(int $userId, int $orderId): array
- {
- return DB::transaction(function () use ($userId, $orderId) {
- try {
- // 获取技师信息
- $user = MemberUser::with(['coach'])->findOrFail($userId);
- $coach = $user->coach;
- abort_if(! $coach, 404, '技师信息不存在');
- // 获取订单信息
- $order = Order::query()
- ->with(['coach', 'coach.wallet'])
- ->where('id', $orderId)
- ->lockForUpdate()
- ->firstOrFail();
- // 验证订单状态和权限
- $this->validateLeaveOrder($order, $coach);
- // 更新订单状态
- $this->updateOrderStatus($order);
- // 记录订单状态变更
- $this->createLeaveRecord($order, $coach);
- // 处理订单分佣
- $this->commissionService->handleOrderCommission($order);
- // 清理技师位置信息
- $this->cleanCoachLocation($coach);
- // 记录日志
- Log::info('技师撤离成功', [
- 'coach_id' => $coach->id,
- 'order_id' => $orderId,
- 'leave_time' => now(),
- ]);
- // TODO: 发送通知给用户
- // event(new ServiceCompletedEvent($order));
- return [
- 'status' => true,
- 'message' => '撤离成功',
- 'data' => [
- 'order_id' => $orderId,
- 'state' => $order->state,
- 'leave_time' => now(),
- ],
- ];
- } catch (\Exception $e) {
- Log::error('技师撤离失败', [
- 'user_id' => $userId,
- 'order_id' => $orderId,
- 'error' => $e->getMessage(),
- 'trace' => $e->getTraceAsString(),
- ]);
- throw $e;
- }
- });
- }
- /**
- * 验证撤离订单
- */
- private function validateLeaveOrder(Order $order, CoachUser $coach): void
- {
- // 检查是否是该技师的订单
- abort_if($order->coach_id !== $coach->id, 403, '无权操作此订单');
- // 检查订单状态
- abort_if(! in_array($order->state, [
- OrderStatus::LEFT->value,
- ]), 400, '订单状态不正确,无法撤离');
- // 检查钱包状态
- abort_if(! $coach->wallet, 400, '技师钱包信息不存在');
- }
- /**
- * 更新订单状态
- */
- private function updateOrderStatus(Order $order): void
- {
- $order->state = OrderStatus::COMPLETED->value;
- $order->save();
- }
- /**
- * 创建撤离记录
- */
- private function createLeaveRecord(Order $order, CoachUser $coach): void
- {
- OrderRecord::create([
- 'order_id' => $order->id,
- 'state' => OrderRecordStatus::COMPLETED->value,
- 'object_id' => $coach->id,
- 'object_type' => CoachUser::class,
- 'remark' => '技师已撤离,服务完成',
- ]);
- }
- /**
- * 清理技师位置信息
- */
- private function cleanCoachLocation(CoachUser $coach): void
- {
- try {
- Redis::zrem(
- 'coach_locations',
- $coach->id.'_'.TechnicianLocationType::CURRENT->value
- );
- } catch (\Exception $e) {
- Log::error('删除技师位置失败', [
- 'coach_id' => $coach->id,
- 'error' => $e->getMessage(),
- ]);
- // 不抛出异常,继续执行
- }
- }
- /**
- * 清理技师相关缓存
- *
- * @param int $coachId 技师ID
- * @param string|null $date 服务日期
- */
- private function clearCoachCache(int $coachId, ?string $date = null): void
- {
- try {
- // 清理技师时间段缓存
- $date = $date ?: now()->toDateString();
- $cacheKey = "coach:timeslots:{$coachId}:{$date}";
- Cache::forget($cacheKey);
- Log::info('成功清理技师缓存', [
- 'coach_id' => $coachId,
- 'date' => $date,
- ]);
- } catch (\Exception $e) {
- Log::error('清理技师缓存失败', [
- 'coach_id' => $coachId,
- 'date' => $date,
- 'error' => $e->getMessage(),
- ]);
- // 缓存清理失败不影响主流程
- }
- }
- /**
- * 订单设置
- *
- * @param int $userId 技师用户ID
- * @param array $data 订单设置数据
- * @return array
- *
- * @throws \Exception
- */
- public function setOrder(int $userId, array $data)
- {
- return DB::transaction(function () use ($userId, $data) {
- try {
- // 获取技师信息
- $user = MemberUser::with(['coach'])->findOrFail($userId);
- $coach = $user->coach;
- abort_if(! $coach, 404, '技师信息不存在');
- // 获取订单配置分组
- $settingGroup = SettingGroup::where('code', 'order')->first();
- abort_if(! $settingGroup, 404, '订单配置分组不存在');
- // 获取距离配置项
- $distanceSettingItem = $settingGroup->items()->where('code', 'distance')->first();
- abort_if(! $distanceSettingItem, 404, '距离配置项不存在');
- // 检查服务距离是否存在,并且是否大于0
- if (isset($data['distance']) && $data['distance'] > 0) {
- // 从全局配置中验证服务距离的合理范围
- abort_if($data['distance'] > $distanceSettingItem->max_value, 422, '服务距离不能超过'.$distanceSettingItem->max_value.'公里');
- // 更新技师服务距离配置
- $coach->settingValues()->updateOrCreate(
- ['item_id' => $distanceSettingItem->id],
- ['value' => $data['distance']],
- ['object_type' => $coach::class],
- ['object_id' => $coach->id],
- );
- }
- // 记录日志
- Log::info('订单设置更新成功', [
- 'coach_id' => $coach->id,
- 'settings' => $data,
- ]);
- return [
- 'message' => '订单设置更新成功',
- 'coach_id' => $coach->id,
- 'settings' => $data,
- ];
- } catch (\Exception $e) {
- Log::error('订单设置更新失败', [
- 'user_id' => $userId,
- 'data' => $data,
- 'error' => $e->getMessage(),
- 'file' => $e->getFile(),
- 'line' => $e->getLine(),
- ]);
- throw $e;
- }
- });
- }
- }
|