Версии систем: общий список в «Обзоре» и строка в разделе каждого модуля

bj собирает версии своих компонентов и внешних систем (в фоне, кэш 10 минут, прогрев при старте):
ядро и модули bj (components.json или «-version»), экспортёр ARQA (служба bj-quikexport, /version),
криптосервис (Health), «Валидата CSP» (zpki1utl), Java, ИШ НРД (deps.json + сайт НРД),
МНП QUIK (VersionInfo), DEPO (OD_DATA_VERSION), PostgreSQL, ОС. «Настройка → Обзор» — таблица
«Версии систем» с кнопкой «Обновить сведения»; в разделах БД, Fancy, QUIK МНП, Экспорт QUIK,
Криптография, НРД, ЛК, НПР и Обновления — строка с версией. В таблице обновлений у компонентов,
выложенных вручную, вместо «неизвестна» — версия самого компонента. bj-component-install: -version.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-09-16 12:00:00 +03:00
parent 6fee3c5ff7
commit 7c653a2490
15 changed files with 523 additions and 3 deletions
+62
View File
@@ -0,0 +1,62 @@
package quikexport
// Версия экспортёра ARQA: строка вида 12.1.5.2 внутри программы export. Кандидат сверяется с версией
// установщика из .actions.log (install_quikexport-12.1.5.run), чтобы не принять чужую версию библиотеки.
import (
"bytes"
"os"
"path/filepath"
"regexp"
"sync"
"time"
)
var (
exporterVerRe = regexp.MustCompile(`^\d{1,3}\.\d+\.\d+\.\d+$`)
exporterInstallerRe = regexp.MustCompile(`install_quikexport-(\d+\.\d+\.\d+)\.run`)
)
var exporterVerCache struct {
sync.Mutex
path string
mod time.Time
ver string
}
// exporterVersion — версия ARQA quikexport в каталоге dir ("" — не определена).
func exporterVersion(dir string) string {
bin := filepath.Join(dir, "export")
fi, err := os.Stat(bin)
if err != nil {
return ""
}
exporterVerCache.Lock()
defer exporterVerCache.Unlock()
if exporterVerCache.path == bin && exporterVerCache.mod.Equal(fi.ModTime()) {
return exporterVerCache.ver
}
installer := ""
if log, err := os.ReadFile(filepath.Join(dir, ".actions.log")); err == nil {
if m := exporterInstallerRe.FindAllSubmatch(log, -1); len(m) > 0 {
installer = string(m[len(m)-1][1])
}
}
ver := ""
if b, err := os.ReadFile(bin); err == nil {
for _, tok := range bytes.Split(b, []byte{0}) {
if len(tok) > 20 || !exporterVerRe.Match(tok) {
continue
}
if installer == "" || bytes.HasPrefix(tok, []byte(installer+".")) {
ver = string(tok)
break
}
}
}
if ver == "" {
ver = installer
}
exporterVerCache.path, exporterVerCache.mod, exporterVerCache.ver = bin, fi.ModTime(), ver
return ver
}
@@ -0,0 +1,25 @@
package quikexport
import (
"os"
"path/filepath"
"testing"
)
func TestExporterVersion(t *testing.T) {
dir := t.TempDir()
// В программе встречаются и чужие версии библиотек — берётся та, что совпадает с установщиком.
bin := []byte("\x00ELF\x008.0.4.2\x00codecvt\x0012.1.5.2\x000123456789\x00")
if err := os.WriteFile(filepath.Join(dir, "export"), bin, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, ".actions.log"), []byte("[14/09/26][./install_quikexport-12.1.5.run][root] Done\n"), 0o644); err != nil {
t.Fatal(err)
}
if v := exporterVersion(dir); v != "12.1.5.2" {
t.Fatalf("версия экспортёра = %q", v)
}
if v := exporterVersion(t.TempDir()); v != "" {
t.Fatalf("без экспортёра версия должна быть пустой, получено %q", v)
}
}
+3
View File
@@ -856,6 +856,9 @@ type QuikExportView struct {
ShowRO bool
ServerIP string
Warnings []string
ModuleVersion string // bj-quikexport
ExporterVersion string // ARQA quikexport (\"\" — не определена)
}
var (
+8 -1
View File
@@ -7,7 +7,8 @@ package quikexport
// в заголовке X-BJ-Indicator — цвет индикатора меню (ok | warn | err | пусто);
// - POST /admin/setup/quikexport[/keys|apply|import|service|dbinit] — действие; итог — 204
// с сообщением в X-BJ-Flash, bj-server показывает его как обычное сообщение страницы настроек;
// - GET /admin/setup/quikexport/sql и /preview — файлы как есть.
// - GET /admin/setup/quikexport/sql и /preview — файлы как есть;
// - GET /version — версии модуля и экспортёра ARQA (JSON) для общего списка версий bj.
//
// Лицензию проверяет bj-server и передаёт в X-BJ-Licensed: 1.
@@ -38,6 +39,7 @@ type Config struct {
StorePath string // собственные настройки модуля
SetupPath string // setup.json bj: отсюда настройки переносятся при первом запуске (раздел quik_export)
KeyPath string // служебный ключ модулей
Version string // версия модуля bj-quikexport
}
// Server — служба модуля.
@@ -77,6 +79,7 @@ func (srv *Server) Handler() http.Handler {
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("ok")) })
mux.HandleFunc("/section", func(w http.ResponseWriter, r *http.Request) {
v := quikExportView(srv.store, licensed(r), r.URL.Query().Get("reveal_ro") == "1")
v.ModuleVersion, v.ExporterVersion = srv.cfg.Version, exporterVersion(v.S.Dir)
var buf bytes.Buffer
if err := srv.section.ExecuteTemplate(&buf, "section", v); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -87,6 +90,10 @@ func (srv *Server) Handler() http.Handler {
w.Header().Set("X-BJ-Indicator", indicator(v))
_, _ = w.Write(buf.Bytes())
})
mux.HandleFunc("/version", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"module": srv.cfg.Version, "exporter": exporterVersion(srv.store.Get().Dir)})
})
mux.HandleFunc("/admin/setup/quikexport", srv.saveQuikExport)
mux.HandleFunc("/admin/setup/quikexport/keys", srv.uploadQuikExportKeys)
mux.HandleFunc("/admin/setup/quikexport/apply", srv.applyQuikExport)
+1
View File
@@ -2,6 +2,7 @@
{{$X := .}}{{$S := $X.S}}
<h1>QUIK — Экспорт биржевой информации</h1>
<p class="muted">Модуль ARQA «Экспорт биржевой информации» выгружает данные сервера QUIK (портфели клиентов, позиции, сделки, справочники) в базу PostgreSQL. bj управляет им отсюда: ключи и логин QUIK, где держим базу, какой объём данных выгружаем, состояние службы и место на диске. На этих данных работают модуль «НПР / УДС» и дополнительные проверки M2M. Лицензионный признак <code>quik-export</code>.</p>
<p class="muted" style="font-size:12.5px;margin:-6px 0 12px">Экспортёр QUIK (ARQA quikexport): {{if $X.ExporterVersion}}<strong>{{$X.ExporterVersion}}</strong>{{else}}не определена{{end}} · модуль bj-quikexport{{with $X.ModuleVersion}} <strong>{{.}}</strong>{{end}}</p>
{{range $X.Warnings}}<div class="card" style="padding:8px 14px;margin-bottom:8px;background:color-mix(in srgb,var(--warn) 10%,var(--card));display:flex;gap:8px;align-items:center"><span class="dot warn"></span>{{.}}</div>{{end}}
<div class="card">