crawlService = new UsedCarCrawlService(); } /** * 获取二手车列表 * * @param int $userId 用户ID * @param string $type 列表类型: all-全部, self-我创建的 * @param int $page 页码 * @param int $pageSize 每页数量 * @param string|null $keyword 搜索关键词 * @param int|null $status 状态筛选 * @return array */ public function getList(int $userId, string $type = 'self', int $page = 1, int $pageSize = 20, ?string $keyword = null, ?int $status = null): array { $query = UsedCar::query(); // 筛选条件 switch ($type) { case 'self': $query->where('user_id', $userId); break; case 'all': // 管理员可以查看所有 break; } // 状态筛选 if ($status !== null) { $query->where('status', $status); } else { // 默认不显示已删除的 $query->where('status', '!=', UsedCar::STATUS_DELETED); } // 关键词搜索 if ($keyword) { $query->where(function ($q) use ($keyword) { $q->where('product_name', 'like', "%{$keyword}%") ->orWhere('code', 'like', "%{$keyword}%"); }); } // 获取总数 $total = $query->count(); // 分页查询 $list = $query->orderBy('sort', 'desc') ->orderBy('id', 'desc') ->offset(($page - 1) * $pageSize) ->limit($pageSize) ->get(); // 格式化数据 $formattedList = $list->map(function ($item) { return $this->formatListItem($item); })->toArray(); return [ 'list' => $formattedList, 'total' => $total, 'page' => $page, 'page_size' => $pageSize, ]; } /** * 获取二手车详情 * * @param int $id 二手车ID * @param int $userId 当前用户ID * @return array * @throws \Exception */ public function getDetail(int $id, int $userId): array { $car = UsedCar::with(['pdfLogs', 'user'])->findOrFail($id); // 格式化数据 return $this->formatDetailItem($car); } /** * 创建二手车记录 * * @param array $data * @param int $userId * @return UsedCar * @throws \Exception */ public function create(array $data, int $userId): UsedCar { // 验证数据 $this->validateCarData($data); return Db::connection()->transaction(function () use ($data, $userId) { // 获取用户信息 $user = User::findOrFail($userId); // 生成唯一编号 $code = $this->generateCode($userId, $user->name ?? ''); // 处理图片 $images = $data['images'] ?? []; if (!is_array($images)) { $images = explode(',', $images); } $images = array_filter($images); // 处理附件 $attachment = $data['attachment'] ?? null; if (is_array($attachment)) { $attachment = json_encode($attachment, JSON_UNESCAPED_UNICODE); } // 检查source_url是否已存在 if (!empty($data['source_url'])) { $exists = UsedCar::where('source_url', $data['source_url']) ->where('user_id', $userId) ->exists(); if ($exists) { throw new \Exception('该车源已收录'); } } // 创建记录 $car = UsedCar::create([ 'code' => $code, 'user_id' => $userId, 'role' => $user->role ?? 1, 'product_name' => $data['product_name'], 'vin' => $data['vin'] ?? null, 'price' => $this->parsePrice($data['price'] ?? 0), 'source_place' => $data['source_place'] ?? null, 'plate_place' => $data['plate_place'] ?? null, 'plate_time' => $data['plate_time'] ?? null, 'displacement' => $data['displacement'] ?? null, 'mileage' => $this->parseMileage($data['mileage'] ?? 0), 'power' => $this->parsePower($data['power'] ?? ''), 'body_color' => $data['body_color'] ?? null, 'images' => implode(',', $images), 'condition_desc' => $data['condition_desc'] ?? null, 'attachment' => $attachment, 'source_url' => $data['source_url'] ?? null, 'crawl_title' => $data['crawl_title'] ?? null, 'crawl_brand' => $data['crawl_brand'] ?? null, 'crawl_brand_id' => $data['crawl_brand_id'] ?? 0, 'crawl_series' => $data['crawl_series'] ?? null, 'crawl_series_id' => $data['crawl_series_id'] ?? 0, 'crawl_year' => $data['crawl_year'] ?? null, 'crawl_msrp' => $this->parsePrice($data['crawl_msrp'] ?? 0), 'crawl_car_id' => $data['crawl_car_id'] ?? 0, 'status' => UsedCar::STATUS_NORMAL, 'sort' => $data['sort'] ?? 0, ]); return $car; }); } /** * 更新二手车记录 * * @param int $id * @param array $data * @param int $userId * @return UsedCar * @throws \Exception */ public function update(int $id, array $data, int $userId): UsedCar { $car = UsedCar::findOrFail($id); // 检查权限 if ($car->user_id !== $userId) { throw new \Exception('无权编辑此记录'); } // 验证数据 $this->validateCarData($data); // 处理图片 if (isset($data['images'])) { $images = is_array($data['images']) ? $data['images'] : explode(',', $data['images']); $data['images'] = implode(',', array_filter($images)); } // 处理附件 if (isset($data['attachment']) && is_array($data['attachment'])) { $data['attachment'] = json_encode($data['attachment'], JSON_UNESCAPED_UNICODE); } // 处理价格和里程 if (isset($data['price'])) { $data['price'] = $this->parsePrice($data['price']); } if (isset($data['mileage'])) { $data['mileage'] = $this->parseMileage($data['mileage']); } if (isset($data['power'])) { $data['power'] = $this->parsePower($data['power']); } if (isset($data['crawl_msrp'])) { $data['crawl_msrp'] = $this->parsePrice($data['crawl_msrp']); } // 只更新允许的字段 $fillable = [ 'product_name', 'vin', 'price', 'source_place', 'plate_place', 'plate_time', 'displacement', 'mileage', 'power', 'body_color', 'images', 'condition_desc', 'attachment', ]; foreach ($fillable as $field) { if (array_key_exists($field, $data)) { $car->$field = $data[$field]; } } $car->save(); return $car; } /** * 删除二手车记录(软删除) * * @param int $id * @param int $userId * @return bool * @throws \Exception */ public function delete(int $id, int $userId): bool { $car = UsedCar::findOrFail($id); // 检查权限 if ($car->user_id !== $userId) { throw new \Exception('无权删除此记录'); } $car->status = UsedCar::STATUS_DELETED; return $car->save(); } /** * 更新状态 * * @param int $id * @param int $status * @param int $userId * @return bool * @throws \Exception */ public function updateStatus(int $id, int $status, int $userId): bool { $car = UsedCar::findOrFail($id); // 检查权限 if ($car->user_id !== $userId) { throw new \Exception('无权修改此记录'); } $car->status = $status; return $car->save(); } /** * URL抓取车辆信息 * * @param string $url * @return array * @throws \Exception */ public function crawlByUrl(string $url): array { $data = $this->crawlService->crawlByUrl($url); // 下载图片到临时目录 $tempDir = runtime_path('tmp/usedcar_images/'); $images = $data['images'] ?? []; return [ 'product_name' => $data['title'] ?? '', 'price' => $this->parsePrice($data['price'] ?? 0), 'source_place' => $data['source_place'] ?? '', 'plate_time' => $data['plate_time'] ?? '', 'displacement' => $data['displacement'] ?? '', 'mileage' => $data['mileage'] ?? 0, 'power' => $data['power'] ?? '', 'body_color' => $data['body_color'] ?? '', 'images' => $images, 'condition_desc' => $data['condition_desc'] ?? '', 'source_url' => $data['source_url'] ?? $url, 'crawl_title' => $data['title'] ?? '', 'crawl_brand' => $data['brand_name'] ?? '', 'crawl_brand_id' => $data['brand_id'] ?? 0, 'crawl_series' => $data['series_name'] ?? '', 'crawl_series_id' => $data['series_id'] ?? 0, 'crawl_year' => $data['year'] ?? '', 'crawl_car_id' => $data['car_id'] ?? 0, 'used_car_id' => $data['used_car_id'] ?? 0, ]; } /** * 生成唯一编号 * * @param int $userId * @param string $userName * @return string */ private function generateCode(int $userId, string $userName): string { // 获取用户名拼音首字母 $nameInitials = $this->getNameInitials($userName); $userCode = str_pad($userId, 3, '0', STR_PAD_LEFT); // 从Redis获取今日计数 $redisKey = 'used_car_count_' . $userId; $todayStart = strtotime('today'); // 检查数据库中今日已创建的数量 $count = UsedCar::where('user_id', $userId) ->where('created_at', '>=', $todayStart) ->count(); // 从Redis获取缓存计数 $cachedCount = Redis::get($redisKey, 0); $count = max($count, $cachedCount) + 1; // 更新Redis缓存(24小时过期) Redis::setex($redisKey, 86400, $count); $countCode = str_pad($count, 3, '0', STR_PAD_LEFT); return $nameInitials . $userCode . date('Ymd') . $countCode; } /** * 获取中文名拼音首字母 */ private function getNameInitials(string $name): string { $initials = ''; $len = mb_strlen($name); for ($i = 0; $i < $len; $i++) { $char = mb_substr($name, $i, 1); $initials .= $this->getFirstChar($char); } return strtoupper($initials); } /** * 获取单个汉字的首字母 */ private function getFirstChar(string $char): string { // 英文字母直接返回 if (preg_match('/^[a-zA-Z]$/', $char)) { return strtoupper($char); } // 转换为GB2312编码 $s = @iconv('UTF-8', 'GB2312//IGNORE', $char); if ($s === false || strlen($s) < 2) { return ''; } $code = ord($s[0]) * 256 + ord($s[1]) - 65536; $map = [ [-20319, -20284, 'A'], [-20283, -19776, 'B'], [-19775, -19219, 'C'], [-19218, -18711, 'D'], [-18710, -18527, 'E'], [-18526, -18240, 'F'], [-18239, -17923, 'G'], [-17922, -17418, 'H'], [-17417, -16475, 'J'], [-16474, -16213, 'K'], [-16212, -15641, 'L'], [-15640, -15166, 'M'], [-15165, -14923, 'N'], [-14922, -14915, 'O'], [-14914, -14631, 'P'], [-14630, -14150, 'Q'], [-14149, -14091, 'R'], [-14090, -13319, 'S'], [-13318, -12839, 'T'], [-12838, -12557, 'W'], [-12556, -11848, 'X'], [-11847, -11056, 'Y'], [-11055, -10247, 'Z'] ]; foreach ($map as [$start, $end, $letter]) { if ($code >= $start && $code <= $end) { return $letter; } } return ''; } /** * 解析价格(单位统一转换为分) */ private function parsePrice($price): int { $price = floatval(str_replace(['万', ','], '', $price)); // 如果小于200,说明是以万为单位 if ($price < 200 && $price > 0) { $price = $price * 10000; } return (int)($price * 100); // 转换为分 } /** * 解析里程(移除单位) */ private function parseMileage($mileage): int { $mileage = str_replace(['万', 'K', 'M', 'k', 'm', '公里'], '', $mileage); return (int)floatval($mileage); } /** * 解析马力(移除单位) */ private function parsePower($power): string { return str_replace(['P', 'S', 'p', 's', '马力', 'Ps', 'PS'], '', $power); } /** * 验证车辆数据 */ private function validateCarData(array $data): void { if (empty($data['product_name'])) { throw new \Exception('产品名称不能为空'); } } /** * 格式化列表项 */ private function formatListItem(UsedCar $car): array { $data = $car->toArray(); // 格式化时间 if ($car->created_at) { $data['created_at'] = $car->created_at->toDateTimeString(); } if ($car->updated_at) { $data['updated_at'] = $car->updated_at->toDateTimeString(); } // 处理图片 $data['images_array'] = []; if (!empty($car->images)) { $data['images_array'] = explode(',', $car->images); } return $data; } /** * 格式化详情项 */ private function formatDetailItem(UsedCar $car): array { $data = $car->toArray(); // 格式化时间 if ($car->created_at) { $data['created_at'] = $car->created_at->toDateTimeString(); } if ($car->updated_at) { $data['updated_at'] = $car->updated_at->toDateTimeString(); } // 处理图片 $data['images_array'] = []; if (!empty($car->images)) { $data['images_array'] = explode(',', $car->images); } // 处理附件 $data['attachment_array'] = []; if (!empty($car->attachment)) { $attachment = json_decode($car->attachment, true); if (is_array($attachment)) { $data['attachment_array'] = $attachment; } } // 添加用户信息 if ($car->user) { $data['user'] = [ 'id' => $car->user->id, 'name' => $car->user->name, ]; } // PDF生成日志 if ($car->pdfLogs) { $data['pdf_logs'] = $car->pdfLogs->map(function ($log) { return [ 'id' => $log->id, 'price_snapshot' => $log->price_snapshot, 'pdf_path' => $log->pdf_path, 'created_at' => $log->created_at ? $log->created_at->toDateTimeString() : null, ]; })->toArray(); } return $data; } }