vallyenfail 34ac3bc175
All checks were successful
Deploy Smart Search Gateway Test / deploy (push) Successful in 1m52s
add service
2026-01-20 13:47:33 +03:00
2026-01-18 20:24:46 +03:00
2026-01-19 19:49:27 +03:00
2026-01-19 20:18:32 +03:00
2026-01-19 19:49:27 +03:00
2026-01-20 13:47:33 +03:00
2026-01-20 13:47:33 +03:00
2026-01-18 13:48:46 +03:00
2026-01-18 01:47:40 +03:00
2026-01-18 20:05:32 +03:00
2026-01-19 17:20:47 +03:00
2026-01-19 17:20:47 +03:00
2026-01-19 18:34:05 +03:00
2026-01-18 01:47:40 +03:00
2026-01-19 20:04:58 +03:00

Smart Search Gateway

API Gateway для Smart Search микросервиса на базе rk-boot и Fiber.

Архитектура

Gateway — stateless HTTP сервер, который:

  • Принимает HTTP запросы от клиентов
  • Валидирует JWT токены
  • Проксирует запросы на backend service через gRPC
  • Возвращает HTTP ответы клиентам

Устойчивость (Resilience):

  • Circuit Breaker — защита от каскадных сбоев при недоступности backend
  • Graceful Degradation — сервис продолжает работать даже если backend недоступен
  • Автоматическое переподключение — восстановление связи с backend в фоне
  • Health Checks — endpoints для мониторинга состояния сервиса

Технологии:

  • rk-boot/v2 — фреймворк для bootstrap
  • fiber/v2 — HTTP фреймворк
  • grpc — коммуникация с service
  • jwt/v5 — валидация токенов
  • protobuf — gRPC контракты
  • gobreaker — circuit breaker паттерн

Структура проекта

smart-search-gateway/
├── cmd/server/main.go          # Точка входа
├── internal/
│   ├── handler/                # HTTP handlers
│   ├── middleware/             # Auth, RequestID, Error handling
│   ├── grpc/                   # gRPC client
│   ├── dto/                    # Request/Response DTOs
│   └── config/                 # Конфигурация
├── api/proto/                  # Proto файлы (shared)
├── pkg/
│   ├── jwt/                    # JWT утилиты
│   └── errors/                 # Error handling
├── config/
│   ├── boot.yaml               # rk-boot config
│   └── config.yaml.example     # Пример конфигурации
└── Makefile

Установка

Требования

  • Go 1.24+
  • protoc (для генерации proto)
  • golangci-lint (для линтера)

Установка зависимостей

make install-deps

Работа с Proto файлами

Философия: Gateway коммитит готовые .pb.go файлы в pkg/pb/, но не хранит исходные .proto файлы.

Зачем:

  • Не нужен protoc при деплое
  • Быстрая сборка без дополнительных инструментов
  • Явный контроль версий proto-контракта
  • Чистая структура импортов

Структура:

gateway/
└── pkg/pb/              # Сгенерированные .pb.go (в git)
    ├── auth/
    ├── user/
    ├── invite/
    ├── request/
    └── supplier/

Импорты в коде:

import (
    authpb "git.techease.ru/Smart-search/smart-search-gateway/pkg/pb/auth"
    userpb "git.techease.ru/Smart-search/smart-search-gateway/pkg/pb/user"
)

Когда обновлять:

Обновляйте proto только когда backend изменил контракты:

make proto

Эта команда:

  1. Копирует .proto файлы из ../smart-search-back/api/proto/ (единый источник истины) во временную .tmp/
  2. Исправляет go_package пути для gateway
  3. Генерирует .pb.go и _grpc.pb.go файлы напрямую в pkg/pb/
  4. Удаляет временную .tmp/

Коммит результата:

git add pkg/pb/
git commit -m "Update proto contracts from smart-search-back"

Важно: Для работы команды make proto репозиторий smart-search-back должен находиться рядом:

Work/
├── smart-search-back/    # Источник истины для .proto
└── smart-search-gateway/ # Хранит .pb.go в pkg/pb/

Конфигурация

Скопируйте config/config.yaml.example в config/config.yaml и настройте:

