Bläddra i källkod

feat:技师定位、技师管理

刘学玺 4 månader sedan
förälder
incheckning
5a670eebd0

+ 32 - 69
app/Http/Controllers/Client/CoachController.php

@@ -20,37 +20,23 @@ class CoachController extends Controller
      *
      * 根据经纬度获取技师列表
      *
-     * @return \Illuminate\Http\JsonResponse
-     *
-     * @OA\Get(
-     *     path="/api/coaches",
-     *     summary="获取技师列表",
-     *     description="根据经纬度获取技师列表",
-     *     tags={"Coaches"},
-     *
-     *     @OA\Parameter(
-     *         name="latitude",
-     *         in="query",
-     *         required=true,
-     *
-     *         @OA\Schema(type="number", format="float"),
-     *         description="纬度"
-     *     ),
-     *
-     *     @OA\Parameter(
-     *         name="longitude",
-     *         in="query",
-     *         required=true,
-     *
-     *         @OA\Schema(type="number", format="float"),
-     *         description="经度"
-     *     ),
-     *
-     *     @OA\Response(
-     *         response=200,
-     *         description="成功获取技师列表"
-     *     )
-     * )
+     * @authenticated
+     *
+     * @queryParam latitude float required 纬度. Example: 34.0522
+     * @queryParam longitude float required 经度. Example: -118.2437
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "获取成功",
+     *   "data": [
+     *     {
+     *       "id": 1,
+     *       "name": "技师A",
+     *       "latitude": 34.0522,
+     *       "longitude": -118.2437
+     *     }
+     *   ]
+     * }
      */
     public function list(Request $request)
     {
@@ -65,47 +51,24 @@ class CoachController extends Controller
      *
      * 根据ID获取技师的详细信息
      *
-     * @param  int  $id
-     * @return \Illuminate\Http\JsonResponse
-     *
-     * @OA\Get(
-     *     path="/api/coaches/{id}",
-     *     summary="获取技师详情",
-     *     description="根据ID获取技师的详细信息",
-     *     tags={"Coaches"},
-     *
-     *     @OA\Parameter(
-     *         name="id",
-     *         in="path",
-     *         required=true,
-     *
-     *         @OA\Schema(type="integer"),
-     *         description="技师ID"
-     *     ),
-     *
-     *     @OA\Parameter(
-     *         name="latitude",
-     *         in="query",
-     *         required=false,
-     *
-     *         @OA\Schema(type="number", format="float"),
-     *         description="纬度"
-     *     ),
+     * @authenticated
      *
-     *     @OA\Parameter(
-     *         name="longitude",
-     *         in="query",
-     *         required=false,
+     * @urlParam id int required 技师ID. Example: 1
      *
-     *         @OA\Schema(type="number", format="float"),
-     *         description="经度"
-     *     ),
+     * @queryParam latitude float 纬度. Example: 34.0522
+     * @queryParam longitude float 经度. Example: -118.2437
      *
-     *     @OA\Response(
-     *         response=200,
-     *         description="成功获取技师详情"
-     *     )
-     * )
+     * @response {
+     *   "code": 200,
+     *   "message": "获取成功",
+     *   "data": {
+     *     "id": 1,
+     *     "name": "技师A",
+     *     "latitude": 34.0522,
+     *     "longitude": -118.2437,
+     *     "details": "详细信息"
+     *   }
+     * }
      */
     public function detail(Request $request, $id)
     {

+ 102 - 0
app/Http/Controllers/Client/CoachLocationController.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace App\Http\Controllers\Client;
+
+use App\Http\Controllers\Controller;
+use App\Services\Client\CoachLocationService;
+use Illuminate\Http\Request;
+
+class CoachLocationController extends Controller
+{
+    protected $coachLocationService;
+
+    /**
+     * CoachLocationController constructor.
+     */
+    public function __construct(CoachLocationService $coachLocationService)
+    {
+        $this->coachLocationService = $coachLocationService;
+    }
+
+    /**
+     * [技师定位管理] 获取定位列表
+     *
+     * @authenticated
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "获取成功",
+     *   "data": [
+     *     {
+     *       "id": 1,
+     *       "type": "home",
+     *       "latitude": 34.0522,
+     *       "longitude": -118.2437,
+     *       "city": "Los Angeles",
+     *       "district": "Downtown",
+     *       "location": "123 Main St",
+     *       "area_code": "90001"
+     *     }
+     *   ]
+     * }
+     */
+    public function index()
+    {
+        return $this->coachLocationService->getAllLocations();
+    }
+
+    /**
+     * [技师定位管理] 创建定位
+     *
+     * @authenticated
+     *
+     * @bodyParam type string required 类型. Example: home
+     * @bodyParam latitude float required 纬度. Example: 34.0522
+     * @bodyParam longitude float required 经度. Example: -118.2437
+     * @bodyParam city string required 市. Example: Los Angeles
+     * @bodyParam district string required 区. Example: Downtown
+     * @bodyParam location string required 详细地址. Example: 123 Main St
+     * @bodyParam area_code string required 区域编码. Example: 90001
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "创建成功",
+     *   "data": {
+     *     "id": 1,
+     *     "type": "home",
+     *     "latitude": 34.0522,
+     *     "longitude": -118.2437,
+     *     "city": "Los Angeles",
+     *     "district": "Downtown",
+     *     "location": "123 Main St",
+     *     "area_code": "90001"
+     *   }
+     * }
+     */
+    public function store(Request $request)
+    {
+        return $this->coachLocationService->createLocation($request->all());
+    }
+
+    /**
+     * [技师定位管理] 删除定位
+     *
+     * @authenticated
+     *
+     * @bodyParam coach_id int required 技师ID. Example: 1
+     * @bodyParam type string required 类型. Example: home
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "删除成功",
+     *   "data": null
+     * }
+     */
+    public function destroy(Request $request)
+    {
+        $type = $request->type;
+        $coachId = $request->coach_id;
+
+        return $this->coachLocationService->deleteLocation($coachId, $type);
+    }
+}

+ 57 - 0
app/Logging/CustomizeFormatter.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace App\Logging;
+
+use Illuminate\Log\Logger;
+use Monolog\Formatter\LineFormatter;
+
+class CustomizeFormatter
+{
+    /**
+     * 自定义给定的日志实例。
+     */
+    public function __invoke(Logger $logger): void
+    {
+        foreach ($logger->getHandlers() as $handler) {
+            $handler->setFormatter(new LineFormatter(
+                "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n",
+                'Y-m-d H:i:s',
+                true,
+                true
+            ));
+
+            // 添加更严格的过滤器
+            $handler->pushProcessor(function ($record) {
+                // 1. 允许SQL日志
+                if (str_contains($record['message'], 'sql:')) {
+                    return $record;
+                }
+
+                // 2. 过滤所有来自vendor目录的日志
+                if (isset($record['file']) && str_contains($record['file'], '/vendor/')) {
+                    return false;
+                }
+
+                // 3. 过滤框架的默认日志通道
+                if (isset($record['channel']) && in_array($record['channel'], [
+                    'laravel',
+                    'framework',
+                    'security',
+                    'request',
+                    'schedule',
+                    'queue',
+                ])) {
+                    return false;
+                }
+
+                // 4. 只允许来自app目录的日志
+                if (isset($record['file']) && str_contains($record['file'], '/app/')) {
+                    return $record;
+                }
+
+                // 5. 默认过滤掉所有其他日志
+                return false;
+            });
+        }
+    }
+}

+ 5 - 3
app/Models/CoachLocation.php

@@ -10,7 +10,9 @@ use Slowlyo\OwlAdmin\Models\BaseModel as Model;
  */
 class CoachLocation extends Model
 {
-	use SoftDeletes;
+    use SoftDeletes;
 
-	protected $table = 'coach_locations';
-}
+    protected $table = 'coach_locations';
+
+    protected $guarded = [];
+}

+ 10 - 1
app/Providers/AppServiceProvider.php

@@ -2,6 +2,8 @@
 
 namespace App\Providers;
 
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
 use Illuminate\Support\ServiceProvider;
 
 class AppServiceProvider extends ServiceProvider
@@ -19,6 +21,13 @@ class AppServiceProvider extends ServiceProvider
      */
     public function boot(): void
     {
-        //
+        if (config('app.debug')) {
+            DB::listen(function ($query) {
+                Log::debug('sql: '.$query->sql, [
+                    'bindings' => $query->bindings,
+                    'time' => $query->time,
+                ]);
+            });
+        }
     }
 }

