7ce4c2c79f
Реализация варианта B из аудита защиты знаний. Прямой garble всего bj-server ломает html/template (он обращается к полям структур по имени, garble их переименовывает). Решение — селективная обфускация: GOGARBLE обфусцирует ТОЛЬКО пакет с секретами, шаблоны и обработчики не затрагиваются. 95 SQL-запросов к DEPO/SPECTRE/QUIK и трансферным таблицам FTV_M2M вынесены из lkgateway в отдельный пакет internal/lkgateway/fsql как переменные (именно var, не const: const компилятор вставил бы обратно в необфусцируемый пакет, и шифрование бы не сработало — проверено). Сами обработчики, типы и параметры sql.Named остались на месте; пакет держит только строки запросов. build-release.sh собирает bj-server с `GOGARBLE=.../fsql garble -literals` (нужен отдельный SDK go1.26 — модуль-кэшный тулчейн garble не подходит); если garble/go1.26 не найдены — обычная сборка с предупреждением. Проверено на реальном garble-бинаре (запускается, /admin и страницы на структурах работают): рецепт чтения чужих систем из бинаря исчез — OD_RESTS 13→0, OD_WIRING 5→0, OD_TURNS 1→0, OD_BALANCES 3→0, D_B_CONTRACTS 1→0, OD_CLIENTS_PORTS 10→0, DepoLimits/MoneyLimits →0. Остаток FTV_M2M — упоминания имён таблиц в операторских embed-шаблонах и логах, не сам рецепт. Стенд собран обычной сборкой (без garble) — трассировки остаются читаемыми. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
127 lines
4.3 KiB
Go
127 lines
4.3 KiB
Go
package lkgateway
|
||
|
||
// lkfx.go — курсы валют к рублю для оценки валютных выпусков.
|
||
//
|
||
// Номинал «замещающих» и еврооблигаций в экспорте QUIK задан в валюте (FaceUnit = USD, EUR,
|
||
// CNY), а цена — в процентах от него. Чтобы показать стоимость в рублях, нужен курс. В базе
|
||
// экспорта курсов нет (валютный рынок не выгружается), поэтому берём официальный курс из
|
||
// учётной системы: OD_VALUES_RATES с MARKET = 0 — сначала DEPO, затем SPECTRE, и берём более
|
||
// свежий. Дата курса показывается клиенту: курс может отставать, и выдавать оценку за
|
||
// сегодняшнюю нельзя.
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway/fsql"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// LKFxRate — курс валюты к рублю и дата, на которую он установлен.
|
||
type LKFxRate struct {
|
||
Rate float64
|
||
At time.Time
|
||
}
|
||
|
||
var fxCache = struct {
|
||
mu sync.Mutex
|
||
at time.Time
|
||
m map[string]LKFxRate
|
||
}{}
|
||
|
||
// lkFxRates — курсы валют для оценки (ключ — код валюты в верхнем регистре).
|
||
// Источник по умолчанию — Банк России (см. cbrfx.go); если интернета нет или
|
||
// в настройках выбрана учётная система, берём OD_VALUES_RATES, а недостающие
|
||
// валюты дополняем последним известным курсом из lk.fx_rates.
|
||
func lkFxRates(ctx context.Context, rc *RuntimeConfig) map[string]LKFxRate {
|
||
set := rc.Snapshot()
|
||
out := map[string]LKFxRate{}
|
||
if !strings.EqualFold(strings.TrimSpace(set.LK.FXSource), "accounting") {
|
||
if cbr, cerr := cbrToday(ctx, rc); len(cbr) > 0 {
|
||
return cbr
|
||
} else if cerr != "" {
|
||
// молча уходим на запасные источники: кабинет не должен падать
|
||
// из-за недоступного сайта ЦБ
|
||
_ = cerr
|
||
}
|
||
}
|
||
for code, r := range lkFxAccounting(ctx, set.Fancy) {
|
||
out[code] = r
|
||
}
|
||
for code, r := range lkFxLoad(ctx, rc, time.Time{}) {
|
||
if old, ok := out[code]; !ok || r.At.After(old.At) {
|
||
out[code] = r
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// lkFxAccounting — курсы из учётной системы (OD_VALUES_RATES, MARKET = 0), кэш на час.
|
||
func lkFxAccounting(ctx context.Context, s FancyMSSQLSettings) map[string]LKFxRate {
|
||
fxCache.mu.Lock()
|
||
if fxCache.m != nil && time.Since(fxCache.at) < time.Hour {
|
||
m := fxCache.m
|
||
fxCache.mu.Unlock()
|
||
return m
|
||
}
|
||
fxCache.mu.Unlock()
|
||
out := map[string]LKFxRate{}
|
||
if strings.TrimSpace(s.Server) == "" {
|
||
return out
|
||
}
|
||
for _, db := range []string{s.DepoDB(), s.SpectreDB()} {
|
||
conn, err := fancyOpen(s, db)
|
||
if err != nil {
|
||
continue
|
||
}
|
||
cctx, cancel := context.WithTimeout(ctx, 15*time.Second)
|
||
rows, err := conn.QueryContext(cctx, fsql.Fx01)
|
||
if err == nil {
|
||
for rows.Next() {
|
||
var code string
|
||
var at sql.NullTime
|
||
var rate float64
|
||
if rows.Scan(&code, &at, &rate) != nil || rate <= 0 {
|
||
continue
|
||
}
|
||
cur := LKFxRate{Rate: rate}
|
||
if at.Valid {
|
||
cur.At = at.Time
|
||
}
|
||
// из двух баз берём курс посвежее
|
||
if old, ok := out[code]; !ok || cur.At.After(old.At) {
|
||
out[code] = cur
|
||
}
|
||
}
|
||
rows.Close()
|
||
}
|
||
cancel()
|
||
conn.Close()
|
||
}
|
||
fxCache.mu.Lock()
|
||
fxCache.m, fxCache.at = out, time.Now()
|
||
fxCache.mu.Unlock()
|
||
return out
|
||
}
|
||
|
||
// lkFxFor — курс валюты к рублю: рубль — единица, неизвестная валюта — 0 (оценивать нельзя).
|
||
func lkFxFor(rates map[string]LKFxRate, cur string) LKFxRate {
|
||
c := strings.ToUpper(strings.TrimSpace(cur))
|
||
switch c {
|
||
case "", "SUR", "RUB", "RUR":
|
||
return LKFxRate{Rate: 1}
|
||
}
|
||
return rates[c]
|
||
}
|
||
|
||
// lkFxReset — сбросить кэши курсов (после смены источника или ручного обновления).
|
||
func lkFxReset() {
|
||
fxCache.mu.Lock()
|
||
fxCache.m, fxCache.at = nil, time.Time{}
|
||
fxCache.mu.Unlock()
|
||
cbrCache.mu.Lock()
|
||
cbrCache.at, cbrCache.err = time.Time{}, ""
|
||
cbrCache.mu.Unlock()
|
||
}
|