Files
zuevav da12a9ec1c 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>
2026-09-15 19:41:12 +03:00

219 lines
7.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}