$result = \Bitrix\Catalog\Model\Price::delete($arFields["ID"]); $result->__destruct(); // Warning. Потому что произошла ошибка, и не сделали $result->isSuccess(), $result->getErrors(), $result->getErrorMessages() |
Warning: CAskaronMyTest: запрещено удалять значение цены in /bitrix/modules/main/lib/orm/data/result.php on line 87 Call Stack # Time Memory Function Location {main}( ) .../test5.php:0 Bitrix\Main\ORM\Data\Result->__destruct( ) .../test5.php:65 trigger_error( $message = 'CAskaronMyTest: запрещено удалять значение цены', $error_level = 512 ) .../result.php:87 |
$eventManager = \Bitrix\Main\EventManager::getInstance(); $eventManager->addEventHandler("catalog", "Bitrix\\Catalog\\Model\\Price::OnBeforeDelete", array("CAskaronMyTest", "PriceOnBeforeDelete"), false, 50 ); class CAskaronMyTest { public static function PriceOnBeforeDelete( \Bitrix\Main\Event $event ) { $result = new \Bitrix\Main\Entity\EventResult(); $arErrors = array(); $arErrors[] = new \Bitrix\Main\Entity\EntityError( __CLASS__.": запрещено удалять значение цены" ); $result->setErrors($arErrors); return $result; } } // Удалить цены для товара $PRODUCT_ID = 15709; if ( \Bitrix\Main\Loader::includeModule( "catalog" ) ) { $res = \Bitrix\Catalog\PriceTable::getList( array( "filter" => array( "=PRODUCT_ID" => $PRODUCT_ID, ), ) ); while ($arFields = $res->fetch()) { $result = \Bitrix\Catalog\Model\Price::delete($arFields["ID"]); $result->__destruct(); // Warning. Потому что произошла ошибка, и не сделали $result->isSuccess(), $result->getErrors(), $result->getErrorMessages() } } |
$result = \Bitrix\Catalog\Model\Price::delete( $arFields["ID"] ); if (!$result->isSuccess()) { // нужна проверка isSuccess, чтобы не было Warning // https://dev.1c-bitrix.ru/community/webdev/user/25773/blog/45090/ //echo "Ошибка"; //echo "<pre>"; print_r( $result->getErrors() ) ;echo "</pre>"; //echo "<pre>"; print_r( $result->getErrorMessages() ) ;echo "</pre>"; } $result->__destruct(); |
В предыдущих примерах есть нюанс: запрос на обновление данных вызывается без проверки результата. Если запрос не прошел из-за "проваленной" валидации, и не была вызвана проверка isSuccess(), система сгенерирует E_USER_WARNING со списком ошибок, который можно будет увидеть в логе сайта (если соответствующим образом настроить .settings.php). |
Самое плохое, если Warning появляется при обмене с 1С, или при самодельном импорте/экспорте. В таких ситуациях, где мы не можем исправить чужой скрипт вывод, вывод предупреждений на сайте надо отключить.
namespace My\Helper\Component; use Bitrix\Main\Application; use Bitrix\Main\IO; use Bitrix\Main\IO\Directory; use CBitrixComponent; /** * Добавляет компоненту функционал * копирования расширения из шаблона в папку /local/js. */ trait Extensions { /** * Преобразовывает класс компонента в часть пути для расширения. * Пример: MyComponentClass в my/component/class */ protected function componentClassName2Path(): string { return strtolower( preg_replace( '/([A-Z])/', '/$1', __CLASS__ ) ); } /** * Копирование расширений из директории /js/ шаблона компонента * в директорию /local/js/ расширений. * @return void */ public function installComponentExtensions(): void { /** * @var $this CBitrixComponent */ $this->initComponentTemplate(); $template = $this->getTemplate(); if(!$template) return; $path = $template->getFolder(); $serverRoot = Application::getDocumentRoot(); $templateJsPath = sprintf('%s%s/js', $serverRoot, $path); $templateJsDir = new IO\Directory($templateJsPath); try { if(!$templateJsDir->isExists()) { return; } $files = $templateJsDir->getChildren(); } catch (IO\FileNotFoundException) { return; } /** * @var IO\Directory|IO\File $extDir */ foreach ($files as $directory) { if (!$directory->isDirectory()) continue; /** @var IO\Directory $file */ $this->installComponentExtension($directory); } } /** * Копирует директорию в /local/js/my/component/class * для компонента MyComponentClass. * Получаем расширение my.component.class.dirname * @param Directory $dir * @return void */ public function installComponentExtension(IO\Directory $dir): void { $pathByTemplateJs = $dir->getName(); $pathByComponent = $this->componentClassName2Path(); $serverRoot = Application::getDocumentRoot(); $localDir = sprintf('%s/local/js/%s/%s', $serverRoot, $pathByComponent, $pathByTemplateJs ); $bundleTimestampFile = $dir->getPath() . '/.timestamp'; $localTimestampFile = $localDir . '/.timestamp'; $bundleTimestamp = (int)file_get_contents($bundleTimestampFile); if($bundleTimestamp == 0) return; $localTimestamp = (int)file_get_contents($localTimestampFile); $isLocalNotExist = $localTimestamp == 0; $isBundleNewer = !$isLocalNotExist && $localTimestamp < $bundleTimestamp; if($isBundleNewer || $isLocalNotExist) { copyDirFiles( $dir->getPath(), $localDir, true, true ); } } } |
use My\Helper\Component\Extensions; class MyComponentClass extends CBitrixComponent { // Трейт копирования расширения из шаблона компонента в /local/js/ use Extensions; public function executeComponent() { // Функция из трейта. $this->installComponentExtensions(); $this->includeComponentTemplate(); } } |
const pluginVue = require('rollup-plugin-vue'); const path = require('path'); const fs = require('fs'); const dayjs = require('dayjs'); module.exports = { input: 'src/index.js', output: 'dist/callback.bundle.js', namespace: 'BX.Catalog', adjustConfigPhp: false, browserslist: false, sourceMaps: false, plugins: { resolve: true, custom: [ pluginVue(), { name: 'bundle-timestamp', buildEnd: async () => { fs.writeFileSync( path.resolve('.timestamp'), dayjs().format('YYYYMMDDHHmm') ) } } ], }, }; |
<?php if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die(); // my/component/class - название компонента. // v1 - директория с расширением в директории /js/ шаблона компонента, в данном случае название равно версии расширения. // dist/callback.bundle.js путь из конфига bundle.config.js для сборщика bitrix. return [ 'css' => '/local/js/my/component/class/v1/dist/callback.bundle.css', 'js' => '/local/js/my/component/class/v1/dist/callback.bundle.js', 'rel' => [], 'skip_core' => true, ]; |
<?php if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die(); use Bitrix\Main\Application; use Bitrix\Main\Localization\Loc; // Чтобы использовать один файл фраз для js-расширения и шаблона. $extLangFile = Application::getDocumentRoot() . $templateFolder . '/js/v1/config.php'; Loc::loadLanguageFile($extLangFile); ?> <!-- Точка монтирования расширения --> <div data-catalog-callback data-callback-hash="#call_me" data-callback-fallback-url="mailto:mail@example.com" hidden ></div> <!-- Ссылка не активна до монтирования или отработки фоллбэка --> <a href="#call_me" class="disabled"> <?= Loc::getMessage('MY_COMPONENT_CLASS_CALL_ME') ?> </a> |
... // Выше поиск точки монтирования, сбор параметров и триггеры монтирования. // Обязательная часть. // Итоговое название расширения для загрузки: // 1) название класса компонента MyComponentClass записываем как my.component.class. // 2) добавляем название директории v1 с расширением из /js/ шаблона. BX.loadExt('my.component.class.v1').then(function(exports) { try { // BX.Catalog - неймспейс из bundle.config.js // Callback - название класса расширения. new BX.Catalog.Callback({ el: node, // Нода в DOM c атрибутом data-catalog-callback hash: hash, // Значение атрибута data-callback-hash }); // Расширение готово к использованию. $('[href="' + hash + '"]').removeClass('disabled'); } catch (e) { showFallBack(); } }); // Ниже функция фоллбэка. ... |
import {Type} from 'main.core'; import {Vue} from 'ui.vue'; import app from './App.vue'; export class Callback { constructor(options) { const { el, hash, } = options; Vue.create({ el, data: () => ({ hash, }), components: { 'catalog-callback-wrapper': app, }, template: `<catalog-callback-wrapper :hash="hash"/>` }) } } |
export default { computed: { localize(state) { const prefix = 'MY_COMPONENT_CLASS_'; const phrases = BX.Vue.getFilteredPhrases(prefix); return Object.keys(phrases) .reduce( (carry, key) => { carry[key.replace(prefix,'')] = phrases[key]; return carry; }, {} ); } }, } |
<?php $MESS['MY_COMPONENT_CLASS_CALL_ME'] = 'Перезвони мне'; $MESS['MY_COMPONENT_CLASS_INTRO'] = 'Оставьте свой номер телефона и мы свяжемся с вами в рабочее время'; $MESS['MY_COMPONENT_CLASS_INTRO_OFFICE_OPEN'] = 'Оставьте свой номер телефона и мы свяжемся с вами в течение 15 минут'; |
namespace My\Components\Controllers; use Bitrix\Main\Engine; class CatalogCallback extends Engine\JsonController { public function isOfficeOpenAction() { $isOpen = date('H') > 10 && date('H') < 20; return [ // Возвращаем коды фраз. 'resultCode' => $isOpen ? 'INTRO_OFFICE_OPEN' : 'INTRO' ]; } } |
<sc ript> import localize from './localize'; export default { mixins: [localize], data() { return { introText: '', }; }, methods: { getIntroText() { const request = BX.ajax.runComponentAction( 'my:component.class', 'isOfficeOpen', { mode:'ajax', json: {} } ); request.then(({data}) => { this.introText = this.localize[data.resultCode] || this.localize.INTRO; }).catch(() => { this.introText = this.localize.INTRO; }); } }, mounted() { this.introText = this.localize.INTRO; this.getIntroText(); this.isOpenChecker = setInterval(this.getIntroText, 60*5*1000); }, beforeDestroy() { clearInterval(this.isOpenChecker); } }; </sc ript> |
Nostalgie en Direct : Plongez dans l'univers intemporel de la musique
La musique a ce pouvoir magique de nous transporter dans le temps, de réveiller des souvenirs enfouis et de nous faire revivre des moments précieux. Si vous êtes un amoureux des grands classiques,
Nostalgie en direct est votre compagnon idéal pour un voyage musical inoubliable.
Бизнес-процессы (bizproc) 24.200.0 от 10.04.2024 |
Дизайнер бизнес-процессов (bizprocdesigner) 24.600.0 от 28.10.2024 Мобильные бизнес-процессы (bizprocmobile) 24.200.0 от 10.04.2024 CRM (crm) 24.1000.0 от 01.11.2024 |
Наш документ: {=System:HostUrl}{{=getdocumenturl()}} 1. Перейдите по ссылке и заполните форму для дополнительных сведений к документу: {=System:HostUrl}/askaron/test.php?ELEMENT_ID={{ID элемента}}&WORKFLOW_ID={=Workflow:ID} 2. После заполнения формы нажмите «Утвердить дополнительные сведения к документу» в этой форме. |
location @php { |
location ~* /\.ht { deny all; } location ~ ^/(?!(\.well-known)) { if (!-e $request_filename) { rewrite ^(.*)$ /bitrix/urlrewrite.php last; } } if (!-f $request_filename) { rewrite ^(.*)/index.php$ $1/ redirect; } |
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php"); use Bitrix\Main\Context; use Bitrix\Main\Application; $request = Context::getCurrent()->getRequest(); $connection = Application::getInstance()->getConnection(); $result = array_values($connection->query("SHOW TABLES")->fetchAll()); if ($request->getPost('table')) { $output = ''; foreach ($request->getPost('table') as $table) { $showTableQuery = "SHOW CRE ATE TABLE " . $table; $showTableResult = $connection->query($showTableQuery)->fetchAll(); foreach($showTableResult as $showTableRow) { $output .= "\n\n" . $showTableRow["Cre ate Table"] . ";\n\n"; } $selectQuery = "SEL ECT * FR OM " . $table; $rows = $connection->query($selectQuery)->fetchAll(); foreach ($rows as $row) { $tableColumnArray = array_keys($row); $tableValueArray = array_values($row); $output .= "\nINSERT INTO $table ("; $output .= "" . implode(", ", $tableColumnArray) . ") VALUES ("; $output .= "'" . implode("','", $tableValueArray) . "');\n"; } //dump($output); } $fileName = $_SERVER['DOCUMENT_ROOT']. '/database_backup_on_' . date('y-m-d') . '.sql'; $fileHandle = fopen($fileName, 'w+'); fwrite($fileHandle, $output); fclose($fileHandle); header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename=' . basename($fileName)); header('Content-Transfer-Encoding: binary'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($fileName)); ob_clean(); flush(); readfile($fileName); unlink($fileName); } ?> <!DO CTYPE html> <ht ml> <head> <title>Создать бекап базы</title> <sc ript src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></sc ript> <li nk href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> </head> <body> <br /> <div class="container"> <div class="row"> <h2 align="center">Создать бекап базы</h2> <br /> <fo rm method="post" id="export_form"> <h3>Выберите таблицы для бекапа</h3> <?php foreach($result as $table) { $table = array_values($table); ?> <div class="checkbox"> <label><input type="checkbox" class="checkbox_table" name="table[]" value="<?=$table[0];?>" /> <?=$table[0];?></label> </div> <?php } ?> <div class="form-group"> <input type="submit" name="submit" id="submit" class="btn btn-info" value="Export" /> </div> </form> </div> </div> </body> </html> <sc ript> $(document).ready(function() { $('#submit').click(function() { var count = 0; $('.checkbox_table').each(function() { if($(this).is(':checked')) { count = count + 1; } }); if(count > 0) { $('#export_form').submit(); } else { alert("Выберите как минимум одну таблицу для экспорта"); return false; } }); }); </sc ript> |
use Bitrix\Main\ORM\Query\QueryHelper; $entity = Iblock::wakeUp($iblockId)->getEntityDataClass(); $referencePropCode = 'PROCEDURE_ID'; // код св-ва тип "Привязка к элементам" которое используется для связи элементов инфоблоков $query = $entity::query() ->setSelect([ 'ID', 'NAME', 'PREVIEW_TEXT', 'PREVIEW_PICTURE' => '', $referencePropCode .'_ID_VALUE' => $referencePropCode, $referencePropCode . '.ELEMENT.NAME', $referencePropCode . '.ELEMENT.COLOR.VALUE' ]) ->setOrder(["ID" => "desc"]) ->setFilter([]) ->setLimit(10) ->setOffset(0); $collection = QueryHelper::decompose($query); foreach ($collection as $element) { $element->getId(); } |
try { if (!\Bitrix\Main\Loader::includeModule('disk')) throw new \Exception('Не подключен модуль disk!'); $driver = \Bitrix\Disk\Driver::getInstance(); $storage = $driver->getStorageByCommonId('shared_files_' . SITE_ID); if (!$storage) throw new \Exception('Не определено хранилище!'); $folder = $storage->addFolder([ 'CODE' => 'budget_base_31_2024', 'NAME' => date('Y') ], []); if ($folder) { echo "Директория \"{$folder->getName()}\" добавлена в хранилище \"{$storage->getName()}\""; } } catch (\Throwable $e) { echo $e->getMessage(); } |
use Bitrix\Disk\Folder; use Bitrix\Main\Engine\CurrentUser; try { if (!\Bitrix\Main\Loader::includeModule('disk')) throw new \Exception('Не подключен модуль disk!'); $folder = Folder::load([ 'CODE' => 'budget_base_31_2024', ]); if (!$folder) throw new \Exception('Не определена директория на диске'); $userId = CurrentUser::get()->getId(); $result = $folder->deleteTree($userId); if ($result) { echo "Директория \"{$folder->getName()}\" удалена из хранилища"; } } catch (\Throwable $e) { echo $e->getMessage(); } |
\Bitrix\Main\Loader::includeModule('sale'); $paymentTable = \Bitrix\Sale\PaymentCollection::getList([ 'select' => ['ID', 'DATE_PAID', 'SUM'], 'filter' => [ '=PAID' => 'Y', 'PAY_SYSTEM_ID' => 12, '>=DATE_PAID' => new \Bitrix\Main\Type\DateTime('15.07.2023 00:00:00'), '<=DATE_PAID' => new \Bitrix\Main\Type\DateTime('30.10.2023 14:00:00'), ] ]); $xml = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . PHP_EOL; $xml .= '<main>' . PHP_EOL; while ($item = $paymentTable->fetch()) { $timestamp = $item['DATE_PAID']->format('d.m.Y H:i:s'); $id = $item['ID']; $sum = number_format((float)$item['SUM'], 2, '.', ''); $check = <<<XM L <check> <timestamp>$timestamp</timestamp> <external_id>sell_correction_$id</external_id> <is_bso>false</is_bso> <correction> <operation>sell_correction</operation> <company> <sno>usn_income</sno> <inn>9718129333</inn> <payment_address>https://example.ru/</payment_address> </company> <correction_info> <type>self</type> <base_date>2023-10-31</base_date> <base_number>1</base_number> <base_name>Акт</base_name> </correction_info> <payments> <payment> <type>1</type> <sum>$sum</sum> </payment> </payments> <vats> <vat> <type>none</type> <sum>0</sum> </vat> </vats> <cashier>Иванов Иван Иванович</cashier> </correction> </check> XML; $xml .= $check . PHP_EOL; } $xml .= '</main>'; echo $xml; |
Многие обратили внимание на неожиданно большое количество условно-бесплатных приложений в маркетплейсе Б24 и преогромное количество вакансий с содержанием "интеграция с 1С и внешними системами"
Есть много готовых удобных инструментов для интеграции, требующие дополнительных ресурсов. Удобные, в два-три клика. Сегодня расскажу про "30 строк кода" или новую тему для обучения в "школах программирования за месяц" "Вложи 20тр, получи 420тр" . Спасибо Равшану Намазову и его партнеру Юлию Кирюше , а то бы мне и в голову не пришла идея опубликовать статью и писать код (
Обмен данными через Web Сервисы ? Теперь это сделать можно легко и быстро. (1С web-service, которую может настроить выпускник ВУЗа) Со стороны Bitrix24 - не надо писать апплеты с валидацией, не надо заморачиваться с токенизацией и форматом данных и подробной документацией, тестированием. Есть FastAPI и fastBitrix24. Весь код представлен на
Для организации обмена данными между 1С и Bitrix24 с использованием PHP, FastAPI и 1С, можно следовать следующей схеме:
Схема обмена данными
Шаги реализации
В вашем бизнес-процессе Bitrix24, используйте curl для отправки данных на FastAPI приложение. Пример кода на PHP:
<?php $data = array( "name" => "Example Name", "email" => "example@example.com" ); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "http://your-fastapi-server/api/endpoint"); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json' )); $response = curl_exec($ch); curl_close($ch); echo $response; ?> |
2. FastAPI приложение
Создайте приложение на FastAPI, которое будет принимать запросы от Bitrix24 и отправлять их в 1С. Пример кода на Python с использованием FastAPI и requests:
from fastapi import FastAPI, Request import requests import json app = FastAPI() @app.post("/api/endpoint") async def handle_request(request: Request): data = await request.json() xdto_data = { "name": data["name"], "email": data["email"] } response = requests.post( "http://your-1c-server/api/endpoint", json=xdto_data, headers={"Content-Type": "application/json"} ) return {"status": "success", "1c_response": response.json()} |
3. Обработка запроса в 1С
Создайте обработчик в 1С, который будет принимать запросы и обрабатывать данные в формате XDTO. Пример кода на 1С
Процедура ОбработатьЗапрос(Запрос) HTTPЗапрос = Новый HTTPЗапрос(Запрос); ТелоЗапроса = HTTPЗапрос.ПолучитьТелоКакСтроку(); Данные = СтрНайти(ТелоЗапроса); // Пример обработки данных Имя = Данные["name"]; ЭлектроннаяПочта = Данные["email"]; // Обработка данных (сохранение в базе и т.д.) HTTPОтвет = Новый HTTPОтвет(200); HTTPОтвет.УстановитьТело(Новый HTTPСообщение(Новый Строка(Формат("Данные успешно получены: %1", Имя)))); Запрос.ОтправитьОтвет(HTTPОтвет); КонецПроцедуры |
Пример схемы взаимодействия
Пример XDTO данных
Формат XDTO данных может быть любым, но для примера это может быть JSON:
{ "name": "Example Name", "email": "example@example.com" } |
Таким образом, схема обмена данными между 1С и Bitrix24 с использованием PHP, FastAPI и 1С будет состоять из четко определенных шагов по передаче данных между системами, обеспечивая гибкость и масштабируемость интеграции.
Весь код представлен на
# Содержимое command-every-second.sh #!/bin/bash command=$@ # Пробелы и косые в команде заменяются на нижние подчеркивания. no_spaces=`echo $command | sed -e 's/\s/_/g' -e 's|/|_|g'` # Имя файла для блокировки на время выполнения основной команды. lockfile=/tmp/$no_spaces.lock # Раз в секунду пытаемся поставить блокировку на команду и выполнить. for i in {1..60} do /usr/bin/flock -n $lockfile $command /bin/sleep 1 done rm $lockfile # Пример команды для крона. crontab -e * * * * * nice -n 1 ionice -c2 -n4 /var/www/www-root/data/command-every-second.sh /opt/php81/bin/php -f /var/www/www-root/data/www/example.ru/bitrix/php_interface/cron_events.php >/dev/null 2>&1 |
yum install brotli #Сборка динамических модулей для NGINX https://serverdiary.com/linux/how-to-install-and-configure-nginx-brotli/ nginx -V wget http://nginx.org/download/nginx-1.16.1.tar.gz tar zxvf nginx-1.16.1.tar.gz git clone https://github.com/google/ngx_brotli.git cd ngx_brotli/ git submodule update --init cd ../nginx-1.16.1 yum groupinstall 'Development Tools' -y yum install gcc-c++ flex bison yajl yajl-devel curl-devel curl GeoIP-devel doxygen zlib-devel yum install lmdb lmdb-devel libxml2 libxml2-devel ssdeep ssdeep-devel lua lua-devel yum install pcre-devel yum install openssl-devel ./configure --add-dynamic-module=../ngx_brotli ...сюда копируем опции сборки из вывода nginx -V... make modules cp objs/ngx_http_brotli_static_module.so /etc/nginx/modules cp objs/ngx_http_brotli_filter_module.so /etc/nginx/modules chmod 644 /etc/nginx/modules/ngx_http_brotli_static_module.so chmod 644 /etc/nginx/modules/ngx_http_brotli_filter_module.so |
vi /etc/nginx/nginx.conf ...... error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; load_module modules/ngx_http_brotli_filter_module.so; load_module modules/ngx_http_brotli_static_module.so; ...... server { ....... ....... brotli on; brotli_static on; # for static compression brotli_comp_level 6; # this setting can vary from 1-11 brotli_types text/xml image/svg+xml application/x-font-ttf image/vnd.microsoft.icon application/x-font-opentype application/json font/eot application/vnd.ms-fontobject application/javascript font/otf application/xml application/xhtml+xml text/javascript application/x-javascript text/plain application/x-font-truetype application/xml+rss image/x-icon font/opentype text/css image/x-win-bitmap; gzip on; ....... ....... } ....... nginx -t nginx -s reload |
yum install libwebp-tools vi ~/webp-convert.sh # Содержимое ~/webp-convert.sh #!/bin/bash # Скрипт для первоначальной конвертации картинок в конкретной директории. # converting JPEG images find $1 -type f -and \( -iname "*.jpg" -o -iname "*.jpeg" \) \ -exec bash -c ' webp_path=$(sed 's/\.[^.]*$/.webp/' <<< "$0"); if [ ! -f "$webp_path" ]; then cwebp -quiet -q 90 "$0" -o "$webp_path"; jpg_size=$(wc -c "$0" | cut -d " " -f 1); webp_size=$(wc -c "$webp_path" | cut -d " " -f 1); if [ $jpg_size -lt $webp_size ]; then if [ -f "$webp_path" ]; then $(rm -f "$webp_path"); fi; fi; fi;' {} \; # converting PNG images find $1 -type f -and -iname "*.png" \ -exec bash -c ' webp_path=$(sed 's/\.[^.]*$/.webp/' <<< "$0"); if [ ! -f "$webp_path" ]; then cwebp -quiet -lossless "$0" -o "$webp_path"; png_size=$(wc -c "$0" | cut -d " " -f 1); webp_size=$(wc -c "$webp_path" | cut -d " " -f 1); if [ $png_size -lt $webp_size ]; then if [ -f "$webp_path" ]; then $(rm -f "$webp_path"); fi; fi; fi;' {} \; chmod a+x ~/webp-convert.sh /var/www/www-root/data/webp-convert.sh /var/www/www-root/data/www/example.ru/upload |
yum install epel-release yum install inotify-tools vi /etc/webp_convert.conf # Содержимое /etc/webp_convert.conf MONITOR=/var/www/www-root/data/www/example.ru/ # Пустой файл convert_webp.log создавался заранее. LOG_FILE=/var/www/www-root/data/www/example.ru/logs/convert_webp.log WEB_SERVER_USER=www-root:www-root # Изначально сервис конвертировал картинки в webp. # Позже было добавлено сжатие brotli, а название сервиса не поменялось. vi /etc/rc.d/init.d/webp_convert.sh # Содержимое /etc/rc.d/init.d/webp_convert.sh #!/bin/bash # webp_convert: Start/Stop convertation images to webp # # chkconfig: - 80 20 # description: use inotifywait for track and convert images to webp. # # processname: webp_convert . /etc/rc.d/init.d/functions . /etc/webp_convert.conf LOCK=/var/lock/subsys/webp_convert RETVAL=0 watching() { /usr/bin/inotifywait \ -q -m -r --format '%e %w%f' \ -s $LOG_FILE \ -e close_write -e moved_from -e moved_to -e delete $MONITOR \ | grep -i -E '\.(jpe?g|png|js|css)$' --line-buffered \ | while read operation path; do ts=$(date +"%C%y%m%d%H%M%S") echo "$ts :: $operation :: $path" >> $LOG_FILE; webp_path="$(sed 's/\.[^.]*$/.webp/' <<< "$path")"; brotli_path="${path}.br"; if [ $operation = "MOVED_FROM" ] || [ $operation = "DELETE" ]; then # if the file is moved or deleted if [ $(grep -i -E '\.(css|js)$' <<< "$path") ]; then if [ -f "$brotli_path" ]; then $(rm -f "$brotli_path"); fi; else if [ -f "$webp_path" ]; then $(rm -f "$webp_path"); fi; fi; elif [ $operation = "CLOSE_WRITE,CLOSE" ] || [ $operation = "MOVED_TO" ]; then # if new file is created if [ $(grep -i '\.png$' <<< "$path") ]; then $(cwebp -quiet -lossless "$path" -o "$webp_path"); $(chown "$WEB_SERVER_USER" "$webp_path"); png_size=$(wc -c "$path" | cut -d " " -f 1); webp_size=$(wc -c "$webp_path" | cut -d " " -f 1); if [ $png_size -lt $webp_size ]; then if [ -f "$webp_path" ]; then $(rm -f "$webp_path"); fi; fi; elif [ $(grep -i -E '\.(css|js)$' <<< "$path") ]; then $(brotli -q 11 -k -f "$path"); $(chown "$WEB_SERVER_USER" "$brotli_path"); else $(cwebp -quiet -q 90 "$path" -o "$webp_path"); $(chown "$WEB_SERVER_USER" "$webp_path"); jpg_size=$(wc -c "$path" | cut -d " " -f 1); webp_size=$(wc -c "$webp_path" | cut -d " " -f 1); if [ $jpg_size -lt $webp_size ]; then if [ -f "$webp_path" ]; then $(rm -f "$webp_path"); fi; fi; fi; fi; done; } start() { start_date=$(date +"%C%y%m%d%H%M%S"); echo "$start_date :: Setting up watches." >> $LOG_FILE; echo "Wathing dir: $MONITOR"; echo "Log to file: $LOG_FILE"; echo "Apache user: $WEB_SERVER_USER"; echo "Starting inotifywait for webp_convert..."; watching & status inotifywait RETVAL=$? [ $RETVAL -eq 0 ] && touch $LOCK return $RETVAL } stop() { echo -n $"Stopping inotifywait and webp_convert: " killproc inotifywait RETVAL=$? echo [ $RETVAL -eq 0 ] && rm -f $LOCK return $RETVAL } case "$1" in start) start ;; stop) stop ;; status) status inotifywait ;; restart) stop start ;; *) echo $"Usage: $0 {start|stop|status|restart}" exit 1 esac exit $? chmod a+x /etc/rc.d/init.d/webp_convert.sh # По умолчанию количество watches 8192, что не хватит для работы # Устанавливаем максимальное количество watches 524288, но помним, что один watch забирает 1Кб ОЗУ. # Для 524288 потребуется 512 мегабайт оперативной памяти. # https://askubuntu.com/questions/154255/how-can-i-tell-if-i-am-out-of-inotify-watches echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p /etc/rc.d/init.d/webp_convert start chkconfig --add webp_convert chkconfig webp_convert on |
# https://alexey.detr.us/en/posts/2018/2018-08-20-webp-nginx-with-fallback/ http { # ... map $http_accept $webp_ext { default ""; "~image\/webp" ".webp"; } map $uri $file_ext { default ""; "~(\.\w+)$" $1; } # ... } server { # ... location ~* "^(?<path>.+)\.(png|jpeg|jpg|gif)$" { try_files $path$webp_ext $path$file_ext =404; } # ... } # В файл /etc/nginx/mime.types добавить строку image/webp webp; nginx -t nginx -s reload |
yum install redis autoconf gcc make systemctl enable redis systemctl start redis |
wget https://github.com/igbinary/igbinary/archive/refs/tags/3.2.14.zip -O igbinary.zip unzip -o ./igbinary.zip cd igbinary-3.2.14/ /opt/php81/bin/phpize ./configure --with-php-config=/opt/php81/bin/php-config make && make install #Обязательное расширение echo 'extension=igbinary.so' > /opt/php81/etc/php.d/igbinary.ini wget https://github.com/phpredis/phpredis/archive/refs/tags/5.3.7.zip -O phpredis.zip unzip -o ./phpredis.zip cd phpredis-5.3.7/ /opt/php81/bin/phpize ./configure --with-php-config=/opt/php81/bin/php-config --enable-redis-igbinary make && make install echo 'extension=redis.so' > /opt/php81/etc/php.d/redis.ini #Рестарт сервера php, в данном случае apache systemctl restart httpd |
Available serializers php, igbinary |