| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- <?php
- namespace App\Jobs;
- use Illuminate\Bus\Queueable;
- use Illuminate\Contracts\Queue\ShouldQueue;
- use Illuminate\Foundation\Bus\Dispatchable;
- use Illuminate\Queue\InteractsWithQueue;
- use Illuminate\Queue\SerializesModels;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Log;
- use Illuminate\Support\Facades\Storage;
- use Illuminate\Support\Str;
- use App\Services\RecordFileService;
- use App\Http\Middleware\TenantMiddleware;
- class ProcessRecordAttachment implements ShouldQueue
- {
- use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
- protected $recordId;
- protected $tempFilePath;
- protected $originalFileName;
- protected $type;
- protected $clientName;
- public $timeout = 300;
- public $tries = 3;
- public $backoff = [10, 30, 60];
- public function boot()
- {
- app(TenantMiddleware::class)->setupTenantConnection();
- }
- public function __construct($recordId, $tempFilePath, $originalFileName, $type = 'OUT', $clientName = null)
- {
- $this->recordId = $recordId;
- $this->tempFilePath = $tempFilePath;
- $this->originalFileName = $originalFileName;
- $this->type = strtolower($type);
- $this->clientName = $clientName ?: session('clientName', 'default');
- $this->clientName = Str::slug($this->clientName, '_');
- }
- public function handle(RecordFileService $recordFileService)
- {
- try {
- Log::info("=== PROCESSING ATTACHMENT JOB START ===");
- Log::info("Client: {$this->clientName}");
- Log::info("Record ID: {$this->recordId}");
- Log::info("Temp file: {$this->tempFilePath}");
- Log::info("Original name: {$this->originalFileName}");
- Log::info("Type: {$this->type}");
- DB::table('records')
- ->where('id', $this->recordId)
- ->update([
- 'attachment_status' => 'processing',
- 'updated_at' => now()
- ]);
- if (!Storage::disk('s3')->exists($this->tempFilePath)) {
- Log::error("Temp file not found on S3: {$this->tempFilePath}");
- try {
- $tempFiles = Storage::disk('s3')->files("{$this->clientName}/temp/uploads");
- Log::info("Available temp files for client '{$this->clientName}' on S3: " . json_encode($tempFiles));
- } catch (\Exception $e) {
- Log::error("Could not list temp files for client '{$this->clientName}': " . $e->getMessage());
- }
- throw new \Exception("Temp file not found on S3: {$this->tempFilePath}");
- }
- $tempFileSize = Storage::disk('s3')->size($this->tempFilePath);
- Log::info("Temp file size: {$tempFileSize} bytes");
- $extension = pathinfo($this->originalFileName, PATHINFO_EXTENSION);
- $fileName = time() . '_' . Str::random(10) . '.' . $extension;
- $finalPath = "{$this->clientName}/records/{$this->type}/{$this->recordId}/attachments/{$fileName}";
- Log::info("Final path: {$finalPath}");
- $copySuccess = $this->copyFileOnS3($this->tempFilePath, $finalPath);
- if (!$copySuccess) {
- throw new \Exception("Failed to copy file from {$this->tempFilePath} to {$finalPath}");
- }
- if (!Storage::disk('s3')->exists($finalPath)) {
- throw new \Exception("Final file not found after copy: {$finalPath}");
- }
- $finalFileSize = Storage::disk('s3')->size($finalPath);
- Log::info("Final file size: {$finalFileSize} bytes");
- if ($finalFileSize !== $tempFileSize) {
- Log::warning("File size mismatch! Temp: {$tempFileSize}, Final: {$finalFileSize}");
- } else {
- Log::info("File sizes match - copy successful");
- }
- DB::table('records')
- ->where('id', $this->recordId)
- ->update([
- 'attachment' => $finalPath,
- 'attachment_status' => 'completed',
- 'updated_at' => now()
- ]);
- $this->cleanupTempFile($this->tempFilePath);
- Log::info("Attachment processing completed successfully for record {$this->recordId}: {$finalPath}");
- Log::info("=== PROCESSING ATTACHMENT JOB END ===");
- } catch (\Exception $e) {
- Log::error("Failed to process attachment for record {$this->recordId}: " . $e->getMessage());
- Log::error("Stack trace: " . $e->getTraceAsString());
- DB::table('records')
- ->where('id', $this->recordId)
- ->update([
- 'attachment_status' => 'failed',
- 'updated_at' => now()
- ]);
- $this->cleanupTempFile($this->tempFilePath);
- throw $e;
- }
- }
- /**
- * Enhanced S3 copy with multiple fallback approaches
- */
- private function copyFileOnS3($sourcePath, $destinationPath)
- {
- Log::info("Attempting S3 copy from {$sourcePath} to {$destinationPath}");
- try {
- Log::info("Trying Method 1: Standard S3 copy");
- $copyResult = Storage::disk('s3')->copy($sourcePath, $destinationPath);
- if ($copyResult && Storage::disk('s3')->exists($destinationPath)) {
- Log::info("Method 1 successful: Standard S3 copy");
- return true;
- } else {
- Log::warning("Method 1 failed: Standard S3 copy returned " . ($copyResult ? 'true' : 'false'));
- }
- } catch (\Exception $e) {
- Log::warning("Method 1 exception: " . $e->getMessage());
- }
- try {
- Log::info("Trying Method 2: Read and write");
- $fileContent = Storage::disk('s3')->get($sourcePath);
- if (!$fileContent) {
- throw new \Exception("Could not read source file content");
- }
- $writeResult = Storage::disk('s3')->put($destinationPath, $fileContent);
- if ($writeResult && Storage::disk('s3')->exists($destinationPath)) {
- Log::info("Method 2 successful: Read and write");
- return true;
- } else {
- Log::warning("Method 2 failed: Write returned " . ($writeResult ? 'true' : 'false'));
- }
- } catch (\Exception $e) {
- Log::warning("Method 2 exception: " . $e->getMessage());
- }
- try {
- Log::info("Trying Method 3: Stream copy");
- $sourceStream = Storage::disk('s3')->readStream($sourcePath);
- if (!$sourceStream) {
- throw new \Exception("Could not open source stream");
- }
- $writeResult = Storage::disk('s3')->writeStream($destinationPath, $sourceStream);
- if (is_resource($sourceStream)) {
- fclose($sourceStream);
- }
- if ($writeResult && Storage::disk('s3')->exists($destinationPath)) {
- Log::info(" Method 3 successful: Stream copy");
- return true;
- } else {
- Log::warning("Method 3 failed: Stream write returned " . ($writeResult ? 'true' : 'false'));
- }
- } catch (\Exception $e) {
- Log::warning("Method 3 exception: " . $e->getMessage());
- }
- Log::error("All S3 copy methods failed");
- return false;
- }
- /**
- * Clean up temp file with error handling
- */
- private function cleanupTempFile($tempPath)
- {
- try {
- if (Storage::disk('s3')->exists($tempPath)) {
- $deleted = Storage::disk('s3')->delete($tempPath);
- if ($deleted) {
- Log::info("Temp file deleted: {$tempPath}");
- } else {
- Log::warning("Failed to delete temp file: {$tempPath}");
- }
- } else {
- Log::info("Temp file already gone: {$tempPath}");
- }
- } catch (\Exception $e) {
- Log::error("Error deleting temp file {$tempPath}: " . $e->getMessage());
- }
- }
- public function failed(\Exception $exception)
- {
- Log::error("=== JOB PERMANENTLY FAILED ===");
- Log::error("Client: {$this->clientName}");
- Log::error("Record ID: {$this->recordId}");
- Log::error("Exception: " . $exception->getMessage());
- DB::table('records')
- ->where('id', $this->recordId)
- ->update([
- 'attachment_status' => 'failed',
- 'updated_at' => now()
- ]);
- $this->cleanupTempFile($this->tempFilePath);
- }
- /**
- * Get job tags for monitoring
- */
- public function tags()
- {
- return [
- 'attachment',
- 'client:' . $this->clientName,
- 'record:' . $this->recordId,
- 'type:' . $this->type,
- 'file:' . basename($this->tempFilePath)
- ];
- }
- }
|