773fa8e0bf
- Шапка мастера: правый блок (шлюз НРД, пользователь, тема) снова справа — после правила [hidden] скрытое меню перестало его распирать. - Домен в шаге «Учётные записи»: bj сам находит домен и контроллеры (логин с @домен, домены поиска DNS, rootDSE DNS-серверов, SRV _ldap._tcp.dc._msdcs); адрес и домен — в расширенных настройках, открываются сами, если не нашлось. Контроллеры по очереди, LDAPS → LDAP+StartTLS. - Неудачная попытка ничего не теряет: введённое (и пароль) — черновиком, форма заполнена; причины словами (неизвестный ЦС, имя в сертификате, IP вместо имени, порт закрыт, нет в DNS). - Сертификат неизвестного центра: карточка «кем выдан, отпечаток (SHA-1 как в Windows и SHA-256)» и «Доверять и подключить»; после входа bj берёт из домена сертификат центра (AIA / Certification Authorities), подписавший сертификат контроллера, — подходят все контроллеры. То же в Настройка → Пользователи при «Проверить». - Лицензия: модуль onec-middle «1С: база мидл-офиса (тест)»; раздел настроек — адрес публикации, пользователь, пароль, проверка через OData $metadata; в мастере — в «Модулях». - ЛК (callback, API, готовность) — только при модуле «ЕСИА Финанс»; карточка режима QUIK и стили настроек как в мастере. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
129 lines
5.7 KiB
Go
129 lines
5.7 KiB
Go
package lkgateway
|
|
|
|
// База 1С мидл-офиса — модуль лицензии onec-middle (07.10.2026, тестовый). Пока это подключение: адрес
|
|
// публикации базы на веб-сервере, пользователь и пароль 1С; bj проверяет их через стандартный интерфейс OData
|
|
// ($metadata). Обмен данными с базой — следующий шаг модуля.
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// OneCSettings — подключение к базе 1С мидл-офиса.
|
|
type OneCSettings struct {
|
|
URL string `json:"url,omitempty"` // адрес публикации: http://1c-server/middle
|
|
User string `json:"user,omitempty"`
|
|
Password string `json:"password,omitempty"`
|
|
CheckedAt time.Time `json:"checked_at,omitempty"`
|
|
CheckOK bool `json:"check_ok,omitempty"`
|
|
CheckMsg string `json:"check_msg,omitempty"`
|
|
}
|
|
|
|
func (r *RuntimeConfig) UpdateOneC(s OneCSettings) error {
|
|
r.mu.Lock()
|
|
r.data.OneC = s
|
|
r.data.UpdatedAt = time.Now().UTC()
|
|
r.mu.Unlock()
|
|
return r.save()
|
|
}
|
|
|
|
// oneCBase — адрес публикации без хвоста /odata/… и без завершающего «/».
|
|
func oneCBase(u string) string {
|
|
u = strings.TrimSpace(u)
|
|
if i := strings.Index(strings.ToLower(u), "/odata/"); i >= 0 {
|
|
u = u[:i]
|
|
}
|
|
u = strings.TrimRight(u, "/")
|
|
if u != "" && !strings.Contains(u, "://") {
|
|
u = "http://" + u
|
|
}
|
|
return u
|
|
}
|
|
|
|
// saveOneC — POST /admin/setup/onec: сохранить подключение и сразу проверить его.
|
|
func (h *setupHandlers) saveOneC(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
s := h.rc.Snapshot().OneC
|
|
s.URL = oneCBase(r.FormValue("onec_url"))
|
|
s.User = strings.TrimSpace(r.FormValue("onec_user"))
|
|
if pw := r.FormValue("onec_password"); pw != "" {
|
|
s.Password = pw
|
|
}
|
|
var miss []string
|
|
for _, f := range []struct{ v, n string }{{s.URL, "адрес публикации базы"}, {s.User, "пользователь 1С"}, {s.Password, "пароль"}} {
|
|
if f.v == "" {
|
|
miss = append(miss, f.n)
|
|
}
|
|
}
|
|
if len(miss) > 0 {
|
|
setupFlash(w, r, "1С: не заполнено: "+strings.Join(miss, ", "))
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(r.Context(), 20*time.Second)
|
|
s.CheckOK, s.CheckMsg = oneCCheck(ctx, s)
|
|
cancel()
|
|
s.CheckedAt = time.Now()
|
|
if err := h.rc.UpdateOneC(s); err != nil {
|
|
setupFlash(w, r, "1С: настройки не сохранены — "+err.Error())
|
|
return
|
|
}
|
|
if s.CheckOK {
|
|
setupFlash(w, r, "1С: подключение сохранено — "+s.CheckMsg)
|
|
return
|
|
}
|
|
setupFlash(w, r, "1С: подключение сохранено, но проверка не прошла — "+s.CheckMsg)
|
|
}
|
|
|
|
// oneCCheck — база отвечает по стандартному интерфейсу OData и пускает пользователя.
|
|
func oneCCheck(ctx context.Context, s OneCSettings) (bool, string) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, oneCBase(s.URL)+"/odata/standard.odata/$metadata", nil)
|
|
if err != nil {
|
|
return false, "адрес не разобран: " + err.Error()
|
|
}
|
|
req.SetBasicAuth(s.User, s.Password)
|
|
cl := &http.Client{Transport: &http.Transport{Proxy: nil, DialContext: (&net.Dialer{Timeout: 8 * time.Second}).DialContext}}
|
|
resp, err := cl.Do(req)
|
|
if err != nil {
|
|
var ne net.Error
|
|
if errors.As(err, &ne) && ne.Timeout() {
|
|
return false, "сервер 1С не ответил вовремя — проверьте адрес и что веб-сервер с публикацией базы доступен с сервера bj"
|
|
}
|
|
if strings.Contains(err.Error(), "certificate") {
|
|
return false, "сертификат HTTPS веб-сервера 1С не доверенный — укажите адрес по http или выпустите доверенный сертификат (" + err.Error() + ")"
|
|
}
|
|
return false, "сервер 1С не отвечает: " + err.Error()
|
|
}
|
|
defer resp.Body.Close()
|
|
body, _ := io.ReadAll(io.LimitReader(resp.Body, 64<<20))
|
|
switch resp.StatusCode {
|
|
case http.StatusOK:
|
|
case http.StatusUnauthorized, http.StatusForbidden:
|
|
return false, "1С не пустила пользователя «" + s.User + "»: неверный логин или пароль, либо у пользователя нет права на внешнее подключение"
|
|
case http.StatusNotFound:
|
|
return false, "по адресу нет стандартного интерфейса OData: в конфигураторе 1С — Администрирование → Публикация на веб-сервере, отметьте «Публиковать стандартный интерфейс OData» и переопубликуйте базу"
|
|
default:
|
|
t := strings.TrimSpace(string(body))
|
|
if len([]rune(t)) > 200 {
|
|
t = string([]rune(t)[:200]) + "…"
|
|
}
|
|
return false, fmt.Sprintf("веб-сервер 1С ответил %d %s", resp.StatusCode, t)
|
|
}
|
|
if !strings.Contains(string(body), "edmx") {
|
|
return false, "по адресу отвечает не 1С (нет описания OData) — проверьте адрес публикации базы"
|
|
}
|
|
n := strings.Count(string(body), "<EntityType ")
|
|
if n == 0 {
|
|
return true, "база отвечает, пользователь входит; объекты для OData ещё не открыты — их состав задаётся в базе 1С (УстановитьСоставСтандартногоИнтерфейсаOData)"
|
|
}
|
|
return true, fmt.Sprintf("база отвечает, пользователь входит; объектов в OData — %d", n)
|
|
}
|