Files
x-server-mobile/backend/app/service/UsedCarService.php
T
2026-03-05 13:15:05 +08:00

530 lines
16 KiB
PHP

<?php
namespace app\Service;
use app\Model\UsedCar;
use app\Model\UsedCarPdfLog;
use app\Model\User;
use support\Db;
use support\Redis;
/**
* 二手车业务服务
*/
class UsedCarService
{
private UsedCarCrawlService $crawlService;
public function __construct()
{
$this->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;
}
}