Создание веб-приложений для работы с 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) для предотвращения блокировки интерфейса.