Google Apps Script: Как создать веб-приложение для сканирования или отправки QR-кодов?

Создание веб-приложений для работы с QR-кодами с помощью Google Apps Script (GAS) открывает широкие возможности для автоматизации и интеграции с экосистемой Google Workspace. Это позволяет быстро разрабатывать решения для сбора данных, отслеживания активов, проведения маркетинговых кампаний и многого другого.

Что такое Google Apps Script и его возможности для веб-приложений

Google Apps Script — это облачная платформа скриптов на основе JavaScript, которая позволяет расширять функциональность приложений Google Workspace (Sheets, Docs, Forms, Drive и т. д.) и создавать автономные веб-приложения. GAS предоставляет простой способ создания серверной логики и пользовательских интерфейсов (UI) с использованием HTML, CSS и JavaScript.

Ключевые возможности GAS для веб-приложений:

Серверная логика: Выполнение кода на серверах Google.

Интеграция с сервисами Google: Легкий доступ к API Google Sheets, Drive, Calendar, Gmail и другим.

Развертывание веб-приложений: Публикация приложений, доступных по уникальному URL.

Обработка HTTP-запросов: Функции doGet(e) и doPost(e) для обработки GET и POST запросов.

HTML Service: Создание динамических пользовательских интерфейсов.

Преимущества использования Google Apps Script для обработки QR-кодов

Интеграция: Бесшовная работа с Google Sheets для хранения и анализа данных, полученных из QR-кодов.

Простота развертывания: Не требуется управление серверами или сложная настройка инфраструктуры.

Безопасность: Возможность использования аутентификации Google и настройки уровней доступа.

Стоимость: Бесплатный уровень использования покрывает многие сценарии.

Гибкость: Возможность как генерировать QR-коды (с помощью внешних API или библиотек), так и сканировать/обрабатывать данные из них.

Обзор необходимых инструментов и компонентов

Google Apps Script Editor: Среда разработки.

HTML: Для структуры веб-интерфейса.

CSS: Для стилизации интерфейса.

Client-side JavaScript: Для интерактивности, управления камерой и сканирования QR-кода.

Библиотека для сканирования QR-кодов: Например, jsQR, QuaggaJS.

Google Sheets (опционально): Для хранения данных.

Разработка интерфейса веб-приложения

Интерфейс пользователя (UI) создается с использованием HTML Service в Google Apps Script. Он будет содержать элементы для взаимодействия с пользователем, включая область для отображения видео с камеры и кнопку для запуска сканирования или поле для ввода данных вручную.

Создание HTML-формы для ввода данных или сканирования QR-кода

Создайте HTML-файл (например, Index.html) в вашем проекте GAS. Этот файл будет содержать разметку для UI.



  
    
    
    
    
    
      #video-container {
        position: relative;
        width: 100%;
        max-width: 500px;
        margin: 10px auto;
      }
      #video {
        width: 100%;
        height: auto;
        border: 1px solid #ccc;
      }
      #output {
        margin-top: 10px;
        font-weight: bold;
      }
    
  
  
    

Сканер QR-кодов

Отсканированные данные:

// Код для сканирования будет здесь

Использование JavaScript для обработки сканирования QR-кода (например, с помощью библиотеки jsQR)

Клиентский JavaScript будет отвечать за доступ к камере устройства, отрисовку видеопотока на элементе <video> и использование библиотеки jsQR для поиска и декодирования QR-кодов в каждом кадре.

// Client-side JavaScript (внутри  в Index.html)
const video = document.getElementById('video');
const scanButton = document.getElementById('scanButton');
const sendButton = document.getElementById('sendButton');
const resultSpan = document.getElementById('result');
const manualDataInput = document.getElementById('manual_data');
let stream = null;
let animationFrameId = null;

/**
 * Запрашивает доступ к камере и начинает трансляцию видео.
 */
async function startScan() {
  try {
    stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
    video.srcObject = stream;
    video.setAttribute('playsinline', true); // Необходимо для iOS
    video.play();
    resultSpan.textContent = 'Наведите камеру на QR-код...';
    scanButton.textContent = 'Остановить сканирование';
    scanButton.onclick = stopScan;
    requestAnimationFrame(tick);
  } catch (err) {
    console.error('Ошибка доступа к камере:', err);
    resultSpan.textContent = 'Ошибка доступа к камере.';
  }
}

