Files
Bridge-and-Join-s/internal/lkgateway/nsd_reply_capture.go
T
fontvielle eb5f08dfa6 Поллер: отлов ответов НРД (анкета участника и неизвестные документы)
После отправки анкеты участника (#M2MPF) НРД присылает ответ, схему которого мы
ещё не разбираем. Фаза 1 — отловить его на боевом контуре:
- входящий с корнем M2MTransferParticipantForm сохраняется как подлинный XML и
  логируется отдельно (ответ НРД на анкету);
- любой неизвестный M2M-документ в default теперь тоже сохраняется (раньше молча
  пропускался), чтобы увидеть реальный тип/схему.
Файлы: /var/lib/bj/.bj/nsd-replies/<время>-<tag>-id<N>-<тип>.xml (последние 100).
Разбор ответа встроим в систему, когда увидим реальный документ на бою.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
2026-10-21 17:28:13 +03:00

83 lines
2.9 KiB
Go

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))
}
}