Учёт DEPO: имя базы обязательно, bj базу по серверу не ищет

Имя базы DEPO задаёт клиент — скрипт и подготовка логином администратора
работают только с ней. Автопоиск по серверу убран (экран, скрипт, подготовка,
проверка подключения, старый экран conn), выбор из нескольких найденных баз —
тоже. Вместо поиска — защита от опечатки: скрипт (SQL Server и PostgreSQL),
подготовка и проверка подключения сначала убеждаются, что в названной базе
есть учётные таблицы OD_FACES и OD_RESTS, иначе останавливаются, ничего не
меняя.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-10-06 15:18:15 +03:00
parent 237203f29a
commit 35f70a1b49
8 changed files with 118 additions and 219 deletions
+33 -32
View File
@@ -2,8 +2,8 @@ package lkgateway
// Подготовка базы учёта DEPO одним действием (06.10.2026). Раньше это были три экрана мастера — логин bj,
// таблицы обмена, задание SQL Agent — и три скрипта. Теперь один скрипт делает всё:
// - находит базу DEPO на сервере сам (по учётным таблицам OD_FACES и OD_RESTS) — имя базы клиенту знать
// не нужно (SQL Server; для PostgreSQL базу назвать придётся: psql не умеет искать по чужим базам);
// - работает с базой, которую назвал клиент (сам по серверу не ищет), и сначала проверяет, что в ней есть
// учётные таблицы DEPO OD_FACES и OD_RESTS — опечатка в имени не уведёт в чужую базу;
// - создаёт логин bj (пароль придумывает bj и запоминает) или обновляет его пароль;
// - создаёт таблицы обмена FTV_M2M* и выдаёт логину bj права: чтение учёта, запись в таблицы обмена;
// - по галочке «обработку запускает SQL Server Agent» — создаёт задание bj2depo, иначе обработку
@@ -50,8 +50,8 @@ func depoGenPassword() string {
// sqlStr — строковый литерал T-SQL / PostgreSQL (одинарная кавычка удваивается).
func sqlStr(s string) string { return strings.ReplaceAll(s, "'", "''") }
// depoSetupScript — один скрипт подготовки базы DEPO. db — имя базы (пусто — найти на сервере; только SQL
// Server), agentJob — создать задание SQL Server Agent (иначе обработку вызывает bj).
// depoSetupScript — один скрипт подготовки базы DEPO. db — имя базы DEPO (задаёт клиент), agentJob — создать
// задание SQL Server Agent (иначе обработку вызывает bj).
func depoSetupScript(f FancyMSSQLSettings, db, login, pass string, agentJob bool) string {
if f.IsPostgres() {
return depoSetupScriptPG(f, db, login, pass)
@@ -65,13 +65,13 @@ func depoSetupScript(f FancyMSSQLSettings, db, login, pass string, agentJob bool
SET NOCOUNT ON;
-- ===== Проверьте перед запуском ==========================================
-- База DEPO: пусто — скрипт найдёт её сам по таблицам OD_FACES и OD_RESTS.
-- База DEPO — точное имя базы учёта на этом сервере.
DECLARE @db sysname = N'%[3]s';
-- Пароль логина bj (%[1]s). Меняете — впишите тот же пароль в bj, шаг «Учёт DEPO».
DECLARE @pass nvarchar(128) = N'%[4]s';
-- =========================================================================
-- Что делает: находит базу DEPO, создаёт логин bj и таблицы обмена FTV_M2M*, выдаёт права
-- Что делает: проверяет, что @db — база DEPO, создаёт логин bj и таблицы обмена FTV_M2M*, выдаёт права
-- (чтение учёта, запись только в таблицы обмена). Обработка обмена: %[2]s.
-- Учётные данные DEPO не меняет. Запускать в SSMS или sqlcmd под логином с правами sysadmin.
-- Можно запускать повторно. В скрипте пароль логина bj — после выполнения файл удалите.
@@ -81,18 +81,19 @@ BEGIN
RAISERROR(N'Впишите пароль логина bj в @pass в начале скрипта и запустите снова.', 16, 1);
RETURN;
END
DECLARE @found int;
IF @db = N''
BEGIN
SELECT @found = COUNT(*) FROM sys.databases WHERE state = 0 AND database_id > 4
AND OBJECT_ID(QUOTENAME(name) + N'.dbo.OD_FACES') IS NOT NULL AND OBJECT_ID(QUOTENAME(name) + N'.dbo.OD_RESTS') IS NOT NULL;
IF @found = 1
SELECT @db = name FROM sys.databases WHERE state = 0 AND database_id > 4
AND OBJECT_ID(QUOTENAME(name) + N'.dbo.OD_FACES') IS NOT NULL AND OBJECT_ID(QUOTENAME(name) + N'.dbo.OD_RESTS') IS NOT NULL;
RAISERROR(N'Впишите имя базы DEPO в @db в начале скрипта и запустите снова.', 16, 1);
RETURN;
END
IF @db = N'' OR DB_ID(@db) IS NULL
IF DB_ID(@db) IS NULL
BEGIN
RAISERROR(N'База DEPO не найдена или на сервере их несколько. Впишите имя базы DEPO в @db в начале скрипта и запустите снова.', 16, 1);
RAISERROR(N'Базы %%s на этом сервере нет. Проверьте имя в @db в начале скрипта.', 16, 1, @db);
RETURN;
END
IF OBJECT_ID(QUOTENAME(@db) + N'.dbo.OD_FACES', N'U') IS NULL OR OBJECT_ID(QUOTENAME(@db) + N'.dbo.OD_RESTS', N'U') IS NULL
BEGIN
RAISERROR(N'В базе %%s нет учётных таблиц DEPO (OD_FACES, OD_RESTS) — это не база DEPO. Проверьте имя в @db.', 16, 1, @db);
RETURN;
END
PRINT N'База DEPO: ' + @db;
@@ -162,6 +163,12 @@ END $$;
GRANT CONNECT ON DATABASE "%[1]s" TO "%[2]s";
\c "%[1]s"
-- это база DEPO? (учётные таблицы OD_FACES и OD_RESTS) — иначе остановиться, ничего не создавая
DO $$ BEGIN
IF to_regclass('od_faces') IS NULL OR to_regclass('od_rests') IS NULL THEN
RAISE EXCEPTION 'В базе %% нет учётных таблиц DEPO (OD_FACES, OD_RESTS) — проверьте имя базы', current_database();
END IF;
END $$;
`, db, login, sqlStr(pass))
for _, s := range depoTablesDDL(true) {
b.WriteString(s + ";\n")
@@ -182,37 +189,31 @@ func depoSetupFileName(f FancyMSSQLSettings) string {
return "bj-depo-setup-mssql.sql"
}
// depoSetupRun — «bj сделает сам»: тот же скрипт выполняется логином администратора (не сохраняется).
// Возвращает найденную базу DEPO.
func depoSetupRun(ctx context.Context, f FancyMSSQLSettings, db, adminUser, adminPass, login, pass string, agentJob bool) (string, error) {
// depoSetupRun — «bj сделает сам»: тот же скрипт выполняется логином администратора (не сохраняется) в базе
// db, которую назвал клиент; сначала — проверка, что это база DEPO.
func depoSetupRun(ctx context.Context, f FancyMSSQLSettings, db, adminUser, adminPass, login, pass string, agentJob bool) error {
adm := f
adm.User, adm.Password = strings.TrimSpace(adminUser), adminPass
if db == "" {
found, err := depoDiscover(ctx, adm)
switch {
case err != nil:
return "", fmt.Errorf("список баз сервера логином %s: %s", adm.User, quikErrorDetail(err))
case len(found) == 0:
return "", fmt.Errorf("на сервере нет базы учёта DEPO (с таблицами %s), доступной логину %s", strings.Join(depoMarkerTables, " и "), adm.User)
case len(found) > 1:
return "", fmt.Errorf("на сервере несколько баз DEPO (%s) — укажите нужную в поле «База DEPO»", strings.Join(found, ", "))
}
db = found[0]
if !validDBName(db) {
return fmt.Errorf("укажите имя базы DEPO")
}
if err := depoCheckIsDepo(ctx, adm, db); err != nil {
return err
}
if f.IsPostgres() {
return db, depoSetupRunPG(ctx, adm, db, login, pass)
return depoSetupRunPG(ctx, adm, db, login, pass)
}
conn, err := fancyOpen(adm, "")
if err != nil {
return db, err
return err
}
defer conn.Close()
cctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()
if _, err := conn.ExecContext(cctx, depoSetupScript(f, db, login, pass, agentJob)); err != nil {
return db, fmt.Errorf("подготовка базы %s логином %s: %s%s", db, adm.User, quikErrorDetail(err), depoTablesRightsHint(f, adm.User, err))
return fmt.Errorf("подготовка базы %s логином %s: %s%s", db, adm.User, quikErrorDetail(err), depoTablesRightsHint(f, adm.User, err))
}
return db, nil
return nil
}
func depoSetupRunPG(ctx context.Context, adm FancyMSSQLSettings, db, login, pass string) error {