+ 78 - 0
app/Services/Client/CoachLocationService.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace App\Services\Client;
+
+use App\Models\CoachLocation;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Redis;
+
+class CoachLocationService
+{
+    /**
+     * 获取技师定位地址列表
+     */
+    public function getAllLocations()
+    {
+        // 从 Redis 中获取所有技师定位地址
+        $locationNames = Redis::zrange('coach_locations', 0, -1);
+
+        $result = [];
+        foreach ($locationNames as $locationName) {
+            $positions = Redis::geopos('coach_locations', $locationName);
+            if (! empty($positions) && isset($positions[0])) {
+                [$longitude, $latitude] = $positions[0];
+                [$coachId, $type] = explode('_', $locationName);
+                $result[] = [
+                    'coach_id' => $coachId,
+                    'type' => $type,
+                    'longitude' => $longitude,
+                    'latitude' => $latitude,
+                ];
+            }
+        }
+
+        return response()->json($result);
+        // return CoachLocation::all();
+    }
+
+    /**
+     * 创建技师定位地址
+     */
+    public function createLocation(array $data)
+    {
+        $user = Auth::user();
+        $coach = $user->coach;
+        $longitude = $data['longitude'];
+        $latitude = $data['latitude'];
+        $type = $data['type'];
+        $locationName = $coach->id.'_'.$type; // 假设数据中有一个名称字段
+
+        // 使用 Redis 的 GEOADD 命令
+        Redis::geoadd('coach_locations', $longitude, $latitude, $locationName);
+        $location = $coach->locations()->where('type', $type)->first();
+        if ($location) {
+            $location->update($data);
+        } else {
+            $coach->locations()->create($data);
+        }
+
+        return response()->json(['message' => 'Location added successfully']);
+    }
+
+    /**
+     * 删除技师定位地址
+     */
+    public function deleteLocation($coachId, $type)
+    {
+        $location = CoachLocation::where('type', $type)
+            ->where('coach_id', $coachId)
+            ->first();
+        $location?->delete();
+
+        // 从 Redis 中删除地理位置记录
+        $locationName = $coachId.'_'.$type;
+        Redis::zrem('coach_locations', $locationName);
+
+        return response()->json(['message' => 'Deleted successfully']);
+    }
+}

