Files
Bridge-and-Join-s/internal/lkgateway/publicip.go
T
zuevav a7cfbb2e74 Мастер, QUIK: короткое письмо в ARQA с внешним адресом, памятка администратору, понятный выбор работы с QUIK
- Письмо в ARQA: организация просит подключить модуль неторговых поручений,
  выдать логин и пароль и разрешить подключение с нашего внешнего адреса —
  о bj и внутреннем устройстве ни слова. Внешний адрес bj узнаёт у сервера
  лицензий (GET /v1/ip, раз в час); пометка — убедиться, что адрес постоянный.
- Под письмом — «Памятка администратору QUIK» (классы, логин InstrEu, справочник,
  перезапуск модуля) со ссылкой на подробную инструкцию.
- Вместо свёрнутого «Автоматический цикл M2M — выключен · настроить» и справочника
  под раскрытием: после проверки подключения — «Как bj работает с QUIK»: «Только
  сверка» или «Автоматически» (+ торговый счёт), сохраняется сразу; справочник —
  строкой состояния с напоминанием о перезапуске. До проверки — одна строка.
  Тонкая настройка — в «Настройка → QUIK», мастер её не трогает.
- Проверка МНП: ошибка сертификата (x509, «doesn't contain any IP SANs») —
  подсказка включить «Доверять сертификату сервера», «Дополнительно» раскрывается.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
2026-10-06 16:59:21 +03:00

75 lines
2.7 KiB
Go

package lkgateway
// Внешний адрес сервера bj (06.10.2026) — каким его видят снаружи. Нужен для письма в ARQA: поставщика просят
// разрешить подключение к модулю неторговых поручений с нашего адреса, и внутренний адрес (10.x, 192.168.x)
// ему ничего не даёт. Изнутри сети внешний адрес не узнать, поэтому спрашиваем наш же сервер лицензий
// (GET /v1/ip — он видит, откуда пришёл запрос); сторонние «узнать мой IP» для сервера финансовой
// организации не годятся. Раз в час, в фоне: письмо строится при показе страницы и сеть не ждёт.
import (
"context"
"io"
"log"
"net"
"net/http"
"strings"
"sync"
"time"
)
var publicIPState struct {
sync.Mutex
ip string
at time.Time
}
// publicIP — последний известный внешний адрес (пусто — сервер лицензий недоступен, например закрытый контур).
func publicIP() string {
publicIPState.Lock()
defer publicIPState.Unlock()
if time.Since(publicIPState.at) > 24*time.Hour {
return ""
}
return publicIPState.ip
}
// publicIPRefresh — спросить внешний адрес у сервера лицензий.
func publicIPRefresh(ctx context.Context) error {
cctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(cctx, http.MethodGet, licenseServer+"/v1/ip", nil)
if err != nil {
return err
}
resp, err := licenseHTTP.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
b, _ := io.ReadAll(io.LimitReader(resp.Body, 64))
ip := net.ParseIP(strings.TrimSpace(string(b)))
if resp.StatusCode != http.StatusOK || ip == nil {
return &net.AddrError{Err: "сервер лицензий не сообщил внешний адрес", Addr: resp.Status}
}
publicIPState.Lock()
changed := publicIPState.ip != ip.String()
publicIPState.ip, publicIPState.at = ip.String(), time.Now()
publicIPState.Unlock()
if changed {
log.Printf("lk-gateway: внешний адрес сервера (по серверу лицензий): %s", ip)
}
return nil
}
// publicIPLoop — внешний адрес при запуске и раз в час (адрес у провайдера может смениться).
func publicIPLoop(ctx context.Context) {
for {
_ = publicIPRefresh(ctx)
select {
case <-ctx.Done():
return
case <-time.After(time.Hour):
}
}
}