Files
fdesk/app/common/utils/excel/ExcelImportTrait.php
T
2025-11-07 15:28:59 +08:00

134 lines
4.5 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
namespace app\common\utils\excel;
use support\Db;
/**
* ExcelImportTrait
*
* 把“读取 Sheet → 映射字段 → 规范化数据 → 打时间戳 → 批量入库”
* 这条通用导入链路抽成 Trait,便于在多个 Service 里复用。
*
* 依赖:
* - ExcelReader:提供 readSheet / readSheetFromUrl / remap / toDecimal / toBool / toDate
* - 你的 OssService:通过 generatePrivateUrl($objectKey) 生成 OSS 私有签名 URL
*/
trait ExcelImportTrait
{
/**
* 读取某个 Sheet 的“行数组”
* - $file 可为本地路径或 URL(由 $isUrl 控制)
* - headerRow:表头所在行(Excel 行号,从 1 开始)
* - startRow:数据起始行(不传则为 headerRow+1)
*/
protected function readSheetRows(string $file, string $sheet, bool $isUrl, int $headerRow = 1, ?int $startRow = null): array
{
$ret = $isUrl
? ExcelReader::readSheetFromUrl($file, $sheet, $headerRow, $startRow)
: ExcelReader::readSheet($file, $sheet, $headerRow, $startRow);
return $ret['rows'] ?? [];
}
/**
* 读取某个 Sheet(传入 OSS 对象键),内部会用你的 OssService 生成签名 URL 再读取
*/
protected function readSheetRowsFromOssKey(string $objectKey, string $sheet, int $headerRow = 1, ?int $startRow = null): array
{
$signedUrl = $this->ossSignedUrl($objectKey);
$ret = ExcelReader::readSheetFromUrl($signedUrl, $sheet, $headerRow, $startRow);
return $ret['rows'] ?? [];
}
/**
* 使用你的 OssService 生成私有签名 URL
* - 兼容你的现有写法:(new \app\common\service\OssService)->generatePrivateUrl($objectKey)
*/
protected function ossSignedUrl(string $objectKey): string
{
return (new \app\common\service\OssService())->generatePrivateUrl($objectKey);
}
/**
* 将一行做字段映射 + 基础类型规范化(decimal/bool/date)
* - $headerMap:表头映射(源列名 => 目标字段名)
* - $decimalFields/$boolFields/$dateFields:需要做类型规范化的目标字段名列表
*/
protected function mapAndNormalizeRow(
array $row,
array $headerMap,
array $decimalFields = [],
array $boolFields = [],
array $dateFields = []
): array {
$rec = ExcelReader::remap($row, $headerMap);
foreach ($decimalFields as $f) {
if (array_key_exists($f, $rec)) {
$rec[$f] = ExcelReader::toDecimal($rec[$f]); // 建议已升级为“去货币符号/千分位/括号负数”的版本
}
}
foreach ($boolFields as $f) {
if (array_key_exists($f, $rec)) {
$rec[$f] = ExcelReader::toBool($rec[$f]);
}
}
foreach ($dateFields as $f) {
if (array_key_exists($f, $rec)) {
$rec[$f] = ExcelReader::toDate($rec[$f]);
}
}
return $rec;
}
/**
* 基于 VIN 的跳过规则:VIN 为空、或是“合计/总计”行
* - $vinAliases:VIN 列的候选表头(如 ['车架号','VIN NO'])
*/
protected function shouldSkipByVinAliases(array $row, array $vinAliases = ['车架号', 'VIN NO']): bool
{
$vin = $this->pickFirstNonEmpty($row, $vinAliases);
if ($vin === null) return true;
return (bool)preg_match('/^(合计|总计)/u', $vin);
}
/**
* 在多个候选键里取第一个非空字符串
*/
protected function pickFirstNonEmpty(array $row, array $keys): ?string
{
foreach ($keys as $k) {
if (isset($row[$k])) {
$v = trim((string)$row[$k]);
if ($v !== '') return $v;
}
}
return null;
}
/**
* 统一补齐 created_at / updated_at 时间戳(int(11))
*/
protected function stamp(array &$rec): void
{
$now = time();
$rec['created_at'] = $now;
$rec['updated_at'] = $now;
}
/**
* 批量入库
* - 返回 ['inserted'=>N, 'error'=>null|string]
* - 发生异常时不抛出,返回 error 字符串,便于上层统一处理
*/
protected function batchInsert(string $table, array $rows): array
{
if (!$rows) return ['inserted' => 0, 'error' => null];
try {
Db::table($table)->insert($rows);
return ['inserted' => count($rows), 'error' => null];
} catch (\Throwable $e) {
return ['inserted' => 0, 'error' => $e->getMessage()];
}
}
}