|
@@ -6,9 +6,11 @@ use App\Enums\OrderStatus;
|
|
|
use App\Enums\TechnicianAuthStatus;
|
|
|
use App\Enums\TechnicianLocationType;
|
|
|
use App\Enums\TechnicianStatus;
|
|
|
+use App\Enums\TechnicianWorkStatus;
|
|
|
use App\Enums\UserStatus;
|
|
|
use App\Models\CoachSchedule;
|
|
|
use App\Models\CoachUser;
|
|
|
+use App\Models\MemberUser;
|
|
|
use App\Models\Order;
|
|
|
use Illuminate\Support\Carbon;
|
|
|
use Illuminate\Support\Facades\Auth;
|
|
@@ -20,67 +22,136 @@ class CoachService
|
|
|
{
|
|
|
/**
|
|
|
* 获取技师列表
|
|
|
+ *
|
|
|
+ * @param int $userId 用户ID
|
|
|
+ * @param float $latitude 纬度
|
|
|
+ * @param float $longitude 经度
|
|
|
+ * @return array ['items' => array, 'total' => int]
|
|
|
+ *
|
|
|
+ * @throws \Exception
|
|
|
*/
|
|
|
- public function getNearCoachList($latitude, $longitude)
|
|
|
+ public function getNearCoachList(int $userId, float $latitude, float $longitude)
|
|
|
{
|
|
|
- $page = request()->get('page', 1);
|
|
|
- $perPage = request()->get('per_page', 15);
|
|
|
- // 获取当前用户
|
|
|
- $user = Auth::user();
|
|
|
-
|
|
|
- Log::info('Current user and coordinates:', [
|
|
|
- 'user' => $user ? $user->id : null,
|
|
|
- 'latitude' => $latitude,
|
|
|
- 'longitude' => $longitude,
|
|
|
- ]);
|
|
|
-
|
|
|
- // 检查用户状态
|
|
|
- if (! $user) {
|
|
|
- throw new \Exception('用户未登录');
|
|
|
- }
|
|
|
+ try {
|
|
|
+ // 参数验证
|
|
|
+ abort_if(! is_numeric($latitude) || ! is_numeric($longitude), 400, '无效的经纬度坐标');
|
|
|
+ abort_if($latitude < -90 || $latitude > 90 || $longitude < -180 || $longitude > 180,
|
|
|
+ 400, '经纬度超出有效范围');
|
|
|
|
|
|
- if ($user->state !== 'enable') {
|
|
|
- throw new \Exception('用户状态异常');
|
|
|
- }
|
|
|
+ $page = max(1, (int) request()->get('page', 1));
|
|
|
+ $perPage = max(1, min(50, (int) request()->get('per_page', 15)));
|
|
|
|
|
|
- // 使用 Redis 的 georadius 命令获取附近的技师 ID
|
|
|
- $nearbyCoachIds = Redis::georadius('coach_locations', $longitude, $latitude, 40, 'km', ['WITHDIST']);
|
|
|
+ // 获取当前用户并检查状态
|
|
|
+ $user = MemberUser::select(['id', 'state'])->find($userId);
|
|
|
+ abort_if(! $user, 404, '用户不存在');
|
|
|
+ abort_if($user->state !== UserStatus::OPEN->value, 400, '用户状态异常');
|
|
|
|
|
|
- $coachData = array_map(function ($item) {
|
|
|
- [$id, $type] = explode('_', $item[0]);
|
|
|
+ Log::info('开始获取附近技师列表', [
|
|
|
+ 'user_id' => $userId,
|
|
|
+ 'latitude' => $latitude,
|
|
|
+ 'longitude' => $longitude,
|
|
|
+ 'page' => $page,
|
|
|
+ 'per_page' => $perPage,
|
|
|
+ ]);
|
|
|
|
|
|
- return ['id' => $id, 'type' => $type, 'distance' => $item[1]];
|
|
|
- }, $nearbyCoachIds);
|
|
|
+ // 使用缓存减少Redis查询压力
|
|
|
+ $cacheKey = "nearby_coaches:{$latitude}:{$longitude}";
|
|
|
+ $nearbyCoachIds = Cache::remember($cacheKey, now()->addMinutes(1), function () use ($longitude, $latitude) {
|
|
|
+ return Redis::georadius(
|
|
|
+ 'coach_locations',
|
|
|
+ $longitude,
|
|
|
+ $latitude,
|
|
|
+ 40,
|
|
|
+ 'km',
|
|
|
+ ['WITHDIST', 'ASC']
|
|
|
+ ) ?: [];
|
|
|
+ });
|
|
|
+
|
|
|
+ if (empty($nearbyCoachIds)) {
|
|
|
+ Log::info('附近没有找到技师', compact('latitude', 'longitude'));
|
|
|
+
|
|
|
+ return ['items' => [], 'total' => 0];
|
|
|
+ }
|
|
|
|
|
|
- // 提取所有的id
|
|
|
- $coachIds = array_unique(array_column($coachData, 'id'));
|
|
|
+ // 处理技师数据
|
|
|
+ $coachData = collect($nearbyCoachIds)->map(function ($item) {
|
|
|
+ [$id, $type] = explode('_', $item[0]);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'id' => (int) $id,
|
|
|
+ 'type' => $type,
|
|
|
+ 'distance' => round((float) $item[1], 2),
|
|
|
+ ];
|
|
|
+ })->unique('id')->values();
|
|
|
+
|
|
|
+ // 分页处理
|
|
|
+ $total = $coachData->count();
|
|
|
+ $offset = ($page - 1) * $perPage;
|
|
|
+ $paginatedCoachIds = $coachData->slice($offset, $perPage)
|
|
|
+ ->pluck('id')
|
|
|
+ ->toArray();
|
|
|
+
|
|
|
+ if (empty($paginatedCoachIds)) {
|
|
|
+ return ['items' => [], 'total' => 0];
|
|
|
+ }
|
|
|
|
|
|
- // 分页截取 coachIds
|
|
|
- $paginatedCoachIds = array_slice($coachIds, ($page - 1) * $perPage, $perPage);
|
|
|
+ // 查询数据库获取技师信息
|
|
|
+ $coaches = CoachUser::query()
|
|
|
+ ->whereIn('id', $paginatedCoachIds)
|
|
|
+ ->where('state', TechnicianStatus::ACTIVE->value)
|
|
|
+ ->whereHas('info', function ($query) {
|
|
|
+ $query->where('state', TechnicianAuthStatus::PASSED->value);
|
|
|
+ })
|
|
|
+ ->whereHas('real', function ($query) {
|
|
|
+ $query->where('state', TechnicianAuthStatus::PASSED->value);
|
|
|
+ })
|
|
|
+ ->whereHas('qual', function ($query) {
|
|
|
+ $query->where('state', TechnicianAuthStatus::PASSED->value);
|
|
|
+ })
|
|
|
+ ->with(['info' => function ($query) {
|
|
|
+ $query->select(['id', 'coach_id', 'nickname', 'avatar', 'gender', 'state']);
|
|
|
+ }])
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ // 设置距离并格式化数据
|
|
|
+ $items = $coaches->map(function ($coach) use ($coachData) {
|
|
|
+ $distanceData = $coachData->firstWhere('id', $coach->id);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'id' => $coach->id,
|
|
|
+ 'nickname' => $coach->info->nickname ?? '',
|
|
|
+ 'avatar' => $coach->info->avatar ?? '',
|
|
|
+ 'gender' => $coach->info->gender ?? '',
|
|
|
+ 'state' => $coach->state,
|
|
|
+ 'state_text' => TechnicianStatus::fromValue($coach->state)->label(),
|
|
|
+ 'work_status' => $coach->work_status,
|
|
|
+ 'work_status_text' => TechnicianWorkStatus::fromValue($coach->work_status)->label(),
|
|
|
+ 'distance' => $distanceData ? $distanceData['distance'] : null,
|
|
|
+ ];
|
|
|
+ })->sortBy('distance')->values()->all();
|
|
|
+
|
|
|
+ Log::info('成功获取附近技师列表', [
|
|
|
+ 'total' => $total,
|
|
|
+ 'page' => $page,
|
|
|
+ 'per_page' => $perPage,
|
|
|
+ 'found' => count($items),
|
|
|
+ ]);
|
|
|
|
|
|
- // 查询数据库获取技师信息
|
|
|
- $coaches = CoachUser::query()
|
|
|
- ->whereIn('id', $paginatedCoachIds)
|
|
|
- ->whereHas('info', function ($query) {
|
|
|
- $query->where('state', 'approved');
|
|
|
- })
|
|
|
- ->whereHas('real', function ($query) {
|
|
|
- $query->where('state', 'approved');
|
|
|
- })
|
|
|
- ->whereHas('qual', function ($query) {
|
|
|
- $query->where('state', 'approved');
|
|
|
- })
|
|
|
- ->with(['info:id,nickname,avatar,gender'])
|
|
|
- ->paginate($perPage);
|
|
|
+ return [
|
|
|
+ 'items' => $items,
|
|
|
+ 'total' => $total,
|
|
|
+ ];
|
|
|
|
|
|
- // 遍历技师并设置距离
|
|
|
- foreach ($coaches as $coach) {
|
|
|
- $coach->distance = round($coachData[array_search($coach->id, array_column($coachData, 'id'))]['distance'] ?? null, 2);
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('获取附近技师列表失败', [
|
|
|
+ 'user_id' => $userId,
|
|
|
+ 'latitude' => $latitude,
|
|
|
+ 'longitude' => $longitude,
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ 'trace' => $e->getTraceAsString(),
|
|
|
+ ]);
|
|
|
+ throw $e;
|
|
|
}
|
|
|
- // 按 distance 升序排序
|
|
|
- $coaches = $coaches->sortBy('distance')->values();
|
|
|
-
|
|
|
- return $coaches;
|
|
|
}
|
|
|
|
|
|
/**
|