/**
 * Останавливает сканирование и освобождает камеру.
 */
function stopScan() {
  if (stream) {
    stream.getTracks().forEach(track => track.stop());
  }
  if (animationFrameId) {
    cancelAnimationFrame(animationFrameId);
    animationFrameId = null;
  }
  video.srcObject = null;
  scanButton.textContent = 'Начать сканирование';
  scanButton.onclick = startScan;
  // resultSpan.textContent = ''; // Опционально: очистить результат
}

/**
 * Функция, вызываемая в каждом кадре анимации для сканирования QR-кода.
 */
function tick() {
  if (video.readyState === video.HAVE_ENOUGH_DATA) {
    const canvasElement = document.createElement('canvas');
    const canvas = canvasElement.getContext('2d');
    canvasElement.height = video.videoHeight;
    canvasElement.width = video.videoWidth;
    canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
    const imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height);

    try {
        const code = jsQR(imageData.data, imageData.width, imageData.height, {
            inversionAttempts: 'dontInvert',
        });

        if (code) {
            drawLine(canvas, code.location.topLeftCorner, code.location.topRightCorner, '#FF3B58');
            // ... (нарисовать остальные линии рамки)
            resultSpan.textContent = code.data;
            manualDataInput.value = code.data; // Заполняем поле ввода
            M.updateTextFields(); // Обновляем Materialize input
            stopScan(); // Останавливаем сканирование после успешного обнаружения
            return; // Выход из цикла tick
        } else {
            resultSpan.textContent = 'QR-код не найден...';
        }
    } catch (e) {
        console.error('Ошибка при декодировании QR:', e);
        resultSpan.textContent = 'Ошибка декодирования.';
    }
  }
  animationFrameId = requestAnimationFrame(tick);
}

/**
 * Вспомогательная функция для отрисовки линии (рамки QR-кода).
 * @param {CanvasRenderingContext2D} canvas Контекст canvas.
 * @param {object} begin Начальная точка { x, y }.
 * @param {object} end Конечная точка { x, y }.
 * @param {string} color Цвет линии.
 */
function drawLine(canvas, begin, end, color) {
  canvas.beginPath();
  canvas.moveTo(begin.x, begin.y);
  canvas.lineTo(end.x, end.y);
  canvas.lineWidth = 4;
  canvas.strokeStyle = color;
  canvas.stroke();
}

/**
 * Отправляет данные (из сканера или ручного ввода) на сервер GAS.
 */
function sendDataToServer() {
  const dataToSend = manualDataInput.value || resultSpan.textContent;
  if (!dataToSend || dataToSend === 'QR-код не найден...' || dataToSend === 'Наведите камеру на QR-код...') {
      M.toast({html: 'Нет данных для отправки!'})
      return;
  }
  
  sendButton.disabled = true; // Блокируем кнопку на время отправки
  M.toast({html: 'Отправка данных...'}) 

  google.script.run
    .withSuccessHandler(response => {
      M.toast({html: response});
      // Очистка полей после успешной отправки
      resultSpan.textContent = '';
      manualDataInput.value = '';
      M.updateTextFields();
      sendButton.disabled = false;
    })
    .withFailureHandler(error => {
      M.toast({html: `Ошибка: ${error.message}`});
      console.error('Ошибка отправки:', error);
      sendButton.disabled = false;
    })
    .processQrData(dataToSend);
}

// Назначение обработчиков
scanButton.onclick = startScan;
sendButton.onclick = sendDataToServer;

document.addEventListener('DOMContentLoaded', function() {
    M.updateTextFields(); // Инициализация полей Materialize
});

Стилизация интерфейса с использованием CSS

Используйте CSS для придания веб-приложению желаемого вида. Можно использовать чистый CSS или фреймворки вроде Materialize CSS (как в примере выше) или Bootstrap для ускорения разработки.

Обработка данных QR-кода на сервере с помощью Google Apps Script

Серверная часть, написанная на GAS (в файле Code.gs), будет принимать данные от клиента, обрабатывать их и выполнять необходимые действия, например, сохранять в Google Sheets.

// Code.gs (Server-side Google Apps Script)

/**
 * @typedef {GoogleAppsScript.Events.DoGet} DoGetEvent
 */

