2026-03-09 17:46:17 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace app\common\service;
|
|
|
|
|
|
|
|
|
|
|
|
use app\common\validate\UsedCarDetailValidate;
|
|
|
|
|
|
use app\common\traits\crud\CrudServiceTrait;
|
|
|
|
|
|
use app\common\model\UsedCarDetail;
|
|
|
|
|
|
use app\common\model\User;
|
|
|
|
|
|
use support\exception\BusinessException;
|
|
|
|
|
|
use support\Log;
|
|
|
|
|
|
use support\Request;
|
|
|
|
|
|
use support\Redis;
|
2026-03-10 11:54:58 +08:00
|
|
|
|
use GuzzleHttp\Client;
|
|
|
|
|
|
use GuzzleHttp\Exception\GuzzleException;
|
|
|
|
|
|
use QL\QueryList;
|
2026-03-09 17:46:17 +08:00
|
|
|
|
|
|
|
|
|
|
class UsedCarDetailService
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
use CrudServiceTrait;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 指定当前要操作的模型
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected string $modelClass = UsedCarDetail::class;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 验证器
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected $validator = UsedCarDetailValidate::class;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取验证器
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getValidator()
|
|
|
|
|
|
{
|
|
|
|
|
|
return new $this->validator;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 构建查询配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function buildQueryConfig(Request $request, string $scene = 'default'): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$p = $request->all();
|
|
|
|
|
|
|
|
|
|
|
|
$config = [];
|
|
|
|
|
|
$config['model'] = $this->modelClass;
|
|
|
|
|
|
|
|
|
|
|
|
switch ($scene) {
|
|
|
|
|
|
case 'default':
|
|
|
|
|
|
$config['where']['status'] = 1;
|
|
|
|
|
|
$config['order']['sort'] = 'desc';
|
|
|
|
|
|
$config['order']['id'] = 'desc';
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'index':
|
|
|
|
|
|
// 默认只显示审核通过的数据
|
|
|
|
|
|
$config['where']['status'] = 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 审核状态筛选
|
|
|
|
|
|
if (isset($p['status'])) {
|
|
|
|
|
|
$config['where']['status'] = $p['status'];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 我的车辆
|
|
|
|
|
|
if (isset($p['scene']) && $p['scene'] == 'self') {
|
|
|
|
|
|
$config['where']['user_id'] = $request->user['id'];
|
|
|
|
|
|
unset($config['where']['status']);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 关键字搜索
|
|
|
|
|
|
if (isset($p['keyword']) && $p['keyword']) {
|
|
|
|
|
|
$k = '%' . $p['keyword'] . '%';
|
|
|
|
|
|
foreach (['product_name', 'code', 'vin', 'manufacturer'] as $col) {
|
|
|
|
|
|
$config['orWhere'][] = [$col, 'like', $k];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 品牌筛选
|
|
|
|
|
|
if (isset($p['crawl_brand_id']) && $p['crawl_brand_id']) {
|
|
|
|
|
|
$config['where']['crawl_brand_id'] = $p['crawl_brand_id'];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 车系筛选
|
|
|
|
|
|
if (isset($p['crawl_series_id']) && $p['crawl_series_id']) {
|
|
|
|
|
|
$config['where']['crawl_series_id'] = $p['crawl_series_id'];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 价格区间
|
|
|
|
|
|
if (isset($p['min_price']) && $p['min_price']) {
|
|
|
|
|
|
$config['where'][] = ['price', '>=', $p['min_price']];
|
|
|
|
|
|
}
|
|
|
|
|
|
if (isset($p['max_price']) && $p['max_price']) {
|
|
|
|
|
|
$config['where'][] = ['price', '<=', $p['max_price']];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$config['order']['sort'] = 'desc';
|
|
|
|
|
|
$config['order']['id'] = 'desc';
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'show':
|
|
|
|
|
|
$config['where']['id'] = $p['id'];
|
|
|
|
|
|
// 关联FAQ
|
|
|
|
|
|
$config['with'] = ['faqs'];
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'store':
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'update':
|
|
|
|
|
|
$config['where']['id'] = $p['id'];
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
case 'destroy':
|
|
|
|
|
|
$config['where']['id'] = $p['id'];
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $config;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建前数据处理
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function beforeServiceStore($data, Request $request): array
|
|
|
|
|
|
{
|
|
|
|
|
|
if (empty($data['product_name'])) {
|
|
|
|
|
|
throw new BusinessException('产品名称不能为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置用户信息
|
|
|
|
|
|
$data['user_id'] = $request->user['id'];
|
|
|
|
|
|
$user = User::where('id', $request->user['id'])->first();
|
|
|
|
|
|
$data['role_wp'] = $user['role_wp'] ?? 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 默认待审核状态
|
|
|
|
|
|
if (!isset($data['status'])) {
|
|
|
|
|
|
$data['status'] = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成车辆编号
|
|
|
|
|
|
$data['code'] = $this->getCode($request->user['id']);
|
|
|
|
|
|
|
|
|
|
|
|
// 处理图片数据
|
|
|
|
|
|
if (!empty($data['images'])) {
|
|
|
|
|
|
if (is_array($data['images'])) {
|
2026-03-10 11:05:30 +08:00
|
|
|
|
$data['images'] = implode(',', $data['images']);
|
2026-03-09 17:46:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$data['images'] = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理附件数据
|
|
|
|
|
|
if (!empty($data['attachment'])) {
|
|
|
|
|
|
if (is_array($data['attachment'])) {
|
|
|
|
|
|
$data['attachment'] = json_encode($data['attachment']);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$data['attachment'] = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理介绍图片
|
|
|
|
|
|
if (!empty($data['intro_images'])) {
|
|
|
|
|
|
if (is_array($data['intro_images'])) {
|
2026-03-10 11:36:24 +08:00
|
|
|
|
$data['intro_images'] = implode(',', $data['intro_images']);
|
2026-03-09 17:46:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$data['intro_images'] = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查来源URL是否重复
|
|
|
|
|
|
if (!empty($data['source_url'])) {
|
|
|
|
|
|
$exists = UsedCarDetail::where('source_url', $data['source_url']);
|
|
|
|
|
|
$exists = $exists->where('user_id', $data['user_id']);
|
|
|
|
|
|
if ($exists->exists()) {
|
|
|
|
|
|
throw new BusinessException('该车源已收录');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-10 11:54:58 +08:00
|
|
|
|
// 如果提供了 crawl_car_id,自动抓取懂车帝车辆参数
|
|
|
|
|
|
if (!empty($data['crawl_car_id'])) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
$params = $this->crawlParams($data['crawl_car_id']);
|
2026-03-11 10:19:55 +08:00
|
|
|
|
$params['official_guide_price'] = str_replace('万','',$params['official_guide_price'])*10000;
|
2026-03-10 11:54:58 +08:00
|
|
|
|
// 合并抓取的数据,手动提交的字段优先级更高
|
|
|
|
|
|
$data = array_merge($params, $data);
|
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
|
// 抓取失败不阻断创建,记录日志
|
|
|
|
|
|
Log::warning('抓取车辆参数失败', [
|
|
|
|
|
|
'crawl_car_id' => $data['crawl_car_id'],
|
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 17:46:17 +08:00
|
|
|
|
return $data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新前数据处理
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function beforeServiceUpdate($data, Request $request): array
|
|
|
|
|
|
{
|
|
|
|
|
|
if (empty($data['product_name'])) {
|
|
|
|
|
|
throw new BusinessException('产品名称不能为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取车辆信息
|
|
|
|
|
|
$carId = $data['id'] ?? null;
|
|
|
|
|
|
if ($carId) {
|
|
|
|
|
|
$car = UsedCarDetail::find($carId);
|
|
|
|
|
|
if (!$car) {
|
|
|
|
|
|
throw new BusinessException('车辆不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 审核通过的车辆禁止修改
|
|
|
|
|
|
if ($car->status == 1) {
|
|
|
|
|
|
throw new BusinessException('审核通过的车辆禁止修改');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 审核拒绝的车辆修改后状态改为待审核
|
|
|
|
|
|
if ($car->status == 2) {
|
|
|
|
|
|
$data['status'] = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理附件数据
|
|
|
|
|
|
if (isset($data['attachment'])) {
|
|
|
|
|
|
if (is_array($data['attachment'])) {
|
|
|
|
|
|
$data['attachment'] = json_encode($data['attachment']);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理介绍图片
|
|
|
|
|
|
if (isset($data['intro_images'])) {
|
|
|
|
|
|
if (is_array($data['intro_images'])) {
|
|
|
|
|
|
$data['intro_images'] = json_encode($data['intro_images']);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 更新状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function serviceUpdateStatus(Request $request, array $data)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (empty($data['id'])) {
|
|
|
|
|
|
throw new BusinessException('id不能为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!isset($data['status'])) {
|
|
|
|
|
|
throw new BusinessException('status错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
return UsedCarDetail::where('id', $data['id'])->update(['status' => $data['status']]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 保存FAQ
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function saveFaqs(int $carId, array $faqs)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 删除旧的FAQ
|
|
|
|
|
|
\app\common\model\UsedCarFaq::where('car_id', $carId)->delete();
|
|
|
|
|
|
|
|
|
|
|
|
// 创建新的FAQ
|
|
|
|
|
|
if (!empty($faqs)) {
|
|
|
|
|
|
foreach ($faqs as $faq) {
|
|
|
|
|
|
\app\common\model\UsedCarFaq::create([
|
|
|
|
|
|
'car_id' => $carId,
|
|
|
|
|
|
'question' => $faq['question'] ?? '',
|
|
|
|
|
|
'answer' => $faq['answer'] ?? '',
|
|
|
|
|
|
'sort' => $faq['sort'] ?? 0,
|
|
|
|
|
|
'status' => $faq['status'] ?? 1,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成车辆编号
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getCode(int $userId): string
|
|
|
|
|
|
{
|
|
|
|
|
|
$userCount = Redis::get('used_car_detail_count_' . $userId, 0);
|
|
|
|
|
|
$todayStart = strtotime('today');
|
|
|
|
|
|
$count = UsedCarDetail::where('user_id', $userId)
|
|
|
|
|
|
->where('created_at', '>=', $todayStart)
|
|
|
|
|
|
->count();
|
|
|
|
|
|
$count = $count < $userCount ? $userCount + 1 : $count + 1;
|
|
|
|
|
|
$countCode = str_pad($count, 3, '0', STR_PAD_LEFT);
|
|
|
|
|
|
$userCode = str_pad($userId, 3, '0', STR_PAD_LEFT);
|
|
|
|
|
|
Redis::set('used_car_detail_count_' . $userId, $count, 86400);
|
|
|
|
|
|
|
|
|
|
|
|
$user = User::where('id', $userId)->first();
|
|
|
|
|
|
$nameInitials = $this->getNameInitials($user['name'] ?? '');
|
|
|
|
|
|
|
|
|
|
|
|
return 'UCD' . $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
|
|
|
|
|
|
{
|
|
|
|
|
|
$s = iconv('UTF-8', 'GB2312//IGNORE', $char);
|
|
|
|
|
|
$asc = ord($s[0]);
|
|
|
|
|
|
|
|
|
|
|
|
if ($asc >= 65 && $asc <= 90) {
|
|
|
|
|
|
return $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 '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 提交审核
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function submitAudit(Request $request, int $carId)
|
|
|
|
|
|
{
|
|
|
|
|
|
$car = UsedCarDetail::find($carId);
|
|
|
|
|
|
if (!$car) {
|
|
|
|
|
|
throw new BusinessException('车辆不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查权限:只能提交自己创建的车辆
|
|
|
|
|
|
if ($car->user_id != $request->user['id']) {
|
|
|
|
|
|
throw new BusinessException('无权操作');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 只有待审核状态才能提交
|
|
|
|
|
|
if ($car->status != 0) {
|
|
|
|
|
|
throw new BusinessException('当前状态不允许提交审核');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return UsedCarDetail::where('id', $carId)->update(['status' => 0]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 审核车辆
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function audit(Request $request, array $data)
|
|
|
|
|
|
{
|
|
|
|
|
|
$carId = $data['car_id'] ?? null;
|
|
|
|
|
|
$auditStatus = $data['audit_status'] ?? null;
|
|
|
|
|
|
$auditReason = $data['audit_reason'] ?? '';
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($carId)) {
|
|
|
|
|
|
throw new BusinessException('车辆ID不能为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!isset($auditStatus) || !in_array($auditStatus, [1, 2])) {
|
|
|
|
|
|
throw new BusinessException('审核状态错误');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$car = UsedCarDetail::find($carId);
|
|
|
|
|
|
if (!$car) {
|
|
|
|
|
|
throw new BusinessException('车辆不存在');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$beforeStatus = $car->status;
|
|
|
|
|
|
|
|
|
|
|
|
// 创建审核记录
|
|
|
|
|
|
\app\common\model\UsedCarDetailAudit::create([
|
|
|
|
|
|
'car_id' => $carId,
|
|
|
|
|
|
'auditor_id' => $request->user['id'],
|
|
|
|
|
|
'audit_status' => $auditStatus,
|
|
|
|
|
|
'audit_reason' => $auditReason,
|
|
|
|
|
|
'before_status' => $beforeStatus,
|
|
|
|
|
|
'after_status' => $auditStatus,
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新车辆状态
|
2026-03-10 18:35:18 +08:00
|
|
|
|
$result = UsedCarDetail::where('id', $carId)->update(['status' => $auditStatus]);
|
|
|
|
|
|
|
|
|
|
|
|
// 审核通过后自动推送到WordPress
|
|
|
|
|
|
$pushResult = ['success' => true, 'message' => ''];
|
|
|
|
|
|
if ($auditStatus == 1) {
|
|
|
|
|
|
$pushResult = $this->pushToWordpressAfterAudit($carId);
|
|
|
|
|
|
|
|
|
|
|
|
// 推送失败,将车辆状态修改为3
|
|
|
|
|
|
if (!$pushResult['success']) {
|
|
|
|
|
|
UsedCarDetail::where('id', $carId)->update(['status' => 3]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 返回审核结果和推送结果
|
|
|
|
|
|
return [
|
|
|
|
|
|
'audit_success' => true,
|
|
|
|
|
|
'push_success' => $pushResult['success'],
|
|
|
|
|
|
'push_message' => $pushResult['message'],
|
|
|
|
|
|
'message' => $pushResult['success'] ? '审核通过并推送成功' : '审核通过,但推送失败:' . $pushResult['message'],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 审核通过后推送车辆到WordPress
|
|
|
|
|
|
* @return array ['success' => bool, 'message' => string]
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function pushToWordpressAfterAudit(int $carId): array
|
|
|
|
|
|
{
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取启用的WordPress站点
|
|
|
|
|
|
$site = \app\common\model\WordpressSite::where('status', 1)->first();
|
|
|
|
|
|
if (!$site) {
|
|
|
|
|
|
$msg = '没有启用的WordPress站点';
|
|
|
|
|
|
Log::warning('WordPress推送失败:' . $msg, ['car_id' => $carId]);
|
|
|
|
|
|
return ['success' => false, 'message' => $msg];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已推送过
|
|
|
|
|
|
$existing = \app\common\model\WordpressUsedCar::where('wp_site_id', $site->id)
|
|
|
|
|
|
->where('used_car_id', $carId)
|
|
|
|
|
|
->where('status', 1)
|
|
|
|
|
|
->first();
|
2026-03-11 15:01:54 +08:00
|
|
|
|
// if ($existing) {
|
|
|
|
|
|
// $msg = '车辆已推送,跳过';
|
|
|
|
|
|
// Log::info('WordPress推送:' . $msg, ['car_id' => $carId]);
|
|
|
|
|
|
// return ['success' => true, 'message' => $msg];
|
|
|
|
|
|
// }
|
2026-03-10 18:35:18 +08:00
|
|
|
|
|
|
|
|
|
|
// 调用推送服务
|
|
|
|
|
|
$wpService = new \app\common\service\WordpressSiteService();
|
|
|
|
|
|
$wpService->createProductForNewSite($site, $carId);
|
|
|
|
|
|
|
|
|
|
|
|
Log::info('WordPress推送成功', ['car_id' => $carId, 'site_id' => $site->id]);
|
|
|
|
|
|
return ['success' => true, 'message' => ''];
|
|
|
|
|
|
|
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
|
$msg = $e->getMessage();
|
|
|
|
|
|
Log::error('WordPress推送失败', [
|
|
|
|
|
|
'car_id' => $carId,
|
|
|
|
|
|
'error' => $msg,
|
|
|
|
|
|
'trace' => $e->getTraceAsString(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
return ['success' => false, 'message' => $msg];
|
|
|
|
|
|
}
|
2026-03-09 17:46:17 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取审核记录列表
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getAuditRecords(int $carId)
|
|
|
|
|
|
{
|
|
|
|
|
|
return \app\common\model\UsedCarDetailAudit::where('car_id', $carId)
|
|
|
|
|
|
->with(['auditor:id,name'])
|
|
|
|
|
|
->orderBy('created_at', 'desc')
|
|
|
|
|
|
->get()
|
|
|
|
|
|
->map(function ($item) {
|
|
|
|
|
|
return [
|
|
|
|
|
|
'id' => $item->id,
|
|
|
|
|
|
'audit_status' => $item->audit_status,
|
|
|
|
|
|
'audit_status_text' => $item->audit_status_text,
|
|
|
|
|
|
'audit_reason' => $item->audit_reason,
|
|
|
|
|
|
'before_status' => $item->before_status,
|
|
|
|
|
|
'after_status' => $item->after_status,
|
|
|
|
|
|
'auditor_name' => $item->auditor->name ?? '',
|
|
|
|
|
|
'created_at' => date('Y-m-d H:i:s', $item->created_at),
|
|
|
|
|
|
];
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取审核统计数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getAuditStats(Request $request)
|
|
|
|
|
|
{
|
|
|
|
|
|
$query = UsedCarDetail::query();
|
|
|
|
|
|
|
|
|
|
|
|
// 如果不是管理员,只能看到自己的数据
|
|
|
|
|
|
if ($request->user['role_wp'] != 3) {
|
|
|
|
|
|
$query->where('user_id', $request->user['id']);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
'pending' => (clone $query)->where('status', 0)->count(),
|
|
|
|
|
|
'approved' => (clone $query)->where('status', 1)->count(),
|
|
|
|
|
|
'rejected' => (clone $query)->where('status', 2)->count(),
|
|
|
|
|
|
'total' => $query->count(),
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
2026-03-10 11:54:58 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 抓取懂车帝车辆参数
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $crawlCarId 车型ID
|
|
|
|
|
|
* @return array 格式化后的参数数据
|
|
|
|
|
|
* @throws BusinessException
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function crawlParams(string $crawlCarId): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$url = 'https://www.dongchedi.com/auto/params-carIds-' . $crawlCarId;
|
|
|
|
|
|
|
|
|
|
|
|
// 请求头配置
|
|
|
|
|
|
$headers = [
|
|
|
|
|
|
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
|
|
|
|
'Accept-Language' => 'zh-CN,zh;q=0.9',
|
|
|
|
|
|
'Referer' => 'https://www.dongchedi.com/',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
$client = new Client();
|
|
|
|
|
|
$response = $client->request('GET', $url, [
|
|
|
|
|
|
'headers' => $headers,
|
|
|
|
|
|
'timeout' => 10,
|
|
|
|
|
|
'verify' => false, // 禁用SSL验证
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
$html = (string)$response->getBody();
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($html)) {
|
|
|
|
|
|
throw new BusinessException('页面内容为空');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析HTML获取JSON数据
|
|
|
|
|
|
$ql = QueryList::html($html);
|
|
|
|
|
|
$jsonText = $ql->find('#__NEXT_DATA__')->text();
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($jsonText)) {
|
|
|
|
|
|
throw new BusinessException('未找到 __NEXT_DATA__ JSON');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$raw = json_decode($jsonText, true);
|
|
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
|
|
|
|
throw new BusinessException('JSON解析失败: ' . json_last_error_msg());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取数据
|
|
|
|
|
|
$properties = $raw['props']['pageProps']['rawData']['properties'] ?? [];
|
|
|
|
|
|
$carInfo = $raw['props']['pageProps']['rawData']['car_info'][0] ?? [];
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($properties) || empty($carInfo)) {
|
|
|
|
|
|
throw new BusinessException('数据结构异常,无法提取参数');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建值映射表和分组
|
|
|
|
|
|
$valuesMap = $this->buildValuesMap($carInfo);
|
|
|
|
|
|
$groups = $this->buildCarSpecGroups($properties, $valuesMap);
|
|
|
|
|
|
|
|
|
|
|
|
// 映射到数据库字段
|
|
|
|
|
|
return $this->mapParamsToDbFields($groups, $carInfo);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (GuzzleException $e) {
|
|
|
|
|
|
Log::error('抓取懂车帝参数失败', [
|
|
|
|
|
|
'crawl_car_id' => $crawlCarId,
|
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
throw new BusinessException('抓取参数失败: ' . $e->getMessage());
|
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
|
Log::error('解析懂车帝参数失败', [
|
|
|
|
|
|
'crawl_car_id' => $crawlCarId,
|
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
throw new BusinessException('解析参数失败: ' . $e->getMessage());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 构建值映射表
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function buildValuesMap(array $carInfo): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$valuesMap = [];
|
|
|
|
|
|
|
|
|
|
|
|
// 顶层字段
|
|
|
|
|
|
foreach ($carInfo as $k => $v) {
|
|
|
|
|
|
if ($k === 'info' || is_array($v)) {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
$valuesMap[$k] = ['value' => $v];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// info中的配置项
|
|
|
|
|
|
if (!empty($carInfo['info']) && is_array($carInfo['info'])) {
|
|
|
|
|
|
foreach ($carInfo['info'] as $k => $v) {
|
|
|
|
|
|
if (is_array($v) && array_key_exists('value', $v)) {
|
|
|
|
|
|
$valuesMap[$k] = $v;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $valuesMap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 构建车辆规格分组
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function buildCarSpecGroups(array $properties, array $valuesMap): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$groups = [];
|
|
|
|
|
|
$currentKey = null;
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($properties as $item) {
|
|
|
|
|
|
$type = (int)($item['type'] ?? 1);
|
|
|
|
|
|
$key = $item['key'] ?? '';
|
|
|
|
|
|
$text = $item['text'] ?? '';
|
|
|
|
|
|
$subList = $item['sub_list'] ?? null;
|
|
|
|
|
|
|
|
|
|
|
|
// 分类字段:type = 0 / 2
|
|
|
|
|
|
if (in_array($type, [0, 2], true)) {
|
|
|
|
|
|
$currentKey = $key ?: ($text ?: ('group_' . count($groups)));
|
|
|
|
|
|
|
|
|
|
|
|
if (!isset($groups[$currentKey])) {
|
|
|
|
|
|
$groups[$currentKey] = [
|
|
|
|
|
|
'group_key' => $currentKey,
|
|
|
|
|
|
'group_text' => $text,
|
|
|
|
|
|
'group_type' => $type,
|
|
|
|
|
|
'items' => [],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 普通字段:挂到最近的分类下
|
|
|
|
|
|
if ($currentKey === null) {
|
|
|
|
|
|
$currentKey = '_default';
|
|
|
|
|
|
$groups[$currentKey] = [
|
|
|
|
|
|
'group_key' => '_default',
|
|
|
|
|
|
'group_text' => '其他',
|
|
|
|
|
|
'group_type' => -1,
|
|
|
|
|
|
'items' => [],
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$value = '';
|
|
|
|
|
|
if ($key && isset($valuesMap[$key])) {
|
|
|
|
|
|
$value = trim((string)($valuesMap[$key]['value'] ?? ''));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$node = [
|
|
|
|
|
|
'key' => $key,
|
|
|
|
|
|
'text' => $text,
|
|
|
|
|
|
'value' => $value,
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
if (is_array($subList) && !empty($subList)) {
|
|
|
|
|
|
$children = [];
|
|
|
|
|
|
foreach ($subList as $sub) {
|
|
|
|
|
|
$subKey = $sub['key'] ?? '';
|
|
|
|
|
|
$subText = $sub['text'] ?? '';
|
|
|
|
|
|
$subValue = '';
|
|
|
|
|
|
|
|
|
|
|
|
if ($subKey && isset($valuesMap[$subKey])) {
|
|
|
|
|
|
$subValue = trim((string)($valuesMap[$subKey]['value'] ?? ''));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$children[] = [
|
|
|
|
|
|
'key' => $subKey,
|
|
|
|
|
|
'text' => $subText,
|
|
|
|
|
|
'value' => $subValue,
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$node['children'] = $children;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$groups[$currentKey]['items'][] = $node;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return array_values($groups);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 从分组数据中获取字段值
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function getFieldValue($groups, string $field): ?string
|
|
|
|
|
|
{
|
|
|
|
|
|
if (is_string($groups)) {
|
|
|
|
|
|
$groups = json_decode($groups, true);
|
|
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!is_array($groups)) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($groups as $group) {
|
|
|
|
|
|
if (isset($group['items']) && is_array($group['items'])) {
|
|
|
|
|
|
foreach ($group['items'] as $item) {
|
|
|
|
|
|
if (($item['key'] ?? null) === $field) {
|
|
|
|
|
|
return $item['value'] ?? null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!empty($item['children']) && is_array($item['children'])) {
|
|
|
|
|
|
foreach ($item['children'] as $child) {
|
|
|
|
|
|
if (($child['key'] ?? null) === $field) {
|
|
|
|
|
|
return $child['value'] ?? null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解析尺寸字符串 (如 "4890x1890x1680" 转为长宽高)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function parseDimensions(string $dimensionStr): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$result = ['body_length' => '', 'body_width' => '', 'body_height' => ''];
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($dimensionStr)) {
|
|
|
|
|
|
return $result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$parts = explode('x', strtolower(str_replace(['×', 'X', 'X'], 'x', $dimensionStr)));
|
|
|
|
|
|
if (count($parts) >= 3) {
|
|
|
|
|
|
$result['body_length'] = trim($parts[0]);
|
|
|
|
|
|
$result['body_width'] = trim($parts[1]);
|
|
|
|
|
|
$result['body_height'] = trim($parts[2]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 转换安全配置值 (●→标配, ○→选装, -→空)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function convertSafetyValue(?string $value): string
|
|
|
|
|
|
{
|
|
|
|
|
|
if ($value === '●') {
|
|
|
|
|
|
return '标配';
|
|
|
|
|
|
} elseif ($value === '○') {
|
|
|
|
|
|
return '选装';
|
|
|
|
|
|
} elseif ($value === '-') {
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
return $value ?? '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 映射参数到数据库字段
|
|
|
|
|
|
* DongChedi 字段 → 数据库字段
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function mapParamsToDbFields(array $groups, array $carInfo): array
|
|
|
|
|
|
{
|
|
|
|
|
|
$result = [];
|
|
|
|
|
|
|
2026-03-11 15:01:54 +08:00
|
|
|
|
// 从 car_info 直接获取的字段,清理掉"万"等字符
|
|
|
|
|
|
$officialPrice = $carInfo['official_price'] ?? '';
|
|
|
|
|
|
$result['official_guide_price'] = $this->convertPriceToYuan($officialPrice);
|
2026-03-10 11:54:58 +08:00
|
|
|
|
|
|
|
|
|
|
// 1. 基本信息
|
|
|
|
|
|
$result['manufacturer'] = $this->getFieldValue($groups, 'sub_brand_name') ?? '';
|
|
|
|
|
|
$result['level'] = $this->getFieldValue($groups, 'jb') ?? '';
|
|
|
|
|
|
$result['energy_type'] = $this->getFieldValue($groups, 'fuel_form') ?? '';
|
|
|
|
|
|
$result['listing_time'] = $this->getFieldValue($groups, 'market_time') ?? '';
|
|
|
|
|
|
$result['engine'] = $this->getFieldValue($groups, 'engine_description') ?? '';
|
|
|
|
|
|
$result['electric_motor'] = $this->getFieldValue($groups, 'electric_description') ?? '';
|
|
|
|
|
|
$result['nedc_range'] = $this->parseNumeric($this->getFieldValue($groups, 'nedc_range'));
|
|
|
|
|
|
$result['cltc_range'] = $this->parseNumeric($this->getFieldValue($groups, 'cltc_range'));
|
|
|
|
|
|
$result['fast_charge_platform'] = $this->getFieldValue($groups, 'fast_charge_platform') ?? '';
|
|
|
|
|
|
$result['charging_time'] = $this->parseDecimal($this->getFieldValue($groups, 'charging_time'));
|
|
|
|
|
|
$result['fast_charge_percent'] = $this->parseNumeric($this->getFieldValue($groups, 'fast_charge_percent'));
|
|
|
|
|
|
$result['max_power_kw'] = $this->parseNumeric($this->getFieldValue($groups, 'energy_elect_max_power'));
|
|
|
|
|
|
$result['max_torque_nm'] = $this->parseNumeric($this->getFieldValue($groups, 'energy_elect_max_torque'));
|
|
|
|
|
|
$result['transmission_desc'] = $this->getFieldValue($groups, 'gearbox_description') ?? '';
|
|
|
|
|
|
$result['dimensions'] = $this->getFieldValue($groups, 'length_width_height') ?? '';
|
|
|
|
|
|
$result['body_structure'] = $this->getFieldValue($groups, 'car_body_struct') ?? '';
|
|
|
|
|
|
$result['max_speed'] = $this->parseNumeric($this->getFieldValue($groups, 'max_speed'));
|
|
|
|
|
|
$result['acceleration_100'] = $this->parseDecimal($this->getFieldValue($groups, 'acceleration_time'));
|
|
|
|
|
|
$result['nedc_fuel_consumption'] = $this->parseDecimal($this->getFieldValue($groups, 'fuel_comprehensive'));
|
|
|
|
|
|
$result['power_consumption_100'] = $this->parseDecimal($this->getFieldValue($groups, 'power_consumption_100'));
|
|
|
|
|
|
$result['equivalent_fuel_consumption'] = $this->parseDecimal($this->getFieldValue($groups, 'equivalent_fuel_consumption'));
|
|
|
|
|
|
$result['maintenance_cost_60k'] = $this->parseDecimal($this->getFieldValue($groups, 'maintain_cost'));
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 车身信息 (解析尺寸)
|
|
|
|
|
|
$dimensionStr = $this->getFieldValue($groups, 'length_width_height');
|
|
|
|
|
|
$dimensions = $this->parseDimensions($dimensionStr ?? '');
|
|
|
|
|
|
$result['body_length'] = $dimensions['body_length'];
|
|
|
|
|
|
$result['body_width'] = $dimensions['body_width'];
|
|
|
|
|
|
$result['body_height'] = $dimensions['body_height'];
|
|
|
|
|
|
|
|
|
|
|
|
$result['wheelbase'] = $this->parseNumeric($this->getFieldValue($groups, 'wheelbase'));
|
|
|
|
|
|
$result['door_count'] = $this->parseNumeric($this->getFieldValue($groups, 'door_nums'));
|
|
|
|
|
|
$result['door_open_type'] = $this->getFieldValue($groups, 'door_open_way') ?? '';
|
|
|
|
|
|
$result['seat_count'] = $this->parseNumeric($this->getFieldValue($groups, 'seat_count'));
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 变速箱
|
|
|
|
|
|
$result['gear_count'] = $this->parseNumeric($this->getFieldValue($groups, 'stalls'));
|
|
|
|
|
|
$result['transmission_type'] = $this->getFieldValue($groups, 'gearbox_type') ?? '';
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 底盘/转向
|
|
|
|
|
|
$result['drive_mode'] = $this->getFieldValue($groups, 'driver_form') ?? '';
|
|
|
|
|
|
$result['front_suspension'] = $this->getFieldValue($groups, 'front_suspension_form') ?? '';
|
|
|
|
|
|
$result['rear_suspension'] = $this->getFieldValue($groups, 'rear_suspension_form') ?? '';
|
|
|
|
|
|
$result['steering_type'] = $this->getFieldValue($groups, 'power_steering_type') ?? '';
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 主动安全配置
|
|
|
|
|
|
$result['has_abs'] = $this->convertSafetyValue($this->getFieldValue($groups, 'abs_anti_lock'));
|
|
|
|
|
|
$result['has_ebd'] = $this->convertSafetyValue($this->getFieldValue($groups, 'brake_force'));
|
|
|
|
|
|
$result['has_bra'] = $this->convertSafetyValue($this->getFieldValue($groups, 'brake_assist'));
|
|
|
|
|
|
$result['has_tcs'] = $this->convertSafetyValue($this->getFieldValue($groups, 'traction_control'));
|
|
|
|
|
|
$result['has_esp'] = $this->convertSafetyValue($this->getFieldValue($groups, 'body_stability_system'));
|
|
|
|
|
|
$result['has_active_brake'] = $this->convertSafetyValue($this->getFieldValue($groups, 'active_brake'));
|
|
|
|
|
|
$result['has_lane_assist'] = $this->convertSafetyValue($this->getFieldValue($groups, 'line_support'));
|
|
|
|
|
|
$result['has_lka'] = $this->convertSafetyValue($this->getFieldValue($groups, 'lane_keeping_assist'));
|
|
|
|
|
|
$result['has_lane_centering'] = $this->convertSafetyValue($this->getFieldValue($groups, 'lane_center'));
|
|
|
|
|
|
$result['has_fatigue_remind'] = $this->convertSafetyValue($this->getFieldValue($groups, 'fatigue_driving_warning'));
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 车轮/制动
|
|
|
|
|
|
$result['front_brake_type'] = $this->getFieldValue($groups, 'front_brake_type') ?? '';
|
|
|
|
|
|
$result['rear_brake_type'] = $this->getFieldValue($groups, 'rear_brake_type') ?? '';
|
|
|
|
|
|
$result['parking_brake_type'] = $this->getFieldValue($groups, 'park_brake_type') ?? '';
|
|
|
|
|
|
$result['front_tire_spec'] = $this->getFieldValue($groups, 'front_tire_size') ?? '';
|
|
|
|
|
|
$result['rear_tire_spec'] = $this->getFieldValue($groups, 'rear_tire_size') ?? '';
|
|
|
|
|
|
|
|
|
|
|
|
// 确保空字符串不被转为null
|
|
|
|
|
|
foreach ($result as $key => $value) {
|
|
|
|
|
|
if ($value === null) {
|
|
|
|
|
|
$result[$key] = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解析数值字段
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function parseNumeric(?string $value): string
|
|
|
|
|
|
{
|
|
|
|
|
|
if ($value === null || $value === '') {
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
// 提取数字部分
|
|
|
|
|
|
if (preg_match('/\d+/', $value, $matches)) {
|
|
|
|
|
|
return $matches[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解析小数
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function parseDecimal(?string $value): string
|
|
|
|
|
|
{
|
|
|
|
|
|
if ($value === null || $value === '') {
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
// 提取数字和小数点
|
|
|
|
|
|
if (preg_match('/[\d.]+/', $value, $matches)) {
|
|
|
|
|
|
return $matches[0];
|
|
|
|
|
|
}
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
2026-03-11 15:01:54 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 抓取懂车帝二手车数据并保存
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $url 懂车帝二手车链接
|
|
|
|
|
|
* @param int $userId 用户ID
|
|
|
|
|
|
* @return array 返回创建的车辆数据
|
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function crawlAndStore(string $url, int $userId): array
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 抓取二手车基本信息
|
|
|
|
|
|
$usedCarCrawl = new \app\common\utils\crawl\service\UsedCarCrawlSingle();
|
|
|
|
|
|
$info = $usedCarCrawl->crawlCar($url);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 如果有 car_id,抓取详细参数
|
|
|
|
|
|
$params = [];
|
|
|
|
|
|
if (!empty($info['car_id'])) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
$params = $this->crawlParams($info['car_id']);
|
|
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
|
|
Log::error('抓取车辆参数失败', [
|
|
|
|
|
|
'car_id' => $info['car_id'],
|
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
// 参数抓取失败不影响主流程
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 构建数据库记录数据
|
|
|
|
|
|
// 处理图片:如果是数组,转为逗号分隔字符串
|
|
|
|
|
|
$imagesValue = $info['image'] ?? '';
|
|
|
|
|
|
if (is_array($imagesValue)) {
|
|
|
|
|
|
$imagesValue = implode(',', array_filter($imagesValue));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$data = [
|
|
|
|
|
|
'code' => $this->generateCode(),
|
|
|
|
|
|
'user_id' => $userId,
|
|
|
|
|
|
'product_name' => $info['title'] ?? $info['car_name'] ?? '',
|
|
|
|
|
|
'price' => $this->convertPriceToYuan($info['price'] ?? 0),
|
|
|
|
|
|
'source_place' => $info['source_place'] ?? '',
|
|
|
|
|
|
'plate_time' => $info['plate_time'] ?? '',
|
|
|
|
|
|
'displacement' => $info['capacity'] ?? '',
|
|
|
|
|
|
'mileage' => $this->convertMileageToKm($info['usage_mileage'] ?? 0),
|
|
|
|
|
|
'body_color' => $info['body_color'] ?? '',
|
|
|
|
|
|
'condition_desc' => $info['condition_desc'] ?? '',
|
|
|
|
|
|
'source_url' => $url,
|
|
|
|
|
|
'crawl_title' => $info['title'] ?? '',
|
|
|
|
|
|
'crawl_brand' => $info['brand_name'] ?? '',
|
|
|
|
|
|
'crawl_brand_id' => $info['brand_id'] ?? 0,
|
|
|
|
|
|
'crawl_series' => $info['series_name'] ?? '',
|
|
|
|
|
|
'crawl_series_id' => $info['series_id'] ?? 0,
|
|
|
|
|
|
'crawl_year' => $info['year'] ?? '',
|
|
|
|
|
|
'crawl_msrp' => $info['new_price'] ?? '',
|
|
|
|
|
|
'crawl_car_id' => $info['car_id'] ?? 0,
|
|
|
|
|
|
'status' => 0, // 待审核
|
|
|
|
|
|
'images' => $imagesValue,
|
|
|
|
|
|
'power' => $info['horsepower'] ?? '',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 合并详细参数,过滤掉数组类型的值
|
|
|
|
|
|
$params = array_filter($params, function($value) {
|
|
|
|
|
|
return !is_array($value);
|
|
|
|
|
|
});
|
|
|
|
|
|
$data = array_merge($data, $params);
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 创建记录
|
|
|
|
|
|
$car = UsedCarDetail::create($data);
|
|
|
|
|
|
|
|
|
|
|
|
return $car->toArray();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成车辆编码
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function generateCode(): string
|
|
|
|
|
|
{
|
|
|
|
|
|
return 'UC' . date('YmdHis') . rand(1000, 9999);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将价格(万)转换为元
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function convertPriceToYuan($price): float
|
|
|
|
|
|
{
|
|
|
|
|
|
if (empty($price)) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果是数字,直接乘以10000
|
|
|
|
|
|
if (is_numeric($price)) {
|
|
|
|
|
|
return floatval($price) * 10000;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果是字符串,移除"万"字后乘以10000
|
|
|
|
|
|
$price = str_replace('万', '', $price);
|
|
|
|
|
|
return floatval($price) * 10000;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将里程(万公里)转换为公里
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function convertMileageToKm($mileage): float
|
|
|
|
|
|
{
|
|
|
|
|
|
if (empty($mileage)) {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果是数字,直接乘以10000
|
|
|
|
|
|
if (is_numeric($mileage)) {
|
|
|
|
|
|
return floatval($mileage) * 10000;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 如果是字符串,移除"万公里"后乘以10000
|
|
|
|
|
|
$mileage = str_replace(['万公里', '万'], '', $mileage);
|
|
|
|
|
|
return floatval($mileage) * 10000;
|
|
|
|
|
|
}
|
2026-03-09 17:46:17 +08:00
|
|
|
|
}
|