CaiWuXiTong/app/Support/CourierStockQuoteImageArchive.php
limeng 9a25db91cc feat(报价管理): 存量报价整本批量录入及签署原图归档 (#346)
保留各家工作表报价原文、公式、独立执行日期及签署原件;审核后按执行日期管理版本。33项测试、336项断言通过。生产业务迁移已获明确授权,服务器私有备份已完成并逐条校验。
2026-10-16 14:56:44 +08:00

60 lines
2.8 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\Support;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
final class CourierStockQuoteImageArchive
{
/** 原样复制工作表内嵌图片,不使用屏幕截图、裁剪结果或重新编码的图片冒充原件。 */
public function store(Worksheet $sheet, string $workbookPath, string $directory, array &$savedPaths): array
{
$files = $warnings = [];
$prefix = 'zip://' . realpath($workbookPath) . '#';
foreach ($sheet->getDrawingCollection() as $index => $drawing) {
if (! $drawing instanceof Drawing || ! str_starts_with($drawing->getPath(), $prefix)) {
$warnings[] = '图片 ' . ($index + 1) . ' 无法无损提取,请另附原图;整本 Excel 已保留。';
continue;
}
$stream = @fopen($drawing->getPath(), 'rb');
if ($stream === false) {
$this->failed($sheet);
}
try {
$bytes = stream_get_contents($stream, 20 * 1024 * 1024 + 1);
} finally {
fclose($stream);
}
if ($bytes === false || $bytes === '' || strlen($bytes) > 20 * 1024 * 1024) {
$this->failed($sheet);
}
$mime = (new \finfo(FILEINFO_MIME_TYPE))->buffer($bytes);
$extension = strtolower(pathinfo($drawing->getPath(), PATHINFO_EXTENSION));
// 不支持浏览器预览的原始格式仍按原字节留存,只提供下载。
$extension = preg_match('/^[a-z0-9]{1,8}$/', $extension) ? $extension : 'bin';
$name = $sheet->getTitle() . '-原件图片-' . ($index + 1) . '.' . $extension;
$path = $directory . '/' . ($index + 1) . '.' . $extension;
$savedPaths[] = $path;
if (! Storage::disk('uploads')->put($path, $bytes, 'private')) {
$this->failed($sheet);
}
$files[] = [
'kind' => 'signed', 'path' => $path, 'name' => $name, 'sha256' => hash('sha256', $bytes),
'mime_type' => $mime, 'size' => strlen($bytes), 'sheet_name' => $sheet->getTitle(),
'anchor' => $drawing->getCoordinates(), 'image_index' => $index + 1,
'source' => 'workbook_embedded', 'archived_at' => now()->toIso8601String(),
];
}
return ['signed_images' => $files, 'image_warnings' => array_values(array_unique($warnings))];
}
private function failed(Worksheet $sheet): never
{
throw ValidationException::withMessages(['source_quote' => '工作表「' . $sheet->getTitle() . '」原件图片提取或存档失败(每张最多 20 MB),本次上传未完成,请重试。']);
}
}