+ 43 - 25
app/Services/Client/CoachService.php

@@ -3,8 +3,8 @@
 namespace App\Services\Client;
 
 use App\Models\CoachUser;
-use App\Models\MemberUser;
 use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Redis;
 
 class CoachService
 {
@@ -13,6 +13,8 @@ class CoachService
      */
     public function getCoachList($latitude, $longitude)
     {
+        $page = request()->get('page', 1);
+        $perPage = request()->get('per_page', 15);
         // 获取当前用户
         $user = Auth::user();
 
@@ -21,9 +23,24 @@ class CoachService
             throw new \Exception('用户状态异常');
         }
 
-        // 获取附近的技师
+        // 使用 Redis 的 georadius 命令获取附近的技师 ID
+        $nearbyCoachIds = Redis::georadius('coach_locations', $longitude, $latitude, 40, 'km', ['WITHDIST']);
+
+        $coachData = array_map(function ($item) {
+            [$id, $type] = explode('_', $item[0]);
+
+            return ['id' => $id, 'type' => $type, 'distance' => $item[1]];
+        }, $nearbyCoachIds);
+
+        // 提取所有的id
+        $coachIds = array_unique(array_column($coachData, 'id'));
+
+        // 分页截取 coachIds
+        $paginatedCoachIds = array_slice($coachIds, ($page - 1) * $perPage, $perPage);
+
+        // 查询数据库获取技师信息
         $coaches = CoachUser::query()
-            ->where('state', 'enable')
+            ->whereIn('id', $paginatedCoachIds)
             ->whereHas('info', function ($query) {
                 $query->where('state', 'approved');
             })
@@ -34,19 +51,14 @@ class CoachService
                 $query->where('state', 'approved');
             })
             ->with(['info:id,nickname,avatar,gender'])
