Мониторинг связи с НРД: участки Шлюз/НРД/M2M/Отправка, сквозная проверка справочником раз в 10 мин, история за сутки на «Статусе», цепочка в шапке (30 с), автоповтор недоставленных до 24 ч, баннер и статус «ожидает отправки» в кабинете клиента

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-09-25 16:17:25 +03:00
parent a0f6ce9f7b
commit 4cbefa41d5
18 changed files with 1075 additions and 62 deletions
+29 -6
View File
@@ -253,13 +253,17 @@ func (s *Server) applyIncoming(ctx context.Context, p igw.Package) error {
log.Printf("lk-gateway: поллер справочник M2M id=%d: разбор: %v", p.ID, err)
return nil
}
saveHandbook(&hb)
nsdProbeReply(time.Now())
if !saveHandbook(&hb) {
log.Printf("lk-gateway: поллер справочник M2M id=%d: без изменений (%d участников) — связь с НРД подтверждена", p.ID, len(hb.Participants.Participants))
return nil
}
go func() {
if err := depoWriteHandbook(s.rc); err != nil {
log.Printf("depo-events: справочник после получения: %v", err)
}
}()
log.Printf("lk-gateway: поллер СПРАВОЧНИК M2M получен id=%d: участников %d, мест расчётов %d",
log.Printf("lk-gateway: поллер СПРАВОЧНИК M2M получен id=%d: участников %d, мест расчётов %d (состав изменился)",
p.ID, len(hb.Participants.Participants), len(hb.SettlementPlaces.Places))
return nil
default:
@@ -300,8 +304,10 @@ type HandbookEntry struct {
EngName string `json:"eng_name,omitempty"`
}
// saveHandbook раскладывает справочник в плоский список и пишет в файл.
func saveHandbook(hb *m2m.M2MTransferHandbook) {
// saveHandbook раскладывает справочник в плоский список и пишет в файл. Возвращает, изменился ли
// состав: справочник запрашивается раз в 10 минут (сквозная проверка связи, nsdhealth.go), и без
// изменений его не нужно ни рассылать, ни переписывать в DEPO.
func saveHandbook(hb *m2m.M2MTransferHandbook) (changed bool) {
out := struct {
At string `json:"at"`
Participants []HandbookEntry `json:"participants"`
@@ -324,13 +330,30 @@ func saveHandbook(hb *m2m.M2MTransferHandbook) {
e.Code, e.Kind = string(p.BrokerPlace.ParticipantCode), "broker"
}
out.Participants = append(out.Participants, e)
log.Printf("lk-gateway: справочник M2M: ИНН %s → %s [%s] %s", e.INN, e.Code, e.Kind, e.Name)
}
for _, pl := range hb.SettlementPlaces.Places {
out.Places = append(out.Places, HandbookEntry{INN: string(pl.INN), Name: pl.ShortName, DisplayName: pl.DisplayName, Kind: "place"})
}
changed = true
if old, err := os.ReadFile(handbookFile); err == nil {
var prev struct {
Participants []HandbookEntry `json:"participants"`
Places []HandbookEntry `json:"settlement_places"`
}
if json.Unmarshal(old, &prev) == nil {
a, _ := json.Marshal([]any{prev.Participants, prev.Places})
b, _ := json.Marshal([]any{out.Participants, out.Places})
changed = string(a) != string(b)
}
}
if raw, err := json.Marshal(out); err == nil {
_ = os.WriteFile(handbookFile, raw, 0o600)
pushHandbookAsync(globalRC, raw) // свежий справочник — на сервер лицензий для всех клиентов
if changed {
for _, e := range out.Participants {
log.Printf("lk-gateway: справочник M2M: ИНН %s → %s [%s] %s", e.INN, e.Code, e.Kind, e.Name)
}
pushHandbookAsync(globalRC, raw) // свежий справочник — на сервер лицензий для всех клиентов
}
}
return changed
}