e31e35e663
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
339 lines
13 KiB
Go
339 lines
13 KiB
Go
package lkgateway
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Хендлеры вкладки «Обмен Fancy (MSSQL)»: подключение, тест, создание БД,
|
|
// наливка схемы. Маршруты вешаются в registerSetup (см. registerFancy).
|
|
|
|
func (h *setupHandlers) registerFancy(mux *http.ServeMux) {
|
|
mux.HandleFunc("/admin/setup/fancy", h.saveFancy)
|
|
mux.HandleFunc("/admin/setup/fancy/test", h.testFancy)
|
|
mux.HandleFunc("/admin/setup/fancy/detect", h.detectFancyRequisites) // реквизиты НРД/организации из DEPO
|
|
mux.HandleFunc("/admin/setup/fancy/depo-job", h.saveFancyDepoJob)
|
|
mux.HandleFunc("/admin/setup/fancy/depo-job/check", h.checkFancyDepoJob)
|
|
mux.HandleFunc("/admin/setup/fancy/depo-job/create", h.createFancyDepoJob)
|
|
mux.HandleFunc("/admin/setup/fancy/depo-tables", h.saveFancyDepoTables)
|
|
mux.HandleFunc("/admin/setup/fancy/depo-tables/create", h.createFancyDepoTables)
|
|
mux.HandleFunc("/admin/setup/fancy/depo-tables/check", h.checkFancyDepoTables)
|
|
mux.HandleFunc("/admin/setup/fancy/depo-tables/script.sql", h.fancyDepoTablesScript)
|
|
mux.HandleFunc("/admin/setup/fancy/depo-login/script.sql", h.fancyDepoLoginScript)
|
|
mux.HandleFunc("/admin/setup/fancy/depo-job/script.sql", h.fancyDepoJobScript)
|
|
mux.HandleFunc("/admin/fancy-queue", h.fancyQueue)
|
|
mux.HandleFunc("/admin/fancy-queue/requeue", h.fancyQueueRequeue)
|
|
}
|
|
|
|
// fancyFromForm собирает настройки из формы (пароль пустой = оставить прежний).
|
|
func (h *setupHandlers) fancyFromForm(r *http.Request) FancyMSSQLSettings {
|
|
cur := h.rc.Snapshot().Fancy
|
|
dbType := strings.TrimSpace(r.FormValue("db_type"))
|
|
if dbType != "postgres" {
|
|
dbType = "mssql"
|
|
}
|
|
port, _ := strconv.Atoi(strings.TrimSpace(r.FormValue("port")))
|
|
if port == 0 {
|
|
if dbType == "postgres" {
|
|
port = 5432
|
|
} else {
|
|
port = 1433
|
|
}
|
|
}
|
|
pass := r.FormValue("password")
|
|
if pass == "" {
|
|
pass = cur.Password // не затираем сохранённый пароль пустым полем
|
|
}
|
|
enc := strings.TrimSpace(r.FormValue("encrypt"))
|
|
if enc == "" {
|
|
enc = "disable"
|
|
}
|
|
// остальные настройки (обработка DEPO, трансферные таблицы) сохраняем как были
|
|
s := cur
|
|
s.DBType, s.Port, s.Password, s.Encrypt = dbType, port, pass, enc
|
|
s.Server = strings.TrimSpace(r.FormValue("server"))
|
|
s.User = strings.TrimSpace(r.FormValue("user"))
|
|
if s.Server != cur.Server || s.DBType != cur.DBType || s.User != cur.User {
|
|
s.DepoTablesCheck, s.DepoTablesCheckOK = "", false
|
|
s.DepoJobCheck, s.DepoJobCheckOK = "", false
|
|
}
|
|
return s
|
|
}
|
|
|
|
// saveFancyDepoTables — POST /admin/setup/fancy/depo-tables: база DEPO и способ создания таблиц.
|
|
func (h *setupHandlers) saveFancyDepoTables(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
|
|
db := strings.TrimSpace(r.FormValue("depo_database"))
|
|
if db != "" && !validDBName(db) {
|
|
setupFlash(w, r, "Таблицы DEPO: имя базы — только латинские буквы/цифры/_, начинать с буквы")
|
|
return
|
|
}
|
|
if m := strings.TrimSpace(r.FormValue("iis_contract_marker")); len([]rune(m)) <= 100 {
|
|
s.IISContractMarker = m
|
|
}
|
|
mode := r.FormValue("depo_tables_mode")
|
|
if mode != "auto" && mode != "script" {
|
|
mode = ""
|
|
}
|
|
if db != s.DepoDatabase {
|
|
s.DepoTablesCheck, s.DepoTablesCheckOK = "", false
|
|
}
|
|
s.DepoDatabase, s.DepoTablesMode = db, mode
|
|
if err := h.rc.UpdateFancy(s); err != nil {
|
|
setupFlash(w, r, "Таблицы DEPO: ошибка сохранения: "+err.Error())
|
|
return
|
|
}
|
|
switch mode {
|
|
case "auto":
|
|
setupFlash(w, r, "Таблицы DEPO: bj создаст их в базе "+s.DepoDB()+" — введите логин с правами и нажмите «Создать таблицы».")
|
|
case "script":
|
|
setupFlash(w, r, "Таблицы DEPO: передайте скрипт администратору СУБД, после выполнения нажмите «Проверить таблицы».")
|
|
default:
|
|
setupFlash(w, r, "Таблицы DEPO: база "+s.DepoDB()+" сохранена, способ создания не выбран")
|
|
}
|
|
}
|
|
|
|
func (h *setupHandlers) saveDepoTablesCheck(msg string, ok bool) {
|
|
s := h.rc.Snapshot().Fancy
|
|
s.DepoTablesCheck, s.DepoTablesCheckOK, s.DepoTablesCheckAt = msg, ok, time.Now()
|
|
_ = h.rc.UpdateFancy(s)
|
|
}
|
|
|
|
// createFancyDepoTables — POST /admin/setup/fancy/depo-tables/create: bj создаёт таблицы сам.
|
|
// Логин с правами берётся из формы и нигде не сохраняется.
|
|
func (h *setupHandlers) createFancyDepoTables(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
|
|
if s.Server == "" {
|
|
setupFlash(w, r, "Таблицы DEPO: сначала сохраните подключение к серверу БД")
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute)
|
|
defer cancel()
|
|
msg, err := depoTablesCreate(ctx, s, r.FormValue("admin_user"), r.FormValue("admin_password"))
|
|
if err != nil {
|
|
log.Printf("depo-tables: создание не удалось: %v", err)
|
|
setupFlash(w, r, "Таблицы DEPO: не созданы — "+err.Error())
|
|
return
|
|
}
|
|
log.Printf("depo-tables: %s", msg)
|
|
check, ok := depoTablesCheck(ctx, s)
|
|
h.saveDepoTablesCheck(check, ok)
|
|
if ok && !s.IsPostgres() {
|
|
if err := depoWriteHandbook(h.rc); err == nil {
|
|
msg += "; справочник участников записан"
|
|
} else {
|
|
log.Printf("depo-tables: справочник: %v", err)
|
|
}
|
|
}
|
|
setupFlash(w, r, "Таблицы DEPO: "+msg+". Проверка: "+check)
|
|
}
|
|
|
|
// checkFancyDepoTables — POST /admin/setup/fancy/depo-tables/check.
|
|
func (h *setupHandlers) checkFancyDepoTables(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
s := h.rc.Snapshot().Fancy
|
|
if s.Server == "" {
|
|
setupFlash(w, r, "Таблицы DEPO: сначала сохраните подключение к серверу БД")
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(r.Context(), time.Minute)
|
|
defer cancel()
|
|
msg, ok := depoTablesCheck(ctx, s)
|
|
h.saveDepoTablesCheck(msg, ok)
|
|
if ok {
|
|
setupFlash(w, r, "Таблицы DEPO: готово — "+msg)
|
|
} else {
|
|
setupFlash(w, r, "Таблицы DEPO: НЕ готово — "+msg)
|
|
}
|
|
}
|
|
|
|
// fancyDepoTablesScript — GET /admin/setup/fancy/depo-tables/script.sql: скрипт для администратора СУБД.
|
|
func (h *setupHandlers) fancyDepoTablesScript(w http.ResponseWriter, r *http.Request) {
|
|
s := h.rc.Snapshot().Fancy
|
|
body := depoTablesScript(s)
|
|
if !s.IsPostgres() {
|
|
body = "\uFEFF" + body // SSMS без BOM читает кириллицу в комментариях как ANSI
|
|
}
|
|
w.Header().Set("Content-Type", "application/sql; charset=utf-8")
|
|
w.Header().Set("Content-Disposition", `attachment; filename="`+depoTablesFileName(s)+`"`)
|
|
_, _ = w.Write([]byte(body))
|
|
}
|
|
|
|
// saveFancyDepoJob — POST /admin/setup/fancy/depo-job: обработка трансферных таблиц силами bj.
|
|
func (h *setupHandlers) saveFancyDepoJob(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
|
|
mode := r.FormValue("depo_job_mode")
|
|
if mode != "bj" && mode != "sql-auto" && mode != "sql-manual" {
|
|
mode = ""
|
|
}
|
|
if s.DepoJobMode != mode {
|
|
s.DepoJobCheck, s.DepoJobCheckOK = "", false
|
|
}
|
|
s.DepoJobMode = mode
|
|
s.DepoJobRun = mode == "bj"
|
|
if v, err := strconv.Atoi(strings.TrimSpace(r.FormValue("depo_job_every_sec"))); err == nil && v >= 5 && v <= 3600 {
|
|
s.DepoJobEverySec = v
|
|
}
|
|
s.DepoJobProc = strings.TrimSpace(r.FormValue("depo_job_proc"))
|
|
if u := strings.TrimSpace(r.FormValue("depo_job_run_as")); u == "" || depoRunAsRe.MatchString(u) {
|
|
if u != s.DepoJobRunAs {
|
|
s.DepoJobCheck, s.DepoJobCheckOK = "", false
|
|
}
|
|
s.DepoJobRunAs = u
|
|
}
|
|
if err := h.rc.UpdateFancy(s); err != nil {
|
|
setupFlash(w, r, "Обработка DEPO: ошибка сохранения: "+err.Error())
|
|
return
|
|
}
|
|
switch mode {
|
|
case "bj":
|
|
setupFlash(w, r, fmt.Sprintf("Обработка DEPO: bj сам вызывает %s каждые %d с. Нажмите «Проверить готовность».", depoJobProcName(s), depoJobEvery(s)))
|
|
case "sql-auto":
|
|
setupFlash(w, r, "Обработка DEPO: джоба SQL Agent, создаёт bj. Выдайте права по инструкции и нажмите «Создать джобу».")
|
|
case "sql-manual":
|
|
setupFlash(w, r, "Обработка DEPO: джоба SQL Agent, настраивает администратор. Передайте инструкцию и нажмите «Проверить готовность».")
|
|
default:
|
|
setupFlash(w, r, "Обработка DEPO: способ не выбран")
|
|
}
|
|
}
|
|
|
|
// checkFancyDepoJob — POST /admin/setup/fancy/depo-job/check: проверка готовности выбранного способа.
|
|
func (h *setupHandlers) checkFancyDepoJob(w http.ResponseWriter, r *http.Request) {
|
|
if !requireDepo(w, r) {
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(r.Context(), 90*time.Second)
|
|
defer cancel()
|
|
msg, ok := depoJobCheck(ctx, h.rc)
|
|
pmsg, pok := depoProcCheck(ctx, h.rc)
|
|
s := h.rc.Snapshot().Fancy
|
|
s.DepoJobCheck, s.DepoJobCheckOK, s.DepoJobCheckAt = msg, ok, time.Now()
|
|
s.DepoProcCheck, s.DepoProcCheckOK, s.DepoProcCheckAt = pmsg, pok, time.Now()
|
|
_ = h.rc.UpdateFancy(s)
|
|
run := "НЕ готово — "
|
|
if ok {
|
|
run = "готово — "
|
|
}
|
|
proc := "НЕ работает — "
|
|
if pok {
|
|
proc = "работает — "
|
|
}
|
|
setupFlash(w, r, "Запуск обработки (джоба): "+run+msg+" · Обработка событий (процедуры): "+proc+pmsg)
|
|
}
|
|
|
|
// createFancyDepoJob — POST /admin/setup/fancy/depo-job/create: создать джобу SQL Agent от имени bj.
|
|
func (h *setupHandlers) createFancyDepoJob(w http.ResponseWriter, r *http.Request) {
|
|
if !requireDepo(w, r) {
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second)
|
|
defer cancel()
|
|
if err := depoJobCreateAgent(ctx, h.rc, r.FormValue("admin_user"), r.FormValue("admin_password")); err != nil {
|
|
setupFlash(w, r, "Создание джобы: "+err.Error())
|
|
return
|
|
}
|
|
msg, ok := depoJobCheck(ctx, h.rc)
|
|
s := h.rc.Snapshot().Fancy
|
|
s.DepoJobCheck, s.DepoJobCheckOK, s.DepoJobCheckAt = msg, ok, time.Now()
|
|
_ = h.rc.UpdateFancy(s)
|
|
setupFlash(w, r, "Джоба SQL Agent создана. Проверка: "+msg)
|
|
}
|
|
|
|
// saveFancy — POST /admin/setup/fancy. Сохраняет параметры подключения.
|
|
func (h *setupHandlers) saveFancy(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.fancyFromForm(r)
|
|
dbLabel := "MSSQL"
|
|
if s.IsPostgres() {
|
|
dbLabel = "PostgreSQL"
|
|
}
|
|
if s.Server == "" || s.User == "" {
|
|
setupFlash(w, r, dbLabel+": укажите сервер и пользователя")
|
|
return
|
|
}
|
|
if err := h.rc.UpdateFancy(s); err != nil {
|
|
setupFlash(w, r, dbLabel+": ошибка сохранения: "+err.Error())
|
|
return
|
|
}
|
|
setupFlash(w, r, fmt.Sprintf("%s: настройки сохранены (%s:%d)", dbLabel, s.Server, s.Port))
|
|
}
|
|
|
|
// testFancy — POST /admin/setup/fancy/test. Проверяет соединение к серверу.
|
|
func (h *setupHandlers) testFancy(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
s := h.rc.Snapshot().Fancy
|
|
if s.Server == "" {
|
|
setupFlash(w, r, "MSSQL: сначала сохраните параметры подключения")
|
|
return
|
|
}
|
|
// Проверяем соединение к серверу (master) — БД может ещё не существовать.
|
|
ver, err := fancyPing(r.Context(), s, "master")
|
|
if err != nil {
|
|
setupFlash(w, r, fancyDBLabel(s)+": соединение не удалось — "+err.Error())
|
|
return
|
|
}
|
|
setupFlash(w, r, fmt.Sprintf("%s: соединение OK. %s", fancyDBLabel(s), strings.TrimSpace(ver)))
|
|
}
|
|
|
|
// fancyDBLabel — «MSSQL» или «PostgreSQL» для сообщений.
|
|
func fancyDBLabel(s FancyMSSQLSettings) string {
|
|
if s.IsPostgres() {
|
|
return "PostgreSQL"
|
|
}
|
|
return "MSSQL"
|
|
}
|
|
|
|
// fancyDepoJobScript — GET /admin/setup/fancy/depo-job/script.sql: скрипт джобы SQL Agent для администратора.
|
|
func (h *setupHandlers) fancyDepoJobScript(w http.ResponseWriter, r *http.Request) {
|
|
s := h.rc.Snapshot().Fancy
|
|
w.Header().Set("Content-Type", "application/sql; charset=utf-8")
|
|
w.Header().Set("Content-Disposition", `attachment; filename="bj-depo-job-mssql.sql"`)
|
|
_, _ = w.Write([]byte("\uFEFF" + depoJobAgentAdminScript(s))) // SSMS без BOM читает кириллицу как ANSI
|
|
}
|