Files
Bridge-and-Join-s/internal/lkgateway/quikdb_handlers.go
T

247 lines
9.6 KiB
Go

package lkgateway
import (
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
)
// registerQuik — маршруты вкладки «QUIK МНП» (подключение к БД Модуля
// неторговых поручений). Только сохранение реквизитов и тест-подключение
// (read-only). БД боевая — запись не делаем.
func (h *setupHandlers) registerQuik(mux *http.ServeMux) {
mux.HandleFunc("/admin/setup/quik", h.saveQuik)
mux.HandleFunc("/admin/setup/quik/test", h.testQuik)
mux.HandleFunc("/admin/quik", h.quikInstructions)
mux.HandleFunc("/admin/setup/quik/cycle", h.saveQuikCycle)
}
// quikInstructions — GET /admin/quik. Поручения M2M из БД МНП QUIK.
// ТОЛЬКО ЧТЕНИЕ (боевая БД): SELECT из экспортной таблицы Export.
func (h *setupHandlers) quikInstructions(w http.ResponseWriter, r *http.Request) {
s := h.rc.Snapshot().Quik
onlyActive := r.URL.Query().Get("all") == ""
var items []QuikInstruction
var loadErr string
if s.Server == "" {
loadErr = "Подключение к QUIK не настроено — Настройка → QUIK МНП"
} else if list, err := quikListM2MInstructions(r.Context(), s, 200, onlyActive); err != nil {
loadErr = "не удалось прочитать поручения: " + err.Error()
} else {
items = list
}
data := struct {
page
Items []QuikInstruction
Error string
OnlyActive bool
DBName string
Links []*QuikLink
Cycle QuikMNPSettings
Flash string
}{nowPage("Поручения QUIK", "quik"), items, loadErr, onlyActive, s.Database, globalQuikLinks.List(100), s, r.URL.Query().Get("flash")}
render(w, h.tpl.a.quikInstr, data)
}
func (h *setupHandlers) quikFromForm(r *http.Request) QuikMNPSettings {
cur := h.rc.Snapshot().Quik
port, _ := strconv.Atoi(strings.TrimSpace(r.FormValue("port")))
if port == 0 {
port = cur.Port
}
pass := r.FormValue("password")
if pass == "" {
pass = cur.Password // не затираем сохранённый пароль пустым полем
}
enc := strings.TrimSpace(r.FormValue("encrypt"))
if enc == "" {
enc = "true"
}
return QuikMNPSettings{
Server: strings.TrimSpace(r.FormValue("server")),
Port: port,
Database: strings.TrimSpace(r.FormValue("database")),
User: strings.TrimSpace(r.FormValue("user")),
Password: pass,
Encrypt: enc,
Trust: r.FormValue("trust") != "",
Verified: cur.Verified,
// поля автоматического цикла M2M правятся отдельной формой — не затираем
WritesEnabled: cur.WritesEnabled, Emulate: cur.Emulate, AutoOut: cur.AutoOut, AutoIn: cur.AutoIn, PollTerminal: cur.PollTerminal,
Account: cur.Account, SecOut: cur.SecOut, SecIn: cur.SecIn, ClientCodeMap: cur.ClientCodeMap,
}
}
// saveQuikCycle — POST /admin/setup/quik/cycle. Настройки автоматического цикла M2M в МНП.
func (h *setupHandlers) saveQuikCycle(w http.ResponseWriter, r *http.Request) {
if !requireAdvanced(w, r) {
return
}
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
s := h.rc.Snapshot().Quik
s.WritesEnabled = r.FormValue("writes_enabled") != ""
if devMode(h.rc) { // флажок имитации есть только в режиме разработки
s.Emulate = r.FormValue("emulate") != ""
}
s.AutoOut = r.FormValue("auto_out") != ""
s.AutoIn = r.FormValue("auto_in") != ""
s.PollTerminal = r.FormValue("poll_terminal") != ""
s.Account = strings.TrimSpace(r.FormValue("account"))
s.SecOut = strings.TrimSpace(r.FormValue("sec_out"))
s.SecIn = strings.TrimSpace(r.FormValue("sec_in"))
m := map[string]string{}
for _, line := range strings.Split(r.FormValue("client_code_map"), "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
kv := strings.SplitN(line, "=", 2)
if len(kv) == 2 && strings.TrimSpace(kv[0]) != "" {
m[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
}
}
s.ClientCodeMap = m
if err := h.rc.UpdateQuik(s); err != nil {
setupFlash(w, r, "QUIK МНП: ошибка сохранения: "+err.Error())
return
}
mode := "записи в МНП ВЫКЛЮЧЕНЫ"
if s.Emulate && !devMode(h.rc) {
mode = "цикл остановлен — записи в МНП не выполняются"
} else if s.Emulate {
mode = fmt.Sprintf("ИМИТАЦИЯ (в МНП ничего не пишется): исходящие %s, входящие %s, поручения из терминала %s", onOff(s.AutoOut), onOff(s.AutoIn), onOff(s.PollTerminal))
} else if s.WritesEnabled {
mode = fmt.Sprintf("записи включены: исходящие %s, входящие %s, поручения из терминала %s", onOff(s.AutoOut), onOff(s.AutoIn), onOff(s.PollTerminal))
}
setupFlash(w, r, "QUIK МНП: цикл M2M сохранён — "+mode)
}
func onOff(b bool) string {
if b {
return "авто"
}
return "вручную"
}
// quikClientCodeMapText — карта исключений в виде «ключ=значение» построчно (для формы).
func quikClientCodeMapText(m map[string]string) string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
var b strings.Builder
for _, k := range keys {
b.WriteString(k + "=" + m[k] + "\n")
}
return b.String()
}
// saveQuik — POST /admin/setup/quik. Сохраняет реквизиты подключения.
func (h *setupHandlers) saveQuik(w http.ResponseWriter, r *http.Request) {
if !requireAdvanced(w, r) {
return
}
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
s := h.quikFromForm(r)
if s.Server == "" || s.User == "" {
setupFlash(w, r, "QUIK МНП: укажите адрес сервера и логин")
return
}
if err := h.rc.UpdateQuik(s); err != nil {
setupFlash(w, r, "QUIK МНП: ошибка сохранения: "+err.Error())
return
}
setupFlash(w, r, fmt.Sprintf("QUIK МНП: настройки сохранены (%s:%d, логин %s)", s.Server, s.Port, s.User))
}
// testQuik — POST /admin/setup/quik/test. Тест-подключение (только чтение).
// Формирует ПОЛНЫЙ отчёт (параметры + ответ сервера) для копирования коллегам.
func (h *setupHandlers) testQuik(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method", http.StatusMethodNotAllowed)
return
}
s := h.rc.Snapshot().Quik
if s.Server == "" {
setupFlash(w, r, "QUIK МНП: сначала сохраните реквизиты подключения")
return
}
now := time.Now()
var b strings.Builder
fmt.Fprintf(&b, "=== Проверка подключения к БД МНП QUIK ===\n")
fmt.Fprintf(&b, "Время: %s\n", now.Format("2006-01-02 15:04:05 MST"))
fmt.Fprintf(&b, "Сервер: %s:%d\n", s.Server, s.Port)
fmt.Fprintf(&b, "БД: %s\n", dfltDB(s.Database))
fmt.Fprintf(&b, "Логин: %s\n", s.User)
fmt.Fprintf(&b, "Шифрование: encrypt=%s, TrustServerCertificate=%v\n", s.Encrypt, s.Trust)
fmt.Fprintf(&b, "Клиент (bj): исходящий IP %s, драйвер go-mssqldb\n", pickStr(outboundIP(s.Server, s.Port), "не определён"))
fmt.Fprintf(&b, "----------------------------------------\n")
res, err := quikProbe(r.Context(), s)
if err != nil {
fmt.Fprintf(&b, "РЕЗУЛЬТАТ: ОШИБКА ПОДКЛЮЧЕНИЯ\n\n")
b.WriteString(quikErrorDetail(err))
s.Verified = false
s.LastReport = b.String()
s.LastAt = now.UTC()
_ = h.rc.UpdateQuik(s)
setupFlash(w, r, "QUIK МНП: подключение не удалось — полный ответ сервера ниже (можно скопировать для коллег)")
return
}
// успех
fmt.Fprintf(&b, "РЕЗУЛЬТАТ: ПОДКЛЮЧЕНИЕ OK\n\n")
fmt.Fprintf(&b, "Версия сервера: %s\n", res.Version)
fmt.Fprintf(&b, "Текущая БД: %s\n", res.DBName)
fmt.Fprintf(&b, "Логин на серв.: %s\n", res.Login)
fmt.Fprintf(&b, "M2M-готовность: %v — %s\n\n", res.M2MReady, res.Note)
fmt.Fprintf(&b, "Объекты МНП/M2M (видимость под логином):\n")
keys := make([]string, 0, len(res.Objects))
for k := range res.Objects {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
mark := "нет"
if res.Objects[k] {
mark = "ДА"
}
fmt.Fprintf(&b, " %-28s %s\n", k, mark)
}
s.Verified = true
s.M2MMissing = ""
if !res.M2MReady {
var miss []string
for _, k := range []string{"table:Export", "table:Contracts", "proc:sp_NewInstrSec"} {
if !res.Objects[k] {
miss = append(miss, strings.SplitN(k, ":", 2)[1])
}
}
s.M2MMissing = strings.Join(miss, ", ")
}
s.LastReport = b.String()
s.LastAt = now.UTC()
_ = h.rc.UpdateQuik(s)
if s.M2MMissing != "" {
setupFlash(w, r, fmt.Sprintf("QUIK МНП: подключение есть (БД=%s), но под этим логином не видно объектов M2M: %s. Проверьте имя базы и права логина (роль InstrEu). Полный отчёт ниже.", res.DBName, s.M2MMissing))
return
}
setupFlash(w, r, fmt.Sprintf("QUIK МНП: ПОДКЛЮЧЕНИЕ OK (БД=%s). Полный отчёт ниже.", res.DBName))
}
func dfltDB(db string) string {
if db == "" {
return "(по умолчанию для логина)"
}
return db
}