From eef6c9d996cb3b7178332a01007813c514fb6c5e Mon Sep 17 00:00:00 2001 From: fontvielle Date: Tue, 20 Oct 2026 14:12:54 +0300 Subject: [PATCH] =?UTF-8?q?DEPO:=20=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D0=B4?= =?UTF-8?q?=D1=83=D1=80=D1=8B=20=D1=81=D1=82=D0=B0=D0=B2=D1=8F=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D0=B2=20=D0=BC=D0=B0=D1=81=D1=82=D0=B5=D1=80=D0=B5=20?= =?UTF-8?q?=C2=AB=D0=9F=D0=BE=D0=B4=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=C2=BB=20+=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=B4=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - «Подготовить» в мастере (step 3, режим «дать логин администратора») теперь ставит и процедуры обработки bj2depo (CREATE OR ALTER) + GRANT, не только логин/таблицы/джобу. - Скачиваемый скрипт мастера включает процедуры. - Тексты процедур редактируются под клиента (Настройка → Обмен DEPO → Процедуры): сохранённый вариант используется при установке и при «Подготовить». Пусто/совпадение со встроенным — исходная версия. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn --- internal/lkgateway/admin.go | 2 + internal/lkgateway/depo_procs.go | 134 +++++++++++++----- internal/lkgateway/fancydb_handlers.go | 1 + internal/lkgateway/runtimeconfig.go | 2 + .../lkgateway/web/templates/part_fancy.html | 6 + internal/lkgateway/wizard_depo.go | 12 ++ 6 files changed, 125 insertions(+), 32 deletions(-) diff --git a/internal/lkgateway/admin.go b/internal/lkgateway/admin.go index 73883a4..c7cefcf 100644 --- a/internal/lkgateway/admin.go +++ b/internal/lkgateway/admin.go @@ -101,6 +101,8 @@ var templateFuncs = template.FuncMap{ "whForm": func(w WorkHoursSettings, ret string) map[string]any { return map[string]any{"W": w, "Ret": ret} }, "weekdayNames": func() []string { return weekdayShort }, "depoAccPrefix": depoAccPrefix, + "depoProcText": depoProcEffective, + "depoProcList": depoProcList, "certDaysLeft": certDaysLeft, "currentContour": func() string { return contourOf(currentNSDProfile()) }, "httpsCert": func() map[string]any { diff --git a/internal/lkgateway/depo_procs.go b/internal/lkgateway/depo_procs.go index 32b8879..2f04a91 100644 --- a/internal/lkgateway/depo_procs.go +++ b/internal/lkgateway/depo_procs.go @@ -43,7 +43,7 @@ func splitSQLBatches(s string) []string { return out } -// depoProcFile — тело встроенной процедуры. +// depoProcFile — тело встроенной (исходной) процедуры из файла. func depoProcFile(file string) (string, error) { b, err := depoProcFS.ReadFile(file) if err != nil { @@ -52,25 +52,83 @@ func depoProcFile(file string) (string, error) { return string(b), nil } +// embeddedProcBody — встроенный текст процедуры по имени. +func embeddedProcBody(name string) string { + for _, p := range depoProcInstallOrder { + if p.Name == name { + b, _ := depoProcFile(p.File) + return b + } + } + return "" +} + +// depoProcEffective — текст процедуры с учётом правки под клиента: сохранённый +// в настройках вариант, иначе встроенный. Используется в предпросмотре, установке +// и скачиваемом скрипте. +func depoProcEffective(f FancyMSSQLSettings, name string) string { + if v, ok := f.DepoProcOverrides[name]; ok && strings.TrimSpace(v) != "" { + return v + } + return embeddedProcBody(name) +} + +// depoProcList — порядок процедур для шаблона (редактор текстов). +func depoProcList() []struct{ Name, File string } { return depoProcInstallOrder } + // depoProcsCombined — единый скрипт установки (для скачивания администратору): USE, процедуры, GRANT. func depoProcsCombined(f FancyMSSQLSettings) string { + return depoProcsScriptFor(f, f.DepoDB(), depoSetupLogin(f)) +} + +// depoProcsScriptFor — скрипт установки процедур в базу db с выдачей EXECUTE логину login. +// Тексты процедур — с учётом правки под клиента (depoProcEffective). +func depoProcsScriptFor(f FancyMSSQLSettings, db, login string) string { var b strings.Builder - login := depoSetupLogin(f) - b.WriteString("/* Bridge&Join: установка процедур обработки bj2depo в базу DEPO.\n") + b.WriteString("/* Bridge&Join: процедуры обработки bj2depo для базы DEPO.\n") b.WriteString(" Порядок WIRING -> OPERATION -> EVENTS -> CREATE -> JOB. Запуск под sysadmin в SSMS.\n") b.WriteString(" CREATE OR ALTER — повторный запуск безопасен (данные не меняются). */\n") - fmt.Fprintf(&b, "USE %s;\nGO\n\n", quoteIdent(f.DepoDB())) + fmt.Fprintf(&b, "USE %s;\nGO\n\n", quoteIdent(db)) for _, p := range depoProcInstallOrder { - body, err := depoProcFile(p.File) - if err != nil { + body := depoProcEffective(f, p.Name) + if strings.TrimSpace(body) == "" { continue } fmt.Fprintf(&b, "/* ===== %s ===== */\n%s\nGO\n\n", p.Name, strings.TrimRight(body, "\n")) } - fmt.Fprintf(&b, "/* Право запуска задания логину bj */\nGRANT EXECUTE ON dbo.FTV_M2M_JOB TO %s;\nGO\n", quoteIdent(login)) + if depoLoginRe.MatchString(login) { + fmt.Fprintf(&b, "/* Право запуска задания логину bj */\nGRANT EXECUTE ON dbo.FTV_M2M_JOB TO %s;\nGO\n", quoteIdent(login)) + } return b.String() } +// depoProcsInstallInto — установить процедуры (CREATE OR ALTER, с учётом правки под клиента) в базу +// dbName соединением adm (логин администратора) и выдать EXECUTE логину grantLogin. +func depoProcsInstallInto(ctx context.Context, adm, f FancyMSSQLSettings, dbName, grantLogin string) error { + db, err := fancyOpen(adm, dbName) + if err != nil { + return err + } + defer db.Close() + for _, p := range depoProcInstallOrder { + body := depoProcEffective(f, p.Name) + if strings.TrimSpace(body) == "" { + return fmt.Errorf("пустой текст процедуры %s", p.Name) + } + for _, batch := range splitSQLBatches(body) { + if _, err := db.ExecContext(ctx, batch); err != nil { + return fmt.Errorf("установка %s: %s", p.Name, quikErrorDetail(err)) + } + } + } + if depoLoginRe.MatchString(grantLogin) { + if _, err := db.ExecContext(ctx, "GRANT EXECUTE ON dbo.FTV_M2M_JOB TO "+quoteIdent(grantLogin)); err != nil { + return fmt.Errorf("право EXECUTE логину %s: %s", grantLogin, quikErrorDetail(err)) + } + } + return nil +} + // quoteIdent — квадратные скобки для идентификатора T-SQL. func quoteIdent(s string) string { return "[" + strings.ReplaceAll(s, "]", "]]") + "]" } @@ -137,35 +195,15 @@ func depoProcsInstall(ctx context.Context, f FancyMSSQLSettings, adminUser, admi if !canApply { return "", fmt.Errorf("нет прав на установку. %s", report) } - conn := f + adm := f if u := strings.TrimSpace(adminUser); u != "" { - conn.User, conn.Password = u, adminPass + adm.User, adm.Password = u, adminPass } - db, err := fancyOpen(conn, f.DepoDB()) - if err != nil { + login := depoSetupLogin(f) + if err := depoProcsInstallInto(ctx, adm, f, f.DepoDB(), login); err != nil { return "", err } - defer db.Close() - for _, p := range depoProcInstallOrder { - body, err := depoProcFile(p.File) - if err != nil { - return "", fmt.Errorf("встроенный файл %s: %w", p.File, err) - } - for _, batch := range splitSQLBatches(body) { - if _, err := db.ExecContext(ctx, batch); err != nil { - return "", fmt.Errorf("установка %s: %s", p.Name, quikErrorDetail(err)) - } - } - } - msg := "установлено процедур: 5 (WIRING, OPERATION, EVENTS, CREATE, JOB)" - login := depoSetupLogin(f) - if depoLoginRe.MatchString(login) { - if _, err := db.ExecContext(ctx, "GRANT EXECUTE ON dbo.FTV_M2M_JOB TO "+quoteIdent(login)); err != nil { - msg += "; право EXECUTE логину " + login + " не выдано: " + quikErrorDetail(err) - } else { - msg += "; право EXECUTE логину " + login + " выдано" - } - } + msg := "установлено процедур: 5 (WIRING, OPERATION, EVENTS, CREATE, JOB); право EXECUTE логину " + login + " выдано" log.Printf("depo-procs: %s", msg) return msg, nil } @@ -237,3 +275,35 @@ func (h *setupHandlers) fancyDepoProcsScript(w http.ResponseWriter, r *http.Requ // UTF-8 BOM (0xEF 0xBB 0xBF): SSMS без него читает кириллицу в комментариях как ANSI. _, _ = w.Write(append([]byte{0xEF, 0xBB, 0xBF}, depoProcsCombined(s)...)) } + +// saveFancyDepoProcsText — POST /admin/setup/fancy/depo-procs/save-text: сохранить правку текстов процедур +// под клиента. Пустое поле или совпадение со встроенным текстом — вернуть встроенную версию (убрать правку). +func (h *setupHandlers) saveFancyDepoProcsText(w http.ResponseWriter, r *http.Request) { + if !requireDepo(w, r) { + return + } + if r.Method != http.MethodPost { + http.Error(w, "method", http.StatusMethodNotAllowed) + return + } + s := h.rc.Snapshot().Fancy + ov := map[string]string{} + changed := 0 + for _, p := range depoProcInstallOrder { + v := strings.ReplaceAll(r.FormValue("proc_"+p.Name), "\r\n", "\n") + if strings.TrimSpace(v) != "" && strings.TrimSpace(v) != strings.TrimSpace(embeddedProcBody(p.Name)) { + ov[p.Name] = v + changed++ + } + } + if changed == 0 { + s.DepoProcOverrides = nil + } else { + s.DepoProcOverrides = ov + } + if err := h.rc.UpdateFancy(s); err != nil { + setupFlash(w, r, "Тексты процедур: ошибка сохранения — "+err.Error()) + return + } + setupFlash(w, r, fmt.Sprintf("Тексты процедур сохранены. Изменённых под клиента: %d (пустые или совпадающие со встроенными — используется встроенная версия).", changed)) +} diff --git a/internal/lkgateway/fancydb_handlers.go b/internal/lkgateway/fancydb_handlers.go index cde4f20..a0d8a7f 100644 --- a/internal/lkgateway/fancydb_handlers.go +++ b/internal/lkgateway/fancydb_handlers.go @@ -29,6 +29,7 @@ func (h *setupHandlers) registerFancy(mux *http.ServeMux) { mux.HandleFunc("/admin/setup/fancy/depo-procs/check", h.checkFancyDepoProcs) mux.HandleFunc("/admin/setup/fancy/depo-procs/install", h.installFancyDepoProcs) mux.HandleFunc("/admin/setup/fancy/depo-procs/script.sql", h.fancyDepoProcsScript) + mux.HandleFunc("/admin/setup/fancy/depo-procs/save-text", h.saveFancyDepoProcsText) mux.HandleFunc("/admin/fancy-queue", h.fancyQueue) mux.HandleFunc("/admin/fancy-queue/requeue", h.fancyQueueRequeue) mux.HandleFunc("/admin/fancy-queue/dismiss", h.fancyQueueDismiss) diff --git a/internal/lkgateway/runtimeconfig.go b/internal/lkgateway/runtimeconfig.go index c0cc74d..3c8358c 100644 --- a/internal/lkgateway/runtimeconfig.go +++ b/internal/lkgateway/runtimeconfig.go @@ -250,6 +250,8 @@ type FancyMSSQLSettings struct { DepoJobName string `json:"depo_job_name,omitempty"` DepoNewUser string `json:"depo_new_user,omitempty"` DepoNewPass string `json:"depo_new_password,omitempty"` + // DepoProcOverrides — правка текстов процедур bj2depo под клиента (имя процедуры -> SQL); пусто = встроенный текст. + DepoProcOverrides map[string]string `json:"depo_proc_overrides,omitempty"` } // IsPostgres сообщает, выбран ли PostgreSQL (иначе — MSSQL по умолчанию). diff --git a/internal/lkgateway/web/templates/part_fancy.html b/internal/lkgateway/web/templates/part_fancy.html index 6a31b49..fc38321 100644 --- a/internal/lkgateway/web/templates/part_fancy.html +++ b/internal/lkgateway/web/templates/part_fancy.html @@ -130,6 +130,12 @@

Скачать скрипт bj-depo-procs-mssql.sql — для ручной установки администратором в SSMS

+
Тексты процедур — изменить под клиента +
+ + {{range depoProcList}}{{end}} +
пустое поле или совпадение со встроенным — вернуть исходную версию; изменённые ставятся при «Установить» и при «Подготовить» в мастере
+
{{if .Settings.Fancy.DepoProcCheck}}

{{if .Settings.Fancy.DepoProcCheckOK}}✓{{else}}⚠{{end}} {{.Settings.Fancy.DepoProcCheck}}

{{end}} {{end}} diff --git a/internal/lkgateway/wizard_depo.go b/internal/lkgateway/wizard_depo.go index 24cb087..e169e83 100644 --- a/internal/lkgateway/wizard_depo.go +++ b/internal/lkgateway/wizard_depo.go @@ -323,6 +323,9 @@ func (h *setupHandlers) wizardDepoScript(w http.ResponseWriter, r *http.Request) } } body := depoSetupScript(s, db, s.User, s.Password, agent) + if !s.IsPostgres() { // процедуры обработки bj2depo — тем же файлом + body += "\nGO\n" + depoProcsScriptFor(s, db, s.User) + } w.Header().Set("Cache-Control", "no-store") if view { w.Header().Set("Content-Type", "text/plain; charset=utf-8") @@ -410,6 +413,15 @@ func (h *setupHandlers) depoSetupDo(ctx context.Context, r *http.Request, step d if err := depoSetupRun(ctx, s, db, au, r.FormValue("admin_password"), s.User, s.Password, agent, step); err != nil { return fail("база не подготовлена — " + err.Error()) } + if !s.IsPostgres() { // процедуры обработки bj2depo (CREATE OR ALTER, с учётом правки под клиента) + step("procs", "run", "Устанавливаю процедуры обработки bj2depo") + adm := s + adm.User, adm.Password = au, r.FormValue("admin_password") + if err := depoProcsInstallInto(ctx, adm, s, db, s.User); err != nil { + return fail("процедуры обработки не установлены — " + err.Error()) + } + step("procs", "ok", "") + } s.DepoTablesMode = "auto" note = "база " + db + " подготовлена: логин " + s.User + ", таблицы обмена, права" if agent {