Files
mila/README.md

285 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mila
Mila — небольшая Go-библиотека для обмена данными между БВК и
медиакомплексом.
Библиотека реализует HTTP-протокол, в котором БВК отправляет в медиакомплекс
текущее состояние отображаемого контента:
```text
GET /set-content?route=<R>&str=<S>&tpl_name=<T>
GET /get-content
```
Mila намеренно не знает ничего про снапшоты, геопозицию, прогресс маршрута,
фронтенд, правила воспроизведения медиа и структуру контента White Nights. Она
только принимает, валидирует, сохраняет, логирует и возвращает состояние,
полученное от БВК.
## Установка
```bash
go get gitea.unprism.ru/KRBL/mila
```
Библиотека собирается под актуальную стабильную ветку Go:
```text
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` оставлен фасадом для удобной интеграции. Прикладному коду
достаточно импортировать только его:
```go
import "gitea.unprism.ru/KRBL/mila"
```
Внутри реализация разнесена по подпакетам:
| Пакет | Ответственность |
| --- | --- |
| `protocol` | модели, конфиг, ошибки, валидация, интерфейс хранилища |
| `storage` | `MemoryStore` и `FileStore` |
| `httpserver` | HTTP-handler, options, коды ответов |
Фасад в корне переэкспортирует основные типы и функции:
```go
mila.ContentState
mila.Config
mila.Store
mila.NewHandler
mila.NewMemoryStore
mila.NewFileStore
mila.WithLogger
mila.WithOnSet
```
## Протокол
Медиакомплекс выступает HTTP-сервером. БВК выступает HTTP-клиентом.
Адрес по документу:
```text
172.16.16.2:13280
```
Ручки:
```text
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`:
```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
текстовый слой неоднозначно распознает исходное значение.
Валидация выполняется через `github.com/go-playground/validator/v10`. Ошибки
валидации заворачиваются в `mila.ErrInvalidData`, поэтому их можно проверять
стандартно:
```go
if errors.Is(err, mila.ErrInvalidData) {
// обработать невалидные данные от БВК
}
```
Для проверки лимитов строка временно конвертируется в `[]rune`, потому что
ограничение задано в символах протокола, а хранить и отдавать данные удобнее как
обычную UTF-8 строку JSON/HTTP.
## Хранилища
`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": "mila: invalid content state: route must be at most 3 characters"
}
```
## Тестирование
```bash
go test ./...
```
Текущие тесты покрывают:
- успешный `set-content` + `get-content`;
- `get-content` до готовности состояния;
- невалидный `route`;
- лимиты для текста/шаблона;
- сохранение и чтение через `FileStore`;
- отсутствие файла состояния в `FileStore`.