-            ->with(['locations'])
-            ->whereHas('locations', function ($query) use ($latitude, $longitude) {
-                $query->where('state', 'enable')
-                    ->whereRaw(
-                        '(6371 * acos(cos(radians(?)) * cos(radians(latitude)) * cos(radians(longitude) - radians(?)) + sin(radians(?)) * sin(radians(latitude)))) < ?',
-                        [$latitude, $longitude, $latitude, 40]
-                    );
-            })
-            ->selectRaw(
-                '*, (6371 * acos(cos(radians(?)) * cos(radians(latitude)) * cos(radians(longitude) - radians(?)) + sin(radians(?)) * sin(radians(latitude)))) as distance',
-                [$latitude, $longitude, $latitude]
-            )
-            ->paginate(10);
+            ->paginate($perPage);
+
+        // 遍历技师并设置距离
+        foreach ($coaches as $coach) {
+            $coach->distance = $coachData[array_search($coach->id, array_column($coachData, 'id'))]['distance'] ?? null;
+        }
+        // 按 distance 升序排序
+        $coaches = $coaches->sortBy('distance')->values();
 
         return $coaches;
     }
@@ -57,8 +69,7 @@ class CoachService
     public function getCoachDetail($coachId, $latitude, $longitude)
     {
         // 获取当前用户
-        $userId = Auth::id();
-        $user = MemberUser::findOrFail($userId);
+        $user = Auth::user();
 
         // 检查用户状态
         if ($user->state !== 'enable') {
@@ -66,16 +77,23 @@ class CoachService
         }
 
         // 获取技师信息
-        $coach = CoachUser::where('id', $coachId)
-            ->where('state', 'enable')
-            ->where('auth_state', 'passed')
-            ->with(['user:id,nickname,avatar,gender'])
-            ->with(['location'])
-            ->firstOrFail();
+        $coach = CoachUser::where('state', 'enable')
+            ->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'])
+            // ->with(['location'])
+            ->find($coachId);
 
         // TODO: 计算距离
         $distance = 0;
-        if ($coach->location) {
+        if ($coach->locations) {
             // 实现距离计算逻辑
         }
 

+ 12 - 15
app/Services/Client/UserService.php

@@ -2,20 +2,16 @@
 
 namespace App\Services\Client;
 
+use App\Models\CoachUser;
 use App\Models\MemberUser;
-use App\Models\MemberSocialAccount;
 use App\Models\SysConfig;
 use App\Models\Wallet;
-use App\Models\CoachUser;
 use App\Models\WalletWithdrawRecord;
 use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Cache;
 use Illuminate\Support\Facades\DB;
 
-
 class UserService
 {
-
     /**
      * 获取用户信息
      */
@@ -30,8 +26,8 @@ class UserService
             'user' => $user,
             'wallet' => $wallet ? [
                 'id' => $wallet->id,
-                'balance' => $wallet->available_balance
-            ] : null
+                'balance' => $wallet->available_balance,
+            ] : null,
         ];
     }
 
@@ -42,6 +38,7 @@ class UserService
     {
         $user = Auth::user();
         $user->update($data);
+
         return ['message' => '修改成功'];
     }
 
