Вот мой полный сценарий: У меня есть статический сайт, размещенный на S3. Это просто интерфейс для загрузки файла. Загрузка выполняется с использованием шлюза API в качестве конечной точки, которая запускает лямбда-функцию.
Эта лямбда-функция прочитает файл XLSX, обработает его и загрузит json в корзину S3. Событие PUT в сегменте запускает другую лямбда-функцию, которая будет читать этот json и отправлять сообщения в очередь SQS. Затем, как только приходит сообщение, он запускает экземпляр EC2 для обработки.
Проблема в том, что я не знаю, что происходит, но мои результаты полностью сбиваются с толку. Это что-то вроде того, что ничто не декодирует base64 после его получения.
Вот пример моего вывода:
API-шлюз настроен правильно, я в этом почти уверен:
При тестировании отправки файла через Postman работает без проблем. Исходя из этого, я полагаю, что API настроен нормально, а также функция, это что-то происходит между браузером и вызовом лямбда-функции \ API.
Вот что показывает мой журнал лямбды:
2024-09-03T22:13:31.337Z 2024-09-03T22:13:31.337Z a107932e-0bde-4b4c-ad8d-478625019503 ИНФОРМАЦИЯ Обработана данные: [ [ { "numCpfCnpj": "Content-Disposition: данные формы", "numAcordo": " name="file"" }
], null, ]....много двоичных данных
Эта часть специально:
"numCpfCnpj": "Content-Disposition: form-data", "numAcordo": " name=\"file\"" }
Привлек мое внимание, потому что похоже что-то не так с декодированием, так как эти заголовки присутствуют там, где должны быть данные.
Это код, который я использую для чтения файла XLSX:
import XLSX from 'xlsx';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
const s3Client = new S3Client({ region: process.env.AWS_REGION });
const processXlsxFile = (fileContent) => { // Corrected arrow function syntax and added closing brace
const workbook = XLSX.read(fileContent, { type: 'buffer' });
const sheet = workbook.Sheets[workbook.SheetNames[0]];
// Convert all rows to JSON, treating every cell as text to preserve leading zeros
const data = XLSX.utils.sheet_to_json(sheet, {
header: 1, // Use the first row as the header
raw: false // Ensure that all cells are treated as strings
}).slice(1); // Skip the header row
// Process the data and pad CPF/CNPJ numbers as needed
return data.reduce((acc, row, index) => { // Corrected arrow function syntax
// Skip empty or invalid rows
if (!row[0] || !row[1]) return acc;
// Determine if the number is CPF (11 digits) or CNPJ (14 digits)
const numCpfCnpj = String(row[0]).length <= 11
? String(row[0]).padStart(11, '0') // Pad CPF to 11 digits
: String(row[0]).padStart(14, '0'); // Pad CNPJ to 14 digits
const groupIndex = Math.floor(index / 2);
if (!acc[groupIndex]) acc[groupIndex] = [];
acc[groupIndex].push({
numCpfCnpj,
numAcordo: String(row[1])
});
return acc;
}, []);
};
export const handler = async (event) => { // Corrected arrow function syntax and added closing brace
try {
console.info("Received event:", JSON.stringify(event, null, 2));
if (event.body) {
// Decode the base64-encoded file content
const fileContent = Buffer.from(event.body, 'base64');
console.info("File content received");
// Process the Excel file content
const processedData = processXlsxFile(fileContent);
console.info("Processed data:", JSON.stringify(processedData, null, 2));
// Flatten the processed data to a single array
const flatData = processedData.flat();
console.info("Flattened data:", JSON.stringify(flatData, null, 2));
// Convert the result to a JSON string
const jsonString = JSON.stringify(flatData);
const jsonFileName = `converted_data_${Date.now()}.json`;
const bucketName = process.env.BUCKET_NAME;
// Upload the JSON to S3
const putObjectParams = {
Bucket: bucketName,
Key: jsonFileName,
Body: jsonString,
ContentType: 'application/json'
};
const command = new PutObjectCommand(putObjectParams);
await s3Client.send(command);
console.info(`JSON file uploaded successfully to S3: ${jsonFileName}`);
return {
statusCode: 200,
body: JSON.stringify({ message: 'File processed and JSON uploaded to S3 successfully.' }),
headers: {
'Content-Type': 'application/json'
}
};
} else {
return {
statusCode: 400,
body: JSON.stringify({ message: 'No file uploaded' }),
headers: {
'Content-Type': 'application/json'
}
};
}
} catch (error) {
console.error("Error processing file:", error);
return {
statusCode: 500,
body: JSON.stringify({
message: 'Internal Server Error',
error: error.message
}),
headers: {
'Content-Type': 'application/json'
}
};
}
};
Любой вклад приветствуется.
Кроме того, если у кого-нибудь возникнут вопросы, я тоже буду рад ответить.
Обновлено: HTML-код
<!DOCTYPE html>
<html lang = "en">
<head>
<meta charset = "UTF-8">
<meta name = "viewport" content = "width=device-width, initial-scale=1.0">
<title>File Processor</title>
<link rel = "stylesheet" href = "styles/styles.css"> <!-- Link to external CSS file -->
</head>
<body>
<div class = "container">
<h1>Process XLSX File</h1>
<input type = "text" id = "txtInput" readonly placeholder = "No file chosen">
<input type = "file" id = "fileInput" style = "display:none" accept = ".xlsx">
<button id = "btnBrowse">Procurar arquivo</button>
<button id = "btnProcess">Processar</button>
<div id = "results"></div>
</div>
<script src = "scripts/main.js"></script> <!-- Link to external JavaScript file -->
</body>
</html>
JS-код:
document.addEventListener('DOMContentLoaded', () => {
const fileInput = document.getElementById('fileInput');
const txtInput = document.getElementById('txtInput');
const btnBrowse = document.getElementById('btnBrowse');
const btnProcess = document.getElementById('btnProcess');
const resultsDiv = document.getElementById('results');
btnBrowse.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', () => {
txtInput.value = fileInput.files[0] ? fileInput.files[0].name : 'No file chosen';
});
btnProcess.addEventListener('click', async () => {
if (!fileInput.files.length) {
alert('Please select a file first.');
return;
}
const formData = new FormData();
formData.append('file', fileInput.files[0]);
try {
const response = await fetch('https://5lvbhojaaf.execute-api.sa-east-1.amazonaws.com/conversion/readFile', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// Format JSON output for better readability
resultsDiv.innerHTML = `<pre>${JSON.stringify(result, null, 2)}</pre>`;
} catch (error) {
console.error('Error processing file:', error);
resultsDiv.innerHTML = '<p>Error processing file. Check the console for details.</p>';
}
});
});
Конечная точка почтальона: https://5lvbhojaaf.execute-api.sa-east-1.amazonaws.com/conversion/readFile
Странные символы появляются после выполнения функции. В журналах CloudWatch я вижу, что данные принимаются в формате Base64, а после выполнения функции они зашифровываются.
Я знаю, что при выполнении этого через браузер в консоли появляется ошибка. Но я игнорировал это, поскольку данные получены функцией. Кроме того, после выполнения функции появляются странные символы. В журналах CloudWatch я вижу, что данные принимаются в формате Base64, а после выполнения функции они зашифровываются. Я добавил код HTML и Js. Конечная точка: 5lvbhojaaf.execute-api.sa-east-1.amazonaws.com/conversion/…
Измените вопрос на что-то вроде: Не удалось загрузить файл в aws лямбда или что-то в этом роде, и удалите часть s3. Изолируйте ошибку
Предупреждение. Если ваш файл имеет тенденцию увеличивать свой размер, преобразование в base64 может стать проблемой (оперативная память, пропускная способность, браузер и т. д.). Вот почему существует тип контента multipart/form-data. Проверьте это и это
На клиенте (html/js) вы не кодируете файл как base64, поэтому на лямбда-уровне это не имеет смысла:
const fileContent = Buffer.from(event.body, 'base64');
Возможно, используя почтальона, вы отправляете файл как base64, но в своем html/js вы не конвертируете файл в base64. Вы отправляете его как Content-Type: multipart/form-data.
(1) Получить контент из multipart/form-data непросто по сравнению, если тип контента — Content-Type: application/json
(2) Обычно отправляется имя файла и его содержимое (двоичное).
(3) Обычно этот тип контента имеет такие разделы, как размер, расположение, содержание и т. д.
Практически никто этим непосредственно не занимается (в nodejs), поэтому конечному разработчику помогают библиотеки на всех языках. Например, библиотека multer делает нашу жизнь проще:
app.post('/upload', upload.single('file'), function(req, res) {
const title = req.body.title;
const file = req.file;
//file is ready to use (pdf, xls, zip, images, etc)
Ссылки
event.body
нет готового к использованию файла. Это еще одна твоя ошибка
Быстрое исследование не дало мне «простых» способов работы с данными multipart/form с использованием лямбда-выражений aws.
Просматривая некоторые библиотеки, я нашел волшебство, позволяющее получить файл из переменной события лямбда:
module.exports.parse = (event, spotText) => {
const boundary = getBoundary(event);
const result = {};
event.body
.split(boundary)
.forEach(item => {
if (/filename = ".+"/g.test(item)) {
result[item.match(/name = ".+";/g)[0].slice(6, -2)] = {
type: 'file',
filename: item.match(/filename = ".+"/g)[0].slice(10, -1),
contentType: item.match(/Content-Type:\s.+/g)[0].slice(14),
content: spotText? Buffer.from(item.slice(item.search(/Content-Type:\s.+/g) + item.match(/Content-Type:\s.+/g)[0].length + 4, -4), 'binary'):
item.slice(item.search(/Content-Type:\s.+/g) + item.match(/Content-Type:\s.+/g)[0].length + 4, -4),
};
} else if (/name = ".+"/g.test(item)){
result[item.match(/name = ".+"/g)[0].slice(6, -1)] = item.slice(item.search(/name = ".+"/g) + item.match(/name = ".+"/g)[0].length + 4, -4);
}
});
return result;
};
Это уже реализовано в этих библиотеках:
Выберите один и попробуйте. Я уверен, что файл будет готов к использованию в вашем
const workbook = XLSX.read(file, { type: 'buffer' });
Я отметил это как ответ, потому что это так. На данный момент я просто изменил преобразование данных в base64 на стороне клиента. Протестировано с таблицами нескольких размеров, работает хорошо. У меня не будет таблиц размером более 1 МБ, так что это хорошо. Однако я все еще хочу реализовать другое решение. Мне просто нужно, чтобы это поработало, прежде чем пытаться улучшить. Кроме того, я понял все, что вы мне рассказали о происходящем, и полностью согласен. Я планирую попробовать aws-lambda-multipart-parser в будущем, думаю, это будет правильное решение.
#1 Поделитесь кодом html/js в клиенте, который отвечает за загрузку файла, и передайте его конечной точке серверной части. #2 Поделитесь почтальоном или локоном успешной попытки. #3 Марсианские символы на вашем снимке экрана находятся до, во время или после. метод
processXlsxFile