# Mila Mila — небольшая Go-библиотека для обмена данными между БВК и медиакомплексом. Библиотека реализует HTTP-протокол, в котором БВК отправляет в медиакомплекс текущее состояние отображаемого контента: ```text GET /set-content?route=&str=&tpl_name= GET /get-content ``` Mila намеренно не знает ничего про снапшоты, геопозицию, прогресс маршрута, фронтенд, правила воспроизведения медиа и структуру контента White Nights. Она только принимает, валидирует, сохраняет, логирует и возвращает состояние, полученное от БВК. ## Установка ```bash go get gitea.unprism.ru/KRBL/mila ``` ## Зона Ответственности Mila — библиотека протокола. Ее нужно встраивать в прикладной сервис, например в `wn-content`, и открывать на адресе, который требуется транспортной интеграцией. Mila делает: - разбирает `GET /set-content`; - разбирает `GET /get-content`; - валидирует `route`, `str` и `tpl_name`; - декодирует URL-параметры стандартным HTTP-парсером Go; - хранит последнее состояние в памяти или JSON-файле; - возвращает JSON, совместимый с протоколом; - логирует каждый HTTP-запрос: метод, путь, статус, удаленный адрес и время; - вызывает опциональный callback после успешного `set-content`. Mila не делает: - не считает прогресс маршрута; - не работает с геопозицией или NMEA; - не сопоставляет `tpl_name` с реальными плейлистами; - не читает и не пишет снапшоты; - не общается с фронтендом напрямую; - не решает, как именно должен отображаться контент. ## Протокол Медиакомплекс выступает HTTP-сервером. БВК выступает HTTP-клиентом. Адрес по документу: ```text 172.16.16.2:13280 ``` Ручки: ```text GET /set-content?route=&str=&tpl_name= GET /get-content ``` `set-content` принимает URL-encoded query-параметры: | Параметр | Обязательный | Описание | | --- | --- | --- | | `route` | да | номер маршрута, по умолчанию 1-3 символа | | `str` | нет | текст бегущей строки, по умолчанию до 200 символов | | `tpl_name` | да | название шаблона медиаконтента, по умолчанию до 200 символов | Ответ `get-content`: ```json { "route": "3", "str": "Гостиный двор - Gostiny dvor", "tpl_name": "Гостиный двор" } ``` Поле `updated_at` хранится внутри состояния, но не отдается через `/get-content`, потому что ответ должен оставаться строгим относительно протокола. ## Использование ```go package main import ( "log" "net/http" "gitea.unprism.ru/KRBL/mila" ) func main() { store := mila.NewFileStore("/srv/persistence/client_logs/bvk_content.json") handler := mila.NewHandler(mila.DefaultConfig(), store, mila.WithOnSet(func(state mila.ContentState) { // Здесь прикладная интеграция: // - передать route/str/tpl_name в состояние приложения; // - сопоставить state.Template с локальным шаблоном; // - записать дополнительные логи. }), ) srv := &http.Server{ Addr: "0.0.0.0:13280", Handler: handler, } log.Fatal(srv.ListenAndServe()) } ``` ## Конфигурация Конфигурация валидации по умолчанию: ```go cfg := mila.DefaultConfig() ``` Сейчас это: ```go mila.Config{ MaxRouteLen: 3, MaxTextLen: 200, MaxTemplateLen: 200, } ``` Лимиты для `str` и `tpl_name` нужно подтвердить у интегратора. В переданном PDF текстовый слой неоднозначно распознает исходное значение. ## Хранилища `MemoryStore` подходит, когда состояние не должно переживать рестарт процесса: ```go store := mila.NewMemoryStore() ``` `FileStore` подходит для устройства, если последнее состояние от БВК нужно сохранять на диске: ```go store := mila.NewFileStore("/srv/persistence/client_logs/bvk_content.json") ``` `FileStore` пишет состояние атомарно через временный файл и держит копию в памяти после первого успешного чтения или записи. ## Callback `WithOnSet` позволяет приложению отреагировать на валидное обновление от БВК: ```go handler := mila.NewHandler(mila.DefaultConfig(), store, mila.WithOnSet(func(state mila.ContentState) { // Возможные действия на стороне приложения: // - опубликовать номер маршрута и бегущую строку; // - сопоставить state.Template с локальным плейлистом; // - записать прикладные логи. }), ) ``` Callback выполняется синхронно. Его нужно держать быстрым. Если интеграционная логика тяжелая, лучше положить состояние во внутреннюю очередь. ## Модель Состояния ```json { "route": "3", "str": "Гостиный двор - Gostiny dvor", "tpl_name": "Гостиный двор", "updated_at": "2026-07-12T12:00:00Z" } ``` Go-тип: ```go type ContentState struct { Route string `json:"route"` Text string `json:"str"` Template string `json:"tpl_name"` UpdatedAt time.Time `json:"updated_at,omitempty"` } ``` ## Коды Ответов | Код | Когда возвращается | | ---: | --- | | 200 | валидный `set-content` или готовый `get-content` | | 400 | невалидные параметры запроса | | 404 | неизвестный путь | | 405 | метод не `GET` | | 500 | ошибка хранилища или внутренняя ошибка | | 503 | `get-content` до первого состояния, отмененный контекст, недоступная зависимость | Формат ошибки: ```json { "error": "route: must be at most 3 characters" } ``` ## Тестирование ```bash go test ./... ``` Текущие тесты покрывают: - успешный `set-content` + `get-content`; - `get-content` до готовности состояния; - невалидный `route`; - UTF-8 и лимиты для текста/шаблона; - сохранение и чтение через `FileStore`; - отсутствие файла состояния в `FileStore`.