@@ -52,7 +49,7 @@ class UserService
     {
         $user = Auth::user();
         $wallet = $user->wallet;
-        if (!$wallet) {
+        if (! $wallet) {
             throw new \Exception('钱包不存在');
         }
 
@@ -67,7 +64,7 @@ class UserService
     {
         // 获取当前用户
         $user = Auth::user();
-        if (!$user || $user->state !== 'enable') {
+        if (! $user || $user->state !== 'enable') {
             throw new \Exception('用户状态异常');
         }
 
@@ -97,14 +94,14 @@ class UserService
         }
 
         // 创建提现记录
-        DB::transaction(function() use ($user, $amount, $fee, $wallet, $type, $area_code) {
+        DB::transaction(function () use ($amount, $fee, $wallet, $type, $area_code) {
             WalletWithdrawRecord::create([
                 'wallet_id' => $wallet->id,
                 'amount' => $amount,    // 提现金额
                 'withdraw_type' => $type,   // 提现方式
                 'fee' => $fee,    // 提现手续费
                 'area_code' => $area_code,    // 行政区划代码
-                'state' => 'processing'    // 状态
+                'state' => 'processing',    // 状态
             ]);
 
             // 扣除钱包余额
@@ -122,7 +119,7 @@ class UserService
     public function feedback($content)
     {
         $user = Auth::user();
-        if (!$user || $user->state !== 'enable') {
+        if (! $user || $user->state !== 'enable') {
             throw new \Exception('用户状态异常');
         }
 
@@ -163,11 +160,11 @@ class UserService
         }
 
         // 创建技师申请
-        DB::transaction(function() use ($user , $mobile, $gender, $work_years, $intention_city) {
+        DB::transaction(function () use ($user, $mobile, $gender, $work_years, $intention_city) {
             // 创建技师基本信息
             $coach = CoachUser::create([
                 'user_id' => $user->id,
-                'state' => 'pending'  // 待审核状态
+                'state' => 'pending',  // 待审核状态
             ]);
 
             $coach->infoRecords()->create([
@@ -177,7 +174,7 @@ class UserService
                 'gender' => $gender ?? 'unknown',  // 性别
                 'work_years' => $work_years ?? '',  // 从业年份
                 'intention_city' => $intention_city ?? '',   // 意向城市
-                'state' => 'pending'
+                'state' => 'pending',
             ]);
 
         });

+ 2 - 16
config/logging.php

@@ -2,38 +2,24 @@
 
 use Monolog\Handler\NullHandler;
 use Monolog\Handler\StreamHandler;
-use Monolog\Handler\SyslogUdpHandler;
 use Monolog\Processor\PsrLogMessageProcessor;
-use Monolog\Formatter\LineFormatter;
 
 return [
     'default' => env('LOG_CHANNEL', 'stack'),
 
-    'deprecations' => [
-        'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
-        'trace' => env('LOG_DEPRECATIONS_TRACE', false),
-    ],
-
     'channels' => [
         'stack' => [
             'driver' => 'stack',
-            'channels' => ['single'],
+            'channels' => ['daily'],
             'ignore_exceptions' => false,
         ],
 
-        'single' => [
-            'driver' => 'single',
-            'path' => storage_path('logs/laravel.log'),
-            'level' => env('LOG_LEVEL', 'debug'),
-            'replace_placeholders' => true,
-        ],
-
         'daily' => [
             'driver' => 'daily',
             'path' => storage_path('logs/laravel.log'),
             'level' => env('LOG_LEVEL', 'debug'),
             'days' => 14,
-            'replace_placeholders' => true,
+            'tap' => [App\Logging\CustomizeFormatter::class],
         ],
 
         'slack' => [

+ 8 - 0
routes/api.php

@@ -2,6 +2,7 @@
 
 use App\Http\Controllers\Client\AccountController;
 use App\Http\Controllers\Client\CoachController;
+use App\Http\Controllers\Client\CoachLocationController;
 use App\Http\Controllers\Client\ProjectController;
 use App\Http\Controllers\Client\UserController;
 use App\Http\Controllers\ScribeController;
@@ -55,7 +56,14 @@ Route::middleware('auth:sanctum')->group(function () {
     });
 
     Route::prefix('coach')->group(function () {
+        Route::prefix('location')->group(function () {
+            Route::get('/', [CoachLocationController::class, 'index']); // 获取所有技师定位
+            Route::post('/', [CoachLocationController::class, 'store']); // 创建新的技师定位
+            Route::delete('/{id}', [CoachLocationController::class, 'destroy']);    // 删除技师定位
+        });
         Route::get('/', [CoachController::class, 'list']); // 获取技师列表
         Route::get('/{id}', [CoachController::class, 'detail']); // 获取技师详情
+
     });
+
 });