6c42e0378d
Третий режим МНП (между «внутренняя имитация» и «боевая запись»): в bj цикл идёт по обычному пользователю, а поручение в QUIK МНП уходит РЕАЛЬНО, но с кодом другого, реально существующего в QUIK клиента (обобщение ручного теста realtest «боевой ЛК → TEST3»). - QuikMNPSettings: RealWrite + RealClientCode (валидация формата кода). - Подмена кода — единая точка quikClientCodeFromPort (после ClientCodeMap); портфель не трогаем. quikActive: RealWrite только при devMode и непустом коде; Emulate (внутренняя имитация) в приоритете. - Шаги боевого пути помечаются «[на подставного клиента <код>]» — видно в «Поручениях QUIK». - Карточка МНП на /admin/emulators — три взаимоисключающих режима (выключено / имитация / реальная запись), поле кода, крупное предупреждение; отдельный красный баннер «Реальные поручения в МНП на клиента <код> (режим разработки)» — не смешиваем с баннером эмуляции НРД. - disableEmulatorsIfDevOff сбрасывает и RealWrite (код остаётся, инертен), новость явно фиксирует прекращение реальных поручений.
244 lines
10 KiB
Go
244 lines
10 KiB
Go
package lkgateway
|
|
|
|
// emulators.go — dev-страница «Эмуляторы» (/admin/emulators, только режим разработки):
|
|
// переключатели и настройки эмуляции НРД (имитатор контрагента для заявок ЛК) и МНП QUIK
|
|
// (шаги цикла без записи в боевую БД). Переключатели пишут существующие флаги
|
|
// LK.EmulateNSD и QuikMNP.Emulate — чекбоксы в «Настройке» показывают то же состояние.
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// emuRejectOption — вариант кода отказа для эмулятора НРД (value — «07», подпись — с названием).
|
|
type emuRejectOption struct{ Code, Label string }
|
|
|
|
func emuRejectOptions() []emuRejectOption {
|
|
out := make([]emuRejectOption, 0, len(nsdEmuRejectCodes))
|
|
for _, c := range nsdEmuRejectCodes {
|
|
out = append(out, emuRejectOption{c, "M2M" + c + " — " + m2mCodeText("M2M"+c)})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// registerEmulators вешает /admin/emulators (GET — страница, POST …/nsd и …/quik — сохранение, ajax).
|
|
func registerEmulators(mux *http.ServeMux, a *admin, rc *RuntimeConfig) {
|
|
mux.HandleFunc("/admin/emulators", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "method", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
s := rc.Snapshot()
|
|
nsdOn, nsdWhy := lkEmulateNSD(rc)
|
|
quikMode := "off"
|
|
if s.Quik.Emulate {
|
|
quikMode = "emulate"
|
|
} else if s.Quik.RealWrite {
|
|
quikMode = "real"
|
|
}
|
|
data := struct {
|
|
page
|
|
NSDEmuOn bool
|
|
NSDEmuWhy string
|
|
NSDFlag bool
|
|
NSD NSDEmuSettings
|
|
RejectCodes []emuRejectOption
|
|
QuikEmuOn bool
|
|
QuikMode string
|
|
QuikRealCode string
|
|
MNP MNPEmuSettings
|
|
Flash string
|
|
}{
|
|
page: nowPage("Эмуляторы", "emulators"),
|
|
NSDEmuOn: nsdOn,
|
|
NSDEmuWhy: nsdWhy,
|
|
NSDFlag: s.LK.EmulateNSD,
|
|
NSD: s.NSDEmu.Defaults(),
|
|
RejectCodes: emuRejectOptions(),
|
|
QuikEmuOn: s.Quik.Emulate && devMode(rc),
|
|
QuikMode: quikMode,
|
|
QuikRealCode: s.Quik.RealClientCode,
|
|
MNP: s.MNPEmu,
|
|
Flash: r.URL.Query().Get("flash"),
|
|
}
|
|
render(w, a.emulators, data)
|
|
})
|
|
|
|
mux.HandleFunc("/admin/emulators/nsd", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
lk := rc.Snapshot().LK
|
|
lk.EmulateNSD = r.FormValue("emulate") != ""
|
|
delay, err := strconv.Atoi(strings.TrimSpace(r.FormValue("delay_sec")))
|
|
if err != nil && strings.TrimSpace(r.FormValue("delay_sec")) != "" {
|
|
emuAjaxReply(w, r, http.StatusBadRequest, "Эмулятор НРД: задержка — целое число секунд")
|
|
return
|
|
}
|
|
emu := NSDEmuSettings{
|
|
DelaySec: delay,
|
|
Outcome: strings.TrimSpace(r.FormValue("outcome")),
|
|
RejectCode: strings.TrimSpace(r.FormValue("reject_code")),
|
|
}
|
|
if err := rc.UpdateNSDEmu(emu); err != nil {
|
|
emuAjaxReply(w, r, http.StatusBadRequest, "Эмулятор НРД: "+err.Error())
|
|
return
|
|
}
|
|
if err := rc.UpdateLK(lk); err != nil {
|
|
emuAjaxReply(w, r, http.StatusInternalServerError, "Эмулятор НРД: не сохранилось: "+err.Error())
|
|
return
|
|
}
|
|
applyNSDEmuSettings(rc)
|
|
msg := "выключен — заявки ЛК идут в НРД по обычному пути"
|
|
if on, why := lkEmulateNSD(rc); on {
|
|
eff := rc.Snapshot().NSDEmu.Defaults()
|
|
msg = fmt.Sprintf("включён (%s) — в НРД ничего не отправляется; решение контрагента: %s через %d сек",
|
|
why, emuOutcomeRu(eff.Outcome), eff.DelaySec)
|
|
}
|
|
emuAjaxReply(w, r, http.StatusOK, "Эмулятор НРД: "+msg)
|
|
})
|
|
|
|
mux.HandleFunc("/admin/emulators/quik", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
q := rc.Snapshot().Quik
|
|
// Режим взаимоисключающий: off | emulate (внутренняя имитация) | real (реальная запись на подставного клиента).
|
|
mode := r.FormValue("mode")
|
|
if mode != "emulate" && mode != "real" {
|
|
mode = "off"
|
|
}
|
|
q.Emulate = mode == "emulate"
|
|
q.RealWrite = mode == "real"
|
|
q.RealClientCode = strings.TrimSpace(r.FormValue("real_client_code"))
|
|
if q.RealWrite && q.RealClientCode == "" {
|
|
emuAjaxReply(w, r, http.StatusBadRequest, "Эмулятор МНП: укажите код клиента QUIK — на него уйдут реальные поручения")
|
|
return
|
|
}
|
|
delay, err := strconv.Atoi(strings.TrimSpace(r.FormValue("step_delay_sec")))
|
|
if err != nil && strings.TrimSpace(r.FormValue("step_delay_sec")) != "" {
|
|
emuAjaxReply(w, r, http.StatusBadRequest, "Эмулятор МНП: задержка — целое число секунд")
|
|
return
|
|
}
|
|
emu := MNPEmuSettings{
|
|
StepDelaySec: delay,
|
|
FailRegister: r.FormValue("fail_register") != "",
|
|
FailSign: r.FormValue("fail_sign") != "",
|
|
FailDeny: r.FormValue("fail_deny") != "",
|
|
FailDeposit: r.FormValue("fail_deposit") != "",
|
|
}
|
|
if err := rc.UpdateMNPEmu(emu); err != nil {
|
|
emuAjaxReply(w, r, http.StatusBadRequest, "Эмулятор МНП: "+err.Error())
|
|
return
|
|
}
|
|
if err := rc.UpdateQuik(q); err != nil {
|
|
emuAjaxReply(w, r, http.StatusBadRequest, "Эмулятор МНП: "+err.Error())
|
|
return
|
|
}
|
|
msg := "выключен — шаги цикла МНП выполняются в QUIK по-настоящему"
|
|
switch {
|
|
case q.Emulate && devMode(rc):
|
|
msg = "внутренняя имитация — шаги цикла (блокировка, подпись, снятие, зачисление) проходят без записи в МНП"
|
|
var fails []string
|
|
for _, f := range []struct {
|
|
on bool
|
|
name string
|
|
}{{emu.FailRegister, "регистрация"}, {emu.FailSign, "подпись"}, {emu.FailDeny, "снятие"}, {emu.FailDeposit, "зачисление"}} {
|
|
if f.on {
|
|
fails = append(fails, f.name)
|
|
}
|
|
}
|
|
if len(fails) > 0 {
|
|
msg += "; отказ на шагах: " + strings.Join(fails, ", ")
|
|
}
|
|
case q.RealWrite && devMode(rc):
|
|
msg = "РЕАЛЬНАЯ запись в боевую МНП на подставного клиента " + q.RealClientCode + " — поручения уходят по-настоящему, код клиента подменяется"
|
|
}
|
|
emuAjaxReply(w, r, http.StatusOK, "Эмулятор МНП: "+msg)
|
|
})
|
|
}
|
|
|
|
// emuAjaxReply — ответ сохранения: ajax (X-BJ-Ajax) — строкой с кодом, без JS — редирект с flash.
|
|
func emuAjaxReply(w http.ResponseWriter, r *http.Request, code int, msg string) {
|
|
if r.Header.Get("X-BJ-Ajax") != "" {
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
w.WriteHeader(code)
|
|
_, _ = w.Write([]byte(msg))
|
|
return
|
|
}
|
|
setupFlash(w, r, msg)
|
|
}
|
|
|
|
// emuOutcomeRu — исход эмулятора НРД по-русски.
|
|
func emuOutcomeRu(outcome string) string {
|
|
switch outcome {
|
|
case "reject":
|
|
return "отказ"
|
|
case "timeout":
|
|
return "не отвечает (таймаут)"
|
|
}
|
|
return "подтверждение"
|
|
}
|
|
|
|
// disableEmulatorsIfDevOff — безопасность: эмуляторы НРД/МНП действуют только в режиме
|
|
// разработки; когда он заканчивается (по времени dev_until или снят на сервере лицензий),
|
|
// флаги сбрасываются из настроек — иначе при следующей выдаче dev-режима эмуляция
|
|
// молча оживёт. Вызывается при старте и в цикле продления лицензии.
|
|
func disableEmulatorsIfDevOff(rc *RuntimeConfig) {
|
|
if rc == nil || devMode(rc) {
|
|
return
|
|
}
|
|
s := rc.Snapshot()
|
|
if !s.LK.EmulateNSD && !s.Quik.Emulate && !s.Quik.RealWrite {
|
|
return
|
|
}
|
|
var off []string
|
|
if s.LK.EmulateNSD {
|
|
lk := s.LK
|
|
lk.EmulateNSD = false
|
|
if err := rc.UpdateLK(lk); err != nil {
|
|
log.Printf("lk-gateway: авто-сброс эмулятора НРД: %v", err)
|
|
} else {
|
|
off = append(off, "эмулятор НРД (заявки ЛК)")
|
|
}
|
|
}
|
|
if s.Quik.Emulate || s.Quik.RealWrite {
|
|
q := s.Quik
|
|
q.Emulate, q.RealWrite = false, false
|
|
if err := rc.UpdateQuik(q); err != nil {
|
|
log.Printf("lk-gateway: авто-сброс эмулятора МНП: %v", err)
|
|
} else {
|
|
if s.Quik.Emulate {
|
|
off = append(off, "эмулятор МНП QUIK (внутренняя имитация)")
|
|
}
|
|
if s.Quik.RealWrite {
|
|
off = append(off, "реальная запись в МНП на подставного клиента "+s.Quik.RealClientCode)
|
|
}
|
|
}
|
|
}
|
|
if len(off) == 0 {
|
|
return
|
|
}
|
|
log.Printf("lk-gateway: режим разработки закончился — эмуляторы отключены: %s", strings.Join(off, ", "))
|
|
body := "Выключены: " + strings.Join(off, ", ") + ". Флаги эмуляции сброшены из настроек: при следующем включении режима разработки эмуляторы не включатся сами — их нужно будет включить заново на странице «Эмуляторы»."
|
|
if s.Quik.RealWrite {
|
|
body += " Реальные поручения в боевую МНП на подставного клиента " + s.Quik.RealClientCode + " прекращены."
|
|
}
|
|
if (s.Quik.Emulate || s.Quik.RealWrite) && !s.Quik.WritesEnabled {
|
|
body += " Цикл M2M в МНП остановлен: записи в МНП не разрешены — для боевой работы выберите «Чтение и запись» в настройках QUIK."
|
|
}
|
|
_ = rc.AddNews(NewsItem{
|
|
ID: "sys-" + strconv.FormatInt(time.Now().Unix()/600, 10) + "-emulators-off",
|
|
At: time.Now(),
|
|
Kind: "system",
|
|
Title: "Эмуляторы отключены: режим разработки закончился",
|
|
Body: body,
|
|
})
|
|
}
|