Google Apps Script: Как развернуть скрипт как исполняемый API?

Что такое Google Apps Script и его возможности

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

Концепция API и зачем развертывать скрипт как API

API (Application Programming Interface) — это контракт, определяющий способы взаимодействия различных программных компонентов. Развертывание скрипта GAS как API позволяет вызывать его функции из внешних приложений (веб-сайтов, мобильных приложений, других скриптов) по протоколу HTTP.

Это открывает возможности для создания кастомных бэкендов, микросервисов для обработки данных из Google Workspace, интеграции с CRM, системами аналитики и другими внешними платформами без необходимости поддерживать собственную серверную инфраструктуру.

Преимущества использования Apps Script в качестве API

  • Бессерверность: Нет необходимости управлять серверами, Google берет на себя инфраструктурные задачи.
  • Интеграция с Google Workspace: Легкий доступ к данным и сервисам Google Sheets, Docs, Drive и т.д.
  • Простота развертывания: Встроенные механизмы для публикации скрипта как веб-приложения.
  • Бесплатный уровень: Щедрые квоты для большинства сценариев использования.
  • Знакомая среда: Использование JavaScript и стандартных веб-технологий.

Подготовка скрипта Google Apps Script к развертыванию

Создание нового скрипта или использование существующего

Вы можете создать новый проект Apps Script через script.google.com или из контейнера (например, Google Sheet: Инструменты -> Редактор скриптов). Для API подходят как автономные (standalone), так и привязанные к контейнеру (container-bound) скрипты.

Написание функций, которые будут доступны через API

Для обработки HTTP-запросов используются специальные функции-триггеры:

  • doGet(e): Обрабатывает GET-запросы.
  • doPost(e): Обрабатывает POST-запросы.

Параметр e (event object) содержит информацию о запросе, включая параметры и тело запроса.

Обработка параметров запроса (query parameters) и тела запроса (request body)

Параметры GET-запроса доступны через e.parameter (для одного значения) и e.parameters (для нескольких значений одного параметра). Тело POST-запроса доступно через e.postData.contents.

/**
 * Обрабатывает GET-запросы, извлекая данные о кампаниях из Google Sheet.
 * @param {GoogleAppsScript.Events.DoGet} e - Объект события, содержащий параметры запроса.
 * @returns {GoogleAppsScript.Content.TextOutput} - JSON с данными или сообщением об ошибке.
 */
function doGet(e: GoogleAppsScript.Events.DoGet): GoogleAppsScript.Content.TextOutput {
  let campaignId: string | null = null;
  let statusFilter: string | null = null;

  // Получение параметров запроса
  if (e.parameter.campaignId) {
    campaignId = e.parameter.campaignId;
  }
  if (e.parameter.status) {
    statusFilter = e.parameter.status;
  }

  // Пример: Логика получения данных (заглушка)
  const data = fetchCampaignData(campaignId, statusFilter);

  return ContentService.createTextOutput(JSON.stringify(data))
    .setMimeType(ContentService.MimeType.JSON);
}

/**
 * Обрабатывает POST-запросы для добавления нового лида.
 * @param {GoogleAppsScript.Events.DoPost} e - Объект события, содержащий тело запроса.
 * @returns {GoogleAppsScript.Content.TextOutput} - JSON с результатом операции.
 */
function doPost(e: GoogleAppsScript.Events.DoPost): GoogleAppsScript.Content.TextOutput {
  let response: { success: boolean; message: string; leadId?: string };
  try {
    if (!e.postData || !e.postData.contents) {
      throw new Error('Request body is missing');
    }
    const leadData: { name: string; email: string; source: string } = JSON.parse(e.postData.contents);

    // Валидация входных данных
    if (!leadData.name || !leadData.email || !leadData.source) {
      throw new Error('Missing required fields: name, email, source');
    }
    if (!validateEmail(leadData.email)) {
        throw new Error('Invalid email format');
    }

    // Пример: Логика добавления лида (заглушка)
    const newLeadId = addLeadToCRM(leadData);

    response = { success: true, message: 'Lead added successfully', leadId: newLeadId };
    return ContentService.createTextOutput(JSON.stringify(response))
      .setMimeType(ContentService.MimeType.JSON);

  } catch (error) {
    Logger.log(`Error in doPost: ${error.message}`);
    response = { success: false, message: error.message || 'An error occurred' };
    // Возвращаем корректный HTTP статус ошибки, например 400 Bad Request
    return ContentService.createTextOutput(JSON.stringify(response))
      .setMimeType(ContentService.MimeType.JSON);
    // Примечание: Apps Script не позволяет напрямую установить код ответа HTTP (всегда 200 OK или редирект 302).
    // Статус ошибки передается в теле ответа.
  }
}

// --- Вспомогательные функции (примеры) ---

/**
 * Заглушка для функции получения данных о кампаниях.
 * @param {string | null} id - ID кампании.
 * @param {string | null} status - Фильтр по статусу.
 * @returns {object[]} - Массив объектов кампаний.
 */
