feat(bj): модуль ЛК на PostgreSQL, обмен с DEPO, боевой ЛК с имитатором НРД, QUIK, НПР
- bj_exchange и «кран» Fancy убраны: данные модуля ЛК в PostgreSQL (схема lk) - трансферные таблицы FTV_M2M: создание при установке, события, справочник участников - сверка с DEPO: счёт, ФИО, дата рождения, ИИС по договору, квал-инвестор из опции DEPO - боевой ЛК ЕСИА Финанс при тестовом ИШ: журнал, отказ через DEPO, статусы в ЛК - онлайн-лицензирование в bj, план тестирования M2M для контрагента (интерактивный HTML) - модули QUIK МНП, экспорт QUIK, НПР/УДС; документы и тестовые материалы Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -20,22 +21,65 @@ type RuntimeConfig struct {
|
||||
|
||||
// Settings — сериализуемое представление настроек.
|
||||
type Settings struct {
|
||||
Postgres PostgresSettings `json:"postgres"`
|
||||
Crypto CryptoSettings `json:"crypto"`
|
||||
NSD NSDSettings `json:"nsd"`
|
||||
LK LKSettings `json:"lk"`
|
||||
CACerts CACertsSettings `json:"ca_certs"`
|
||||
News NewsSettings `json:"news"`
|
||||
Update UpdateSettings `json:"update"`
|
||||
License LicenseSettings `json:"license"`
|
||||
LastTest *TestRunResult `json:"last_test,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Postgres PostgresSettings `json:"postgres"`
|
||||
Fancy FancyMSSQLSettings `json:"fancy_mssql"`
|
||||
Quik QuikMNPSettings `json:"quik_mnp"`
|
||||
Crypto CryptoSettings `json:"crypto"`
|
||||
NSD NSDSettings `json:"nsd"`
|
||||
LK LKSettings `json:"lk"`
|
||||
CACerts CACertsSettings `json:"ca_certs"`
|
||||
NSDCerts NSDCertsSettings `json:"nsd_certs"`
|
||||
News NewsSettings `json:"news"`
|
||||
Alerts AlertSettings `json:"alerts"`
|
||||
Incoming IncomingNotifySettings `json:"incoming_notify"`
|
||||
Update UpdateSettings `json:"update"`
|
||||
License LicenseSettings `json:"license"`
|
||||
Branding BrandingSettings `json:"branding"`
|
||||
NPR NPRSettings `json:"npr"`
|
||||
QuikExport QuikExportSettings `json:"quik_export"`
|
||||
Auth AuthSettings `json:"auth"`
|
||||
LastTest *TestRunResult `json:"last_test,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// QuikMNPSettings — подключение к БД Модуля неторговых поручений QUIK (MSSQL).
|
||||
// Используется для блокировки бумаг при выводе (M2M). БД боевая — работаем на
|
||||
// чтение + вызовы штатных хранимых процедур API (роль InstrEu). Прямых INSERT/
|
||||
// UPDATE в таблицы НЕ делаем.
|
||||
type QuikMNPSettings struct {
|
||||
Server string `json:"server"` // host или host\instance
|
||||
Port int `json:"port"` // напр. 8849 (нестандартный)
|
||||
Database string `json:"database"` // имя БД МНП (если требуется)
|
||||
User string `json:"user"` // SQL-логин (роль InstrEu), напр. BROKER_88
|
||||
Password string `json:"password"` // пароль
|
||||
Encrypt string `json:"encrypt"` // disable | true (по умолчанию true+trust для боевой)
|
||||
Trust bool `json:"trust"` // TrustServerCertificate (самоподписанный серт)
|
||||
Verified bool `json:"verified"` // последнее тест-подключение прошло
|
||||
LastReport string `json:"last_report"` // полный текст последнего теста (для копирования коллегам)
|
||||
LastAt time.Time `json:"last_at"` // время последнего теста
|
||||
|
||||
// Автоматический цикл M2M в МНП (пишущие операции к боевой БД).
|
||||
WritesEnabled bool `json:"writes_enabled"` // главный предохранитель: без него ни одной записи в МНП
|
||||
AutoOut bool `json:"auto_out"` // исходящие: регистрировать «Вывод ЦБ (M2M)» при отправке, подписывать/снимать по ответу
|
||||
AutoIn bool `json:"auto_in"` // входящие: регистрировать «Ввод ЦБ (M2M)» после приёма поручения НРД
|
||||
PollTerminal bool `json:"poll_terminal"` // подхватывать поручения, поданные клиентами из QUIK (Export)
|
||||
Account string `json:"account"` // торговый счёт для регистрации (напр. L01+00000F00)
|
||||
SecOut string `json:"sec_out"` // код МНП-инструмента вывода (пусто = определить по классу INSTROUTM2M)
|
||||
SecIn string `json:"sec_in"` // код МНП-инструмента ввода (пусто = по классу INSTRINM2M)
|
||||
ClientCodeMap map[string]string `json:"client_code_map"` // исключения: номер портфеля DEPO («БР-4-4») → код клиента QUIK
|
||||
}
|
||||
|
||||
// LicenseSettings — лицензионный ключ (подписанный токен).
|
||||
type LicenseSettings struct {
|
||||
Key string `json:"key"` // компактный токен payload.signature.keyid
|
||||
PublicKey string `json:"public_key"` // base64 (если не зашит в бинарь)
|
||||
Key string `json:"key"` // компактный токен payload.signature.keyid
|
||||
PublicKey string `json:"public_key,omitempty"` // устарело: ключ проверки зашит в bj, поле не используется
|
||||
|
||||
// Онлайн-лицензирование (licenseclient.go).
|
||||
InstallID string `json:"install_id,omitempty"` // идентификатор этой установки на сервере лицензий
|
||||
LastSync time.Time `json:"last_sync,omitempty"` // последнее успешное обращение к серверу лицензий
|
||||
LastAttempt time.Time `json:"last_attempt,omitempty"` // последняя попытка
|
||||
LastOK bool `json:"last_ok,omitempty"`
|
||||
LastMsg string `json:"last_msg,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateSettings — авто-обновления из артефактории (#18/#20).
|
||||
@@ -55,10 +99,10 @@ type UpdateSettings struct {
|
||||
// doc-watcher'ом и cron-задачами. Каждое событие может быть скрыто (Dismissed)
|
||||
// оператором, но не удалено — лента служит «журналом» для аудита.
|
||||
type NewsSettings struct {
|
||||
Items []NewsItem `json:"items"`
|
||||
DocSources []DocSource `json:"doc_sources"` // URL'ы для авто-проверки (NSD pages)
|
||||
LastDocCheck time.Time `json:"last_doc_check"`
|
||||
DocCheckResult string `json:"doc_check_result"`
|
||||
Items []NewsItem `json:"items"`
|
||||
DocSources []DocSource `json:"doc_sources"` // URL'ы для авто-проверки (NSD pages)
|
||||
LastDocCheck time.Time `json:"last_doc_check"`
|
||||
DocCheckResult string `json:"doc_check_result"`
|
||||
}
|
||||
|
||||
// NewsItem — одно событие в ленте.
|
||||
@@ -76,21 +120,21 @@ type NewsItem struct {
|
||||
|
||||
// DocSource — страница НРД, которую doc-watcher периодически проверяет.
|
||||
type DocSource struct {
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"` // человекочитаемое имя
|
||||
LastChecked time.Time `json:"last_checked"`
|
||||
KnownPDFs map[string]string `json:"known_pdfs"` // url → sha256
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"` // человекочитаемое имя
|
||||
LastChecked time.Time `json:"last_checked"`
|
||||
KnownPDFs map[string]string `json:"known_pdfs"` // url → sha256
|
||||
}
|
||||
|
||||
// CACertsSettings — URL'ы для авто-загрузки сертификатов УЦ НРД и нашего
|
||||
// УЦ. Список редактируется пользователем; раз в сутки фоновая горутина
|
||||
// перекачивает каждый URL и сохраняет сертификат, если он поменялся.
|
||||
type CACertsSettings struct {
|
||||
URLs []string `json:"urls"`
|
||||
AutoUpdate bool `json:"auto_update"`
|
||||
LastFetch time.Time `json:"last_fetch"`
|
||||
LastFetchLog string `json:"last_fetch_log"`
|
||||
FetchedCerts []FetchedCACert `json:"fetched_certs"`
|
||||
URLs []string `json:"urls"`
|
||||
AutoUpdate bool `json:"auto_update"`
|
||||
LastFetch time.Time `json:"last_fetch"`
|
||||
LastFetchLog string `json:"last_fetch_log"`
|
||||
FetchedCerts []FetchedCACert `json:"fetched_certs"`
|
||||
}
|
||||
|
||||
// FetchedCACert — информация о последнем удачно скачанном сертификате.
|
||||
@@ -110,6 +154,53 @@ type PostgresSettings struct {
|
||||
DSN string `json:"dsn"`
|
||||
}
|
||||
|
||||
// FancyMSSQLSettings — подключение к MSSQL-серверу клиента: канал обмена с
|
||||
// Fancy (очередь запросов из ЛК + ответы «крана») и журнал обмена с НРД.
|
||||
// Аутентификация — SQL-логин (SQL Server Authentication).
|
||||
type FancyMSSQLSettings struct {
|
||||
DBType string `json:"db_type"` // "mssql" | "postgres" (пусто = mssql)
|
||||
Server string `json:"server"` // host или host\instance
|
||||
Port int `json:"port"` // 1433 (MSSQL) / 5432 (Postgres)
|
||||
User string `json:"user"` // логин
|
||||
Password string `json:"password"` // пароль
|
||||
Encrypt string `json:"encrypt"` // MSSQL: disable|false|true; Postgres: sslmode
|
||||
|
||||
// Обработка трансферных таблиц DEPO силами bj (вместо джобы SQL Agent bj2depo):
|
||||
// bj сам вызывает процедуру DepoJobProc каждые DepoJobEverySec секунд.
|
||||
DepoJobRun bool `json:"depo_job_run"`
|
||||
DepoJobMode string `json:"depo_job_mode"` // bj | sql-auto | sql-manual (пусто = не выбран)
|
||||
DepoJobCheck string `json:"depo_job_check"` // результат последней проверки готовности
|
||||
DepoJobCheckOK bool `json:"depo_job_check_ok"`
|
||||
DepoJobCheckAt time.Time `json:"depo_job_check_at"`
|
||||
DepoJobEverySec int `json:"depo_job_every_sec"`
|
||||
DepoJobProc string `json:"depo_job_proc"`
|
||||
DepoJobRunAs string `json:"depo_job_run_as,omitempty"` // пользователь базы DEPO для EXECUTE AS процедуры (пусто = SYSDBA)
|
||||
DepoJobLast string `json:"depo_job_last"` // последний результат (для карточки)
|
||||
DepoJobLastAt time.Time `json:"depo_job_last_at"`
|
||||
|
||||
// Трансферные таблицы FTV_M2M* в базе DEPO (depotables.go).
|
||||
DepoDatabase string `json:"depo_database"` // база DEPO; пусто = BJ_DEPO_DATA
|
||||
IISContractMarker string `json:"iis_contract_marker,omitempty"` // начало названия договора ИИС в DEPO (через запятую); пусто = БРИИС
|
||||
DepoTablesMode string `json:"depo_tables_mode"` // auto (создаёт bj) | script (администратор СУБД)
|
||||
DepoTablesCheck string `json:"depo_tables_check"`
|
||||
DepoTablesCheckOK bool `json:"depo_tables_check_ok"`
|
||||
DepoTablesCheckAt time.Time `json:"depo_tables_check_at"`
|
||||
}
|
||||
|
||||
// IsPostgres сообщает, выбран ли PostgreSQL (иначе — MSSQL по умолчанию).
|
||||
func (s FancyMSSQLSettings) IsPostgres() bool { return s.DBType == "postgres" }
|
||||
|
||||
// DefaultPort возвращает порт по типу СУБД, если он не задан явно.
|
||||
func (s FancyMSSQLSettings) DefaultPort() int {
|
||||
if s.Port != 0 {
|
||||
return s.Port
|
||||
}
|
||||
if s.IsPostgres() {
|
||||
return 5432
|
||||
}
|
||||
return 1433
|
||||
}
|
||||
|
||||
// CryptoSettings — путь к PKCS#11 модулю и тип провайдера.
|
||||
type CryptoSettings struct {
|
||||
Provider string `json:"provider"` // "stub" | "validata"
|
||||
@@ -129,11 +220,44 @@ type NSDSettings struct {
|
||||
DeponentCode string `json:"deponent_code"` // депкод, напр. MC0413600000
|
||||
AccountID string `json:"account_id"` // депозитарный счёт
|
||||
SectionID string `json:"section_id"` // раздел депозитарного счёта
|
||||
|
||||
// IGWv904 — ИШ обновлён до версии 107.0.0.904+ (Astra 107.0.907+): с этой
|
||||
// версии в config.xml ОБЯЗАТЕЛЬНО указывать businessLine=MOST для M2M; на
|
||||
// версиях младше — НЕ допускается. По умолчанию false (совместимо со старым
|
||||
// ИШ — элемент не добавляется).
|
||||
IGWv904 bool `json:"igw_v904"`
|
||||
}
|
||||
|
||||
// LKSettings — настройки callback в ЛК клиента.
|
||||
type LKSettings struct {
|
||||
CallbackURL string `json:"callback_url"`
|
||||
// PublicURL — адрес личного кабинета клиента (стенд/бой), куда оператор
|
||||
// заходит подать тестовую заявку. Показывается ссылкой на дашборде.
|
||||
PublicURL string `json:"lk_public_url"`
|
||||
// APIKey — ключ доступа для модуля Личного кабинета клиента (bj-lk).
|
||||
// Модуль ставится на веб-сервер клиента и обращается к bj-server по этому
|
||||
// ключу (заголовок X-BJ-Key). Пустой ключ = модульный API выключен.
|
||||
APIKey string `json:"lk_api_key"`
|
||||
|
||||
// Интеграция с ЛК (потребление back_office API платформы): два окружения.
|
||||
ActiveEnv string `json:"lk_active_env"` // "test" | "prod"
|
||||
Test LKEndpoint `json:"lk_test"`
|
||||
Prod LKEndpoint `json:"lk_prod"`
|
||||
|
||||
// EmulateNSD — заявки ЛК проходят весь путь (сверка DEPO, блокировка QUIK, сделка,
|
||||
// статус в ЛК), но в НРД не уходят: отправку и ответ контрагента имитирует bj.
|
||||
// Для боевого ЛК при тестовом профиле ИШ имитация включается принудительно.
|
||||
EmulateNSD bool `json:"lk_emulate_nsd"`
|
||||
}
|
||||
|
||||
// LKEndpoint — реквизиты подключения к API ЛК (HTTP Basic) для одного окружения.
|
||||
type LKEndpoint struct {
|
||||
BaseURL string `json:"base_url"` // напр. https://fontvielle.ef.winr.pro/manager/api
|
||||
Login string `json:"login"` // Basic-логин
|
||||
Password string `json:"password"` // Basic-пароль
|
||||
LastOK bool `json:"last_ok"` // результат последней «Проверить связь»
|
||||
LastMsg string `json:"last_msg"` // текст результата
|
||||
LastAt time.Time `json:"last_at"` // время последней проверки
|
||||
}
|
||||
|
||||
// TestRunResult — результат последнего тестового прогона.
|
||||
@@ -181,6 +305,21 @@ func (r *RuntimeConfig) Snapshot() Settings {
|
||||
return out
|
||||
}
|
||||
|
||||
// BrandingSettings — оформление отчётов (логотип организации в шапке).
|
||||
type BrandingSettings struct {
|
||||
LogoDataURI string `json:"logo_data_uri,omitempty"` // data:image/...;base64,... (пусто = встроенный логотип по умолчанию)
|
||||
LogoName string `json:"logo_name,omitempty"` // имя загруженного файла (для UI)
|
||||
}
|
||||
|
||||
// UpdateBranding сохраняет настройки оформления (логотип отчётов).
|
||||
func (r *RuntimeConfig) UpdateBranding(s BrandingSettings) error {
|
||||
r.mu.Lock()
|
||||
r.data.Branding = s
|
||||
r.data.UpdatedAt = time.Now().UTC()
|
||||
r.mu.Unlock()
|
||||
return r.save()
|
||||
}
|
||||
|
||||
// UpdatePostgres сохраняет postgres-настройки.
|
||||
func (r *RuntimeConfig) UpdatePostgres(s PostgresSettings) error {
|
||||
r.mu.Lock()
|
||||
@@ -190,6 +329,24 @@ func (r *RuntimeConfig) UpdatePostgres(s PostgresSettings) error {
|
||||
return r.save()
|
||||
}
|
||||
|
||||
// UpdateFancy сохраняет настройки подключения к MSSQL (канал обмена с Fancy).
|
||||
func (r *RuntimeConfig) UpdateFancy(s FancyMSSQLSettings) error {
|
||||
r.mu.Lock()
|
||||
r.data.Fancy = s
|
||||
r.data.UpdatedAt = time.Now().UTC()
|
||||
r.mu.Unlock()
|
||||
return r.save()
|
||||
}
|
||||
|
||||
// UpdateQuik сохраняет настройки подключения к БД МНП QUIK.
|
||||
func (r *RuntimeConfig) UpdateQuik(s QuikMNPSettings) error {
|
||||
r.mu.Lock()
|
||||
r.data.Quik = s
|
||||
r.data.UpdatedAt = time.Now().UTC()
|
||||
r.mu.Unlock()
|
||||
return r.save()
|
||||
}
|
||||
|
||||
// SaveLicense сохраняет лицензионные настройки.
|
||||
func (r *RuntimeConfig) SaveLicense(s LicenseSettings) error {
|
||||
r.mu.Lock()
|
||||
@@ -223,6 +380,7 @@ func (r *RuntimeConfig) UpdateNSD(s NSDSettings) error {
|
||||
r.data.NSD = s
|
||||
r.data.UpdatedAt = time.Now().UTC()
|
||||
r.mu.Unlock()
|
||||
applyBusinessLine(s.IGWv904) // сразу применяем businessLine к генератору config.xml
|
||||
return r.save()
|
||||
}
|
||||
|
||||
@@ -285,6 +443,128 @@ func (r *RuntimeConfig) DismissNews(id string) error {
|
||||
return r.save()
|
||||
}
|
||||
|
||||
// AlertSettings — каналы внешней доставки системных оповещений (сверх ленты
|
||||
// «Новости»). SMTP — почта оператору; Zabbix — trapper-хук в мониторинг.
|
||||
type AlertSettings struct {
|
||||
SMTP SMTPSettings `json:"smtp"`
|
||||
Zabbix ZabbixSettings `json:"zabbix"`
|
||||
}
|
||||
|
||||
// SMTPSettings — параметры отправки уведомлений по e-mail.
|
||||
type SMTPSettings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"` // 587 (STARTTLS), 465 (SSL), 25
|
||||
Security string `json:"security"` // "starttls" | "ssl" | "none"
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"` // получатели через запятую
|
||||
LastMsg string `json:"last_msg"`
|
||||
LastAt time.Time `json:"last_at"`
|
||||
}
|
||||
|
||||
// ZabbixSettings — trapper-хук: bj шлёт значение элемента данных на сервер/прокси
|
||||
// Zabbix (протокол zabbix_sender). В Zabbix заводится host + item типа
|
||||
// «Zabbix trapper» с указанным ключом; триггер по значению шлёт уведомления.
|
||||
type ZabbixSettings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Server string `json:"server"` // адрес сервера/прокси Zabbix
|
||||
Port int `json:"port"` // 10051 по умолчанию
|
||||
Host string `json:"host"` // имя хоста в Zabbix (host.host)
|
||||
Key string `json:"key"` // ключ элемента-trapper, напр. bj.alert
|
||||
LastMsg string `json:"last_msg"`
|
||||
LastAt time.Time `json:"last_at"`
|
||||
}
|
||||
|
||||
func (r *RuntimeConfig) UpdateAlerts(s AlertSettings) error {
|
||||
r.mu.Lock()
|
||||
r.data.Alerts = s
|
||||
r.data.UpdatedAt = time.Now().UTC()
|
||||
r.mu.Unlock()
|
||||
return r.save()
|
||||
}
|
||||
|
||||
// IncomingNotifySettings — уведомления депозитарию о входящих бумагах (перевод
|
||||
// НАМ по M2M). Пока — письмо на почту «к вам переведена бумага, требует учёта»;
|
||||
// позже здесь же сформируем пакет для загрузки в учётную систему.
|
||||
type IncomingNotifySettings struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Emails string `json:"emails"` // депозитарий (через запятую)
|
||||
EmailsBack string `json:"emails_back"` // бэк-офис
|
||||
EmailsMiddle string `json:"emails_middle"` // мидл-офис
|
||||
// Маршрутизация: тип уведомления → группы через запятую (depo,back,middle).
|
||||
// Типы: incoming (входящие переводы), orders (сроки депо-поручений), quik (QUIK МНП), system (прочие).
|
||||
Routes map[string]string `json:"routes"`
|
||||
}
|
||||
|
||||
// Группы получателей и типы уведомлений (порядок — для формы).
|
||||
var notifyGroups = []struct{ Key, Title string }{{"depo", "Депозитарий"}, {"back", "Бэк-офис"}, {"middle", "Мидл-офис"}}
|
||||
var notifyKinds = []struct{ Key, Title string }{
|
||||
{"incoming", "Входящие переводы (получен / принят)"},
|
||||
{"orders", "Сроки депо-поручений НРД (MS010 5 мин / 30 дней)"},
|
||||
{"quik", "QUIK МНП (ошибки блокировки / списания / зачисления)"},
|
||||
{"system", "Прочие системные оповещения"},
|
||||
}
|
||||
|
||||
// defaultNotifyRoutes — маршруты по умолчанию, пока оператор не настроил свои.
|
||||
var defaultNotifyRoutes = map[string]string{"incoming": "depo,back", "orders": "depo,back", "quik": "back,middle", "system": "back"}
|
||||
|
||||
// RouteHas — получает ли группа уведомления данного типа.
|
||||
func (s IncomingNotifySettings) RouteHas(kind, group string) bool {
|
||||
r := s.Routes
|
||||
if r == nil {
|
||||
r = defaultNotifyRoutes
|
||||
}
|
||||
for _, g := range strings.Split(r[kind], ",") {
|
||||
if strings.TrimSpace(g) == group {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GroupEmails — адреса группы.
|
||||
func (s IncomingNotifySettings) GroupEmails(group string) string {
|
||||
switch group {
|
||||
case "depo":
|
||||
return s.Emails
|
||||
case "back":
|
||||
return s.EmailsBack
|
||||
case "middle":
|
||||
return s.EmailsMiddle
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Recipients — адреса всех групп, на которые направлен тип уведомления (без дублей).
|
||||
func (s IncomingNotifySettings) Recipients(kind string) string {
|
||||
seen := map[string]bool{}
|
||||
var out []string
|
||||
for _, g := range notifyGroups {
|
||||
if !s.RouteHas(kind, g.Key) {
|
||||
continue
|
||||
}
|
||||
for _, e := range strings.Split(s.GroupEmails(g.Key), ",") {
|
||||
e = strings.TrimSpace(e)
|
||||
if e == "" || seen[strings.ToLower(e)] {
|
||||
continue
|
||||
}
|
||||
seen[strings.ToLower(e)] = true
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
return strings.Join(out, ", ")
|
||||
}
|
||||
|
||||
func (r *RuntimeConfig) UpdateIncoming(s IncomingNotifySettings) error {
|
||||
r.mu.Lock()
|
||||
r.data.Incoming = s
|
||||
r.data.UpdatedAt = time.Now().UTC()
|
||||
r.mu.Unlock()
|
||||
return r.save()
|
||||
}
|
||||
|
||||
func (r *RuntimeConfig) UpdateLK(s LKSettings) error {
|
||||
r.mu.Lock()
|
||||
r.data.LK = s
|
||||
|
||||
Reference in New Issue
Block a user