Перейти к основному содержанию

Введение

Этот гид охватывает рекомендации по безопасности, оптимизацию производительности и руководства по интеграции, помогающие создавать надежные приложения с API Lovi.

🔒 Лучшие Практики Безопасности

Управление API-Ключами

✅ ДЕЛАТЬ:
  • Хранить API-ключи в переменных окружения или безопасных системах управления конфигурацией
  • Использовать разные API-ключи для разных сред (разработка, staging, производство)
  • Регулярно обновлять API-ключи (рекомендуется каждые 90 дней)
  • Мониторить использование API-ключей и настраивать оповещения о необычной активности
  • Использовать принцип наименьших привилегий (отдельные ключи для разных функций, если доступно)
❌ НЕ ДЕЛАТЬ:
  • Хардкодить API-ключи в исходном коде или коммитить в систему контроля версий
  • Передавать API-ключи по email, чату или другим незащищенным каналам
  • Использовать ключи производства в среде разработки
  • Логировать полные API-ключи в журналах приложений

Безопасность Аутентификации

// ✅ Хорошо - Хранить в переменных окружения
const ACCESS_KEY = process.env.LOVI_ACCESS_KEY;

// ❌ Плохо - Хардкодировано в исходном коде
const ACCESS_KEY = 'your-actual-api-key-here';

Безопасность Запросов

Всегда использовать HTTPS:
// ✅ Хорошо - Только HTTPS
const BASE_URL = 'https://cloud.lovi.ai';

// ❌ Плохо - Незащищенный HTTP
const BASE_URL = 'http://cloud.lovi.ai';
Валидировать входные данные:
function validatePhoneNumber(number) {
  // Удалить все нецифровые символы
  const cleaned = number.replace(/\D/g, '');

  // Проверить, является ли это допустимым международным форматом
  if (!/^\d{10,15}$/.test(cleaned)) {
    throw new Error('Недопустимый формат номера телефона');
  }

  return cleaned;
}

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

Управление Соединениями

Повторно использовать HTTP-соединения:
// ✅ Хорошо - Повторно использовать пул соединений
const https = require('https');
const agent = new https.Agent({
  keepAlive: true,
  maxSockets: 10
});

const fetch = require('node-fetch');

async function makeRequest(url, data) {
  return fetch(url, {
    method: 'POST',
    agent: agent,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
}

Пакетная Обработка

Обрабатывать уведомления пакетами:
async function sendNotificationsBatch(notifications, batchSize = 10) {
  const results = [];

  for (let i = 0; i < notifications.length; i += batchSize) {
    const batch = notifications.slice(i, i + batchSize);
    const batchPromises = batch.map(notification =>
      sendNotification(notification)
    );

    const batchResults = await Promise.allSettled(batchPromises);
    results.push(...batchResults);

    // Ограничение скорости: ждать между пакетами
    if (i + batchSize < notifications.length) {
      await sleep(100); // 100ms задержка
    }
  }

  return results;
}

Стратегии Кеширования

Кешировать информацию о шаблонах:
class TemplateCache {
  constructor(ttl = 3600000) { // 1 час TTL
    this.cache = new Map();
    this.ttl = ttl;
  }

  async getTemplate(templateName) {
    const cached = this.cache.get(templateName);

    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.data;
    }

    const template = await fetchTemplateFromAPI(templateName);
    this.cache.set(templateName, {
      data: template,
      timestamp: Date.now()
    });

    return template;
  }
}

📊 Ограничение Скорости и Регулировка

Понимание Ограничений Скорости

ОперацияЛимитОкно
Аутентификация10 запросов1 минута
Уведомления100 запросов1 минута
Получение шаблонов50 запросов1 минута
Создание шаблонов5 запросов1 час

Обработка Ограничений Скорости

class RateLimitHandler {
  constructor() {
    this.requestQueue = [];
    this.processing = false;
  }

  async makeRequest(url, data) {
    return new Promise((resolve, reject) => {
      this.requestQueue.push({ url, data, resolve, reject });
      this.processQueue();
    });
  }

  async processQueue() {
    if (this.processing || this.requestQueue.length === 0) return;

    this.processing = true;

    while (this.requestQueue.length > 0) {
      const request = this.requestQueue.shift();

      try {
        const response = await this.executeRequest(request);
        request.resolve(response);
      } catch (error) {
        if (error.status === 429) {
          // Ограничение скорости - вернуть запрос и ждать
          this.requestQueue.unshift(request);
          await sleep(error.retryAfter * 1000);
          continue;
        }
        request.reject(error);
      }

      // Небольшая задержка между запросами
      await sleep(100);
    }

    this.processing = false;
  }
}

🏗️ Архитектурные Паттерны

Паттерн Репозитория

class NotificationRepository {
  constructor(apiClient) {
    this.apiClient = apiClient;
  }

  async sendWhatsAppNotification(notification) {
    const payload = this.formatWhatsAppPayload(notification);
    return await this.apiClient.post('/notify', payload);
  }

  async sendVoiceNotification(notification) {
    const payload = this.formatVoicePayload(notification);
    return await this.apiClient.post('/notify/voice', payload);
  }

  formatWhatsAppPayload(notification) {
    return {
      contact: {
        number: this.validatePhoneNumber(notification.phoneNumber),
        name: notification.contactName
      },
      language_template: notification.language,
      name_template: notification.templateName,
      recipient_id: notification.phoneNumber,
      notification_type: notification.type,
      campaign_name: notification.campaign
    };
  }
}

🧪 Стратегии Тестирования

Модульное Тестирование

// Мокировать ответы API для тестирования
const mockAPIClient = {
  post: jest.fn()
};

describe('NotificationService', () => {
  test('должен успешно отправить WhatsApp-уведомление', async () => {
    mockAPIClient.post.mockResolvedValue({
      success: true,
      notification_id: 'test-123'
    });

    const service = new NotificationService(mockAPIClient);
    const result = await service.sendWhatsApp({
      phoneNumber: '34666033135',
      templateName: 'welcome',
      contactName: 'John Doe'
    });

    expect(result.success).toBe(true);
    expect(mockAPIClient.post).toHaveBeenCalledWith('/notify', expect.any(Object));
  });
});

🔍 Мониторинг и Логирование

Структурированное Логирование

const logger = require('winston');

logger.configure({
  format: logger.format.combine(
    logger.format.timestamp(),
    logger.format.errors({ stack: true }),
    logger.format.json()
  ),
  transports: [
    new logger.transports.File({ filename: 'api-calls.log' })
  ]
});

async function loggedAPICall(endpoint, data) {
  const requestId = generateRequestId();

  logger.info('API-запрос запущен', {
    request_id: requestId,
    endpoint: endpoint,
    method: 'POST',
    timestamp: new Date().toISOString()
  });

  try {
    const response = await makeAPICall(endpoint, data);

    logger.info('API-запрос завершен', {
      request_id: requestId,
      endpoint: endpoint,
      status_code: response.status,
      success: true,
      duration: Date.now() - startTime
    });

    return response;
  } catch (error) {
    logger.error('API-запрос не удался', {
      request_id: requestId,
      endpoint: endpoint,
      error: error.message,
      status_code: error.statusCode,
      duration: Date.now() - startTime
    });

    throw error;
  }
}

📱 Интеграция Мобильных и Фронтенд

Пример React Hook

import { useState, useEffect } from 'react';

function useLoviNotification() {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const sendNotification = async (notificationData) => {
    setLoading(true);
    setError(null);

    try {
      const response = await fetch('/api/send-notification', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(notificationData)
      });

      if (!response.ok) {
        throw new Error('Не удалось отправить уведомление');
      }

      const result = await response.json();
      return result;
    } catch (err) {
        setError(err.message);
        throw err;
      } finally {
        setLoading(false);
      }
    };

    return { sendNotification, loading, error };
}

