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:
@@ -0,0 +1,218 @@
|
||||
package lkgateway
|
||||
|
||||
// ish_version.go — определение установленной версии Интеграционного шлюза НРД
|
||||
// (ИШ) и сравнение с версией, доступной на сайте НРД.
|
||||
//
|
||||
// Установленная версия берётся из igate.deps.json (world-readable), доступная —
|
||||
// со страницы дистрибутивов НРД. ВАЖНО: на странице соседствуют сборки под разные
|
||||
// ОС с РАЗНЫМИ номерами (на момент внедрения Windows x86/x64 = 107.0.0.933, а
|
||||
// Astra-Linux/amd64 = 107.0.0.930). Мы работаем на Linux (.deb, amd64), поэтому
|
||||
// сравнивать нужно именно с Linux-сборкой, иначе система ложно покажет «доступно
|
||||
// обновление» на версию, которой под нашу ОС ещё нет.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ishDepsPath — файл манифеста .NET-сборки ИШ; содержит "igate/<версия>".
|
||||
const ishDepsPath = "/opt/nsd/igate/igate/igate.deps.json"
|
||||
|
||||
// ishSiteURL — страница дистрибутивов ПО НРД (там же ИШ).
|
||||
const ishSiteURL = "https://www.nsd.ru/workflow/system/programs/web-service/"
|
||||
|
||||
var (
|
||||
ishVerRe = regexp.MustCompile(`"igate/(\d+(?:\.\d+){2,3})"`)
|
||||
ishAstraRe = regexp.MustCompile(`Astra-Linux[^"]*?(\d{2,3}(?:\.\d+){3})`)
|
||||
ishAnyRe = regexp.MustCompile(`\d{2,3}(?:\.\d+){3}`)
|
||||
)
|
||||
|
||||
// IshVersionInfo — сводка по версиям ИШ для отображения в интерфейсе.
|
||||
type IshVersionInfo struct {
|
||||
Installed string `json:"installed"` // напр. 107.0.930 (из deps.json)
|
||||
SiteLinux string `json:"site_linux,omitempty"` // Astra-Linux сборка на сайте НРД
|
||||
SiteLatest string `json:"site_latest,omitempty"` // самая новая любая ОС (инфо)
|
||||
UpToDate bool `json:"up_to_date"` // installed соответствует Linux-сборке
|
||||
Note string `json:"note,omitempty"` // человекочитаемый комментарий
|
||||
CheckedAt time.Time `json:"checked_at"`
|
||||
}
|
||||
|
||||
// ishInstalledVersion читает версию ИШ из deps.json. Пусто, если ИШ не найден.
|
||||
func ishInstalledVersion() string {
|
||||
b, err := os.ReadFile(ishDepsPath)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
m := ishVerRe.FindSubmatch(b)
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
return string(m[1])
|
||||
}
|
||||
|
||||
// canonVer приводит версию к форме без внутренних нулей: 107.0.0.930 и 107.0.930
|
||||
// дают одинаковый результат (сборочная версия .deb опускает нулевой компонент).
|
||||
func canonVer(v string) string {
|
||||
p := strings.Split(strings.TrimSpace(v), ".")
|
||||
if len(p) < 2 {
|
||||
return v
|
||||
}
|
||||
core := []string{p[0]}
|
||||
for _, x := range p[1 : len(p)-1] {
|
||||
if x != "0" {
|
||||
core = append(core, x)
|
||||
}
|
||||
}
|
||||
core = append(core, p[len(p)-1])
|
||||
return strings.Join(core, ".")
|
||||
}
|
||||
|
||||
var (
|
||||
ishVerMu sync.Mutex
|
||||
ishVerCache *IshVersionInfo
|
||||
ishVerTTL = 6 * time.Hour
|
||||
)
|
||||
|
||||
// ishVersionInfo возвращает сводку по версиям ИШ (с кэшем на 6ч, чтобы не дёргать
|
||||
// сайт НРД на каждый показ статуса). Сетевая часть best-effort: при недоступности
|
||||
// сайта возвращается только установленная версия.
|
||||
func ishVersionInfo(ctx context.Context) IshVersionInfo {
|
||||
ishVerMu.Lock()
|
||||
if ishVerCache != nil && time.Since(ishVerCache.CheckedAt) < ishVerTTL {
|
||||
c := *ishVerCache
|
||||
ishVerMu.Unlock()
|
||||
return c
|
||||
}
|
||||
ishVerMu.Unlock()
|
||||
|
||||
info := IshVersionInfo{
|
||||
Installed: ishInstalledVersion(),
|
||||
CheckedAt: time.Now().UTC(),
|
||||
}
|
||||
linux, latest := ishSiteVersions(ctx)
|
||||
info.SiteLinux, info.SiteLatest = linux, latest
|
||||
|
||||
switch {
|
||||
case info.Installed == "":
|
||||
info.Note = "ИШ не обнаружен (deps.json недоступен)"
|
||||
case linux == "":
|
||||
info.UpToDate = true // не с чем сравнивать — не тревожим
|
||||
info.Note = "версия на сайте НРД недоступна (проверьте вручную)"
|
||||
case canonVer(info.Installed) == canonVer(linux):
|
||||
info.UpToDate = true
|
||||
info.Note = "актуальная версия для Linux (Astra/amd64)"
|
||||
if latest != "" && canonVer(latest) != canonVer(linux) {
|
||||
info.Note += "; для Windows доступна " + latest
|
||||
}
|
||||
default:
|
||||
info.UpToDate = false
|
||||
info.Note = "доступно обновление до " + linux + " (Astra-Linux)"
|
||||
}
|
||||
|
||||
ishVerMu.Lock()
|
||||
ishVerCache = &info
|
||||
ishVerMu.Unlock()
|
||||
return info
|
||||
}
|
||||
|
||||
// ishSiteVersions скачивает страницу НРД и возвращает (Linux-сборка, самая новая
|
||||
// по всем ОС). Обе строки пусты при ошибке сети/парсинга.
|
||||
func ishSiteVersions(ctx context.Context) (linux, latest string) {
|
||||
c := &http.Client{Timeout: 15 * time.Second}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, ishSiteURL, nil)
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
// WAF сайта НРД отвечает 403 на «Mozilla»-UA без заголовка Accept (эвристика
|
||||
// анти-бот). Отправляем полноценный браузерный набор заголовков.
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0")
|
||||
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||
req.Header.Set("Accept-Language", "ru-RU,ru;q=0.9,en;q=0.8")
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", ""
|
||||
}
|
||||
body := make([]byte, 0, 256*1024)
|
||||
buf := make([]byte, 32*1024)
|
||||
for len(body) < 4*1024*1024 { // не читаем больше 4 МБ
|
||||
n, rerr := resp.Body.Read(buf)
|
||||
body = append(body, buf[:n]...)
|
||||
if rerr != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if m := ishAstraRe.FindSubmatch(body); m != nil {
|
||||
linux = string(m[1])
|
||||
}
|
||||
// Самая новая версия по всем сборкам (для информационной строки).
|
||||
var best string
|
||||
for _, v := range ishAnyRe.FindAllString(string(body), -1) {
|
||||
if best == "" || verLess(best, v) {
|
||||
best = v
|
||||
}
|
||||
}
|
||||
latest = best
|
||||
return linux, latest
|
||||
}
|
||||
|
||||
// verLess сравнивает версии покомпонентно как числа: verLess(a,b)=true если a<b.
|
||||
func verLess(a, b string) bool {
|
||||
pa, pb := strings.Split(a, "."), strings.Split(b, ".")
|
||||
for i := 0; i < len(pa) || i < len(pb); i++ {
|
||||
var x, y int
|
||||
if i < len(pa) {
|
||||
x = atoiSafe(pa[i])
|
||||
}
|
||||
if i < len(pb) {
|
||||
y = atoiSafe(pb[i])
|
||||
}
|
||||
if x != y {
|
||||
return x < y
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func atoiSafe(s string) int {
|
||||
n := 0
|
||||
for _, r := range s {
|
||||
if r < '0' || r > '9' {
|
||||
return n
|
||||
}
|
||||
n = n*10 + int(r-'0')
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ishVersionStatus — версия ИШ как отдельная проверка для панели статуса.
|
||||
func ishVersionStatus(ctx context.Context) Status {
|
||||
info := ishVersionInfo(ctx)
|
||||
s := Status{Name: "Версия Интеграционного шлюза", Optional: true}
|
||||
if info.Installed == "" {
|
||||
s.OK = true
|
||||
s.Message = "ИШ не обнаружен на этом сервере"
|
||||
return s
|
||||
}
|
||||
s.OK = info.UpToDate
|
||||
if info.UpToDate {
|
||||
s.Message = "установлена " + info.Installed + " — " + info.Note
|
||||
} else {
|
||||
s.Message = "установлена " + info.Installed + "; " + info.Note
|
||||
}
|
||||
if info.SiteLinux != "" {
|
||||
s.Detail = "на сайте НРД (Linux): " + info.SiteLinux
|
||||
if info.SiteLatest != "" && info.SiteLatest != info.SiteLinux {
|
||||
s.Detail += ", последняя (Windows): " + info.SiteLatest
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
Reference in New Issue
Block a user