diff --git a/deploy/release/component-versions.json b/deploy/release/component-versions.json index 1cac9d0..7e81d34 100644 --- a/deploy/release/component-versions.json +++ b/deploy/release/component-versions.json @@ -20,8 +20,8 @@ "version": "2026.10.21.6" }, "bj-server": { - "fingerprint": "6cf5639b24a45d8c39e1896ab11301d00de8eb4d40f4364be70c31fb2b5bb6df", - "version": "2026.10.21.10" + "fingerprint": "f47475987712791a70a636cfa23a2a8d28f1e1ad898d3ff505c595f828d64a0b", + "version": "2026.10.21.11" }, "crypto-service.jar": { "fingerprint": "2c4fd536bc9b1c20c69a70e8dcd374a61847b263aeb2e474c45debb01fbd8233", diff --git a/internal/lkgateway/nsd_reply_capture.go b/internal/lkgateway/nsd_reply_capture.go new file mode 100644 index 0000000..302658a --- /dev/null +++ b/internal/lkgateway/nsd_reply_capture.go @@ -0,0 +1,82 @@ +package lkgateway + +// nsd_reply_capture.go — «отлов» ответов НРД, которые bj пока не разбирает +// (фаза 1). После отправки анкеты участника (#M2MPF) и других новых документов +// НРД присылает ответ; его тип/схему фиксируем на боевом контуре: сохраняем +// подлинный XML в файл и явно логируем, чтобы потом встроить разбор в систему +// для всех клиентов. + +import ( + "fmt" + "log" + "os" + "path/filepath" + "regexp" + "sort" + "time" + + "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw" + "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdxml" +) + +const nsdRepliesDir = "/var/lib/bj/.bj/nsd-replies" + +var nsdReplyTypeRe = regexp.MustCompile(`[^A-Za-z0-9_.-]+`) +var nsdReplyRootRe = regexp.MustCompile(`<(?:[A-Za-z0-9_]+:)?([A-Za-z0-9_]+)[\s>]`) + +// captureNSDReply сохраняет подлинный XML входящего пакета НРД, который bj пока не +// разбирает, и возвращает путь к файлу и имя корневого элемента. Храним последние +// 100 файлов, чтобы каталог не рос без предела. +func captureNSDReply(p igw.Package, docXML []byte, tag string) (path, root string) { + root = nsdReplyRoot(docXML) + if err := os.MkdirAll(nsdRepliesDir, 0o750); err != nil { + log.Printf("lk-gateway: nsd-replies: mkdir: %v", err) + return "", root + } + name := fmt.Sprintf("%s-%s-id%d-%s.xml", time.Now().Format("20060102-150405"), tag, p.ID, nsdReplySanitize(p.Type)) + path = filepath.Join(nsdRepliesDir, name) + if err := os.WriteFile(path, docXML, 0o640); err != nil { + log.Printf("lk-gateway: nsd-replies: запись %s: %v", name, err) + return "", root + } + nsdRepliesTrim(100) + return path, root +} + +// nsdReplyRoot — имя корневого элемента XML (без префикса), для лога. +func nsdReplyRoot(docXML []byte) string { + utf := string(nsdxml.DecodeWindows1251(docXML)) + if m := nsdReplyRootRe.FindStringSubmatch(utf); m != nil { + return m[1] + } + return "?" +} + +func nsdReplySanitize(t string) string { + t = nsdReplyTypeRe.ReplaceAllString(t, "_") + if t == "" { + return "notype" + } + return t +} + +// nsdRepliesTrim оставляет keep самых свежих файлов (имена начинаются с метки времени). +func nsdRepliesTrim(keep int) { + ents, err := os.ReadDir(nsdRepliesDir) + if err != nil { + return + } + var names []string + for _, e := range ents { + if !e.IsDir() { + names = append(names, e.Name()) + } + } + if len(names) <= keep { + return + } + sort.Strings(names) // по возрастанию метки времени — старые первыми + for _, n := range names[:len(names)-keep] { + _ = os.Remove(filepath.Join(nsdRepliesDir, n)) + } +} diff --git a/internal/lkgateway/nsdpoller.go b/internal/lkgateway/nsdpoller.go index 2604667..a4491c9 100644 --- a/internal/lkgateway/nsdpoller.go +++ b/internal/lkgateway/nsdpoller.go @@ -264,8 +264,17 @@ func (s *Server) applyIncoming(ctx context.Context, p igw.Package) error { log.Printf("lk-gateway: поллер СПРАВОЧНИК M2M получен id=%d: участников %d, мест расчётов %d (состав изменился)", p.ID, len(hb.Participants.Participants), len(hb.SettlementPlaces.Places)) return nil + case strings.Contains(doc, "M2MTransferParticipantForm"): + // Ответ НРД на нашу анкету участника (#M2MPF): подтверждение регистрации. + // Схему ответа фиксируем на боевом контуре — сохраняем подлинный XML, чтобы + // затем встроить разбор в систему (для всех клиентов). + path, root := captureNSDReply(p, unpacked.DocXML, "participant-form") + log.Printf("lk-gateway: поллер ОТВЕТ НРД на анкету участника id=%d (%s): корень <%s>, сохранён %s", p.ID, p.Type, root, path) + return nil default: - log.Printf("lk-gateway: поллер пакет id=%d (%s) — неизвестный M2M-документ, пропуск", p.ID, p.Type) + // Неизвестный документ НРД — тоже «отлавливаем»: сохраняем подлинный XML для разбора. + path, root := captureNSDReply(p, unpacked.DocXML, "unknown") + log.Printf("lk-gateway: поллер пакет id=%d (%s) — неизвестный M2M-документ <%s>, сохранён %s", p.ID, p.Type, root, path) return nil } }