'营业执照', 'id_front' => '身份证人像面', 'id_back' => '身份证国徽面', 'bank_front' => '银行卡正面', 'bank_back' => '银行卡背面', ]; public static function definitions(string $type): array { $sheets = [ 'identity' => ['name' => '身份证推送单', 'images' => ['id_front', 'id_back']], 'bank' => ['name' => '银行卡推送单', 'images' => ['bank_front', 'bank_back']], ]; return match ($type) { 'authorization' => $sheets, 'entity-change' => ['license' => ['name' => '营业执照盖章推送单', 'images' => ['license']]] + $sheets, default => [], }; } public static function rules(string $type, bool $requested): array { $allowed = self::definitions($type); $required = $requested ? array_merge(...array_column($allowed ?: [['images' => []]], 'images')) : []; $rules = [ 'credential_required' => ['nullable', 'boolean', Rule::prohibitedIf($allowed === [] && $requested)], 'credential_signer' => ['nullable', 'integer', 'in:0,1'], 'credential_images' => ['nullable', 'array:' . implode(',', array_keys(self::IMAGES))], ]; foreach (self::IMAGES as $key => $label) { $active = in_array($key, $required, true); $rules['credential_images.' . $key] = [Rule::requiredIf($active), Rule::prohibitedIf(! $active), 'nullable', 'image', 'mimes:jpg,jpeg,png', 'max:10240', 'dimensions:max_width=5000,max_height=5000']; } return $rules; } public function prepare(CourierAgreementSignature $signature, array $uploads): array { $definitions = self::definitions($signature->document_type); if (! ($signature->payload['credential_required'] ?? false)) { return []; } $disk = Storage::disk('uploads'); $base = "courier-agreements/{$signature->company_id}/{$signature->business_no}/credentials"; $result = []; foreach ($definitions as $key => $definition) { $pictures = []; $originals = []; foreach ($definition['images'] as $imageKey) { $upload = $uploads[$imageKey] ?? null; if (! $upload instanceof UploadedFile || ! $upload->isValid()) { throw new RuntimeException('证件图片不完整,请重新上传。'); } $extension = $upload->getMimeType() === 'image/png' ? 'png' : 'jpg'; $originalPath = "{$base}/{$imageKey}.{$extension}"; $original = file_get_contents($upload->getRealPath()); if (! $disk->put($originalPath, $original)) { throw new RuntimeException('证件原始图片无法写入私有存储。'); } $image = (new ImageManager(['driver' => 'gd']))->make($upload->getRealPath())->orientate(); $image->resize(2400, 2400, static function ($constraint): void { $constraint->aspectRatio(); $constraint->upsize(); }); $maxHeight = $key === 'license' ? 470 : 180; $scale = min(450 / $image->width(), $maxHeight / $image->height()); $pictures[] = ['label' => self::IMAGES[$imageKey], 'uri' => 'data:image/png;base64,' . base64_encode((string) $image->encode('png')), 'width' => round($image->width() * $scale, 2), 'height' => round($image->height() * $scale, 2)]; $originals[$imageKey] = ['path' => $originalPath, 'sha256' => hash('sha256', $original)]; $image->destroy(); } $pdf = app('dompdf.wrapper'); $pdf->setPaper('a4', 'portrait'); $pdf->loadHTML(view('business.pdf.courier-credential', [ 'title' => $definition['name'], 'pictures' => $pictures, 'stampOnly' => $key === 'license', 'signature' => $signature, 'fontPath' => str_replace('\\', '/', resource_path('fonts/DroidSansFallback.ttf')), ])->render(), 'UTF-8'); $bytes = $pdf->output(); if (! str_starts_with($bytes, '%PDF-') || $pdf->getDomPDF()->getCanvas()->get_page_count() !== 1) { throw new RuntimeException('证件推送单必须为一页 PDF,请检查上传图片。'); } $sourcePath = "{$base}/{$key}-source.pdf"; if (! $disk->put($sourcePath, $bytes)) { throw new RuntimeException('证件推送单无法写入私有存储。'); } $result[$key] = ['name' => $definition['name'], 'source_path' => $sourcePath, 'source_sha256' => hash('sha256', $bytes), 'originals' => $originals]; } return $result; } }