🔧 Управление Конфигурацией

Конфигурация Окружения

// config/lovi.js
module.exports = {
  development: {
    baseURL: 'https://cloud.lovi.ai',
    accessKey: process.env.LOVI_DEV_ACCESS_KEY,
    rateLimits: {
      requests: 10,
      window: 60000 // 1 минута
    },
    retries: 3,
    timeout: 30000
  },
  production: {
    baseURL: 'https://cloud.lovi.ai',
    accessKey: process.env.LOVI_PROD_ACCESS_KEY,
    rateLimits: {
      requests: 100,
      window: 60000
    },
    retries: 5,
    timeout: 60000
  }
};

📚 Документация и Руководства Команды

Документация Кода

/**
 * Отправить WhatsApp-уведомление с использованием шаблона
 *
 * @param {Object} notification - Данные уведомления
 * @param {string} notification.phoneNumber - Номер телефона получателя (международный формат, без +)
 * @param {string} notification.templateName - Название утвержденного шаблона
 * @param {string} notification.language - Код языка (например, 'ru_RU', 'en_US')
 * @param {Object} notification.variables - Переменные шаблона
 * @param {string} [notification.scheduledTime] - ISO 8601 дата/время для планирования
 * @param {string} [notification.timezone] - IANA код часового пояса
 *
 * @returns {Promise<Object>} Ответ API с notification_id
 *
 * @throws {ValidationError} Когда отсутствуют обязательные параметры
 * @throws {APIError} Когда вызов API не удался
 *
 * @example
 * const result = await sendWhatsAppNotification({
 *   phoneNumber: '34666033135',
 *   templateName: 'welcome_user',
 *   language: 'ru_RU',
 *   variables: { name: 'Иван', course: 'JavaScript' }
 * });
 */
async function sendWhatsAppNotification(notification) {
  // Реализация...
}

Стандарты Команды

Чек-лист Интеграции API:
  • ✅ Реализовать правильную обработку ошибок
  • ✅ Добавить логирование запросов/ответов
  • ✅ Включить модульные тесты
  • ✅ Валидировать входные параметры
  • ✅ Обработать ограничение скорости
  • ✅ Использовать конфигурации специфичные для окружения
  • ✅ Документировать все функции и классы
  • ✅ Реализовать проверки здоровья
  • ✅ Добавить мониторинг и оповещения

🚨 Распространенные Ошибки и Решения

Ошибка: Жестко Закодированные Значения

// ❌ Плохо
const templateName = 'welcome_template_final_v2';

// ✅ Хорошо
const templateName = config.templates.welcome;

Ошибка: Плохая Обработка Ошибок

// ❌ Плохо
try {
  await sendNotification(data);
} catch (error) {
  console.log('Error:', error);
}

// ✅ Хорошо
try {
  await sendNotification(data);
} catch (error) {
  logger.error('Отправка уведомления не удалась', {
    error: error.message,
    data: sanitizeForLog(data),
    stack: error.stack
  });

  throw new NotificationError(
    'Не удалось отправить уведомление',
    error.statusCode,
    error.requestId
  );
}

Ошибка: Игнорирование Ограничений Скорости

// ❌ Плохо - Отправить все сразу
notifications.forEach(notification => {
  sendNotification(notification);
});

// ✅ Хорошо - Учитывать ограничения скорости
await sendNotificationsBatch(notifications, 10);
Помните: Следование этим лучшим практикам поможет вам создавать более надежные, безопасные и поддерживаемые интеграции с API Lovi.