/**
 * Обслуживает GET-запросы, отображая HTML-интерфейс.
 * @param {DoGetEvent} e Объект события.
 * @returns {GoogleAppsScript.HTML.HtmlOutput} HTML-вывод.
 */
function doGet(e) {
  Logger.log('Запрос GET получен: %s', JSON.stringify(e.parameter));
  return HtmlService.createHtmlOutputFromFile('Index')
      .setTitle('QR Code Scanner/Sender')
      .addMetaTag('viewport', 'width=device-width, initial-scale=1');
}

/**
 * Обрабатывает данные, полученные от клиента (сканированный или введенный QR-код).
 * @param {string} qrData Данные из QR-кода.
 * @returns {string} Сообщение о результате операции.
 */
function processQrData(qrData) {
  if (!qrData) {
    Logger.log('Ошибка: Пустые данные получены.');
    throw new Error('Получены пустые данные.');
  }

  // --- Валидация данных (пример) ---
  // Например, проверяем, является ли строка URL-адресом
  let dataType = 'Текст';
  if (isValidUrl(qrData)) {
    dataType = 'URL';
    // Дополнительная логика для URL, например, проверка доступности
  }
  Logger.log('Получены данные: [%s], Тип: %s', qrData, dataType);

  try {
    // --- Сохранение данных в Google Sheets ---
    const sheetId = 'YOUR_SHEET_ID'; // Замените на ID вашей таблицы
    const sheetName = 'QR_Data';
    const ss = SpreadsheetApp.openById(sheetId);
    let sheet = ss.getSheetByName(sheetName);
    if (!sheet) {
      sheet = ss.insertSheet(sheetName);
      sheet.appendRow(['Timestamp', 'QR Data', 'Data Type', 'User Email']); // Заголовки
    }

    const timestamp = new Date();
    const userEmail = Session.getActiveUser().getEmail() || Session.getEffectiveUser().getEmail() || 'anonymous'; // Получаем email пользователя

    sheet.appendRow([timestamp, qrData, dataType, userEmail]);
    Logger.log('Данные успешно записаны в таблицу: %s', sheet.getName());

    // --- Отправка уведомления (опционально) ---
    // sendEmailNotification(userEmail, qrData);

    return `Данные "${qrData.substring(0, 20)}..." успешно обработаны и сохранены.`;

  } catch (error) {
    Logger.log('Ошибка при обработке данных: %s', error.message);
    Logger.log('Стек ошибки: %s', error.stack);
    // Вместо простой ошибки можно возвращать более детальное сообщение
    throw new Error(`Ошибка сервера при сохранении данных: ${error.message}`);
  }
}

/**
 * Вспомогательная функция для валидации URL.
 * @param {string} urlString Строка для проверки.
 * @returns {boolean} True, если строка является валидным URL.
 */
function isValidUrl(urlString) {
  try {
    new URL(urlString);
    return true;
  } catch (_) {
    return false;
  }
}

/**
 * Отправляет уведомление по электронной почте.
 * @param {string} recipientEmail Email получателя.
 * @param {string} data Данные QR-кода.
 */
function sendEmailNotification(recipientEmail, data) {
  const subject = 'Новые данные QR-кода обработаны';
  const body = `Получены и сохранены новые данные из QR-кода:\n\n${data}\n\nВремя: ${new Date()}`;
  try {
      if(recipientEmail !== 'anonymous') { // Не отправляем анонимным
          MailApp.sendEmail(recipientEmail, subject, body);
          Logger.log('Уведомление отправлено на %s', recipientEmail);
      } else {
          Logger.log('Пользователь анонимный, уведомление не отправлено.');
      }
  } catch (e) {
      Logger.log('Ошибка отправки email: %s', e.message);
  }
}
Реклама

Получение данных из формы и их валидация

Функция processQrData(qrData) на сервере получает данные, отправленные клиентом через google.script.run. Первым шагом является проверка на наличие данных и их базовая валидация (например, проверка формата, если ожидается URL или JSON).

Сохранение данных в Google Sheets или другую базу данных

В примере выше данные сохраняются в Google Sheets с помощью SpreadsheetApp. Это удобный вариант для многих задач. Альтернативно, можно использовать JDBC Service для подключения к Google Cloud SQL или другим реляционным базам данных, или UrlFetchApp для отправки данных во внешние API или NoSQL базы данных.

Реализация логики отправки данных

