DEPO Firebird: путь к базе — и строкой подключения Fancy целиком (база на другом компьютере)

Поле пути в мастере и в «Таблицах обмена» принимает строку подключения из настройки Fancy: «сервер:путь»,
«сервер/порт:путь», «\\сервер\D:\путь», «\\сервер\D$\путь», «inet://сервер:порт/путь» — адрес и порт
bj берёт из неё (в мастере поля заполняются сразу при вставке). Сетевая папка «\\сервер\папка\…» — ошибка с
подсказкой: Firebird открывает базу только на своём диске, адрес — компьютер с сервером Firebird.
Проверено мастером на копии базы: пустой адрес + «127.0.0.1/3050:путь» — подготовка и BJ_PING.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
fontvielle
2026-10-27 19:15:46 +03:00
parent 50e420f4a0
commit 1d8bb0c032
8 changed files with 123 additions and 9 deletions
+19 -1
View File
@@ -77,7 +77,25 @@ func (h *setupHandlers) saveFancyDepoTables(w http.ResponseWriter, r *http.Reque
}
s := h.rc.Snapshot().Fancy
db := strings.TrimSpace(r.FormValue("depo_database"))
if db != "" && !validDBName(db) {
if s.IsFirebird() && db != "" { // путь к базе на сервере Firebird или строка подключения Fancy целиком
host, p, path, err := fbSplitConn(db)
if err != nil {
setupFlash(w, r, "Таблицы DEPO: "+err.Error())
return
}
if !validFBPath(path) {
setupFlash(w, r, "Таблицы DEPO: путь к базе — без кавычек, ; ? и #")
return
}
if host != "" && (host != s.Server || (p != 0 && p != s.Port)) {
s.Server = host
if p != 0 {
s.Port = p
}
s.DepoJobCheck, s.DepoJobCheckOK = "", false
}
db = path
} else if db != "" && !validDBName(db) {
setupFlash(w, r, "Таблицы DEPO: имя базы — только латинские буквы/цифры/_, начинать с буквы")
return
}
+47
View File
@@ -468,3 +468,50 @@ func fbErr(orig, sent string, err error) error {
}
return err
}
var (
fbDriveRe = regexp.MustCompile(`^[A-Za-z]:[\\/]`)
fbHostRe = regexp.MustCompile(`^([A-Za-z0-9._\-]{2,}|\[[0-9A-Fa-f:]+\])(?:/(\d{1,5}))?:(.+)$`)
fbUNCRe = regexp.MustCompile(`^\\\\([^\\/]+)[\\/](.+)$`)
fbAdmShRe = regexp.MustCompile(`^([A-Za-z])\$[\\/](.*)$`)
fbInetURLRe = regexp.MustCompile(`(?i)^inet[46]?://([^/:]+)(?::(\d{1,5}))?/(.+)$`)
)
// fbSplitConn — строка подключения Fancy к базе Firebird целиком: «сервер:путь», «сервер/порт:путь»,
// «\\сервер\D:\путь», «\\сервер\D$\путь», «inet://сервер:порт/путь». База обычно на другом компьютере — на
// том, где работает сервер Firebird; bj подключается к нему по сети, а путь — на диске этого компьютера.
// host="" — указан только путь (сервер — в отдельном поле). Сетевую папку (\\сервер\папка\…) Firebird не
// открывает — ошибка с подсказкой.
func fbSplitConn(s string) (host string, port int, path string, err error) {
s = strings.TrimSpace(s)
switch {
case fbDriveRe.MatchString(s) || strings.HasPrefix(s, "/") || !strings.ContainsAny(s, `:\/`):
return "", 0, s, nil // путь на сервере или псевдоним
case fbInetURLRe.MatchString(s):
m := fbInetURLRe.FindStringSubmatch(s)
port, _ = strconv.Atoi(m[2])
path = m[3]
if !fbDriveRe.MatchString(path) && !strings.ContainsAny(path, `\/`) { // inet://host/alias
return m[1], port, path, nil
}
if !fbDriveRe.MatchString(path) {
path = "/" + path
}
return m[1], port, path, nil
case fbUNCRe.MatchString(s):
m := fbUNCRe.FindStringSubmatch(s)
rest := m[2]
if fbDriveRe.MatchString(rest) { // \\сервер\D:\путь — так Fancy пишет подключение по именованным каналам
return m[1], 0, rest, nil
}
if a := fbAdmShRe.FindStringSubmatch(rest); a != nil { // \\сервер\D$\путь — административный общий ресурс
return m[1], 0, strings.ToUpper(a[1]) + `:\` + a[2], nil
}
return "", 0, "", fmt.Errorf("%s — сетевая папка: Firebird не открывает базы по сети как файл. Укажите компьютер %s в поле «Адрес сервера» (на нём должен работать сервер Firebird), а путь — на его диске, например D:\\Fancy\\DEPO.GDB, — как в подключении Fancy", s, m[1])
case fbHostRe.MatchString(s):
m := fbHostRe.FindStringSubmatch(s)
port, _ = strconv.Atoi(m[2])
return strings.Trim(m[1], "[]"), port, m[3], nil
}
return "", 0, s, nil
}
+28
View File
@@ -115,3 +115,31 @@ func TestFBScriptsMatchDeploy(t *testing.T) {
}
}
}
func TestFBSplitConn(t *testing.T) {
cases := []struct {
in, host string
port int
path string
bad bool
}{
{`D:\Fancy\Bases\DEPO.GDB`, "", 0, `D:\Fancy\Bases\DEPO.GDB`, false},
{`/var/db/depo.fdb`, "", 0, `/var/db/depo.fdb`, false},
{`DEPO`, "", 0, `DEPO`, false},
{`10.0.0.5:D:\Fancy\Bases\DEPO.GDB`, "10.0.0.5", 0, `D:\Fancy\Bases\DEPO.GDB`, false},
{`srv-depo/3051:D:\Show Depo\Bases\SPI_DEPO.GDB`, "srv-depo", 3051, `D:\Show Depo\Bases\SPI_DEPO.GDB`, false},
{`fbsrv:/var/db/depo.fdb`, "fbsrv", 0, `/var/db/depo.fdb`, false},
{`fbsrv:DEPO`, "fbsrv", 0, `DEPO`, false},
{`\\SRV-DEPO\D:\Fancy\DEPO.GDB`, "SRV-DEPO", 0, `D:\Fancy\DEPO.GDB`, false},
{`\\SRV-DEPO\d$\Fancy\DEPO.GDB`, "SRV-DEPO", 0, `D:\Fancy\DEPO.GDB`, false},
{`inet://10.0.0.5:3050/D:\Fancy\DEPO.GDB`, "10.0.0.5", 3050, `D:\Fancy\DEPO.GDB`, false},
{`inet://10.0.0.5/depo`, "10.0.0.5", 0, `depo`, false},
{`\\SRV-DEPO\Bases\DEPO.GDB`, "", 0, "", true},
}
for _, c := range cases {
h, p, path, err := fbSplitConn(c.in)
if (err != nil) != c.bad || h != c.host || p != c.port || path != c.path {
t.Errorf("fbSplitConn(%q) = %q, %d, %q, %v", c.in, h, p, path, err)
}
}
}
+1 -1
View File
@@ -308,7 +308,7 @@ func registerIgateAdmin(mux *http.ServeMux, rc *RuntimeConfig) {
return
}
depoDB := strings.TrimSpace(r.FormValue("depo_db"))
if depoDB != "" && !validDBName(depoDB) {
if depoDB != "" && !validDBName(depoDB) && !(s.Fancy.IsFirebird() && validFBPath(depoDB)) { // у Firebird — путь к базе
jsonOut(w, map[string]any{"ok": false, "error": "имя базы DEPO — латинские буквы, цифры и _"})
return
}
@@ -156,10 +156,10 @@
<div class="dq-grid dq-srv">
<label>Адрес сервера<input type="text" name="server" data-need="адрес сервера DEPO" value="{{.Settings.Fancy.Server}}" placeholder="IP-адрес или имя\экземпляр" required autofocus spellcheck="false"></label>
<label>Порт<input type="text" name="port" value="{{if .Settings.Fancy.Port}}{{.Settings.Fancy.Port}}{{else if $pg}}5432{{else if $fb}}3050{{else}}1433{{end}}" inputmode="numeric"></label>
<label><span data-nofb>Имя базы</span><span data-fb>Путь к базе</span><input type="text" name="depo_database" data-need="имя базы DEPO" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="{{if $fb}}D:\Fancy\DEPO.GDB или псевдоним{{else}}как называется на сервере{{end}}" required {{if not $fb}}pattern="[A-Za-z][A-Za-z0-9_]*" {{end}}title="латинские буквы, цифры и _, начинается с буквы" spellcheck="false"></label>
<label><span data-nofb>Имя базы</span><span data-fb>Путь к базе</span><input type="text" name="depo_database" data-need="имя базы DEPO" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="{{if $fb}}D:\Fancy\DEPO.GDB или 10.0.0.5:D:\Fancy\DEPO.GDB{{else}}как называется на сервере{{end}}" required {{if not $fb}}pattern="[A-Za-z][A-Za-z0-9_]*" {{end}}title="латинские буквы, цифры и _, начинается с буквы" spellcheck="false"></label>
</div>
<p class="dq-note" data-nofb>Точное имя базы учёта DEPO — bj будет работать только с ней. Перед подготовкой bj проверит, что в ней есть учётные таблицы DEPO, и в чужую базу ничего не запишет.</p>
<p class="dq-note" data-fb>Путь к файлу базы учёта на сервере Firebird — как в подключении Fancy (например, <span class="dq-mono">D:\Fancy\Bases\DEPO.GDB</span>), или её псевдоним из aliases.conf. Перед подготовкой bj проверит, что это база DEPO и функции Fancy на сервере доступны.</p>
<p class="dq-note" data-fb>База обычно на другом компьютере: «Адрес сервера» — компьютер, где работает сервер Firebird, путь — на его диске (например, <span class="dq-mono">D:\Fancy\Bases\DEPO.GDB</span>) или псевдоним из aliases.conf. Можно вставить строку подключения из настройки Fancy целиком — <span class="dq-mono">10.0.0.5:D:\Fancy\Bases\DEPO.GDB</span>, адрес и порт bj возьмёт из неё. Сетевую папку (\\компьютер\папка) Firebird не открывает. Перед подготовкой bj проверит, что это база DEPO и функции Fancy на сервере доступны.</p>
</section>
<section class="dq-s">
@@ -247,7 +247,7 @@
if(['1433','5432','3050'].indexOf(f.port.value)>=0) f.port.value={mssql:'1433',postgres:'5432',firebird:'3050'}[t];
f.admin_user.placeholder=pg?'postgres':fb?'SYSDBA':'sa';
var dbi=f.depo_database; // Firebird: путь к файлу базы или псевдоним, а не имя
if(fb){ dbi.removeAttribute('pattern'); dbi.placeholder='D:\\Fancy\\DEPO.GDB или псевдоним'; }
if(fb){ dbi.removeAttribute('pattern'); dbi.placeholder='D:\\Fancy\\DEPO.GDB или 10.0.0.5:D:\\Fancy\\DEPO.GDB'; }
else { dbi.setAttribute('pattern','[A-Za-z][A-Za-z0-9_]*'); dbi.placeholder='как называется на сервере'; }
if(fb&&f.bj_user.value==='bj_user') f.bj_user.value='SYSDBA_BJ'; else if(!fb&&f.bj_user.value==='SYSDBA_BJ') f.bj_user.value='bj_user';
var jn=f.depo_job_name, dbv=(f.depo_database.value||'').trim(); // имя задания — по базе, пока не поправили руками
@@ -270,6 +270,14 @@
else { pre.textContent=''; pre.dataset.msg=x.t.trim().replace(/^./,function(c){return c.toUpperCase()})+' — скрипт появится здесь.'; } })
.catch(function(){ if(my!==seq) return; last=''; pre.classList.remove('dq-busy'); pre.textContent=''; pre.dataset.msg='Не получилось собрать скрипт — обновите страницу.'; });
}
function splitFB(){ // строка подключения Fancy целиком: «сервер[/порт]:путь», «\\сервер\D:\путь», «\\сервер\D$\путь»
if(f.db_type.value!=='firebird') return;
var v=f.depo_database.value.trim(), m;
if(/^[A-Za-z]:[\\/]/.test(v)) return;
if((m=/^\\\\([^\\/]+)[\\/]([A-Za-z]):?\$?[\\/](.*)$/.exec(v))){ f.server.value=m[1]; f.depo_database.value=m[2].toUpperCase()+':\\'+m[3]; return; }
if((m=/^([A-Za-z0-9._-]{2,})(?:\/(\d{1,5}))?:(.+)$/.exec(v))){ f.server.value=m[1]; if(m[2]) f.port.value=m[2]; f.depo_database.value=m[3]; }
}
f.depo_database.addEventListener('change', function(){ splitFB(); changed(); });
function changed(){ sync(); clearTimeout(timer); timer=setTimeout(load, 350); }
f.depo_job_name.addEventListener('input', function(){ this.dataset.auto=this.value.trim()?'0':'1'; });
f.addEventListener('input', changed); f.addEventListener('change', changed);
@@ -35,7 +35,7 @@
<form method="post" action="/admin/setup/fancy/depo-tables" style="margin-top:10px;display:grid;gap:10px;max-width:720px">
<input type="hidden" name="section" value="sec-fancy-depo-tables">
<label>База DEPO
<input type="text" name="depo_database" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="{{.DepoTables.DB}}" pattern="[A-Za-z][A-Za-z0-9_]*" style="width:100%"></label>
<input type="text" name="depo_database" value="{{.Settings.Fancy.DepoDatabase}}" placeholder="{{if .Settings.Fancy.IsFirebird}}D:\Fancy\Bases\DEPO.GDB или 10.0.0.5:D:\Fancy\Bases\DEPO.GDB{{else}}{{.DepoTables.DB}}{{end}}" {{if not .Settings.Fancy.IsFirebird}}pattern="[A-Za-z][A-Za-z0-9_]*" {{end}}style="width:100%"></label>
<label>Признак ИИС в названии брокерского договора <span class="muted" style="font-weight:400">(с чего начинается номер договора ИИС в DEPO; несколько — через запятую)</span>
<input type="text" name="iis_contract_marker" value="{{.Settings.Fancy.IISContractMarker}}" placeholder="БРИИС" style="width:100%"></label>
<label style="display:flex;gap:8px;align-items:flex-start;font-weight:600"><input type="radio" name="depo_tables_mode" value="auto" {{if eq .Settings.Fancy.DepoTablesMode "auto"}}checked{{end}}> <span>Создаёт bj <span class="muted" style="font-weight:400">— вводите логин с правами на создание таблиц в базе DEPO. Он используется один раз и не сохраняется; bj заодно выдаст права своему логину.</span></span></label>
+12 -2
View File
@@ -248,10 +248,20 @@ func (h *setupHandlers) depoSetupForm(r *http.Request) (FancyMSSQLSettings, stri
return s, "", agent, "укажите имя базы DEPO — как она называется на сервере"
}
if dbType == "firebird" {
if !validFBPath(db) {
host, p, path, err := fbSplitConn(db) // строка подключения Fancy целиком: «сервер:путь» — сервер из неё
if err != nil {
return s, "", agent, err.Error()
}
if host != "" {
s.Server = host
if p != 0 {
s.Port = p
}
}
if !validFBPath(path) {
return s, "", agent, "путь к базе DEPO — без кавычек, ; ? и #"
}
return s, db, agent, ""
return s, path, agent, ""
}
if !validDBName(db) {
return s, "", agent, "имя базы DEPO — только латинские буквы, цифры и _, начинается с буквы"