jwt:
  secret: YOUR_JWT_SECRET_HERE

grpc:
  service_addr: localhost:9091
  timeout: 30s

Переменные окружения:

  • JWT_SECRET — секрет для JWT (обязательно)
  • SERVICE_GRPC_ADDR — адрес backend service (по умолчанию: localhost:9091)
  • LOG_LEVEL — уровень логирования (по умолчанию: info)

Запуск

Локальный запуск

make run

Docker

# Сборка образа
make docker-build

# Запуск через docker-compose
docker-compose up

API Endpoints

Authentication

POST /api/v1/register

Регистрация нового пользователя

Request:

{
  "email": "newuser@example.com",
  "password": "password123",
  "name": "John Doe",
  "phone": "+1234567890",
  "inviteCode": 123456
}

Response:

{
  "accessToken": "eyJhbGci...",
  "refreshToken": "eyJhbGci..."
}

Status Codes:

  • 201 Created — успешная регистрация
  • 400 Bad Request — невалидные данные
  • 409 Conflict — email уже существует
  • 412 Precondition Failed — инвайт-код невалидный/истёк

POST /api/v1/login

Вход в систему

Request:

{
  "email": "user@example.com",
  "password": "password123"
}

Response:

{
  "accessToken": "eyJhbGci...",
  "refreshToken": "eyJhbGci..."
}

POST /api/v1/refresh

Обновление токенов

Request:

{
  "refreshToken": "eyJhbGci..."
}

Response:

{
  "accessToken": "eyJhbGci...",
  "refreshToken": "eyJhbGci..."
}

POST /api/v1/logout

Выход из системы

Headers:

Authorization: Bearer <access_token>

Response:

{
  "success": true
}

GET /api/v1/validate

Валидация токена

Headers:

Authorization: Bearer <access_token>

Response:

{
  "valid": true,
  "userId": 123
}

User

Все эндпоинты требуют Authorization: Bearer <access_token>

POST /api/v1/user/info

Получить информацию о пользователе

Response:

{
  "email": "user@example.com",
  "name": "John Doe",
  "phone": "+1234567890",
  "companyName": "ACME Corp",
  "paymentStatus": "active"
}

POST /api/v1/user/balance

Получить баланс

Response:

{
  "balance": 1000.50
}

POST /api/v1/user/statistics

Получить статистику

Response:

{
  "totalRequests": 100,
  "successfulRequests": 95,
  "failedRequests": 5,
  "totalSpent": 500.25
}

POST /api/v1/user/balance-statistics

Получить баланс и статистику

Response:

{
  "balance": 1000.50,
  "totalRequests": 100,
  "totalSpent": 500.25
}

Invites

POST /api/v1/invite/generate

Сгенерировать инвайт-код

Request:

{
  "ttlDays": 7,
  "maxUses": 10
}

Response:

{
  "code": "12345678",
  "maxUses": 10,
  "expiresAt": "2024-01-20T15:04:05Z"
}

POST /api/v1/invite/info

Получить информацию об инвайт-коде

Request:

{
  "code": "12345678"
}

Response:

{
  "code": "12345678",
  "userId": 123,
  "canBeUsedCount": 10,
  "usedCount": 3,
  "expiresAt": "2024-01-20T15:04:05Z",
  "isActive": true,
  "createdAt": "2024-01-13T15:04:05Z"
}

Requests

POST /api/v1/request/create-tz

Создать техническое задание

Request:

{
  "requestTxt": "Нужны офисные столы деревянные, 10 шт"
}

Response:

{
  "requestId": "uuid-here",
  "tzText": "ТЕХНИЧЕСКОЕ ЗАДАНИЕ\n\n1. ПРЕДМЕТ\nПоставка офисных столов..."
}

POST /api/v1/request/approve-tz

Утвердить техническое задание

Request:

{
  "requestId": "uuid-here",
  "finalTz": "Окончательное ТЗ..."
}

Response:

{
  "success": true,
  "mailingStatus": "pending"
}

POST /api/v1/request/mailing-list

Получить список рассылок

Response:

{
  "items": [
    {
      "requestId": "uuid-here",
      "requestTxt": "Нужны офисные столы...",
      "finalTz": "ТЕХНИЧЕСКОЕ ЗАДАНИЕ...",
      "mailingStatus": "completed",
      "createdAt": "2024-01-13T15:04:05Z",
      "suppliersFound": 15
    }
  ]
}

POST /api/v1/request/mailing-list-by-id

Получить рассылку по ID

Request:

{
  "requestId": "uuid-here"
}

Response:

{
  "item": {
    "requestId": "uuid-here",
    "requestTxt": "Нужны офисные столы...",
    "finalTz": "ТЕХНИЧЕСКОЕ ЗАДАНИЕ...",
    "mailingStatus": "completed",
    "createdAt": "2024-01-13T15:04:05Z",
    "suppliersFound": 15
  }
}

Suppliers

POST /api/v1/suppliers/export-excel

Экспортировать список поставщиков в Excel

Request:

{
  "requestId": "uuid-here"
}

Response: Binary file (application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)

Error Handling

Все ошибки возвращаются в формате:

{
  "error": "ERROR_CODE",
  "message": "Human readable error message",
  "request_id": "request-uuid"
}

Коды ошибок:

  • AUTH_INVALID_CREDENTIALS (401) — Неверный email или пароль
  • AUTH_MISSING_TOKEN (401) — Отсутствует токен авторизации
  • AUTH_INVALID_TOKEN (401) — Невалидный или истёкший токен
  • INVITE_LIMIT_REACHED (429) — Достигнут лимит инвайт-кодов
  • INVITE_INVALID_OR_EXPIRED (412) — Инвайт-код невалидный или истёк
  • EMAIL_ALREADY_EXISTS (409) — Email уже зарегистрирован
  • INSUFFICIENT_BALANCE (402) — Недостаточно средств
  • USER_NOT_FOUND (404) — Пользователь не найден
  • INVALID_REQUEST (400) — Невалидный запрос
  • INTERNAL_SERVER_ERROR (500) — Внутренняя ошибка сервера
  • SERVICE_UNAVAILABLE (503) — Сервис недоступен

Разработка

Makefile команды

make proto          # Генерация proto кода
make build          # Сборка бинарника
make run            # Запуск сервера
make lint           # Запуск линтера
make test           # Запуск тестов
make docker-build   # Сборка Docker образа
make clean          # Очистка сгенерированных файлов

Линтер

make lint

Тесты

make test

Мониторинг

Gateway поддерживает:

  • Prometheus метрики (:8080/metrics)
  • Request ID в каждом ответе (header X-Request-ID)
  • Structured logging (JSON формат)
  • OpenTelemetry tracing (Jaeger)

Health Check Endpoints

GET /health — Общее состояние сервиса

{
  "status": "healthy",      // или "degraded"
  "backend": true           // доступность backend
}
  • 200 — сервис полностью работоспособен
  • 503 — сервис в деградированном режиме (backend недоступен)

GET /health/ready — Готовность к приему трафика

{
  "status": "ready"         // или "not_ready"
}
  • 200 — готов принимать запросы
  • 503 — не готов (backend недоступен)

GET /health/live — Жизнеспособность сервиса

{
  "status": "alive"
}
  • 200 — процесс работает корректно

Circuit Breaker

Gateway использует circuit breaker для защиты от каскадных сбоев при проблемах с backend.

Конфигурация:

  • Порог срабатывания: 3 запроса с 60% ошибок
  • Таймаут открытого состояния: 30 секунд
  • Интервал проверки: 10 секунд
  • Попытки переподключения: каждые 5 секунд

Состояния:

  • Closed — нормальная работа, все запросы проходят
  • Open — слишком много ошибок, запросы блокируются
  • Half-Open — тестирование восстановления backend

Поведение при недоступности backend:

{
  "error": "SERVICE_UNAVAILABLE",
  "message": "Backend service is temporarily unavailable. Please try again later.",
  "request_id": "uuid"
}

Gateway не падает при старте, если backend недоступен. Он продолжает работать и автоматически восстанавливает соединение при появлении backend.

License

Proprietary

Description
No description provided
Readme 18 MiB
Languages
Go 94.3%
Makefile 4.9%
Dockerfile 0.8%