function fetchCampaignData(id: string | null, status: string | null): object[] {
  // Здесь должна быть реальная логика чтения из Google Sheet или другого источника
  Logger.log(`Fetching data for campaignId: ${id}, status: ${status}`);
  // Пример возвращаемых данных
   const allData = [
    { id: 'cmp1', name: 'Весенняя акция', status: 'active', budget: 1000 },
    { id: 'cmp2', name: 'Летняя распродажа', status: 'paused', budget: 500 },
    { id: 'cmp3', name: 'Новогоднее предложение', status: 'active', budget: 1500 }
  ];

  return allData.filter(campaign => 
     (!id || campaign.id === id) && 
     (!status || campaign.status === status)
  );
}

/**
 * Заглушка для функции добавления лида в CRM.
 * @param {object} data - Данные лида.
 * @returns {string} - ID созданного лида.
 */
function addLeadToCRM(data: { name: string; email: string; source: string }): string {
  // Реальная логика взаимодействия с CRM или Google Sheet
  Logger.log(`Adding lead: ${JSON.stringify(data)}`);
  const newId = 'lead_' + Math.random().toString(36).substring(2, 9);
  // Например, добавление строки в Google Sheet
  // SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Leads').appendRow([newId, data.name, data.email, data.source, new Date()]);
  return newId;
}

/**
 * Простая валидация email.
 * @param {string} email - Email для проверки.
 * @returns {boolean} - True, если email валиден.
 */
function validateEmail(email: string): boolean {
    const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(String(email).toLowerCase());
}

Валидация входных данных и обработка ошибок

Критически важно валидировать все входные данные (e.parameter, e.postData.contents), чтобы предотвратить ошибки и обеспечить безопасность. Используйте блоки try...catch для перехвата исключений и возвращайте осмысленные сообщения об ошибках в формате JSON. Функция ContentService используется для формирования HTTP-ответа.

Развертывание скрипта как Web App (веб-приложения)

Настройка параметров развертывания (доступ, права доступа)

  1. В редакторе скриптов перейдите в раздел «Развертывание» -> «Новое развертывание».
  2. Выберите тип развертывания: «Веб-приложение».
  3. Заполните описание (опционально).

Выбор типа доступа: только я, любой пользователь, анонимный доступ

  • Веб-приложение:
    • Выполнять как:
      • Я: Скрипт будет выполняться от вашего имени, используя ваши разрешения. Подходит для доступа к вашим личным данным.
      • Пользователь, обращающийся к приложению: Скрипт будет выполняться от имени пользователя, который вызывает API. Пользователю потребуется авторизовать скрипт при первом обращении. Подходит для работы с данными пользователя.
    • Кто имеет доступ:
      • Только я: Доступ только для вашего Google-аккаунта.
      • Все пользователи в домене : Доступ для пользователей вашего Google Workspace.
      • Все: Анонимный доступ для любого пользователя в интернете.

Важно: Для создания публичного API, доступного внешним системам без аутентификации Google, выберите «Выполнять как: Я» и «Кто имеет доступ: Все». Это самый распространенный вариант для API.

Реклама

Публикация скрипта как веб-приложения

Нажмите «Развернуть». При первом развертывании или при изменении областей действия (scopes) потребуется предоставить авторизацию скрипту.

Получение URL-адреса развернутого API

После успешного развертывания будет предоставлен URL вида https://script.google.com/macros/s/ВАШ_ИДЕНТИФИКАТОР_РАЗВЕРТЫВАНИЯ/exec. Этот URL является конечной точкой (endpoint) вашего API.

Примечание: При каждом изменении кода скрипта необходимо создавать новое развертывание («Развертывание» -> «Управление развертываниями» -> Выбрать активное развертывание -> Карандаш (Редактировать) -> Выбрать «Новая версия» -> «Развернуть»), чтобы изменения вступили в силу для URL /exec. Тестовый URL /dev всегда отражает последнюю сохраненную версию кода.

Тестирование и использование API

Тестирование API с помощью Postman, curl или других инструментов

Используйте URL /exec, полученный при развертывании, для отправки запросов.

  • GET-запрос (curl):
    bash
    curl -L "https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec?campaignId=cmp1&status=active"
  • POST-запрос (curl):
    bash
    curl -L -X POST -H "Content-Type: application/json" \
    -d '{"name":"John Doe","email":"john.doe@example.com","source":"Website Form"}' \
    "https://script.google.com/macros/s/YOUR_DEPLOYMENT_ID/exec"
  • Postman: Создайте новый запрос, выберите метод (GET/POST), введите URL, добавьте параметры на вкладке «Params» (для GET) или тело запроса на вкладке «Body» (выбрав «raw» и «JSON» для POST), и нажмите «Send».

Отправка запросов с параметрами и телом запроса

