Работает загрузк файлов на S3
This commit is contained in:
parent
d184307981
commit
64df06db36
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
.idea
|
||||||
# Keep environment variables out of version control
|
# Keep environment variables out of version control
|
||||||
.env
|
.env
|
||||||
|
736
package-lock.json
generated
736
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { AuthRequest } from '../../../middleware/auth';
|
import { AuthRequest } from '../../../middleware/auth';
|
||||||
import { galleryService } from '../../../services/galleryService';
|
import { galleryService } from '../../../services/galleryService';
|
||||||
import { s3Service } from '../../../services/s3Service';
|
|
||||||
import { logger } from '../../../config/logger';
|
import { logger } from '../../../config/logger';
|
||||||
|
|
||||||
export async function createGalleryImage(req: AuthRequest, res: Response) {
|
export async function createGalleryImage(req: AuthRequest, res: Response) {
|
||||||
|
@ -1,95 +1,52 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import multer from 'multer';
|
|
||||||
import multerS3 from 'multer-s3';
|
|
||||||
import { auth } from '../../middleware/auth';
|
import { auth } from '../../middleware/auth';
|
||||||
import {createS3Client, s3Service} from '../../services/s3MulterService';
|
import {createS3Client, getMulterS3Config, uploadToS3} from '../../services/s3Service';
|
||||||
import { logger } from '../../config/logger';
|
import { logger } from '../../config/logger';
|
||||||
import { imageResolutions } from '../../config/imageResolutions';
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
//const upload = multer();
|
|
||||||
|
|
||||||
// Инициализация Multer-S3
|
// Конфигурация для подключения к S3
|
||||||
const s3Client = createS3Client(); // Экспортируем клиент из s3Service
|
const BUCKET_NAME = process.env.AWS_S3_BUCKET || '';
|
||||||
const upload = multer({
|
const REGION = process.env.AWS_REGION || 'ru-central1'
|
||||||
storage: multerS3({
|
const AWS_ENDPOINT = process.env.AWS_ENDPOINT || ''
|
||||||
s3: s3Client,
|
const ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || '';
|
||||||
bucket: process.env.AWS_S3_BUCKET || '',
|
const SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || '';
|
||||||
metadata: (req, file, cb) => {
|
|
||||||
cb(null, { fieldName: file.fieldname });
|
|
||||||
},
|
|
||||||
key: (req, file, cb) => {
|
|
||||||
const fileName = `${Date.now()}-${file.originalname}`;
|
|
||||||
cb(null, `uploads/${fileName}`);
|
|
||||||
},
|
|
||||||
contentType: multerS3.AUTO_CONTENT_TYPE,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
// Создаем клиент S3
|
||||||
router.post('/upload-url', auth, async (req, res) : Promise<void> => {
|
const s3Client = createS3Client(REGION, AWS_ENDPOINT, ACCESS_KEY_ID, SECRET_ACCESS_KEY);
|
||||||
try {
|
|
||||||
const { fileName, fileType, resolution } = req.body;
|
|
||||||
|
|
||||||
if (!fileName || !fileType || !resolution) {
|
// Настройка Multer
|
||||||
res.status(400).json({ error: 'Missing required fields' });
|
const upload = getMulterS3Config(s3Client, BUCKET_NAME);
|
||||||
|
|
||||||
|
// Роут для загрузки изображения
|
||||||
|
router.post('/upload-url', auth, upload, async (req, res): Promise<void> => {
|
||||||
|
if (!req.file) {
|
||||||
|
res.status(400).json({ error: 'No file uploaded' });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedResolution = imageResolutions.find(r => r.id === resolution);
|
|
||||||
if (!selectedResolution) {
|
|
||||||
res.status(400).json({ error: 'Invalid resolution' });
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { uploadUrl, imageId, key } = await s3Service.getUploadUrl(fileName, fileType);
|
|
||||||
|
|
||||||
logger.info(`Generated upload URL for image: ${fileName}`);
|
|
||||||
res.json({ uploadUrl, imageId, key });
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error generating upload URL:', error);
|
|
||||||
res.status(500).json({ error: 'Failed to generate upload URL' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Маршрут для загрузки изображения
|
|
||||||
router.post('/upload-url', auth, upload.single('file'), async (req, res): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const file = req.file as Express.MulterS3.File;
|
const file = req.file as Express.MulterS3.File;
|
||||||
const { resolutionId } = req.body;
|
const folder = req.body.folder as string;
|
||||||
|
const resolutionId = req.body.resolutionId as string;
|
||||||
|
const quality = 80;
|
||||||
|
|
||||||
if (!file) {
|
try {
|
||||||
res.status(400).json({ error: 'Файл не найден' });
|
// Загружаем файл в S3 после оптимизации
|
||||||
return;
|
const fileUrl = await uploadToS3(s3Client, BUCKET_NAME, folder, file, resolutionId, quality);
|
||||||
}
|
res.json({ message: 'Файл успешно загружен', fileUrl });
|
||||||
|
|
||||||
// Сохраняем оригинал и обрабатываем изображение
|
|
||||||
const originalKey = await s3Service.uploadOriginalFile(file);
|
|
||||||
const optimizedResult = await s3Service.optimizeAndUpload(file.buffer, originalKey, resolutionId);
|
|
||||||
|
|
||||||
res.status(200).json({
|
|
||||||
message: 'Файл успешно загружен и оптимизирован',
|
|
||||||
originalKey,
|
|
||||||
optimizedKey: optimizedResult.key,
|
|
||||||
dimensions: {
|
|
||||||
width: optimizedResult.width,
|
|
||||||
height: optimizedResult.height,
|
|
||||||
},
|
|
||||||
format: optimizedResult.format,
|
|
||||||
size: optimizedResult.size,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Ошибка загрузки изображения:', error);
|
logger.error(`Ошибка загрузки файла в S3: ${file.key}`);
|
||||||
res.status(500).json({ error: 'Ошибка загрузки изображения' });
|
console.error(error);
|
||||||
|
res.status(500).json({ error: 'Ошибка загрузки файла в S3' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
router.get('/:id', auth, async (req, res) => {
|
router.get('/:id', auth, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const image = await s3Service.getImage(id);
|
const image = await getImage(id, BUCKET_NAME);
|
||||||
res.json(image);
|
res.json(image);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error fetching image:', error);
|
logger.error('Error fetching image:', error);
|
||||||
@ -97,4 +54,5 @@ router.get('/:id', auth, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default router;
|
export default router;
|
@ -1,128 +1,105 @@
|
|||||||
import { S3Client, PutObjectCommand, GetObjectCommand, PutBucketCorsCommand } from '@aws-sdk/client-s3';
|
import { S3Client, PutObjectCommand, GetObjectCommand} from '@aws-sdk/client-s3';
|
||||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import multer from 'multer';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { logger } from '../config/logger';
|
import { imageResolutions } from '../config/imageResolutions';
|
||||||
|
|
||||||
// Инициализация клиента S3
|
// Функция для создания клиента S3
|
||||||
const s3Client = new S3Client({
|
export const createS3Client = (region: string, endpoint: string, accessKeyId: string, secretAccessKey: string) => {
|
||||||
region: process.env.AWS_REGION || 'ru-central1',
|
return new S3Client({
|
||||||
endpoint: process.env.AWS_ENDPOINT || '',
|
region: region,
|
||||||
|
endpoint: endpoint,
|
||||||
credentials: {
|
credentials: {
|
||||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID || '',
|
accessKeyId: accessKeyId,
|
||||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || ''
|
secretAccessKey: secretAccessKey,
|
||||||
},
|
|
||||||
forcePathStyle: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const BUCKET_NAME = process.env.AWS_S3_BUCKET || '';
|
|
||||||
|
|
||||||
// Конфигурация CORS
|
|
||||||
const corsConfig = {
|
|
||||||
Bucket: BUCKET_NAME,
|
|
||||||
CORSConfiguration: {
|
|
||||||
CORSRules: [
|
|
||||||
{
|
|
||||||
AllowedOrigins: ['http://127.0.0.1:5173'], // Домен фронтенда
|
|
||||||
AllowedMethods: ['GET', 'PUT', 'POST', 'DELETE'], // Разрешенные методы
|
|
||||||
AllowedHeaders: ['*'], // Разрешенные заголовки
|
|
||||||
ExposeHeaders: ['ETag', 'x-amz-meta-custom-header'], // Заголовки в ответе
|
|
||||||
MaxAgeSeconds: 3000, // Кеширование CORS
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Применение конфигурации
|
|
||||||
const applyCors = async () => {
|
|
||||||
try {
|
|
||||||
const command = new PutBucketCorsCommand(corsConfig);
|
|
||||||
await s3Client.send(command);
|
|
||||||
console.log('CORS успешно настроен.');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка при настройке CORS:', error);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
//applyCors();
|
// Функция для изменения размеров и оптимизации изображения
|
||||||
|
const resizeAndOptimizeImage = async (buffer: Buffer, width: number, height: number, quality: number): Promise<Buffer> => {
|
||||||
|
return await sharp(buffer)
|
||||||
|
.resize(width, height, {
|
||||||
|
fit: sharp.fit.inside
|
||||||
|
})
|
||||||
|
.webp({ quality })
|
||||||
|
.toBuffer();
|
||||||
|
};
|
||||||
|
|
||||||
export const s3Service = {
|
// Функция для получения конфигурации Multer-S3
|
||||||
getUploadUrl: async (fileName: string, fileType: string) => {
|
export const getMulterS3Config = (
|
||||||
const imageId = uuidv4();
|
s3Client: S3Client,
|
||||||
const key = `uploads/${imageId}-${fileName}`;
|
bucketName: string
|
||||||
|
) => {
|
||||||
|
return multer({
|
||||||
|
storage: multer.memoryStorage(), // Храним файл в памяти перед обработкой
|
||||||
|
fileFilter: (req, file, cb) => {
|
||||||
|
if (!file.mimetype.startsWith('image/')) {
|
||||||
|
return cb(new Error('Разрешены только файлы изображений!'));
|
||||||
|
}
|
||||||
|
cb(null, true);
|
||||||
|
},
|
||||||
|
}).single('file'); // Обрабатываем один файл
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для загрузки файла в S3 после оптимизации
|
||||||
|
export const uploadToS3 = async (
|
||||||
|
s3Client: S3Client,
|
||||||
|
bucketName: string,
|
||||||
|
folder: string,
|
||||||
|
file: Express.Multer.File,
|
||||||
|
resolutionId: string,
|
||||||
|
quality: number
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const selectedResolution = imageResolutions.find(r => r.id === resolutionId);
|
||||||
|
if (!selectedResolution) {
|
||||||
|
throw new Error('Недопустимое разрешение изображения');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Оптимизируем изображение
|
||||||
|
const optimizedBuffer = await resizeAndOptimizeImage(
|
||||||
|
file.buffer,
|
||||||
|
selectedResolution.width,
|
||||||
|
selectedResolution.height,
|
||||||
|
quality
|
||||||
|
);
|
||||||
|
|
||||||
|
// Генерируем уникальное имя файла
|
||||||
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
||||||
|
const fileNameWithoutExt = file.originalname.replace(/\.[^.]+$/, '');
|
||||||
|
const fileName = `${folder}/${uniqueSuffix}-${fileNameWithoutExt}.webp`;
|
||||||
|
|
||||||
|
// Загружаем файл в S3
|
||||||
const command = new PutObjectCommand({
|
const command = new PutObjectCommand({
|
||||||
Bucket: BUCKET_NAME,
|
Bucket: bucketName,
|
||||||
Key: key,
|
Key: fileName,
|
||||||
ContentType: fileType
|
Body: optimizedBuffer,
|
||||||
|
ContentType: file.mimetype,
|
||||||
|
ACL: 'public-read',
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await s3Client.send(command);
|
||||||
const uploadUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 });
|
|
||||||
logger.info(`Generated pre-signed URL for upload: ${key}`);
|
|
||||||
return { uploadUrl, imageId, key };
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error generating pre-signed URL:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getImage: async (imageId: string) => {
|
// Возвращаем URL загруженного файла
|
||||||
|
return `https://${bucketName}.s3.regru.cloud/${fileName}`;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Ошибка загрузки файла в S3: ${error}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
export const getImage = async (imageId: string, bucketName: string) => {
|
||||||
try {
|
try {
|
||||||
const command = new GetObjectCommand({
|
const command = new GetObjectCommand({
|
||||||
Bucket: BUCKET_NAME,
|
Bucket: bucketName,
|
||||||
Key: `uploads/${imageId}`
|
Key: `uploads/${imageId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = await getSignedUrl(s3Client, command, { expiresIn: 3600 });
|
const url = await getSignedUrl(s3Client, command, { expiresIn: 3600 });
|
||||||
return { url };
|
return { url };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting image:', error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
optimizeAndUpload: async (buffer: Buffer, key: string, resolution: { width: number; height: number }) => {
|
|
||||||
try {
|
|
||||||
let sharpInstance = sharp(buffer);
|
|
||||||
|
|
||||||
// Get image metadata
|
|
||||||
const metadata = await sharpInstance.metadata();
|
|
||||||
|
|
||||||
// Resize if resolution is specified
|
|
||||||
if (resolution.width > 0 && resolution.height > 0) {
|
|
||||||
sharpInstance = sharpInstance.resize(resolution.width, resolution.height, {
|
|
||||||
fit: 'inside',
|
|
||||||
withoutEnlargement: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to WebP for better compression
|
|
||||||
const optimizedBuffer = await sharpInstance
|
|
||||||
.webp({ quality: 80 })
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
// Upload optimized image
|
|
||||||
const optimizedKey = key.replace(/\.[^/.]+$/, '.webp');
|
|
||||||
await s3Client.send(new PutObjectCommand({
|
|
||||||
Bucket: BUCKET_NAME,
|
|
||||||
Key: optimizedKey,
|
|
||||||
Body: optimizedBuffer,
|
|
||||||
ContentType: 'image/webp'
|
|
||||||
}));
|
|
||||||
|
|
||||||
logger.info(`Successfully optimized and uploaded image: ${optimizedKey}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
key: optimizedKey,
|
|
||||||
width: metadata.width,
|
|
||||||
height: metadata.height,
|
|
||||||
format: 'webp',
|
|
||||||
size: optimizedBuffer.length
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Error optimizing and uploading image:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user