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:
zuevav
2026-09-15 19:41:12 +03:00
parent 59fc774f08
commit da12a9ec1c
132 changed files with 29969 additions and 1089 deletions
+60 -2
View File
@@ -5,6 +5,8 @@ import (
"errors"
"net"
"net/http"
neturl "net/url"
"strings"
"github.com/jackc/pgx/v5/pgxpool"
"os"
@@ -55,6 +57,7 @@ func CheckAll(ctx context.Context, o CheckOptions) SystemStatus {
out.Checks = append(out.Checks, checkPostgres(ctx, o))
out.Checks = append(out.Checks, checkCryptoSocket(o))
out.Checks = append(out.Checks, checkNSDAdapter(ctx, o))
out.Checks = append(out.Checks, ishVersionStatus(ctx))
out.Checks = append(out.Checks, checkLKCallback(ctx, o))
return out
@@ -156,6 +159,14 @@ func checkLKCallback(ctx context.Context, o CheckOptions) Status {
}
func httpHealth(ctx context.Context, url string, timeout time.Duration, s Status) Status {
// Подключаемся по реальному url (для локальных сервисов — loopback), но в
// Detail показываем адрес/имя сервера, а не «localhost» — так оператору
// понятно, на каком узле работает компонент. Для loopback добавляем пометку,
// что порт не открыт наружу (это подпись, а не кликабельная ссылка).
disp := displayURL(url)
if isLoopbackURL(url) {
disp += " (только локально — доступ с сервера или через SSH-туннель, порт не открыт наружу)"
}
c := &http.Client{Timeout: timeout}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
@@ -175,14 +186,61 @@ func httpHealth(ctx context.Context, url string, timeout time.Duration, s Status
if resp.StatusCode != http.StatusOK {
s.OK = false
s.Message = "HTTP " + http.StatusText(resp.StatusCode)
s.Detail = url
s.Detail = disp
return s
}
s.OK = true
s.Message = "OK"
s.Detail = url
s.Detail = disp
return s
}
// displayHost возвращает отображаемое имя/адрес этого сервера: значение
// BJ_PUBLIC_HOST (если задано, напр. DNS-имя) либо системный hostname.
func displayHost() string {
if h := strings.TrimSpace(os.Getenv("BJ_PUBLIC_HOST")); h != "" {
return h
}
if h, err := os.Hostname(); err == nil && h != "" {
return h
}
return "server"
}
// isLoopbackURL сообщает, указывает ли url на loopback-хост (сервис слушает
// только на этом сервере и недоступен извне).
func isLoopbackURL(raw string) bool {
u, err := neturl.Parse(raw)
if err != nil {
return false
}
switch u.Hostname() {
case "localhost", "127.0.0.1", "0.0.0.0", "::1":
return true
}
return false
}
// displayURL заменяет loopback-хост (localhost/127.0.0.1/0.0.0.0/[::1]) в url на
// displayHost(). Внешние адреса не трогает. Порт и путь сохраняются.
func displayURL(raw string) string {
u, err := neturl.Parse(raw)
if err != nil || u.Host == "" {
return raw
}
host := u.Hostname()
switch host {
case "localhost", "127.0.0.1", "0.0.0.0", "::1":
newHost := displayHost()
if p := u.Port(); p != "" {
u.Host = net.JoinHostPort(newHost, p)
} else {
u.Host = newHost
}
return u.String()
}
return raw
}
// ErrUnknown — общий placeholder.
var ErrUnknown = errors.New("lkgateway: unknown error")