Files
mila/README.md

9.4 KiB
Raw Blame History

Mila

Mila — небольшая Go-библиотека для обмена данными между БВК и медиакомплексом.

Библиотека реализует HTTP-протокол, в котором БВК отправляет в медиакомплекс текущее состояние отображаемого контента:

GET /set-content?route=<R>&str=<S>&tpl_name=<T>
GET /get-content

Mila намеренно не знает ничего про снапшоты, геопозицию, прогресс маршрута, фронтенд, правила воспроизведения медиа и структуру контента White Nights. Она только принимает, валидирует, сохраняет, логирует и возвращает состояние, полученное от БВК.

Установка

go get gitea.unprism.ru/KRBL/mila

Библиотека собирается под актуальную стабильную ветку Go:

go 1.26.5

Зона Ответственности

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 с реальными плейлистами;
  • не читает и не пишет снапшоты;
  • не общается с фронтендом напрямую;
  • не решает, как именно должен отображаться контент.

Структура Пакетов

Корневой пакет mila оставлен фасадом для удобной интеграции. Прикладному коду достаточно импортировать только его:

import "gitea.unprism.ru/KRBL/mila"

Внутри реализация разнесена по подпакетам:

Пакет Ответственность
protocol модели, конфиг, ошибки, валидация, интерфейс хранилища
storage MemoryStore и FileStore
httpserver HTTP-handler, options, коды ответов

Фасад в корне переэкспортирует основные типы и функции:

mila.ContentState
mila.Config
mila.Store
mila.NewHandler
mila.NewMemoryStore
mila.NewFileStore
mila.WithLogger
mila.WithOnSet

Протокол

Медиакомплекс выступает HTTP-сервером. БВК выступает HTTP-клиентом.

Адрес по документу:

172.16.16.2:13280

Ручки:

GET /set-content?route=<R>&str=<S>&tpl_name=<T>
GET /get-content

set-content принимает URL-encoded query-параметры:

Параметр Обязательный Описание
route да номер маршрута, по умолчанию 1-3 символа
str нет текст бегущей строки, по умолчанию до 200 символов
tpl_name да название шаблона медиаконтента, по умолчанию до 200 символов

Ответ get-content:

{
  "route": "3",
  "str": "Гостиный двор - Gostiny dvor",
  "tpl_name": "Гостиный двор"
}

Поле updated_at хранится внутри состояния, но не отдается через /get-content, потому что ответ должен оставаться строгим относительно протокола.

Использование

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())
}

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

Конфигурация валидации по умолчанию:

cfg := mila.DefaultConfig()

Сейчас это:

mila.Config{
	MaxRouteLen:    3,
	MaxTextLen:     200,
	MaxTemplateLen: 200,
}

Лимиты для str и tpl_name нужно подтвердить у интегратора. В переданном PDF текстовый слой неоднозначно распознает исходное значение.

Валидация выполняется через github.com/go-playground/validator/v10. Ошибки валидации заворачиваются в mila.ErrInvalidData, поэтому их можно проверять стандартно:

if errors.Is(err, mila.ErrInvalidData) {
	// обработать невалидные данные от БВК
}

Для проверки лимитов строка временно конвертируется в []rune, потому что ограничение задано в символах протокола, а хранить и отдавать данные удобнее как обычную UTF-8 строку JSON/HTTP.

Хранилища

MemoryStore подходит, когда состояние не должно переживать рестарт процесса:

store := mila.NewMemoryStore()

FileStore подходит для устройства, если последнее состояние от БВК нужно сохранять на диске:

store := mila.NewFileStore("/srv/persistence/client_logs/bvk_content.json")

FileStore пишет состояние атомарно через временный файл и держит копию в памяти после первого успешного чтения или записи.

Callback

WithOnSet позволяет приложению отреагировать на валидное обновление от БВК:

handler := mila.NewHandler(mila.DefaultConfig(), store,
	mila.WithOnSet(func(state mila.ContentState) {
		// Возможные действия на стороне приложения:
		// - опубликовать номер маршрута и бегущую строку;
		// - сопоставить state.Template с локальным плейлистом;
		// - записать прикладные логи.
	}),
)

Callback выполняется синхронно. Его нужно держать быстрым. Если интеграционная логика тяжелая, лучше положить состояние во внутреннюю очередь.

Модель Состояния

{
  "route": "3",
  "str": "Гостиный двор - Gostiny dvor",
  "tpl_name": "Гостиный двор",
  "updated_at": "2026-07-12T12:00:00Z"
}

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 до первого состояния, отмененный контекст, недоступная зависимость

Формат ошибки:

{
  "error": "mila: invalid content state: route must be at most 3 characters"
}

Тестирование

go test ./...

Текущие тесты покрывают:

  • успешный set-content + get-content;
  • get-content до готовности состояния;
  • невалидный route;
  • лимиты для текста/шаблона;
  • сохранение и чтение через FileStore;
  • отсутствие файла состояния в FileStore.