validator; } /** * 构建查询条件 */ protected function buildQueryConfig(Request $request, string $scene = 'default'): array { $p = $request->all(); $config = []; $config['model'] = $this->modelClass; switch ($scene) { case 'index': // ====== ====== 排序 ====== ====== $config['order']['sort'] = 'desc'; $config['order']['id'] = 'desc'; // ====== ====== 筛选 ====== ====== $config['where']['brand_id'] = $p['brand_id']; // 状态 if (isset($p['status']) && in_array($p['status'], [0, 1])) { $config['where']['status'] = $p['status']; } // 关键字 if (isset($p['keyword']) && $p['keyword']) { $k = '%' . $p['keyword'] . '%'; foreach (['series_code', 'series_name'] as $col) { $config['orWhere'][] = [$col, 'like', $k]; } } //$config['where']['status'] = 1; break; case 'show': $config['where']['id'] = $p['id']; //$config['where']['status'] = 1; break; case 'store': break; case 'update': $config['where']['id'] = $p['id']; break; case 'destroy': $config['where']['id'] = $p['id']; break; } return $config; } /** * 生成唯一的7位颜色编码 * 格式:C + (O/I) + 5位字母数字组合 * * @param int $type 类型 * @return string 7位唯一编码 */ public function generateUniqueModificationCode($brand_id) { $chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; $maxAttempts = 10; // 防止无限循环 $brand_code = Brand::where('id', $brand_id)->value('brand_code'); $brand_code = $brand_code ?? 'BS'; for ($attempt = 0; $attempt < $maxAttempts; $attempt++) { // 生成后5位随机字符 $suffix = ''; for ($i = 0; $i < 6; $i++) { $suffix .= $chars[rand(0, strlen($chars) - 1)]; } $code = $brand_code . $suffix; // 检查是否已存在 $existing = BrandSeries::where('series_code', $code)->first(); if (!$existing) { return $code; } } // 如果1000次尝试后仍然重复,使用时间戳确保唯一性 $timestamp = substr(time(), -6); return $brand_code . $timestamp; } /** * 添加前置处理 */ public function beforeServiceStore($data, Request $request) { $brand_id = $data['brand_id'] ?? 0; $data['series_code'] = $this->generateUniqueModificationCode($brand_id); return $data; } /** * 抓取车型数据(通过接口) * @param Request $request * @return array * @throws \Exception */ public function crawlModel(Request $request): array { $series_id = $request->get('brand_series_id'); // 验证品牌是否存在 $series = BrandSeries::where('id', $series_id)->first(); if (!$series) { throw new \Exception("系列ID {$series_id} 不存在"); } // 获取品牌信息 $series = BrandSeries::where('id', $series_id)->first(); if (!$series) { throw new \Exception("系列ID {$series_id} 不存在"); } // 获取品牌信息 $brand = Brand::where('id', $series->brand_id)->first(); // 执行抓取 $crawlService = new CrawlService(); $href = $crawlService->modelSearch($brand->brand_name . ' ' . $series->series_name); $seriesId = $this->extractSeriesId($href); if (empty($seriesId)) { throw new \Exception('抓取失败,未找到该系列车型'); } try { return $this->crawlSeriesModel($seriesId); } catch (\Exception $e) { throw new \Exception($e->getMessage()); } // return $models; } /** * 从懂车帝URL中提取series ID * @param string $url 完整的URL * @return string|null series ID或null */ function extractSeriesId($url) { // 解析URL获取路径部分 $path = parse_url($url, PHP_URL_PATH); if (!$path) { return null; } // 按斜杠分割路径 $pathParts = explode('/', trim($path, '/')); // 查找 'series' 的位置,下一个元素就是series ID $seriesIndex = array_search('series', $pathParts); if ($seriesIndex !== false && isset($pathParts[$seriesIndex + 1])) { return $pathParts[$seriesIndex + 1]; } return null; } /** * 抓取车型数据(通过页面抓取) * @param Request $request * @return array * @throws \Exception */ /*public function crawlModel(Request $request) { $series_id = $request->get('brand_series_id'); // 验证品牌是否存在 $series = BrandSeries::where('id', $series_id)->first(); if (!$series) { throw new \Exception("系列ID {$series_id} 不存在"); } // 获取品牌信息 $series = BrandSeries::where('id', $series_id)->first(); if (!$series) { throw new \Exception("系列ID {$series_id} 不存在"); } // 获取品牌信息 $brand = Brand::where('id', $series->brand_id)->first(); // 执行抓取 $crawlService = new CrawlService(); $models = $crawlService->seriesModel($brand->brand_name . ' ' . $series->series_name); foreach ($models as $key => $model) { $models[$key]['year'] = $this->extractYearFromCarModel($model['title']); } return $models; }*/ /** * @param Request $request * @return array|string * @throws \Exception */ public function crawlModelCreate(Request $request) { $series_id = $request->input('series_id'); // $brand_id = $request->input('brand_id'); $models = $request->input('models'); // 验证系列是否存在 $series = BrandSeries::where('id', $series_id)->first(); if (!$series) { throw new \Exception("车辆系列未收录,联系管理员处理"); } // 获取品牌信息 $brand = Brand::where('id', $series->brand_id)->first(); if (!$brand) { throw new \Exception("车辆品牌未收录,联系管理员处理"); } // 保存车型数据 $createdCount = 0; $repeat = 0; $fail = 0; $msg = ''; foreach ($models as $item) { $modelData = [ 'brand_code' => $brand->brand_code, 'brand_name' => $brand->brand_name, 'series_code' => $series->series_code, 'series_name' => $series->series_name, 'model_name' => $item['title'], 'model_year' => $item['year'], 'msrp' => $item['msrp'], ]; //指纹数组 $fingerprintArr = [ 'brand_name' => $modelData['brand_name'], 'series_name' => $modelData['series_name'], 'model_name' => $modelData['model_name'], 'model_year' => $modelData['model_year'], 'msrp' => $modelData['msrp'], ]; // 生成指纹 $modelData['fingerprint'] = Fingerprint::makeFromArray($fingerprintArr); $modelData['model_code'] = ModelNaming::buildModelCode($modelData); $modelData['sku_code'] = SkuCodeBuilder::build($modelData); try { $model = VehVehicleSku::where('brand_name', $brand->brand_name)->where('series_name', $series->series_name)->where('model_name', $item['title'])->first(); if (!empty($model)) { $repeat++; continue; } CrudUtil::create(VehVehicleSku::class, $modelData); $createdCount++; } catch (\Exception $e) { // 记录错误但继续处理其他车型 $msg .= "创建车型失败: " . $e->getMessage() . "\n"; Log::error("创建车型失败:" . $msg, ['series_id' => $series_id, 'model' => $item]); $fail++; } } Log::info('series-crawl-' . $series_id . ' ' . $msg); $msg = '创建车型成功' . $createdCount . '条'; $msg .= (!empty($repeat)) ? ',重复' . $repeat . '条' : ''; $msg .= (!empty($fail)) ? ',失败' . $fail . '条' : ''; return $msg; } /** * 抓取车型数据(通过接口) * @param Request $request * @return array * @throws \Exception */ public function crawlKeyword(Request $request): array { $keyword = $request->get('keyword'); // 执行抓取 $crawlService = new CrawlService(); //懂车帝模糊搜索返回车系地址|单个车系id抓取 $href = $crawlService->modelSearch($keyword); $seriesId = $this->extractSeriesId($href); //懂车帝车系id // if (empty($seriesId)) { //没有抓取到懂车帝系列id时,按关键词为品牌名抓取所有的系列名 // $crawlSeries = $crawlService->brandSeries($keyword); // } //按关键词为品牌名抓取所有的系列名 $crawlSeries = $crawlService->brandSeries($keyword); if (empty($seriesId) && empty($crawlSeries)) { throw new \Exception('抓取失败,未找到该系列车型'); } $brandSerieses = []; if(empty($seriesId) && !empty($crawlSeries) || count($crawlSeries)>1){ //关键词为品牌名时抓取所有车系的懂车帝id $href = $crawlService->modelSearch($crawlSeries[0]); $seriesId = $this->extractSeriesId($href); //懂车帝车系id //抓取品牌与系列 $brandSeries = $crawlService->crawlBrandSeries($seriesId); foreach ($crawlSeries as $item) { // $brandSerieses[] = $crawlService->brandSeries($keyword); $brandSerieses[] = ['brand' => $brandSeries['brand'], 'series' => $item]; } }elseif (!empty($seriesId) && empty($crawlSeries)){ //抓取品牌与系列 $brandSerieses[] = $crawlService->crawlBrandSeries($seriesId); } $result = []; try { foreach ($brandSerieses as $brandSeries) { $item = []; // 验证品牌是否存在 $brand = Brand::where('brand_name', $brandSeries['brand'])->first(); // 获取品牌信息 $series = BrandSeries::where('series_name', $brandSeries['series'])->first(); $item['brand'] = ['brand_name' => $brandSeries['brand'], 'exists' => $brand ? 1 : 0]; if ($brand) { $item['brand']['id'] = $brand['id']; } $item['series'] = ['series_name' => $brandSeries['series'], 'exists' => $series ? 1 : 0]; if ($series) { $item['series']['id'] = $series['id']; if (empty($item['brand']['id'])) { $item['brand']['id'] = $series['brand_id']; } } //获取车型数据 $item['model'] = []; if (count($brandSerieses)<2) { $item['model'] = $this->crawlSeriesModel($seriesId); } $result[] = $item; } return ['is_series' => (count($brandSerieses)<2)?1:0, 'data' => $result]; } catch (\Exception $e) { throw new \Exception($e->getMessage()); } } /** * @param Request $request * @return array|string * @throws \Exception */ public function crawlKeywordCreate(Request $request) { $brand_id = $request->input('brand_id'); $brand_name = $request->input('brand_name'); $brand_code = $request->input('brand_code'); $series_id = $request->input('series_id'); $series_name = $request->input('series_name'); $models = $request->input('models'); if(empty($brand_id) && (empty($brand_name) || empty($brand_code))){ throw new \Exception("请选择品牌"); } if(empty($series_id) && empty($series_name)){ throw new \Exception("请选择车系"); } // 获取品牌信息 if (empty($brand_id)) { $brand = Brand::where('id', $brand_id)->where('brand_name', $brand_name)->first(); if (empty($brand)) { //如果没有该车系则创建 $brandService = new \app\admin\service\BrandService(); $brandData = [ 'brand_code' => $brandService->generateUniqueCode(), 'brand_name' => $brand_name, ]; $brand = CrudUtil::create(Brand::class, $brandData); } $brand_id = $brand->id; }else{ $brand = Brand::where('id', $brand_id)->first(); } if (empty($series_id)) { $series = BrandSeries::where('brand_id', $brand_id)->where('series_name', $series_name)->first(); if (empty($series)) { //如果没有该车系则创建 $seriesService = new \app\admin\service\BrandSeriesService(); $seriesData = [ 'brand_id' => $brand_id, 'series_code' => $seriesService->generateUniqueModificationCode($brand_id), 'series_name' => $series_name, ]; $series = CrudUtil::create(BrandSeries::class, $seriesData); } $series_id = $series->id; }else{ $series = BrandSeries::where('id', $series_id)->first(); } // 保存车型数据 $createdCount = 0; $repeat = 0; $fail = 0; $msg = ''; foreach ($models as $item) { $modelData = [ 'brand_code' => $brand->brand_code, 'brand_name' => $brand->brand_name, 'series_code' => $series->series_code, 'series_name' => $series->series_name, 'model_name' => $item['title'], 'model_year' => $item['year'], 'msrp' => $item['msrp'], ]; //指纹数组 $fingerprintArr = [ 'brand_name' => $modelData['brand_name'], 'series_name' => $modelData['series_name'], 'model_name' => $modelData['model_name'], 'model_year' => $modelData['model_year'], 'msrp' => $modelData['msrp'], ]; // 生成指纹 $modelData['fingerprint'] = Fingerprint::makeFromArray($fingerprintArr); $modelData['model_code'] = ModelNaming::buildModelCode($modelData); $modelData['sku_code'] = SkuCodeBuilder::build($modelData); try { $model = VehVehicleSku::where('brand_name', $brand->brand_name)->where('series_name', $series->series_name)->where('model_name', $item['title'])->first(); if (!empty($model)) { $repeat++; continue; } CrudUtil::create(VehVehicleSku::class, $modelData); $createdCount++; } catch (\Exception $e) { // 记录错误但继续处理其他车型 $msg .= "创建车型失败: " . $e->getMessage() . "\n"; Log::error("创建车型失败:" . $msg, ['series_id' => $series_id, 'model' => $item]); $fail++; } } Log::info('series-crawl-' . $series_id . ' ' . $msg); $msg = '创建车型成功' . $createdCount . '条'; $msg .= (!empty($repeat)) ? ',重复' . $repeat . '条' : ''; $msg .= (!empty($fail)) ? ',失败' . $fail . '条' : ''; return $msg; } /** * 抓取 */ public function crawl(Request $request): array { $keyword = $request->get('keyword'); if(filter_var($keyword, FILTER_VALIDATE_URL)){ return $this->crawlUrl($request); }else{ return $this->crawlKeyword($request); } } /** * 抓取车型数据(通过接口) * @param Request $request * @return array * @throws \Exception */ public function crawlUrl(Request $request): array { //$url = $request->get('url'); $url = $request->get('keyword'); // 执行抓取 $crawlService = new CrawlService(); //懂车帝提取车系id $seriesId = $this->get_series_from_url($url); //懂车帝车系id if (empty($seriesId)) { throw new \Exception('抓取失败,未找到该系列车型'); } $brandSerieses = []; //抓取品牌与系列 $brandSeries = $crawlService->crawlBrandSeries($seriesId); $brandSerieses[] = ['brand' => $brandSeries['brand'], 'series' => $brandSeries['series']]; $result = []; try { foreach ($brandSerieses as $brandSeries) { $item = []; // 验证品牌是否存在 $brand = Brand::where('brand_name', $brandSeries['brand'])->first(); // 获取品牌信息 $series = BrandSeries::where('series_name', $brandSeries['series'])->first(); $item['brand'] = ['brand_name' => $brandSeries['brand'], 'exists' => $brand ? 1 : 0]; if ($brand) { $item['brand']['id'] = $brand['id']; } $item['series'] = ['series_name' => $brandSeries['series'], 'exists' => $series ? 1 : 0]; if ($series) { $item['series']['id'] = $series['id']; if (empty($item['brand']['id'])) { $item['brand']['id'] = $series['brand_id']; } } //获取车型数据 $item['model'] = []; if (count($brandSerieses)<2) { $item['model'] = $this->crawlSeriesModel($seriesId); } $result[] = $item; } return ['is_series' => (count($brandSerieses)<2)?1:0, 'data' => $result]; } catch (\Exception $e) { throw new \Exception($e->getMessage()); } } public function get_series_from_url($url) { // 使用正则表达式匹配 "https://www.dongchedi.com/auto/series/{数字}" $pattern = '/^https:\/\/www\.dongchedi\.com\/auto\/series\/(\d+)$/'; if (preg_match($pattern, $url, $matches)) { // 提取数字部分作为 series ID $series_id = $matches[1]; return $series_id; // 返回系列ID } else { throw new \Exception("URL格式不匹配"); } } /** * 获取车系下车型数据 * @return array * @throws \GuzzleHttp\Exception\GuzzleException */ public function crawlSeriesModel($seriesId) { $client = new Client([ 'verify' => false, // 禁用 SSL 验证 'timeout' => 10 ]); try { $response = $client->get('https://www.dongchedi.com/motor/pc/car/series/car_list', [ 'query' => ['series_id' => $seriesId], 'headers' => [ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ] ]); $data = json_decode($response->getBody()->getContents(), true); if($data['status'] != 0 || (!isset($data['data']) && !isset($data['data']['tab_list']))){ Log::error('抓取车型数据失败' . $data['message'], $data); throw new \Exception('抓取车型数据失败' . $data['message']); } $models = []; foreach ($data['data']['tab_list'] as $item) { if($item['tab_text'] == '在售' || $item['tab_text'] == '停售'){ unset($item['data'][0]); $models = array_merge($models, $item['data']); } } foreach ($models as $key => $model) { if(!isset($model["info"]['car_name']) || $model['type'] != "1115"){ unset($models[$key]); continue; } $official_price = 0; if(!isset($model["info"]['official_price'])){ if(str_contains($model["info"]['official_price_str'],'万')){ $official_price = (int)(str_replace("万", "", $model["info"]['official_price_str']) * 10000) ?? 0; }else{ $official_price = 0; } }else{ $official_price = $model["info"]['official_price'] * 10000; } $models[$key] = [ 'title' => $model["info"]['year'] . '款 ' .$model["info"]['car_name'], 'brand_name' => $model["info"]['brand_name'], 'series_name' => $model["info"]['series_name'], 'year' => $model["info"]['year'], 'msrp' => $official_price, 'car_id' => $model["info"]['id'], ]; } return array_values($models); } catch (\Exception $e) { throw new \Exception($e->getMessage()); } } }