bucket = $config['bucket']; // Настройка для российского S3-совместимого хранилища $this->client = new AwsS3Client([ 'version' => $config['version'], 'region' => $config['region'], 'endpoint' => $config['endpoint'], 'use_path_style_endpoint' => $config['use_path_style_endpoint'], 'credentials' => [ 'key' => $config['key'], 'secret' => $config['secret'], ], 'http' => [ 'verify' => true, ] ]); } /** * Загрузка файла в S3 */ public function uploadFile($localPath, $s3Key, $options = []) { try { $putObjectParams = [ 'Bucket' => $this->bucket, 'Key' => $s3Key, 'SourceFile' => $localPath, ]; // Добавляем ContentType если указан if (isset($options['ContentType'])) { $putObjectParams['ContentType'] = $options['ContentType']; } else { $putObjectParams['ContentType'] = $this->getMimeType($localPath); } // Добавляем метаданные если указаны if (isset($options['Metadata']) && is_array($options['Metadata'])) { // AWS SDK ожидает все значения метаданных как строки $metadata = []; foreach ($options['Metadata'] as $key => $value) { $metadata[$key] = (string)$value; } $putObjectParams['Metadata'] = $metadata; } $result = $this->client->putObject($putObjectParams); return [ 'success' => true, 'url' => $this->getPublicUrl($s3Key), 's3_key' => $s3Key, 'etag' => $result['ETag'] ]; } catch (AwsException $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Создание временной ссылки для скачивания * @param string $s3Key S3 ключ файла * @param mixed $expiresIn Время жизни URL в секундах (число) или строка типа '+10 minutes' */ public function getPresignedUrl($s3Key, $expiresIn = 3600) { try { // Преобразуем строку TTL в секунды, если нужно if (is_string($expiresIn)) { // Если строка начинается с '+', используем её как есть для strtotime if (strpos($expiresIn, '+') === 0) { $expiresIn = strtotime($expiresIn) - time(); } else { // Иначе пытаемся распарсить как число секунд $expiresIn = (int)$expiresIn; } } // Минимум 60 секунд, максимум 7 дней $expiresIn = max(60, min($expiresIn, 604800)); $cmd = $this->client->getCommand('GetObject', [ 'Bucket' => $this->bucket, 'Key' => $s3Key ]); $request = $this->client->createPresignedRequest($cmd, "+{$expiresIn} seconds"); return [ 'success' => true, 'url' => (string) $request->getUri() ]; } catch (AwsException $e) { return [ 'success' => false, 'error' => $e->getMessage(), 'error_code' => $e->getAwsErrorCode(), 'request_id' => $e->getAwsRequestId() ]; } } /** * Удаление файла из S3 */ public function deleteObject($s3Key) { try { $result = $this->client->deleteObject([ 'Bucket' => $this->bucket, 'Key' => $s3Key ]); return [ 'success' => true, 'deleted_key' => $s3Key ]; } catch (AwsException $e) { return [ 'success' => false, 'error' => $e->getMessage() ]; } } /** * Скачивание файла во временную папку */ public function downloadToTemp($s3Key) { $tempFile = sys_get_temp_dir() . '/' . uniqid('s3_download_') . '_' . basename($s3Key); try { $this->client->getObject([ 'Bucket' => $this->bucket, 'Key' => $s3Key, 'SaveAs' => $tempFile ]); return $tempFile; } catch (AwsException $e) { throw new Exception('S3 download failed: ' . $e->getMessage()); } } /** * Проверка существования файла */ public function fileExists($s3Key) { try { $this->client->headObject([ 'Bucket' => $this->bucket, 'Key' => $s3Key ]); return true; } catch (AwsException $e) { return false; } } /** * Получение публичного URL */ private function getPublicUrl($s3Key) { return $this->client->getObjectUrl($this->bucket, $s3Key); } /** * Определение MIME типа файла */ private function getMimeType($filePath) { $finfo = finfo_open(FILEINFO_MIME_TYPE); $mimeType = finfo_file($finfo, $filePath); finfo_close($finfo); return $mimeType ?: 'application/octet-stream'; } /** * Генерация ключа для S3 на основе CRM данных */ public function generateS3Key($crmData, $isNewVersion = false) { $module = $crmData['module'] ?? 'Documents'; $recordId = $crmData['record_id'] ?? 'unknown'; $fileName = $crmData['file_name'] ?? 'file'; $basePath = strtolower($module) . '/' . $recordId; if ($isNewVersion) { $timestamp = date('Y-m-d_H-i-s'); $fileName = pathinfo($fileName, PATHINFO_FILENAME) . '_v' . $timestamp . '.' . pathinfo($fileName, PATHINFO_EXTENSION); } return $basePath . '/' . $fileName; } }