Помимо сохранения, серверная функция может выполнять и другие действия: отправлять email-уведомления с помощью MailApp, создавать события в Google Calendar (CalendarApp), сохранять файлы в Google Drive (DriveApp) или взаимодействовать с внешними системами через UrlFetchApp.

Интеграция сканера QR-кодов

Ключевая часть приложения – это реализация сканирования QR-кода на стороне клиента.

Выбор библиотеки для сканирования QR-кодов

jsQR: Легковесная библиотека без зависимостей, проста в использовании. Хорошо подходит для большинства случаев.

QuaggaJS: Более функциональная библиотека, может сканировать различные типы штрих-кодов (не только QR), но может быть сложнее в настройке и иметь больший размер.

ZXing-JS: Порт популярной библиотеки ZXing от Google/ Zebra Crossing.

Для данного примера используется jsQR из-за её простоты.

Реализация сканирования QR-кода с камеры устройства

Код на клиентском JavaScript (в Index.html) использует navigator.mediaDevices.getUserMedia для доступа к камере. Видеопоток направляется в элемент <video>. Затем, с помощью requestAnimationFrame, каждый кадр видео анализируется библиотекой jsQR. При обнаружении QR-кода его данные извлекаются.

Обработка ошибок и предоставление обратной связи пользователю

Важно обрабатывать возможные ошибки:

Доступ к камере: Пользователь может не дать разрешение. Необходимо отловить ошибку getUserMedia и сообщить пользователю.

QR-код не найден: Предоставлять статусное сообщение ("Наведите камеру", "Поиск…").

Ошибка декодирования: Редко, но возможно. Логировать и информировать.

Ошибка сервера: При отправке данных через google.script.run использовать withFailureHandler для обработки ошибок GAS и отображения сообщения пользователю.

Использование M.toast (из Materialize) или аналогичных методов позволяет ненавязчиво информировать пользователя о статусе операций (сканирование, отправка, ошибки).

Развертывание и публикация веб-приложения

После завершения разработки приложение необходимо опубликовать.

Публикация веб-приложения в Google Apps Script

В редакторе GAS нажмите Deploy > New deployment.

Выберите тип: Web app.

Настройте параметры:

Description: Описание развертывания.

Execute as:

Me: Скрипт будет выполняться от вашего имени, используя ваши разрешения.

User accessing the web app: Скрипт будет выполняться от имени пользователя, открывшего приложение (потребуется авторизация пользователя).

Who has access:

Only myself: Только вы.

Anyone within [Your Domain]: Пользователи вашего Google Workspace домена.

Anyone: Любой пользователь (анонимный доступ или с Google аккаунтом, в зависимости от Execute as).

Нажмите Deploy.

Предоставьте необходимые разрешения, если скрипт запрашивает их.

Скопируйте предоставленный URL веб-приложения. Это точка доступа к вашему приложению.

Настройка разрешений для доступа к приложению

Выбор Execute as и Who has access определяет, кто сможет использовать приложение и от чьего имени будут выполняться действия (например, запись в таблицу). Для приложений, собирающих данные от разных пользователей, часто используется Execute as: Me и Who has access: Anyone или Anyone within [Your Domain], чтобы избежать необходимости авторизации для каждого пользователя, но при этом все действия выполняются от имени владельца скрипта.

Тестирование и отладка приложения

Тестовое развертывание: Используйте Deploy > Test deployments для тестирования последних изменений без создания новой версии.

Логирование: Используйте Logger.log() или console.log() в GAS и console.log() в клиентском JS. Просматривайте логи в Apps Script Dashboard (View > Executions).

Инструменты разработчика браузера: Используйте для отладки клиентского JavaScript, проверки сетевых запросов к GAS и анализа HTML/CSS.

Оптимизация производительности и безопасности

Минимизация вызовов google.script.run: Группируйте данные для отправки на сервер, если это возможно.

Кэширование: Используйте CacheService для кэширования часто запрашиваемых данных.

Валидация данных: Всегда валидируйте данные как на клиенте (для быстрой обратной связи), так и на сервере (для безопасности).

Ограничение доступа: Тщательно выбирайте настройки доступа при развертывании.

Обработка ошибок: Обеспечьте надежную обработку ошибок на клиенте и сервере.

Асинхронность: Используйте асинхронные операции (async/await в клиентском JS) для предотвращения блокировки интерфейса.


Добавить комментарий