Files
Bridge-and-Join-s/internal/lkgateway/fancygate.go
fontvielle 50e420f4a0 DEPO на Firebird в мастере: подготовка базы одним действием, как для MSSQL
Мастер, шаг «Учёт DEPO», и «Обмен с DEPO» в настройках: СУБД «Firebird» (Fancy на Firebird 2.5).
- Подключение: драйвер nakagami/firebirdsql, путь к файлу базы или псевдоним, WIN1251. Запросы bj к DEPO
  (T-SQL) подключение переводит само (fancyfb.go): механически (dbo., TOP, ISNULL, GETDATE, LEN, N, exec)
  вне строк и комментариев, точные тексты — fsql/fb.go (каталоги, LIKE [_], COUNT(*) OVER, SELECT без FROM,
  PATINDEX); @имена — позиционные ?, GUID — в верхнем регистре. Системные запросы из кода — в fsql/depocatalog.go.
- «Подготовить» логином SYSDBA (depo_setup_fb.go): проверка базы и UDF Fancy, логин SYSDBA_BJ (Fancy даёт права
  на документы логинам SYSDBA%; для Firebird — обычный пользователь), оператор-робот Fancy, кодировщик M2M,
  привязки FTV_M2M_SETTINGS по признакам Fancy, таблицы, процедуры, права; обработку запускает bj.
  «Получить скрипт» — то же для isql в WIN1251; письмо администратору, установка процедур, плашка обновления.
- Скрипты Firebird встроены (depoprocs/firebird, тест сверяет с deploy/); количества — DOUBLE PRECISION: из
  диалекта 3 в базу диалекта 1 процедуру с NUMERIC(18,7) не создать.
- Проверено на копии базы Fancy (отдельный экземпляр bj): мастер с нуля — все проверки, BJ_PING; тестовый режим
  DEPO — К1 Бубликов, зачисление и списание до исполнения без ошибок; очередь, справочник НРД.
  Тест FBLive готовит на базе все запросы bj к DEPO: 94 из 101, оставшиеся 7 — ЛК и ручной ввод (следующий этап).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
2026-10-27 18:55:02 +03:00

129 lines
5.6 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
// Защита логина DEPO от блокировки (06.10.2026). На SQL Server логины с CHECK_POLICY подчиняются политике
// блокировки Windows: несколько неудачных входов подряд — и логин заблокирован для всех, кто им пользуется.
// Службы bj ходят в DEPO каждые несколько секунд; с неверным паролем (сменили на сервере, опечатка в
// настройке, второй bj с тем же логином) они за минуты блокировали логин — так 06.10 тестовая ВМ с логином
// bj_user остановила обмен боевого bj. Поэтому после неудачного входа bj с этими логином и паролем больше
// не пробует depoLoginPause — кроме входа, который человек запустил сам (проверка в мастере, кнопки
// настройки): для него depoLoginForce. Новый пароль — новая попытка сразу.
import (
"context"
"crypto/sha256"
"database/sql"
"database/sql/driver"
"encoding/hex"
"errors"
"fmt"
"strconv"
"sync"
"time"
"github.com/jackc/pgx/v5/pgconn"
mssql "github.com/microsoft/go-mssqldb"
)
// depoLoginPause — сколько не повторять вход после неудачи (политика блокировки считает попытки за окно;
// раз в 15 минут — заведомо реже порога).
const depoLoginPause = 15 * time.Minute
type depoLoginFail struct {
at time.Time
err string
}
var depoLoginFails = struct {
sync.Mutex
m map[string]depoLoginFail
}{m: map[string]depoLoginFail{}}
type depoLoginForceKey struct{}
// depoLoginForce — вход по действию человека: выполняется и во время паузы после неудачи.
func depoLoginForce(ctx context.Context) context.Context {
return context.WithValue(ctx, depoLoginForceKey{}, true)
}
// depoLoginKey — сервер, логин и отпечаток пароля (сам пароль в памяти ключом не держим).
func depoLoginKey(s FancyMSSQLSettings) string {
h := sha256.Sum256([]byte(s.Password))
return fancyDriver(s) + "|" + s.Server + "|" + strconv.Itoa(s.Port) + "|" + s.User + "|" + hex.EncodeToString(h[:8])
}
// depoIsLoginFailure — сервер отверг логин или пароль (то, что считает политика блокировки).
func depoIsLoginFailure(err error) bool {
var me mssql.Error
if errors.As(err, &me) {
switch me.Number {
case 18456, 18486, 18487, 18488: // неверный логин/пароль, заблокирован, пароль истёк, нужно сменить
return true
}
}
var pe *pgconn.PgError
if errors.As(err, &pe) {
return pe.Code == "28P01" || pe.Code == "28000"
}
return fbIsLoginFailure(err)
}
// fancyConnector — подключение к DEPO через паузу после неудачного входа.
type fancyConnector struct {
base driver.Connector
key string
user string
srv string
}
func (c *fancyConnector) Driver() driver.Driver { return c.base.Driver() }
func (c *fancyConnector) Connect(ctx context.Context) (driver.Conn, error) {
force, _ := ctx.Value(depoLoginForceKey{}).(bool)
depoLoginFails.Lock()
f, failed := depoLoginFails.m[c.key]
depoLoginFails.Unlock()
if failed && !force && time.Since(f.at) < depoLoginPause {
return nil, fmt.Errorf("вход в DEPO логином %s не удался в %s (%s) — bj не повторяет его до %s, чтобы неудачные попытки не заблокировали логин на сервере. Проверьте логин и пароль: Настройка → «Учёт DEPO»",
c.user, f.at.Format("15:04"), f.err, f.at.Add(depoLoginPause).Format("15:04"))
}
conn, err := c.base.Connect(ctx)
switch {
case err != nil && depoIsLoginFailure(err):
depoLoginFails.Lock()
depoLoginFails.m[c.key] = depoLoginFail{at: time.Now(), err: quikErrorDetail(err)}
depoLoginFails.Unlock()
if !failed && !force {
raiseSystemAlert(globalRC, "DEPO: вход логином "+c.user+" не удался",
"Сервер "+c.srv+" отверг логин или пароль bj. Чтобы неудачные попытки не заблокировали логин, bj не повторяет вход "+
strconv.Itoa(int(depoLoginPause/time.Minute))+" минут. Проверьте логин и пароль в настройке «Учёт DEPO».\n\n"+quikErrorDetail(err))
}
case err == nil && failed:
depoLoginFails.Lock()
delete(depoLoginFails.m, c.key)
depoLoginFails.Unlock()
}
return conn, err
}
// fancyOpenGated — пул подключений к DEPO с паузой после неудачного входа.
func fancyOpenGated(s FancyMSSQLSettings, dsn string) (*sql.DB, error) {
probe, err := sql.Open(fancyDriver(s), dsn) // без подключения: только чтобы взять драйвер
if err != nil {
return nil, err
}
drv := probe.Driver()
_ = probe.Close()
dc, ok := drv.(driver.DriverContext)
if !ok {
return sql.Open(fancyDriver(s), dsn)
}
base, err := dc.OpenConnector(dsn)
if err != nil {
return nil, err
}
if s.IsFirebird() { // запросы bj (T-SQL) — в диалект Firebird
base = fbConnector{base: base}
}
return sql.OpenDB(&fancyConnector{base: base, key: depoLoginKey(s), user: s.User, srv: s.Server}), nil
}