Ver Fonte

Merge branch 'master' of ssh://gogs.yinbin.ink:30004/didong/owl-admin

Yin Bin há 4 meses atrás
pai
commit
8c97f67801

+ 32 - 0
app/Http/Controllers/Coach/OrderController.php

@@ -243,4 +243,36 @@ class OrderController extends Controller
     {
         return $this->service->arrive(Auth::user()->id, $order_id);
     }
+
+    /**
+     * [订单]开始服务
+     *
+     * @description 技师扫描客户二维码开始服务
+     *
+     * @bodyParam order_id integer required 订单ID Example: 1
+     * @bodyParam qr_code string required 二维码内容 Example: order_1_1677123456_abcdef
+     *
+     * @response {
+     *  "status": true,
+     *  "message": "开始服务成功",
+     *  "data": {
+     *    "order_id": 1,
+     *    "state": "serving",
+     *    "service_start_time": "2024-01-01 12:00:00"
+     *  }
+     * }
+     */
+    public function startService(Request $request)
+    {
+        $validated = $request->validate([
+            'order_id' => 'required|integer',
+            'qr_code' => 'required|string',
+        ]);
+
+        return $this->service->startService(
+            Auth::user()->id,
+            $validated['order_id'],
+            $validated['qr_code']
+        );
+    }
 }

+ 92 - 0
app/Services/Client/OrderService.php

@@ -1242,4 +1242,96 @@ class OrderService
             'coach_id' => $coachId,
         ]);
     }
+
+    /**
+     * 生成订单核销码
+     *
+     * @param  int  $orderId  订单ID
+     * @return array 返回核销码信息
+     *
+     * @throws Exception
+     */
+    public function generateVerificationCode(int $orderId): array
+    {
+        try {
+            // 1. 验证订单
+            $order = Order::where('id', $orderId)
+                ->where('user_id', Auth::id())
+                ->where('state', OrderStatus::PAID->value)
+                ->firstOrFail();
+
+            // 2. 生成时间戳
+            $timestamp = time();
+
+            // 3. 生成签名 (使用订单ID、时间戳和应用密钥)
+            $sign = md5("order_{$order->id}_{$timestamp}_".config('app.key'));
+
+            // 4. 组装二维码内容
+            $qrCode = "order_{$order->id}_{$timestamp}_{$sign}";
+
+            return [
+                'qr_code' => $qrCode,
+                'expired_at' => date('Y-m-d H:i:s', $timestamp + 300), // 5分钟后过期
+            ];
+
+        } catch (Exception $e) {
+            Log::error('生成订单核销码失败:', [
+                'order_id' => $orderId,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+            throw $e;
+        }
+    }
+
+    /**
+     * 验证核销码
+     *
+     * @param  string  $qrCode  核销码
+     * @param  int  $orderId  订单ID
+     *
+     * @throws Exception
+     */
+    public function verifyCode(string $qrCode, int $orderId): bool
+    {
+        try {
+            // 1. 验证订单
+            $order = Order::where('id', $orderId)
+                ->where('state', OrderStatus::PAID->value)
+                ->firstOrFail();
+
+            // 2. 解析二维码
+            // 二维码格式: order_{order_id}_{timestamp}_{sign}
+            $parts = explode('_', $qrCode);
+            abort_if(count($parts) !== 4, 400, '二维码格式错误');
+
+            [$prefix, $scanOrderId, $timestamp, $sign] = $parts;
+
+            // 3. 验证前缀
+            abort_if($prefix !== 'order', 400, '无效的二维码');
+
+            // 4. 验证订单ID
+            abort_if((int) $scanOrderId !== $order->id, 400, '二维码与订单不匹配');
+
+            // 5. 验证时间戳(二维码5分钟内有效)
+            $qrTimestamp = (int) $timestamp;
+            $now = time();
+            abort_if($now - $qrTimestamp > 300, 400, '二维码已过期');
+
+            // 6. 验证签名
+            $correctSign = md5("order_{$order->id}_{$timestamp}_".config('app.key'));
+            abort_if($sign !== $correctSign, 400, '二维码签名错误');
+
+            return true;
+
+        } catch (Exception $e) {
+            Log::error('验证核销码失败:', [
+                'order_id' => $orderId,
+                'qr_code' => $qrCode,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+            throw $e;
+        }
+    }
 }

+ 112 - 0
app/Services/Coach/OrderService.php

@@ -724,4 +724,116 @@ class OrderService
             }
         });
     }
+
+    /**
+     * 技师扫码开始服务
+     *
+     * @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' => '开始服务',
+                ]);
+
+                // 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, '二维码签名错误');
+    }
 }

+ 2 - 0
routes/api.php

@@ -163,6 +163,8 @@ Route::middleware(['auth:sanctum', 'verified'])->prefix('coach')->group(function
         Route::post('/depart/{order_id}', [CoachOrderController::class, 'depart']);
         // 技师到达
         Route::post('/arrive/{order_id}', [CoachOrderController::class, 'arrive']);
+        // 技师开始服务
+        Route::post('/start-service', [CoachOrderController::class, 'startService']);
     });
 
     // 项目相关路由