Преобразование XLSX в Json с помощью NodeJs и пакета XLSX

Вот мой полный сценарий: У меня есть статический сайт, размещенный на S3. Это просто интерфейс для загрузки файла. Загрузка выполняется с использованием шлюза API в качестве конечной точки, которая запускает лямбда-функцию.

Эта лямбда-функция прочитает файл XLSX, обработает его и загрузит json в корзину S3. Событие PUT в сегменте запускает другую лямбда-функцию, которая будет читать этот json и отправлять сообщения в очередь SQS. Затем, как только приходит сообщение, он запускает экземпляр EC2 для обработки.

Проблема в том, что я не знаю, что происходит, но мои результаты полностью сбиваются с толку. Это что-то вроде того, что ничто не декодирует base64 после его получения.

Вот пример моего вывода:

API-шлюз настроен правильно, я в этом почти уверен:

  1. Я установил тип двоичного носителя как application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.
  2. Добавлены шаблоны отображения тела.

При тестировании отправки файла через 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, а после выполнения функции они зашифровываются.

#1 Поделитесь кодом html/js в клиенте, который отвечает за загрузку файла, и передайте его конечной точке серверной части. #2 Поделитесь почтальоном или локоном успешной попытки. #3 Марсианские символы на вашем снимке экрана находятся до, во время или после. метод processXlsxFile

JRichardsz 04.09.2024 06:46

Я знаю, что при выполнении этого через браузер в консоли появляется ошибка. Но я игнорировал это, поскольку данные получены функцией. Кроме того, после выполнения функции появляются странные символы. В журналах CloudWatch я вижу, что данные принимаются в формате Base64, а после выполнения функции они зашифровываются. Я добавил код HTML и Js. Конечная точка: 5lvbhojaaf.execute-api.sa-east-1.amazonaws.com/conversion/…

Pablo Costa 04.09.2024 14:18

Измените вопрос на что-то вроде: Не удалось загрузить файл в aws лямбда или что-то в этом роде, и удалите часть s3. Изолируйте ошибку

JRichardsz 04.09.2024 15:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Краткое содержание

  • Ваш клиент (html/js) не кодирует файл в base64 перед загрузкой.
  • Ваша лямбда ожидает тело base64

Совет

  • Попробуйте локально и отладьте (или распечатайте) построчно, если ваш файл отправляется так, как ожидает серверная часть.
  • Исправить это непосредственно на aws будет непросто. Проверьте это, чтобы попробовать локально перед развертыванием на aws:
  • Если вы хотите избежать multipart/form-data, преобразуйте файл в base64 на клиентском уровне (html/js), а затем отправьте содержимое как json, чтобы в обработчике лямбда было легко получить содержимое
    • Предупреждение. Если ваш файл имеет тенденцию увеличивать свой размер, преобразование в base64 может стать проблемой (оперативная память, пропускная способность, браузер и т. д.). Вот почему существует тип контента multipart/form-data. Проверьте это и это

# 1 База64

На клиенте (html/js) вы не кодируете файл как base64, поэтому на лямбда-уровне это не имеет смысла:

const fileContent = Buffer.from(event.body, 'base64');

Возможно, используя почтальона, вы отправляете файл как base64, но в своем html/js вы не конвертируете файл в base64. Вы отправляете его как Content-Type: multipart/form-data.

#2 Данные многочастной формы

(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)

Ссылки

#3. Данные многочастной формы с помощью Aws Lambda

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 в будущем, думаю, это будет правильное решение.

Pablo Costa 04.09.2024 19:01

Другие вопросы по теме