Files
orico-official-website/app/admin/controller/v1/ProductCompat.php
jsasg 6c43f4e3f2
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 3s
refactor: 产品兼容性相关功能
2026-08-11 17:14:13 +08:00

469 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare (strict_types = 1);
namespace app\admin\controller\v1;
use app\admin\model\v1\ProductCompatModel;
use app\admin\model\v1\ProductCompatModelsModel;
use app\admin\model\v1\ProductCompatPartsModel;
use app\admin\model\v1\ProductCompatTypesModel;
use app\admin\validate\v1\ProductCompatValidate;
class ProductCompat
{
/**
* 产品兼容性数据列表
*/
public function index()
{
$params = request()->param([
'compatible_model',
'model',
'type_id',
'part_id',
'brand_name',
'page/d' => 1,
'size/d' => 10
]);
$list = ProductCompatModel::withoutField([
'language_id',
'deleted_at'
])
->with([
'types' => function ($query) {
$query->field(['id', 'name']);
},
'parts' => function ($query) {
$query->field(['id', 'name']);
},
'compatible_models' => function ($query) {
$query->field(['compat_id', 'compatible_model']);
}
])
->language(request()->lang_id)
->where(function ($query) use($params) {
if (!empty($params['type_id'])) {
$query->typeId($params['type_id']);
}
if (!empty($params['part_id'])) {
$query->partId($params['part_id']);
}
})
->withSearch(['model', 'brand_name', 'compatible_model'], [
'model' => $params['model'] ?? null,
'brand_name' => $params['brand_name'] ?? null,
'compatible_model' => $params['compatible_model'] ?? null,
])
->order(['updated_at' => 'desc'])
->paginate([
'list_rows' => $params['size'],
'page' => $params['page'],
])
?->bindAttr('types', ['type_name' => 'name'])
?->bindAttr('parts', ['part_name' => 'name'])
?->hidden(['types', 'parts'])
?->each(function ($item) {
$compatible_models = [];
foreach ($item['compatible_models'] as $model) {
$compatible_models[] = $model['compatible_model'];
}
$item['compatible_models'] = implode(',', $compatible_models);
});
return success("获取成功", $list);
}
/**
* 导入产品兼容性数据
*/
public function import()
{
// 获取上传文件
$file = request()->file('file');
if (empty($file)) {
return error('请上传文件');
}
$lang_id = request()->lang_id;
// 读取文件
$keys_map = [
'A' => 'type_name',
'B' => 'part_name',
'C' => 'brand_name',
'D' => 'level_name',
'E' => 'spec_name',
'F' => 'series_name',
'G' => 'model',
'H' => 'frequency',
'I' => 'capacity',
'J' => 'compatible_models',
];
$chunk = 500; // 每批次处理的条数
$items = 0; // 已处理的条数
$xlsx_data = [];
$xlsx_reader = xlsx_stream_reader($file->getRealPath(), 2, $keys_map, true);
\think\facade\Db::startTrans();
try {
foreach ($xlsx_reader as $row) {
$items++;
$row['seq_no'] = $items; // 记录行序号,防止后续顺序打乱
$xlsx_data[] = $row;
if ($items % $chunk == 0) {
// 每500条为一批次进行处理
$this->handleImport($xlsx_data, $lang_id);
$xlsx_data = [];
}
}
if (!empty($xlsx_data)) {
// 处理剩余的
$this->handleImport($xlsx_data, $lang_id);
}
} catch (\Throwable $th) {
\think\facade\Db::rollback();
return error($th->getMessage());
}
\think\facade\Db::commit();
return success('操作成功');
}
private function handleImport(array $xlsx_data, int $lang_id): void
{
list(
$models,
$exists_compat_ids,
$compat_models,
$compat_datas
) = $this->matchExistsCompatData($xlsx_data, $lang_id);
// 校验数据并组装sql语句
$raw_sql = $this->validateAndBuildSql($compat_datas);
if (false === \think\facade\Db::execute($raw_sql)) {
throw new \Exception(sprintf('第【%s】行执行导入 SQL 失败', implode(',', array_column($xlsx_data, 'seq_no'))));
}
// 清除旧兼容型号数据
if (!empty($exists_compat_ids)) {
ProductCompatModelsModel::destroy(function ($query) use($exists_compat_ids) {
$query->where('compat_id', 'in', $exists_compat_ids);
});
}
// 为不遗漏id重新查询
$compat_map = ProductCompatModel::language($lang_id)
->modelName($models)
->column('id', 'model');
// 插入兼容型号数据
$compatible_models = [];
foreach ($compat_models as $m) {
$compat_id = $compat_map[$m['model']];
foreach ($m['compatible_models'] as $compatible_model) {
$compatible_models[] = [
'compat_id' => $compat_id,
'compatible_model' => $compatible_model,
];
}
}
if (!empty($compatible_models)) {
ProductCompatModelsModel::insertAll($compatible_models);
}
}
private function matchExistsCompatData(array $datas, int $lang_id)
{
$models = array_column($datas, 'model');
$compat_map = ProductCompatModel::language($lang_id)
->modelName($models)
->column('id', 'model');
$type_names = array_unique(array_column($datas, 'type_name'));
$type_map = ProductCompatTypesModel::language($lang_id)
->typeName($type_names)
->column('id', 'name');
$part_names = array_unique(array_column($datas, 'part_name'));
$part_map = ProductCompatPartsModel::language($lang_id)
->partName($part_names)
->column('id', 'name');
// 匹配已存在数据的主键、类型id及配件id
$exists_compat_ids = [];
$errors = [];
$compat_models = [];
foreach ($datas as &$d) {
// 确认要更新的数据的主键
$d['id'] = null;
if (isset($compat_map[$d['model']])) {
$d['id'] = $compat_map[$d['model']];
$exists_compat_ids[] = $d['id'];
}
// 所属语言
$d['language_id'] = $lang_id;
// 确认产品类型id
if (empty($type_map[$d['type_name']])) {
$errors[] = "第【{$d['seq_no']}】行,产品类型【{$d['type_name']}】无效";
continue;
}
$d['type_id'] = $type_map[$d['type_name']];
// 确认配件类型id
if (empty($part_map[$d['part_name']])) {
$errors[] = "第【{$d['seq_no']}】行,配件类型【{$d['part_name']}】无效";
continue;
}
$d['part_id'] = $part_map[$d['part_name']];
// 处理兼容型号数据
$compatible_models_str = str_replace('', ',', $d['compatible_models']);
$compatible_models_arr = explode(',', $compatible_models_str);
if (!empty($compatible_models_arr)) {
$compat_models[] = [
'model' => $d['model'],
'compatible_models' => $compatible_models_arr,
];
}
}
unset($d);
if (!empty($errors)) {
throw new \Exception(implode("\n", $errors));
}
return [$models, $exists_compat_ids, $compat_models, $datas];
}
private function validateAndBuildSql(array $compat_datas)
{
$sql_values = [];
$validate = new ProductCompatValidate;
foreach ($compat_datas as $compat) {
// 校验数据
$scene = is_null($compat['id']) ? 'add' : 'edit';
if (!$validate->scene($scene)->check($compat)) {
throw new \Exception(sprintf('第【%s】行%s', $compat['seq_no'], $validate->getError()));
}
// 组装 sql values
$sql_values[] = $this->buildSqlValue($compat);
}
return $this->buildRawSql($sql_values);
}
private function buildSqlValue(array $compat)
{
$pdo = \think\facade\Db::connect()->getPdo();
return sprintf(
'(%d, %d, %d, %d, %s, %s, %s, %s, %s, %s, %s)',
$compat['id'],
$compat['language_id'],
$compat['type_id'],
$compat['part_id'],
is_string($compat['model']) ? $pdo->quote($compat['model']) : $compat['model'],
is_string($compat['brand_name']) ? $pdo->quote($compat['brand_name']) : $compat['brand_name'],
is_string($compat['level_name']) ? $pdo->quote($compat['level_name']) : $compat['level_name'],
is_string($compat['spec_name']) ? $pdo->quote($compat['spec_name']) : $compat['spec_name'],
is_string($compat['series_name']) ? $pdo->quote($compat['series_name']) : $compat['series_name'],
is_string($compat['capacity']) ? $pdo->quote($compat['capacity']) : $compat['capacity'],
is_string($compat['frequency']) ? $pdo->quote($compat['frequency']) : $compat['frequency']
);
}
private function buildRawSql(array $values)
{
return sprintf(
'INSERT INTO %s (
`id`,
`language_id`,
`type_id`,
`part_id`,
`model`,
`brand_name`,
`level_name`,
`spec_name`,
`series_name`,
`capacity`,
`frequency`
) VALUES %s
ON DUPLICATE KEY UPDATE
`type_id` = VALUES(`type_id`),
`part_id` = VALUES(`part_id`),
`model` = VALUES(`model`),
`brand_name` = VALUES(`brand_name`),
`level_name` = VALUES(`level_name`),
`spec_name` = VALUES(`spec_name`),
`series_name` = VALUES(`series_name`),
`capacity` = VALUES(`capacity`),
`frequency` = VALUES(`frequency`)
',
(new ProductCompatModel)->getTable(),
implode(',', $values)
);
}
/**
* 产品兼容性数据添加
*/
public function save()
{
$post = request()->post([
'type_id',
'part_id',
'model',
'brand_name',
'level_name',
'spec_name',
'series_name',
'capacity',
'frequency',
'compatible_models',
'sort',
'disabled'
]);
$data = array_merge($post, ['language_id' => request()->lang_id]);
// 校验输入
$validate = new ProductCompatValidate;
if (!$validate->scene('add')->check($data)) {
return error($validate->getError());
}
\think\facade\Db::startTrans();
try {
$compat = ProductCompatModel::create($data);
if ($compat->isEmpty()) {
throw new \Exception('操作失败');
}
$compatible_models = [];
$models = explode(',', str_replace('', ',', $post['compatible_models']));
foreach ($models as $model) {
$compatible_models[] = [
'compat_id' => $compat->id,
'compatible_model' => trim($model),
];
}
if (empty($compatible_models)) {
throw new \Exception('请确认兼容型号数据为有效');
}
ProductCompatModelsModel::insertAll($compatible_models);
} catch (\Throwable $e) {
\think\facade\Db::rollback();
return error($e->getMessage());
}
\think\facade\Db::commit();
return success('操作成功');
}
/**
* 产品兼容性数据详细
*/
public function read()
{
$id = request()->param('id');
$data = ProductCompatModel::withoutField(['language_id', 'deleted_at'])
->with([
'compatible_models' => function ($query) {
$query->field(['compat_id', 'compatible_model']);
}
])
->bypk($id)
->find();
if (empty($data)) {
return error('获取失败');
}
if (!empty($data->compatible_models)) {
$compatible_models = [];
foreach ($data->compatible_models as $model) {
$compatible_models[] = $model['compatible_model'];
}
$data->compatible_models = implode(',', $compatible_models);
}
return success("获取成功", $data);
}
/**
* 产品兼容性数据更新
*/
public function update()
{
$id = request()->param('id');
$put = request()->put([
'type_id',
'part_id',
'model',
'brand_name',
'level_name',
'spec_name',
'series_name',
'capacity',
'frequency',
'compatible_models',
'sort',
'disabled'
]);
$data = array_merge($put, ['id' => $id, 'language_id' => request()->lang_id]);
// 校验输入
$validate = new ProductCompatValidate;
if (!$validate->scene('edit')->check($data)) {
return error($validate->getError());
}
\think\facade\Db::startTrans();
try {
$compat = ProductCompatModel::bypk($id)->find();
if (empty($compat)) {
throw new \Exception('请确认操作对象是否正确');
}
if (!$compat->save($data)) {
throw new \Exception('操作失败');
}
$compatible_models = [];
$models = explode(',', str_replace('', ',', $put['compatible_models']));
foreach ($models as $model) {
$compatible_models[] = [
'compat_id' => $compat->id,
'compatible_model' => trim($model),
];
}
if (empty($compatible_models)) {
throw new \Exception('请确认兼容型号数据为有效');
}
// 清除旧数据
$deleted = ProductCompatModelsModel::destroy(function($query) use ($compat) {
$query->where('compat_id', '=', $compat->id);
});
if (!$deleted) {
throw new \Exception('删除兼容型号数据失败');
}
ProductCompatModelsModel::insertAll($compatible_models);
} catch (\Throwable $e) {
\think\facade\Db::rollback();
return error($e->getMessage());
}
\think\facade\Db::commit();
return success('操作成功');
}
/**
* 产品兼容性数据删除
*/
public function delete()
{
$id = request()->param('id');
$data = ProductCompatModel::bypk($id)->find();
if (empty($data)) {
return error("请确认操作对象是否正确");
}
if (!$data->delete()) {
return error("操作失败");
}
return success("操作成功");
}
}