Параметры для doGet передаются как query string (?key1=value1&key2=value2). Тело запроса для doPost передается в теле HTTP-запроса, обычно в формате JSON с соответствующим заголовком Content-Type: application/json.

Обработка ответов API и кодов состояния HTTP

Apps Script Web App всегда возвращает HTTP статус 200 OK (или 302 Found при редиректах, которые обрабатываются автоматически клиентами вроде curl -L или Postman). Поэтому статус операции (успех/ошибка) и данные должны передаваться в теле ответа, как правило, в формате JSON. Клиентское приложение должно парсить JSON и анализировать его содержимое для определения результата.

Примеры использования API в других приложениях и сервисах

  • Веб-форма: JavaScript на фронтенде может отправлять данные формы на ваш API с помощью fetch или XMLHttpRequest.
  • Мобильное приложение: Нативное или гибридное приложение может вызывать API для сохранения или получения данных, связанных с Google Workspace.
  • Другой Apps Script: Использование UrlFetchApp.fetch() для взаимодействия между скриптами.
  • Zapier/Integromat/Make: Интеграционные платформы могут вызывать ваш API как webhook.
  • Системы аналитики: Отправка данных о конверсиях или событиях из CRM в Google Analytics через Measurement Protocol, используя GAS API как промежуточный обработчик.

Безопасность и ограничения

Аутентификация и авторизация (OAuth 2.0)

Если выбрано «Выполнять как: Пользователь, обращающийся к приложению», Apps Script автоматически управляет процессом OAuth 2.0, запрашивая у пользователя разрешение при первом доступе. Для сценариев «Выполнять как: Я» и «Доступ: Все», API является публичным. Для защиты таких эндпоинтов можно реализовать собственные механизмы:

  • Секретный токен/ключ API: Передавать секретный ключ в заголовке (Authorization: Bearer YOUR_SECRET_TOKEN) или параметре запроса и проверять его на стороне сервера.
  • Проверка IP-адресов: Ограничивать доступ только с определенных IP (менее надежно).

Защита от злоупотреблений и ограничение количества запросов (rate limiting)

Apps Script имеет встроенные квоты на выполнение скриптов, время выполнения, количество вызовов UrlFetchApp и т.д. Для предотвращения злоупотреблений можно реализовать кастомный rate limiting, например, используя CacheService для отслеживания количества запросов с одного IP или по API-ключу за определенный период.

Использование сервиса Script Properties для хранения секретов

Никогда не храните API-ключи, пароли или другие секреты непосредственно в коде. Используйте PropertiesService.getScriptProperties() для безопасного хранения и извлечения конфигурационных данных и секретов.

/**
 * Пример проверки API ключа, хранящегося в Script Properties.
 * @param {GoogleAppsScript.Events.DoGet | GoogleAppsScript.Events.DoPost} e - Объект события.
 * @returns {boolean} - True, если авторизация пройдена.
 */
function checkApiKey(e: GoogleAppsScript.Events.DoGet | GoogleAppsScript.Events.DoPost): boolean {
  const scriptProperties = PropertiesService.getScriptProperties();
  const storedApiKey = scriptProperties.getProperty('API_KEY');

  if (!storedApiKey) {
      Logger.log('API_KEY is not set in Script Properties.');
      return false; // Не настроено - запрещаем доступ
  }

  // Ищем ключ в заголовке Authorization: Bearer <key>
  let providedKey: string | null = null;
  if (e.headers && e.headers.authorization) {
    const authHeader = e.headers.authorization;
    if (authHeader.toLowerCase().startsWith('bearer ')) {
        providedKey = authHeader.substring(7);
    }
  } 
  // Или ищем в параметре запроса apiKey=<key>
  else if (e.parameter && e.parameter.apiKey) {
     providedKey = e.parameter.apiKey;
  }

  if (!providedKey || providedKey !== storedApiKey) {
    Logger.log('Invalid or missing API Key.');
    return false;
  }

  return true;
}

// В doGet/doPost добавить вызов:
// if (!checkApiKey(e)) {
//   return ContentService.createTextOutput(JSON.stringify({ success: false, message: 'Unauthorized' }))
//     .setMimeType(ContentService.MimeType.JSON);
// }

Ограничения Google Apps Script и способы их обхода

  • Время выполнения: Максимум 6 минут для consumer-аккаунтов и 30 минут для Google Workspace.
  • Одновременное выполнение: Ограничения на количество одновременно запущенных скриптов.
  • Квоты API: Суточные лимиты на вызовы сервисов Google (SpreadsheetApp, DriveApp и т.д.).
  • Холодный старт: Первый запуск после периода неактивности может занимать несколько секунд.
  • Отсутствие контроля над HTTP-статусом ответа: Всегда 200 или 302.

Способы обхода: Декомпозиция задач, использование триггеров по времени для длительных операций, кэширование (CacheService), оптимизация вызовов API (пакетные операции), использование Cloud Functions для более требовательных задач.


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