From 867fe7bc66a93d1471952ee3351ead5e10918e2e Mon Sep 17 00:00:00 2001 From: Kavalar Date: Mon, 14 Sep 2026 16:41:44 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=BA=D1=81=D0=B8=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B2=D0=BD=D1=83=D1=82=D1=80=D0=B5=D0=BD=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9=20IP:=20=D0=B0=D0=BD=D0=B0=D0=BB=D0=B8=D0=B7=20=D0=B8=20?= =?UTF-8?q?=D1=81=D0=BA=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20REST-=D1=8D=D0=BD=D0=B4=D0=BF?= =?UTF-8?q?=D0=BE=D0=B8=D0=BD=D1=82=D1=8B=20WordPress=20(=D1=80=D0=B5?= =?UTF-8?q?=D0=B6=D0=B8=D0=BC=20proxy/direct,=20=D0=BD=D0=B0=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B9=D0=BA=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 +++++++-- assets/js/med-diag.js | 24 +++++-- med-diagnostics.php | 150 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 191 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 965e36e..1a24c1a 100644 --- a/README.md +++ b/README.md @@ -37,23 +37,44 @@ zip -r med-diagnostics.zip . -x README.md ## Настройки -**Настройки → Med Diagnostics** — поле «URL API-бэкенда» (по умолчанию `https://med.my-dpr.ru`). +**Настройки → Med Diagnostics**: + +| Поле | Описание | +|---|---| +| **Режим подключения** | `Через сервер (внутренний IP)` — браузер шлёт видео на этот WordPress, PHP проксирует запрос на внутренний IP бэкенда (по умолчанию, рекомендуется когда WordPress и API на одном сервере). `Напрямую из браузера (CORS)` — браузер обращается к публичному URL API. | +| **Внутренний URL бэкенда** | Адрес API, доступный с этого сервера (по умолчанию `http://127.0.0.1:18018`). Используется в режиме «Через сервер». | +| **Публичный URL API-бэкенда** | Публичный адрес Flask-приложения (по умолчанию `https://med.my-dpr.ru`). Используется в режиме «Напрямую» и для ссылки «Поделиться». | + Можно также переопределить кодом: ```php add_filter('med_diag_api_url', fn() => 'https://другой-домен.ru'); +add_filter('med_diag_internal_url', fn() => 'http://10.0.0.5:18018'); +add_filter('med_diag_mode', fn() => 'direct'); ``` ## Как это работает -- Плагин выводит ту же вёрстку, что и главная страница сервиса (стили с префиксом `med-`, чтобы не конфликтовать с темой). -- JS отправляет видео на API-бэкенд (`POST /analyze`) с абсолютным URL — поэтому нужен CORS на стороне Flask. +- **Режим «Через сервер» (по умолчанию)**: JS отправляет видео на REST-эндпоинт этого же WordPress (`/wp-json/med-diag/v1/analyze`), PHP пересылает его на внутренний IP бэкенда (`http://127.0.0.1:18018/analyze`). Скачивания PDF/DOCX/видео идут через `/wp-json/med-diag/v1/download//`. CORS не нужен — все запросы same-origin. +- **Режим «Напрямую»**: JS отправляет видео на публичный URL API (`POST /analyze`) — тогда нужен CORS на стороне Flask. - Результат (сводка, показатели, рекомендации, кнопки PDF/DOCX/видео/поделиться) рендерится на странице WordPress. - Сканирование с камеры работает на мобильных (canvas 720×720 + MediaRecorder). +## Требования к серверу WordPress (режим «Через сервер») + +Видео сначала загружается в PHP, поэтому в `php.ini` должны быть подняты лимиты: + +```ini +upload_max_filesize = 100M +post_max_size = 100M +max_execution_time = 300 +``` + +Проверить текущие значения: `php -i | grep -E "upload_max_filesize|post_max_size|max_execution_time"`. + ## Требования к бэкенду (Flask) -В `app.py` серверного репозитория уже добавлена поддержка CORS: +В `app.py` серверного репозитория уже добавлена поддержка CORS (нужна только в режиме «Напрямую»): - `Access-Control-Allow-Origin` — по умолчанию `*`, можно ограничить через env `CORS_ORIGINS` (список через запятую, например `https://site1.ru,https://site2.ru`). - Preflight `OPTIONS /analyze` возвращает `204`. @@ -69,7 +90,8 @@ add_filter('med_diag_api_url', fn() => 'https://другой-домен.ru'); - Шорткод используйте **один раз на странице** (id элементов фиксированы). - Для работы сканирования с камеры нужен HTTPS на сайте WordPress (getUserMedia требует secure context). -- Загрузка видео идёт напрямую в API-бэкенд — убедитесь, что `client_max_body_size` в nginx бэкенда ≥ 100 МБ. +- В режиме «Через сервер» видео загружается в PHP — следите за лимитами `upload_max_filesize`/`post_max_size`/`max_execution_time` (см. выше). +- Ссылка «Поделиться» всегда ведёт на публичный URL API (`/r/`). ## Структура diff --git a/assets/js/med-diag.js b/assets/js/med-diag.js index 3fc0ee3..47f1e53 100644 --- a/assets/js/med-diag.js +++ b/assets/js/med-diag.js @@ -3,6 +3,9 @@ 'use strict'; var API_URL = (window.MED_DIAG && MED_DIAG.apiUrl) || 'https://med.my-dpr.ru'; + var PROXY_URL = (window.MED_DIAG && MED_DIAG.proxyUrl) || ''; + var MODE = (window.MED_DIAG && MED_DIAG.mode) || 'proxy'; + var USE_PROXY = (MODE === 'proxy') && !!PROXY_URL; // Абсолютный URL: если ссылка относительная — добавляем домен API function absUrl(url) { @@ -10,6 +13,13 @@ return url.indexOf('http') === 0 ? url : API_URL + url; } + // URL для скачивания: в режиме прокси — через этот же WordPress (PHP → внутренний IP) + function downloadUrl(path) { + if (!path) return path; + if (path.indexOf('http') === 0) return path; + return USE_PROXY ? PROXY_URL + path : API_URL + path; + } + var uploadArea = document.getElementById('uploadArea'); var fileInput = document.getElementById('fileInput'); var analyzeBtn = document.getElementById('analyzeBtn'); @@ -61,11 +71,15 @@ var formData = new FormData(); formData.append('video', blob, filename); + // В режиме прокси шлём на этот же WordPress (PHP перешлёт на внутренний IP бэкенда), + // иначе — напрямую на публичный URL API. + var analyzeUrl = USE_PROXY ? PROXY_URL + '/analyze' : API_URL + '/analyze'; + // Автоповтор при обрыве соединения (нестабильная мобильная сеть) var MAX_ATTEMPTS = 3; for (var attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { try { - var response = await fetch(API_URL + '/analyze', { + var response = await fetch(analyzeUrl, { method: 'POST', body: formData }); @@ -119,7 +133,7 @@ var html = '
Ошибка: ' + msg + '
'; if (videoUrl) { html += '
' + - '🎬 Скачать записанное видео' + + '🎬 Скачать записанное видео' + '
'; } resultContent.innerHTML = html; @@ -134,10 +148,10 @@ // Кнопки скачивания (вверху блока результатов) if (data.share_id) { html += ''; diff --git a/med-diagnostics.php b/med-diagnostics.php index 449544a..b00527b 100644 --- a/med-diagnostics.php +++ b/med-diagnostics.php @@ -2,7 +2,7 @@ /** * Plugin Name: Med Diagnostics * Description: Интеграция сервиса «Диагностика по видео» (med.my-dpr.ru) в WordPress. Выводит вёрстку сервиса через шорткод [med_diagnostics]. - * Version: 1.0.0 + * Version: 1.1.0 * Author: apuc * License: GPL-2.0-or-later * Text Domain: med-diagnostics @@ -18,11 +18,12 @@ if ( ! defined( 'ABSPATH' ) ) { exit; // Прямой доступ запрещён } -define( 'MED_DIAG_VERSION', '1.0.0' ); +define( 'MED_DIAG_VERSION', '1.1.0' ); define( 'MED_DIAG_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); /** * URL API-бэкенда (Flask-приложение med.my-dpr.ru). + * Используется в режиме «напрямую» и для публичных ссылок (share). * Можно переопределить фильтром: add_filter('med_diag_api_url', fn() => 'https://...'); */ function med_diag_api_url() { @@ -30,6 +31,26 @@ function med_diag_api_url() { return apply_filters( 'med_diag_api_url', untrailingslashit( $url ) ); } +/** + * Внутренний URL API-бэкенда (для проксирования через PHP). + * По умолчанию — внутренний IP сервера, где стоит и WordPress, и API. + * Можно переопределить фильтром: add_filter('med_diag_internal_url', fn() => 'http://10.0.0.5:18018'); + */ +function med_diag_internal_url() { + $url = get_option( 'med_diag_internal_url', 'http://127.0.0.1:18018' ); + return apply_filters( 'med_diag_internal_url', untrailingslashit( $url ) ); +} + +/** + * Режим подключения к бэкенду: + * 'proxy' — браузер шлёт запрос на этот же WordPress, PHP проксирует на внутренний IP (по умолчанию); + * 'direct' — браузер шлёт запрос напрямую на публичный URL API (нужен CORS на бэкенде). + */ +function med_diag_mode() { + $mode = get_option( 'med_diag_mode', 'proxy' ); + return apply_filters( 'med_diag_mode', in_array( $mode, array( 'proxy', 'direct' ), true ) ? $mode : 'proxy' ); +} + /* ─── Регистрация ассетов ─────────────────────────────────── */ add_action( 'wp_enqueue_scripts', 'med_diag_register_assets' ); @@ -49,10 +70,101 @@ function med_diag_register_assets() { true // в футере ); wp_localize_script( 'med-diag-js', 'MED_DIAG', array( - 'apiUrl' => med_diag_api_url(), + 'apiUrl' => med_diag_api_url(), + 'proxyUrl' => rest_url( 'med-diag/v1' ), + 'mode' => med_diag_mode(), ) ); } +/* ─── Прокси на внутренний IP (REST) ──────────────────────── */ + +add_action( 'rest_api_init', 'med_diag_register_routes' ); + +function med_diag_register_routes() { + // Анализ: браузер → этот WordPress → внутренний IP бэкенда + register_rest_route( 'med-diag/v1', '/analyze', array( + 'methods' => 'POST', + 'permission_callback' => '__return_true', + 'callback' => 'med_diag_proxy_analyze', + ) ); + + // Скачивания (PDF/DOCX/видео): /download// + register_rest_route( 'med-diag/v1', '/download/(?P[^/]+)/(?P[a-z]+)', array( + 'methods' => 'GET', + 'permission_callback' => '__return_true', + 'callback' => 'med_diag_proxy_download', + ) ); +} + +function med_diag_proxy_analyze( $request ) { + $files = $request->get_file_params(); + if ( empty( $files['video'] ) ) { + return new WP_Error( 'med_diag_no_video', 'Файл video не получен', array( 'status' => 400 ) ); + } + $file = $files['video']; + if ( $file['error'] !== UPLOAD_ERR_OK ) { + return new WP_Error( 'med_diag_upload_error', 'Ошибка загрузки файла (код ' . $file['error'] . ')', array( 'status' => 400 ) ); + } + if ( $file['size'] > 100 * 1024 * 1024 ) { + return new WP_Error( 'med_diag_too_large', 'Видео больше 100 МБ', array( 'status' => 413 ) ); + } + + $boundary = wp_generate_password( 24, false ); + $body = '--' . $boundary . "\r\n"; + $body .= 'Content-Disposition: form-data; name="video"; filename="' . basename( $file['name'] ) . '"' . "\r\n"; + $body .= 'Content-Type: ' . $file['type'] . "\r\n\r\n"; + $body .= file_get_contents( $file['tmp_name'] ) . "\r\n"; + $body .= '--' . $boundary . "--\r\n"; + + $response = wp_remote_post( med_diag_internal_url() . '/analyze', array( + 'timeout' => 300, + 'headers' => array( 'Content-Type' => 'multipart/form-data; boundary=' . $boundary ), + 'body' => $body, + ) ); + + if ( is_wp_error( $response ) ) { + return new WP_Error( 'med_diag_proxy_error', 'Бэкенд недоступен: ' . $response->get_error_message(), array( 'status' => 502 ) ); + } + + $status = wp_remote_retrieve_response_code( $response ); + $data = json_decode( wp_remote_retrieve_body( $response ), true ); + if ( json_last_error() !== JSON_ERROR_NONE ) { + return new WP_Error( 'med_diag_bad_response', 'Некорректный ответ бэкенда', array( 'status' => 502 ) ); + } + + return new WP_REST_Response( $data, $status ); +} + +function med_diag_proxy_download( $request ) { + $id = rawurlencode( $request['id'] ); + $type = $request['type']; + + $response = wp_remote_get( med_diag_internal_url() . '/download/' . $id . '/' . $type, array( + 'timeout' => 300, + ) ); + + if ( is_wp_error( $response ) ) { + return new WP_Error( 'med_diag_proxy_error', 'Бэкенд недоступен', array( 'status' => 502 ) ); + } + + $status = wp_remote_retrieve_response_code( $response ); + if ( $status !== 200 ) { + return new WP_Error( 'med_diag_download_error', 'Ошибка скачивания (HTTP ' . $status . ')', array( 'status' => $status ) ); + } + + $body = wp_remote_retrieve_body( $response ); + $headers = wp_remote_retrieve_headers( $response ); + $content_type = isset( $headers['content-type'] ) ? $headers['content-type'] : 'application/octet-stream'; + + $filename = 'diagnostic_' . $request['id'] . '.' . ( 'video' === $type ? 'webm' : $type ); + + header( 'Content-Type: ' . $content_type ); + header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); + header( 'Content-Length: ' . strlen( $body ) ); + echo $body; + exit; +} + /* ─── Шорткод ─────────────────────────────────────────────── */ add_shortcode( 'med_diagnostics', 'med_diag_shortcode' ); @@ -150,6 +262,16 @@ function med_diag_register_settings() { 'sanitize_callback' => 'esc_url_raw', 'default' => 'https://med.my-dpr.ru', ) ); + register_setting( 'med_diag_settings', 'med_diag_internal_url', array( + 'type' => 'string', + 'sanitize_callback' => 'esc_url_raw', + 'default' => 'http://127.0.0.1:18018', + ) ); + register_setting( 'med_diag_settings', 'med_diag_mode', array( + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => 'proxy', + ) ); } function med_diag_settings_page() { @@ -160,11 +282,29 @@ function med_diag_settings_page() { - + + + + + + + + +