Хранилище
m.DB — постоянное key-value хранилище, которое пишется на диск и переживает перезапуски.
Владелец и ключ
Каждое значение живёт под владельцем — используйте m.Name(), чтобы ваши данные оставались вашими:
m.DB.SetString(m.Name(), "last_city", "Москва")
city := m.DB.GetString(m.Name(), "last_city", "")Геттеры принимают значение по умолчанию, которое вернётся при отсутствии ключа, так что плясок «а есть ли такой ключ» не требуется.
Типизированный доступ
m.DB.SetString(m.Name(), "city", "Москва")
m.DB.SetInt(m.Name(), "count", 42)
m.DB.SetInt64(m.Name(), "user_id", 123456789)
m.DB.SetBool(m.Name(), "enabled", true)
m.DB.SetStringSlice(m.Name(), "cities", []string{"Москва", "Казань"})
m.DB.SetInt64Slice(m.Name(), "admins", []int64{1, 2, 3})
m.DB.SetStringMap(m.Name(), "labels", map[string]string{"ru": "Москва"})
m.DB.SetAnyMap(m.Name(), "state", map[string]any{"n": 1})У каждого есть парный геттер со значением по умолчанию:
city := m.DB.GetString(m.Name(), "city", "Москва")
count := m.DB.GetInt(m.Name(), "count", 0)
enabled := m.DB.GetBool(m.Name(), "enabled", false)
cities := m.DB.GetStringSlice(m.Name(), "cities", nil)Обрабатывайте ошибки записи
Запись идёт на диск и может не удаться. Проигнорировать ошибку — значит молча потерять данные:
if err := m.DB.SetString(m.Name(), "city", city); err != nil {
return fmt.Errorf("сохранение города: %w", err)
}Чтение упасть не может — для этого и нужно значение по умолчанию.
Данные по чатам и пользователям
Составьте ключ:
func chatKey(chatID int64) string {
return "chat:" + strconv.FormatInt(chatID, 10)
}
m.DB.SetString(m.Name(), chatKey(msg.ChatID), "Москва")Если мелких значений много, карта под одним ключом дешевле, чем много ключей:
cities := m.DB.GetStringMap(m.Name(), "cities_by_chat", nil)
if cities == nil {
cities = map[string]string{}
}
cities[strconv.FormatInt(msg.ChatID, 10)] = city
if err := m.DB.SetStringMap(m.Name(), "cities_by_chat", cities); err != nil {
return fmt.Errorf("сохранение городов: %w", err)
}Что можно хранить
Только то, что сериализуется в JSON: строки, числа, булевы значения, а также слайсы и карты из них. Попытка сохранить что-то другое вернёт ошибку, а не испортит файл.
Геттеры возвращают копию, поэтому изменение полученной карты не меняет сохранённое — записывайте её обратно.
Цена
Каждая запись сериализует базу и перезаписывает файл. При обычной частоте это нормально, но не пишите по разу на каждое входящее сообщение в вотчере. Накапливайте в памяти и сбрасывайте периодически:
type Counter struct {
goroku.Base
mu sync.Mutex
counts map[int64]int
}
func (m *Counter) flush() error {
m.mu.Lock()
snapshot := make(map[string]int, len(m.counts))
for id, n := range m.counts {
snapshot[strconv.FormatInt(id, 10)] = n
}
m.mu.Unlock()
return m.DB.SetStringMapInt(m.Name(), "counts", snapshot)
}Секреты
Значения, которых не должно быть в бэкапе, объявляйте полем настройки с Secret: true — см. Настройки. Обычные значения в DB попадают в бэкап как есть.