package lkgateway // База 1С бэк-офиса — модуль лицензии onec-back (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), "