Link Tracker — многомодульный Java/Spring Boot проект для отслеживания обновлений по ссылкам и доставки уведомлений пользователям Telegram. Система хранит подписки пользователей, периодически проверяет внешние источники, формирует события обновлений и отправляет их через HTTP или Kafka-пайплайн с дополнительной обработкой в ai-agent.
Проект был реализован в рамках прохождения 2 семестра в Т-Академии, интеграционное тестирование через Testcontainers и запуск инфраструктуры через Docker Compose.
- Возможности
- Архитектура
- Структура проекта
- Требования
- Конфигурация
- Локальный запуск
- Docker Compose
- Telegram-команды
- HTTP API
- Kafka
- Хранилища и миграции
- Наблюдаемость
- Тесты и проверки качества
- Полезные ссылки
- Регистрация Telegram-чата и управление подписками на ссылки.
- Отслеживание GitHub, Stack Overflow и Stack Exchange ссылок.
- Диалоговый сценарий добавления ссылки с тегами.
- Просмотр всех подписок или подписок по тегу.
- Удаление подписки по ссылке.
- Периодическая проверка обновлений во внешних API.
- Два режима доступа к БД Scrapper:
SQLиORM. - Liquibase-миграции PostgreSQL.
- Kafka-доставка обновлений через outbox и DLQ.
- HTTP-доставка обновлений в Bot с Kafka fallback.
- AI Agent для фильтрации, группировки, приоритизации и сжатия сообщений.
- Valkey-кэш списка ссылок в Scrapper.
- Rate limiting и Resilience4J для HTTP-клиентов.
- Метрики Micrometer/Prometheus и готовый Docker Compose для Grafana.
- Интеграционные тесты с Testcontainers.
flowchart LR
user["Telegram user"] --> telegram["Telegram Bot API"]
telegram --> bot["bot :8080"]
bot --> scrapper["scrapper :8081"]
scrapper --> postgres[("PostgreSQL")]
scrapper --> github["GitHub API"]
scrapper --> stackoverflow["Stack Exchange API"]
scrapper --> valkey[("Valkey")]
scrapper --> rawTopic["Kafka: link.raw-updates"]
rawTopic --> ai["ai-agent :8082"]
ai --> processedTopic["Kafka: link.processed-updates"]
processedTopic --> bot
scrapper -. "HTTP transport" .-> bot
bot --> prometheus["Prometheus /metrics"]
scrapper --> prometheus
prometheus --> grafana["Grafana"]
Основной поток в Kafka-режиме:
- Пользователь добавляет ссылку через Telegram-бота.
- Bot вызывает Scrapper API и регистрирует подписку.
- Scrapper по расписанию проверяет внешние источники.
- Новые события сохраняются в outbox и публикуются в
link.raw-updates. - AI Agent читает raw-события, фильтрует, группирует, приоритизирует и публикует результат в
link.processed-updates. - Bot читает processed-события из Kafka и отправляет сообщения пользователям.
В HTTP-режиме Scrapper отправляет обновления напрямую в Bot через POST /updates. При включенном fallback неуспешная HTTP-доставка может быть сохранена в Kafka outbox.
.
├── ai-agent/ # Kafka consumer/producer для интеллектуальной обработки обновлений
├── bot/ # Telegram-бот и API приема готовых уведомлений
├── build-report-aggregate/ # Служебный модуль для агрегированных отчетов сборки
├── migrations/ # Liquibase changelog и SQL-миграции PostgreSQL
├── observability/ # Prometheus и Grafana provisioning
├── scrapper/ # Сервис подписок, планировщик проверок и интеграции с внешними API
├── docker-compose.yaml # Локальная инфраструктура и сервисы Bot/Scrapper
├── pom.xml # Parent POM
├── mvnw, mvnw.cmd # Maven Wrapper
├── pmd.xml # PMD ruleset
└── spotbugs-excludes.xml # Исключения SpotBugs
| Модуль | Назначение |
|---|---|
bot |
Telegram-интерфейс, команды пользователя, REST endpoint /updates, Kafka consumer готовых уведомлений. |
scrapper |
REST API подписок, PostgreSQL, Liquibase, scheduler, GitHub/Stack Overflow clients, Kafka/HTTP sender, Valkey cache. |
ai-agent |
Читает сырые Kafka-обновления, применяет фильтры, summary, приоритеты и группировку, пишет обработанные обновления. |
build-report-aggregate |
Служебный модуль для интеграционных проверок и отчетов по сборке. |
- JDK 25 или новее.
- Maven 3.9.12. Рекомендуется использовать Maven Wrapper из репозитория.
- Docker Desktop / Docker Engine с Docker Compose v2.
- Telegram Bot token для полноценного запуска
bot. - Опционально: GitHub token и Stack Exchange credentials для повышения лимитов внешних API.
- IntelliJ IDEA для удобного запуска Spring Boot модулей локально.
Проверить Java и Docker:
java -version
docker version
docker compose versionСекреты и локальные настройки задаются через переменные окружения. В репозитории есть безопасный шаблон .env.example.
Создать локальный .env:
Copy-Item .env.example .envДля Unix-like окружения:
cp .env.example .envПосле копирования заполните реальные значения. Файл .env добавлен в .gitignore; не коммитьте токены и пароли.
| Переменная | Используется | Описание |
|---|---|---|
TELEGRAM_TOKEN |
bot |
Токен Telegram-бота. Обязателен для реального Telegram-запуска. |
SCRAPPER_BASE_URL |
bot |
URL Scrapper API. Для IDE обычно http://localhost:8081, внутри compose http://scrapper:8081. |
BOT_BASE_URL |
scrapper |
URL Bot API для HTTP transport. Для IDE http://localhost:8080, внутри compose http://bot:8080. |
POSTGRES_URL |
scrapper |
JDBC URL PostgreSQL. |
POSTGRES_USER / POSTGRES_PASSWORD |
scrapper |
Учетные данные PostgreSQL. |
ACCESS_TYPE |
scrapper |
Режим доступа к БД: SQL или ORM. |
KAFKA_BOOTSTRAP_SERVERS |
все Kafka-модули | Kafka bootstrap servers. Для IDE localhost:19092,localhost:29092,localhost:39092. |
NOTIFICATION_TRANSPORT |
scrapper |
Основной транспорт уведомлений: KAFKA или HTTP. |
NOTIFICATION_FALLBACK_ENABLED |
scrapper |
Включает Kafka fallback/outbox для HTTP-режима. |
LINK_RAW_UPDATES_TOPIC |
scrapper, ai-agent |
Topic сырых обновлений. По умолчанию link.raw-updates. |
LINK_RAW_UPDATES_DLQ_TOPIC |
scrapper |
DLQ для сырых обновлений. По умолчанию link.raw-updates-dlq. |
LINK_PROCESSED_UPDATES_TOPIC |
ai-agent, bot |
Topic обработанных обновлений. По умолчанию link.processed-updates. |
LINK_PROCESSED_UPDATES_DLQ_TOPIC |
bot |
DLQ для обработанных обновлений. По умолчанию link.processed-updates-dlq. |
VALKEY_ENABLED |
scrapper |
Включает Valkey-кэш списка ссылок. |
VALKEY_PASSWORD |
scrapper |
Пароль Valkey. |
GITHUB_TOKEN |
scrapper |
GitHub token. Можно оставить dummy для локального smoke-run. |
STACKOVERFLOW_KEY / STACKOVERFLOW_ACCESS_KEY |
scrapper |
Stack Exchange API credentials. |
RATE_LIMIT_ENABLED |
bot, scrapper |
Включает rate limiting публичных endpoints. |
| Переменная | Описание |
|---|---|
AI_AGENT_CONSUMER_GROUP |
Consumer group для чтения link.raw-updates. |
AI_AGENT_STOP_WORDS |
Stop words для фильтрации обновлений. |
AI_AGENT_EXCLUDED_AUTHORS |
Авторы, обновления которых игнорируются. |
AI_AGENT_MIN_LENGTH |
Минимальная длина сообщения после фильтрации. |
AI_AGENT_SUMMARIZATION_THRESHOLD |
Порог длины текста для summary. |
AI_AGENT_HIGH_KEYWORDS |
Ключевые слова высокого приоритета. |
AI_AGENT_LOW_KEYWORDS |
Ключевые слова низкого приоритета. |
AI_AGENT_GROUPING_WINDOW_MS |
Окно группировки обновлений. |
AI_AGENT_GROUPING_FLUSH_INTERVAL_MS |
Интервал flush группировки. |
Windows:
.\mvnw.cmd clean package -DskipTestsLinux/macOS/WSL:
./mvnw clean package -DskipTestsДля запуска Spring Boot приложений из IDE поднимите только зависимости:
docker compose up -d postgres zookeeper kafka-1 kafka-2 kafka-3 kafka-init valkey-node-0 valkey-node-1 valkey-node-2 valkey-initСоздайте Spring Boot run configuration для классов:
| Модуль | Main class | Порт |
|---|---|---|
scrapper |
backend.academy.linktracker.scrapper.ScrapperApplication |
8081 |
bot |
backend.academy.linktracker.bot.BotApplication |
8080, management 8011 |
ai-agent |
backend.academy.linktracker.ai.AiAgentApplication |
8082 |
В каждую конфигурацию подключите переменные из корневого .env. Для запуска из IDE используйте localhost-значения из .env.example.
Рекомендуемый порядок запуска:
scrapperai-agent, если используетсяNOTIFICATION_TRANSPORT=KAFKAbot
Если вы хотите проверить прямую доставку без AI Agent, установите:
NOTIFICATION_TRANSPORT=HTTPТогда Scrapper будет отправлять обновления напрямую в Bot через HTTP.
docker-compose.yaml поднимает:
- PostgreSQL
localhost:5432 - Zookeeper
localhost:2181 - Kafka cluster из трех broker'ов
localhost:19092,localhost:29092,localhost:39092 - Kafka topics init container
- Valkey cluster
localhost:6379,localhost:6380,localhost:6381 - Scrapper
localhost:8081 - Bot
localhost:8080, managementlocalhost:8011 - Prometheus
localhost:9090 - Grafana
localhost:3000
Перед запуском compose-сервисов bot и scrapper должны существовать executable jar'ы:
.\mvnw.cmd package -DskipTestsЗапуск всего compose-файла:
docker compose --env-file .env up -d --buildЛоги:
docker compose logs -f scrapper botОстановка:
docker compose downОстановка с удалением volume'ов:
docker compose down -vВ текущем compose-файле нет отдельного контейнера ai-agent. Для полного Kafka-пайплайна запустите ai-agent из IDE/Maven рядом с compose-инфраструктурой или используйте NOTIFICATION_TRANSPORT=HTTP для прямой доставки Scrapper -> Bot.
| Команда | Назначение |
|---|---|
/start |
Зарегистрировать текущий Telegram-чат в Scrapper. |
/help |
Показать список доступных команд. |
/track |
Начать диалог добавления ссылки. Бот запросит URL и теги. |
/list |
Показать все отслеживаемые ссылки. |
/list <tag> |
Показать ссылки по тегу. |
/untrack <url> |
Прекратить отслеживание ссылки. |
/cancel |
Отменить текущий диалог /track. |
Поддерживаются ссылки с http и https схемами на домены GitHub, Stack Overflow и Stack Exchange.
Base URL при локальном запуске: http://localhost:8081.
| Метод | Endpoint | Назначение |
|---|---|---|
POST |
/tg-chat/{id} |
Зарегистрировать чат. |
DELETE |
/tg-chat/{id} |
Удалить чат и его подписки. |
GET |
/links |
Получить ссылки чата. Требуется header Tg-Chat-Id. Опционально ?tag=.... |
POST |
/links |
Добавить ссылку. Требуется header Tg-Chat-Id. |
DELETE |
/links |
Удалить ссылку. Требуется header Tg-Chat-Id. |
Пример добавления ссылки:
curl -X POST http://localhost:8081/links \
-H "Content-Type: application/json" \
-H "Tg-Chat-Id: 123" \
-d '{"link":"https://github.com/spring-projects/spring-boot","tags":["spring"],"filters":[]}'Base URL при локальном запуске: http://localhost:8080.
| Метод | Endpoint | Назначение |
|---|---|---|
POST |
/updates |
Принять готовое уведомление от Scrapper или другого producer'а. |
OpenAPI UI при наличии запущенного сервиса обычно доступен по /swagger-ui/index.html, API docs — по /v3/api-docs.
Используемые topics:
| Topic | Producer | Consumer | Назначение |
|---|---|---|---|
link.raw-updates |
scrapper |
ai-agent |
Сырые события обновлений. |
link.raw-updates-dlq |
scrapper |
операционная диагностика | DLQ сырых событий. |
link.processed-updates |
ai-agent |
bot |
Обработанные уведомления для отправки пользователю. |
link.processed-updates-dlq |
bot |
операционная диагностика | DLQ обработанных уведомлений. |
Scrapper использует outbox: событие сначала фиксируется в PostgreSQL, затем плановый publisher отправляет его в Kafka и помечает как доставленное.
PostgreSQL хранит:
- Telegram-чаты;
- отслеживаемые ссылки;
- связи чат-ссылка;
- теги и фильтры;
- outbox-сообщения.
Схема управляется Liquibase. Changelog находится в migrations/master.xml, SQL-файлы — в migrations/*.sql.
Scrapper может использовать Valkey для кэширования списка ссылок. Основные настройки:
VALKEY_ENABLED=true
VALKEY_CACHE_TTL=10m
VALKEY_CLIENT_SIDE_CACHE_ENABLED=falseВ compose поднимается Valkey cluster из трех узлов. Для Docker Compose сервис scrapper сейчас запускается с VALKEY_ENABLED=false, чтобы не зависеть от cluster-клиента внутри контейнера. Для IDE-запуска можно включить кэш через .env.
Actuator endpoints имеют base path /. Prometheus endpoint замаплен на /metrics.
| Сервис | Метрики | Health |
|---|---|---|
scrapper |
http://localhost:8081/metrics |
http://localhost:8081/health |
bot |
http://localhost:8011/metrics |
http://localhost:8011/health |
Prometheus:
http://localhost:9090
Grafana:
http://localhost:3000
Локальные учетные данные Grafana из compose:
login: admin
password: admin
Полная проверка проекта:
.\mvnw.cmd clean verifyЗапуск тестов:
.\mvnw.cmd testЗапуск тестов конкретного модуля:
.\mvnw.cmd -pl scrapper test
.\mvnw.cmd -pl bot test
.\mvnw.cmd -pl ai-agent testЗапуск одного теста:
.\mvnw.cmd -pl ai-agent -Dtest=RawLinkUpdateKafkaConsumerIntegrationTest testСтатические проверки:
.\mvnw.cmd compile -am spotless:check modernizer:modernizer spotbugs:check pmd:check pmd:cpd-checkАвтоформатирование:
.\mvnw.cmd spotless:applyСборка executable jar'ов без тестов:
.\mvnw.cmd package -DskipTestsИнтеграционные тесты используют Testcontainers. Для них должен быть запущен Docker Engine.
Проект собирается на Java 25. Установите JDK 25 и проверьте, что IntelliJ IDEA и терминал используют именно его.
Используйте Maven Wrapper: mvnw.cmd на Windows или ./mvnw на Unix-like системах.
Dockerfile'ы копируют jar из bot/target и scrapper/target. Перед docker compose up --build выполните:
.\mvnw.cmd package -DskipTestsПроверьте, что запущен ai-agent. Scrapper публикует сырые события в link.raw-updates, Bot читает уже обработанные события из link.processed-updates.
Для проверки без AI Agent переключите транспорт:
NOTIFICATION_TRANSPORT=HTTP