Русский
Русский
English
Статистика
Реклама

Битрикс

Интеграция интернет-магазина на 1С-Битрикс с Mindbox

17.07.2020 10:14:14 | Автор: admin
Для развития систем лояльности интернет-магазины обращаются к платформам автоматизации маркетинга, Customer Data Platform (CDP). При этом иногда для успешной интеграции нужно сохранять больше данных, чем указано в документации к API.

Рассказываем, какие данные понадобились нам для интеграции магазина на 1С-Битрикс с платформой Mindbox, как их можно получить с помощью API и SDK и как использовать комбинированный подход с асинхронной отправкой данных.



С помощью сервисов Customer Data Platform ритейлеры узнают портрет своего покупателя, в том числе поведенческие данные. Эта информация хранится в CDP в защищенном виде и помогает ритейлерам в проведении маркетинговых кампаний и аналитике.

Когда покупатель добавляет в корзину телевизор или любой другой товар, CDP сохраняет эти данные. На их основе ритейлеры получают возможность расширить свое взаимодействие с пользователями, например предложить рекомендации и скидки на похожие товары.

Один из наших клиентов сеть магазинов электроники принял решение подключиться к CDP-платформе Mindbox и обратился к нам за помощью в интеграции. Мы проводили интеграцию по ключевым пользовательским сценариям: авторизация, добавление в корзину, оплата и др.

Предыстория


Интернет-магазины могут подключиться к Mindbox двумя основными способами: с помощью API либо JavaScript SDK (об отличиях мы расскажем далее).

Для выбора оптимального способа мы обратились к документации Mindbox, а если информации не хватало, то задавали вопросы менеджеру. Мы выяснили, что наше сотрудничество совпало с периодом бурного роста платформы Mindbox: среднесуточная нагрузка по вызовам API Mindbox увеличилась вдвое (до 120 тысяч запросов в минуту, в пик до 250 тысяч). Это означало, что в период Черной пятницы и прочих распродаж из-за дополнительного роста нагрузки возникал риск, что CDP-сервис окажется недоступен и не получит данные интернет-магазина, который с ним интегрирован.

Mindbox быстро отреагировал на эту проблему и начал улучшать архитектуру и инфраструктуру своих IT-систем, чтобы добиться четырехкратного запаса прочности. Нам, в свою очередь, нужно было обеспечить бесперебойную отправку в Mindbox данных о покупках. Для этого требовалось выбрать самый надежный способ интеграции.

Методы интеграции с Mindbox


Как отмечено выше, Mindbox предлагает использовать для подключения API или JavaScript SDK. Далее рассмотрим их особенности.

  • JavaScript SDK


Библиотека-обёртка над API, предоставляемая сервисом. Её плюсы простота интеграции и возможность асинхронной передачи данных. Оптимально подходит для тех случаев, когда нужно поддерживать только web-платформу.

Ограничения: возможна потеря данных, если Mindbox недоступен в момент отправки. Скрипты платформы не подгрузятся, если на стороне интернет-магазина есть js-ошибки.

  • Интеграция по API


Интеграцию магазина с Mindbox можно провести через API. Этот способ снижает зависимость от JavaScript и также подходит для настройки асинхронной отправки данных.

Ограничения: мы столкнулись с тем, что не получали некоторые данные cookie, а именно уникальный идентификатор пользователя на устройстве (mindboxDeviceUUID). Его необходимо передавать в большинстве операций Mindbox для склеивания информации по пользователю.

В документации эти cookie обязательны не для всех операций. И всё же, стремясь к бесперебойной передаче данных, мы обсудили этот вопрос с менеджером Mindbox. Выяснили, что для максимальной надежности желательно всегда отправлять cookie. При этом для получения cookie нужно использовать JavaScript SDK.

Комбинированный метод


Мы рассмотрели описанные выше способы интеграции, но в чистом виде они не подходили для нашего проекта. Для решения бизнес-задач ритейлера и построения системы лояльности нужно было передавать в Mindbox полный набор данных о действиях пользователей, включая идентификатор из cookie. Одновременно с этим мы стремились снизить зависимость от JavaScript и риски потери данных в случае временной недоступности Mindbox.

Поэтому мы обратились к третьему, комбинированному методу: работаем и с API, и с JavaScript SDK, используя наш модуль очередей.

С помощью Javascript SDK мы идентифицируем пользователя на сайте (mindboxDeviceUUID). Затем на стороне сервера формируем запрос со всеми необходимыми данными и помещаем его в очередь. Запросы из очереди через API отправляются сервису Mindbox. В случае отрицательного ответа запрос повторно помещается в очередь. Таким образом, при отправке данных Mindbox получает полный комплект необходимой информации.

В приведенном далее примере класс Sender позволяет собрать и отправить запрос, выполнив первичную обработку ответа. Класс использует данные из самой команды (тип запроса/ответа, deviceUUID и др.) и из настроек модуля (параметры работы с API, токены и т.п.).

<?phpdeclare(strict_types=1);namespace Simbirsoft\MindBox;use Bitrix\Main\Web\Uri;use Bitrix\Main\Web\HttpClient;use Simbirsoft\Base\Converters\ConverterFactory;use Simbirsoft\MindBox\Contracts\SendableCommand;class Sender{    /** @var Response Тело ответа */    protected $response;    /** @var SendableCommand Команда */    protected $command;    /**     * Sender constructor.     *     * @param SendableCommand $command     */    public function __construct(SendableCommand $command)    {        $this->command = $command;    }    /**     * Сформировать массив заголовков запроса.     *     * @return array     */    protected function getHeaders(): array    {        return [            'Accept'        => Type\ContentType::REQUEST[$this->command->getRequestType()],            'Content-Type'  => Type\ContentType::RESPONSE[$this->command->getResponseType()],            'Authorization' => 'Mindbox secretKey="'. Options::get('secretKey') .'"',            'User-Agent'    => $this->command->getHttpInfo('HTTP_USER_AGENT'),            'X-Customer-IP' => $this->command->getHttpInfo('REMOTE_ADDR'),        ];    }    /**     * Сформировать адрес запроса.     *     * @return string     */    protected function getUrl(): string    {        $uriParts = [            Options::get('apiUrl'),            $this->command->getOperationType(),        ];        $uriParams = [            'operation'  => $this->command->getOperation(),            'endpointId' => Options::get('endpointId'),        ];        $deviceUUID = $this->command->getHttpInfo('deviceUUID');        if (!empty($deviceUUID)) {            $uriParams['deviceUUID'] = $deviceUUID;        }        return (new Uri(implode('/', $uriParts)))            ->addParams($uriParams)            ->getUri();    }    /**     * Отправить запрос.     *     * @return bool     */    public function send(): bool    {        $httpClient = new HttpClient();        $headers = $this->getHeaders();        foreach ($headers as $name => $value) {            $httpClient->setHeader($name, $value, false);        }        $encodedData = null;        $request = $this->command->getRequestData();        if (!empty($request)) {            $converter = ConverterFactory::factory($this->command->getRequestType());            $encodedData = $converter->encode($request);        }        $url = $this->getUrl();        if ($httpClient->query($this->command->getMethod(), $url, $encodedData)) {            $converter = ConverterFactory::factory($this->command->getResponseType());            $response = $converter->decode($httpClient->getResult());            $this->response = new Response($response);            return true;        }        return false;    }    /**     * @return Response     */    public function getResponse(): Response    {        return $this->response;    }}


Трейт Sendable содержит все возможные настройки команды для отправки запроса в Mindbox, в том числе предустановленные, такие как тип запроса/ответа, метод запроса и параметр синхронности/асинхронности. Также в нем присутствуют методы, общие для всех команд.

<?phpdeclare(strict_types=1);namespace Simbirsoft\MindBox\Traits;use RuntimeException;use Bitrix\Main\Context;use Simbirsoft\MindBox\Type;use Simbirsoft\MindBox\Sender;use Simbirsoft\MindBox\Response;use Bitrix\Main\Localization\Loc;use Simbirsoft\MindBox\Contracts\SendableCommand;Loc::loadMessages($_SERVER['DOCUMENT_ROOT'] .'/local/modules/simbirsoft.base/lib/Contracts/Command.php');trait Sendable{    /** @var string Метод отправки (GET/POST) */    protected $method = Type\OperationMethod::POST;    /** @var string Тип операции (sync/async) */    protected $operationType = Type\OperationType::ASYNC;    /** @var string Тип запроса (json/xml) */    protected $requestType = Type\ContentType::JSON;    /** @var string Тип ответа (json/xml) */    protected $responseType = Type\ContentType::JSON;    /** @var array Вспомогательные данные */    protected $data = [];    /**     * Название операции.     * @return string     */    abstract public function getOperation(): string;    /**     * Формируем данные.     *     * @return array     */    abstract public function getRequestData(): array;    /**     * HTTP метод запроса     *     * @return string     */    public function getMethod(): string    {        return $this->method;    }    /**     * Тип операции     *     * @return string     *     * @noinspection PhpUnused     */    public function getOperationType(): string    {        return $this->operationType;    }    /**     * Тип запроса.     *     * @return string     *     * @noinspection PhpUnused     */    public function getRequestType(): string    {        return $this->requestType;    }    /**     * Тип ответа.     *     * @return string     *     * @noinspection PhpUnused     */    public function getResponseType(): string    {        return $this->responseType;    }    /**     * Вспомогательные данные запроса     *     * @return void     */    public function initHttpInfo(): void    {        $server = Context::getCurrent()->getServer();        $request = Context::getCurrent()->getRequest();        $this->data = [            'X-Customer-IP' => $server->get('REMOTE_ADDR'),            'User-Agent'    => $server->get('HTTP_USER_AGENT'),            'deviceUUID'    => $request->getCookieRaw('mindboxDeviceUUID'),        ];    }    /**     * Получить вспомогательные данные запроса     *     * @param string $key     * @param string $default     *     * @return string     *     * @noinspection PhpUnused     */    public function getHttpInfo(string $key, string $default = ''): string    {        return $this->data[$key] ?? $default;    }    /**     * Выполняем команду.     *     * @return void     *     * @throws RuntimeException     */    public function execute(): void    {        /** @var SendableCommand $thisCommand */        $thisCommand = $this;        $sender = new Sender($thisCommand);        if ($sender->send()) {            throw new RuntimeException(Loc::getMessage('BASE_COMMAND_NOT_EXECUTED'));        }        $response = $sender->getResponse();        if (!$response->isSuccess()) {            throw new RuntimeException(Loc::getMessage('BASE_COMMAND_NOT_EXECUTED'));        }        if (!$this->prepareResponse($response)) {            throw new RuntimeException(Loc::getMessage('BASE_COMMAND_NOT_EXECUTED'));        }    }    /**     * Обработка ответа запроса.     *     * @param Response $response     *     * @return bool     */    public function prepareResponse(Response $response): bool    {        // $body   = $response->getBody();        // $status = $body['customer']['processingStatus'];        /**         * Возможные статусы:         * AuthenticationSucceeded - Если пароль верен         * AuthenticationFailed         - Если пароль не верен         * NotFound                          - Если потребитель не найден         */        return true;    }}


В качестве примера рассмотрим событие авторизации пользователя. В обработчике события авторизации мы добавляем в нашу очередь объект класса AuthorizationCommand. В этом классе происходит минимально необходимая подготовка информации, поскольку в момент выполнения команды данные в базе могут измениться, и нужно их сохранить. Также устанавливаются соответствующие параметры для запроса в Mindbox, в данном случае это название операции (узнаем в админ. панели Mindbox). Дополнительно можно указать тип запроса/ответа, метод запроса и параметр синхронности/асинхронности согласно трейту Sendable.

<?phpdeclare(strict_types=1);namespace Simbirsoft\MindBox\Commands;use Simbirsoft\Queue\Traits\Queueable;use Simbirsoft\MindBox\Traits\Sendable;use Simbirsoft\Queue\Contracts\QueueableCommand;use Simbirsoft\MindBox\Contracts\SendableCommand;final class AuthorizationCommand implements QueueableCommand, SendableCommand{    use Queueable, Sendable;    /** @var array Данные пользователя */    protected $user;    /**     * AuthorizationCommand constructor.     *     * @param array $user     */    public function __construct(array $user)    {        $keys = ['ID', 'EMAIL', 'PERSONAL_MOBILE'];        $this->user = array_intersect_key($user, array_flip($keys));        $this->initHttpInfo();    }    /**     * Название операции.     *     * @return string     */    public function getOperation(): string    {        return 'AuthorizationOnWebsite';    }    /**     * Формируем данные.     *     * @return array     */    public function getRequestData(): array    {        return [            'customer' => [                'email' => $this->user['EMAIL'],            ],        ];    }}


Схема взаимодействия модулей


В нашем проекте мы выделили три модуля:

  • Базовый


Служит для хранения общих вспомогательных классов и интерфейсов (например, интерфейсов команд), которые можно потом использовать во всем проекте.

  • Модуль очередей


Взаимодействие с Mindbox реализовано через команды. Для их последовательного выполнения мы используем наш модуль очередей. Этот модуль сохраняет поступившие команды и выполняет их, когда наступает время выполнения.

  • Модуль интеграции с Mindbox


Этот модуль ловит события на сайте, такие, как авторизация, регистрация, создание заказа, добавление в корзину и прочие, а затем формирует команды и отправляет их в модуль очередей.



Модуль Mindbox отслеживает на сайте события и сопутствующую информацию, в том числе из cookie, формирует из них команду и помещает её в очередь. Когда модуль очередей извлекает команду из очереди и выполняет её, происходит отправка данных. Если ответ от Mindbox отрицательный неудачно выполненная команда переносится в конец очереди, если положительный успешно выполненная команда удаляется из очереди.

Таким образом, с помощью описанного выше комбинированного метода мы смогли обеспечить бесперебойную передачу данных в Mindbox.

Подводя итоги


В этой статье мы рассмотрели, какими способами интернет-магазин может подключиться к Customer Data Platform для развития систем лояльности.

В нашем примере в документации Mindbox были описаны два основных способа подключения: через Javascript SDK и через API. Для повышения надежности передачи данных, даже в случае временной недоступности CDP-сервиса, мы выбрали и реализовали третий, комбинированный способ: с помощью API и Javascript SDK, с асинхронной отправкой данных.

Спасибо за внимание! Надеемся, эта статья была для вас полезна.
Подробнее..

QR-коды для учета мусоровозов. Ч. 1

01.10.2020 20:16:38 | Автор: admin

Зачем я занялся этим вопросом? Меня беспокоит, как гражданина проблема мусора. А учет работы мусоровозов, одна из первых задач для возможностей снижения загаживания так полюбившейся мне Ленинградской области.

Пожалуй боюсь показаться некомпетентной сволочью, но мне реально хотелось бы услышать от ИТ-сообщества доводы в ту или иную сторону без перехода на личности. Я новичок, который первый раз решил написать на хабре.

На сегодняшний день в Ленинградской области есть Комитет Цифрового Развития. Эта организация совместно с другими комитетами в т.ч. реализовывает различные ИТ-проекты, закупая их естественно по 44-ФЗ. Я не вижу ничего плохого, когда появляется необходимость в той или иной системе, ее заказывают и запускают в работу. Есть рабочие места, есть улучшение работы и деятельности организаций, оказывающих от имени государства услуги.

Не так давно Губернатор Ленинградской области Александр Юрьевич Дрозденко заявил, что для того, чтобы эффективнее отслеживать мусоровозы Санкт-Петербурга и Ленинградской области, необходимо им всем выдать QR-коды. Сделать такую систему.

Spoiler

"Самое страшное, что у нас сейчас происходит - это свалки в лесах, эти "закапушки".

Александр Дрозденко, губернатор Ленинградской области

Что меня настораживает во всем этом?

  1. У государства уже есть закупленная: Региональная информационно-навигационная система Ленинградской области (РИНС)

  2. У любого мусоровоза есть ГРЗ (государственный регистрационный знак, типа: А123ВЕ47RUS), а так же с завода установленные системы ГЛОНАСС, ЭРА-ГЛОНАСС и может доукомплектовываться тахографом.

Закупленная система наблюдает за скорыми, школьными автобусами и т.д. В уже имеющуюся систему вполне можно встроить необходимый модуль, который позволит регистрировать перевозимые грузы и обеспечить фиксацию действий в т.ч. с помощью трекинга. В добавок любой гражданин увидев мусоровоз или машину, перевозящую мусор сможет проверить отсканировав QR-код, размещенный где? На лобовом стекле? ... .

Конечно я решил подкараулить машины и понял, что единственное, что в принципе удобно и доступно разглядеть, это государственный регистрационный знак. Я могу его увидеть и запомнить даже у проезжающих авто. А значит машина должна быть в едином реестре, что упрощает задачу. И если уж наносить QR-код, то по бокам на весь размер и со всех сторон, в том числе и на крыше авто. Тогда по этому коду-ссылке можно перейти на страницу такого реестра. А тут как говориться уже не нужно магии.

Какой же должна быть логика системы?

Увидел гражданин машину с мусором, перешел на сайт "мусоровозы" и ввел номер в формочку. Или отсканировал громадный QR-код. Который не самой накладной на лобовом стеклышке листа А4, а нормальный, большой, со всех сторон и даже на крыше.

По результатам выдачи формы увидел: транспортную накладную, данные об авто и фото, а так же точку на карте. Всё как в том же AIS, да и любой другой системе подобного типа.

Логика работы мусорной компании или мусоровоза-частника?

  1. Регистрируемся как пользователь гос.портала;

  2. Регистрируем каждую машину и водителя;

  3. У водителя смартфон, на котором он может запустить мобильную версию сайта (зачем писать еще и мобильное приложение - верно?) и поставить машину в рейс, с указанием точек или принять готовое задание, сфотографировать к заданию накладные и даже фактические фотографии того, как и откуда он увез и как и куда он вывез;

  4. На каждый авто мы можем в публичной части вывести нужный нам отчет :)

Сразу опережая события скажу - я гуманитарий по диплому, но в душе (или душе) я конечно же всю сознательную жизнь занимаюсь ИТ-проектами.

Смоделировать такую историю я взялся на 1С Битрикс, а если точнее Битрикс24. Под рукой оказался именно он, в нем можно накликав мышкой автоматизировать кучу бизнес-процессов. Хотя может стоит и на Python + Django.

Почему выбор пал именно на него?

Во первых - платформа универсальная и масштабируемая; Только пожалуйста не минусуйте - я исхожу из точки зрения бизнес-пользователя, а не гения DevOps.

Во вторых - реализация автоматизации задач на Битрикс доступна любому мыслящему руководителю;

В третьих - легко оценить бюджет и стоимость проекта для внедрения, а значит и понять, что и как израсходует государство (уложится в пару миллионов рублей или создаст очередную вундервафлю, которую вы можете оценить).

Что я прошу от сообщества?

На Хабре очень много классных ребят и девчат. А значит, можно обсудить данный вопрос и его целесообразность, а так же возможно озвучить свои мысли и предложения для того, чтобы наконец наши государственные слуги смогли качественно реализовывать системы, не флудя в соц.сетях о том, что любые решения кроме тех, которые они делают жутко проприетарными с непонятными стоимостями и функционалом являются отстоем. Мы же знаем, что Битрикс, это всего лишь конструктор сайтов? :)

Кто, как видит решение проблемы учета мусоровозов и возимых ими отходов без серьёзных бюджетных затрат?

Не кидаться в друг друга какахами, не минусовать меня или кого либо еще. Может я и дурак, но давайте жить дружно хотя бы по этому вопросу?

И да, предлагаю принять участие в разработке. Пусть и кривой, но за-то бесплатной. А значит мы сможем помочь не потратить бюджетные деньги на очередную "вундервафлю", а вместо этого к примеру деньги перераспределятся на лечение детей или еще один ремонт в детском садике, детском доме и т.д. и т.п.

И немного самокритики.

Подробнее..

Ещё один велосипед пишем свой автозагрузчик классов для Битрикс

03.07.2020 20:16:17 | Автор: admin
Кто бы что ни говорил, но я считаю, что изобретение велосипедов штука полезная. Использование готовых библиотек и фреймворков, конечно, хорошо, но порой стоит их отложить и создать что-то своё. Так мы поддерживаем мозг в тонусе и реализуем свой творческий потенциал.

Статья обещает быть длинной, поэтому устраивайтесь поудобнее, я начинаю.

Итак, Битрикс, а точнее, Bitrix Framework. Несмотря на наличие богатого API, периодически возникает необходимость в создании своих классов/библиотек, а также подключении сторонних. Поэтому для начала рассмотрим уже имеющиеся способы автозагрузки.

Старый добрый include/require. Я его добавил сугубо для исторической справки. Хотя на заре своего программерского пути я складывал нужные классы и библиотеки в отдельную папку, создавал отдельный файл, куда инклудил все эти классы и уже потом инклудил файл с инклудами (прошу прощения за тавтологию).

Composer. Позволяет подключать как собственные классы, так и сторонние библиотеки. Однако при добавлении новых классов требует ручного обновления. Кроме того, сами классы, файлы и неймспейсы также нужно писать вручную. О том, как подружить Битрикс с композером, можно почитать тут

Битриксовый лоадер. Используется как для подключения модулей, так и для автозагрузки классов. Однако прежде, чем подключить нужные классы, придётся сформировать массив, где ключами будут выступать имена классов, а значениями пути к ним. И выглядеть это всё будет примерно так:

$classes = [    'Namespace\\Package\\ClassName' => '/path/to/class.php'];Loader::registerAutloadClasses(null, $classes);


Свои модули. Говорят, что это самый рекомендуемый способ создаёшь модуль, устанавливаешь его в админке, затем подключаешь в любом месте и пользуешься в своё удовольствие. С виду всё просто, а в реальности мы имеем следующее:

  • Помимо написания классов нужно также прописать процедуру установки и удаления модуля. Там есть ряд обязательных параметров и методов, без которых модуль, возможно, работать не будет (хотя не знаю, не проверял)
  • Без подключения модуля классы работать не будут
  • Не всегда имеет смысл выносить класс в отдельный модуль

Тем не менее, если вы написали свой локальный модуль и потом решили добавить в него ещё пару классов, то для их использования вам уже не потребуется переустановка модуля просто вызываете нужные методы в нужном месте, и всё!

Ну а теперь, собственно, сам велосипед...


Проанализировав все вышеописанные методы, я задумался о том, что бы такое придумать, чтобы достаточно было просто складывать новые классы в определённое место, а они бы подгружались потом автоматически, без добавления новых элементов в массив неймспейсов и путей к файлам.

В итоге было решено написать специальный модуль как бы странно это ни звучало, но мне эта идея показалась более удачной, чем добавлять несколько функций в init.php который будет автоматически подгружать все классы из необходимой директории.

Опущу процесс написания установки/удаления модуля кому надо, посмотрят в исходниках, а сразу перейду к основному функционалу.

Т.к. изначально неизвестно количество уровней вложенности папок, то нужно, чтобы методы были рекурсивными. Также мы будем использовать класс Bitrix\Main\Loader, который и будет грузить классы.

Представим, что мы решили складировать все наши классы в директорию /local/php_interface/lib:

image

Также у нас могут быть файлы, которые не содержат классов и, соответственно, не должны быть включены в автозагрузчик, поэтому нужно также учесть и этот момент.

Итак, поехали.

namespace Ramapriya\LoadManager;use Bitrix\Main\Loader;class Autoload{}


Первым делом нам нужно получить всё содержимое нашей папки. Для этого напишем метод scanDirectory:

    public static function scanDirectory(string $dir) : array    {        $result = [];        $scanner = scandir($dir); // сканируем содержимое директории        foreach ($scanner as $scan) {            switch ($scan) {                // пропускаем                case '.':                 case '..':                    break;                default:// получаем путь вложенной папки или файла                                        $item = $dir . '/' . $scan;                     $SplFileInfo = new \SplFileInfo($item);                        if($SplFileInfo->isFile()) {// если элемент является файлом, кладём в возвращаемый массив                            $result[] = $scan;                                             } elseif ($SplFileInfo->isDir()) {// если элемент является директорией, вызываем текущую функцию и результаты кладём в возвращаемый массив                                                $result[$scan] = self::scanDirectory($item, $result[$scan]);                         }            }        }            return $result;    }


На выходе должно получиться следующее:



Как мы видим, структура файлов соблюдена, поэтому можно приступать к формированию массива для автозагрузки:

/* Тут уже добавляется переменная $defaultNamespace, которая и будет являться основой для имени класса. Также добавим массив php-файлов, которые не должны попасть в автозагрузчик*/    public static function prepareAutoloadClassesArray(string $directory, string $defaultNamespace, array $excludeFiles) : array    {        $result = [];// вызываем предыдущий метод        $scanner = self::scanDirectory($directory);             foreach ($scanner as $key => $value) {                $sep = '\\';                        switch(gettype($key)) {                                case 'string':// если тип ключа является строкой, скорее всего это директория                    $SplFileInfo = new \SplFileInfo($directory . '/' . $key);                    $classNamespace = $defaultNamespace . $sep . $key;                        if($SplFileInfo->isDir()) {// ещё раз проверяем, является ли ключ директорией, и если является, то вызываем текущую функцию, передавая в качестве аргументов полученные значения                        $tempResult = self::prepareAutoloadClassesArray($directory . '/' . $key, $classNamespace, $excludeFiles);                        foreach($tempResult as $class => $file) {// делаем прогон массива и записываем полученные данные в результат                            $result[$class] = $file;                         }                    }                        break;                    case 'integer':// если тип ключа - число, то с большой долей вероятности значением является файл                    $SplFileInfo = new \SplFileInfo($directory . '/' . $value);// получаем название класса из файла (поэтому я рекомендую именовать файлы и папки с соблюдением того же регистра, что и в классах)                    $classNamespace = $defaultNamespace . $sep . str_ireplace('.php', '', $SplFileInfo->getBasename()); // далее проверяем является ли значение php-файлом                    if(                        $SplFileInfo->isFile() &&                        $SplFileInfo->getExtension() === 'php'                    ) { // прогоняем массив исключаемых файлов и проверяем, нет ли их среди наших значений                        foreach($excludeFiles as $excludeFile) {                            if($SplFileInfo->getBasename() !== $excludeFile) {// записываем в массив относительный путь файла с классом                                $result[$classNamespace] = str_ireplace($_SERVER['DOCUMENT_ROOT'], '', $directory . '/' . $value);                             }                        }                                                                    }                        break;                                }            }            return $result;    }


Если всё сделано правильно, то в итоге мы получим сформированный массив для автозагрузки с помощью битриксового лоадера:



Для проверки работоспособности добавим в папку с исключениями файл MainException.php, содержащий следующий класс:

<?phpnamespace Ramapriya\Exceptions;class MainException extends \Exception{    public function __construct($message = null, $code = 0, Exception $previous = null)    {        parent::__construct($message, $code, $previous);    }}


Как мы видим, наш файл подгрузился в массив классов:



Забегая вперёд, попробуем вызвать наше новое исключение:

throw new Ramapriya\Exceptions\MainException('test exception');


В результате увидим:

[Ramapriya\Exceptions\MainException]
test exception (0)


Итак, нам осталось реализовать метод автозагрузки полученного массива. Для этой цели напишем метод с самым что ни на есть банальным названием loadClasses, куда передадим полученный массив:

    public static function loadClasses(array $classes, $moduleId = null)    {        Loader::registerAutoloadClasses($moduleId, $classes);    }


Данный метод использует битриксовый лоадер, который и регистрирует массив с нашими классами.

Теперь осталось совсем немного сформировать массив с классами и загрузить их с помощью написанного нами класса. Для этого в нашей папке lib создадим файл include.php:

<?phpuse Bitrix\Main\Loader;use Bitrix\Main\Application;use Ramapriya\LoadManager\Autoload;// загружаем наш модуль - обязательно нужно перед этим его установить, иначе ничего не будет работатьLoader::includeModule('ramapriya.loadmanager');$defaultNamespace = 'Ramapriya';$excludeFiles = ['include.php'];$libDir = Application::getDocumentRoot() . '/local/php_interface/lib';$autoloadClasses = Autoload::prepareAutoloadClassesArray($libDir, $defaultNamespace, $excludeFiles);Autoload::loadClasses($autoloadClasses);

Далее подключим данный файл в init.php:

// init.php$includeFile = $_SERVER['DOCUMENT_ROOT'] . '/local/php_interface/lib/include.php';if(file_exists($includeFile)) {    require_once $includeFile;}

Вместо заключения


Ну что же, поздравляю, наш велосипед готов и со своей функцией прекрасно справляется.
Исходники, как всегда, на гитхабе.

Спасибо за внимание.
Подробнее..

Из песочницы Доверяй, но проверяй контроль неотправленных писем в Битриксе с уведомлением админу

09.07.2020 20:08:37 | Автор: admin

Предыстория


Когда-то у меня возникла необходимость проверять наличие неотправленных сообщений в 1С-Битрикс: Управление сайтом (далее Битрикс) и получать уведомления об этом. Проблемы с отправкой почты были крайне редкими, однако довольно неприятными событиями, т.к. обычно это были заказы, подтверждения регистрации и прочие важные письма.

Сложность была в том, что если используемый Битриксом метод отправки почты перестал работать(с большой вероятностью причина в этом), то этим же самым методом отправлять уведомление будет ненадёжно.

Погуглив, я не нашёл чего-то бесплатного и готового, однако встретил немало вопросов/ответов о неотправленных письмах из Битрикса каким образом их найти, какими могут быть причины их появления и т.п. Поэтому счёл нужным поделиться своим решением.

Задача


  1. Получить реквизиты для соединения с БД из конфига сайта Битрикса
  2. Соединиться с БД
  3. Проверить количество неотправленных писем
  4. Сравнить количество с предельно допустимым
  5. Принять решение об отправке уведомления


Реализация


На вход shell-скрипту будут поступать 3 параметра:

  1. Путь к конфигу сайта Битрикса (path_to_bxdb_config)
  2. Текст запроса к БД (single_num_value_query)
  3. Предельно допустимая величина (max_num_value)

Для корректной работы запрос к БД должен возвращать одиночное числовое значение.

Код скрипта check_bx_db_value.sh
#!/bin/bash## Site: https://github.com/AlexeyGogolev/check-bx-db-value#mysql="$(which mysql)" # получение пути к mysqlphp="$(which php)" # получение пути к phpdeclare -A CLParams # массив значений параметров КСdeclare -a CLParams_keys # массив ключей для массива параметров КСdeclare -A DBSettings # массив значений переменных конфигаdeclare -a DBSettings_keys # массив ключей для массива значений переменных конфигаDBSettings_keys=(DBLogin DBPassword DBName)CLParams_keys=(path_to_bxdb_config single_num_value_query max_num_value)param_num=0 # счетчик параметров КС# получение параметров КСfor key in "${CLParams_keys[@]}" ; do     ((param_num++))                     CLParams[$key]=${!param_num}    # ${!param_num} здесь генери-т $1 $2... done# если нет последнего параметра, показываем справкуif  [ -z "${CLParams[${CLParams_keys[$param_num-1]}]}" ] ; then    printf "Script compares result returned by <${CLParams_keys[1]}> to given <${CLParams_keys[2]}>.\nIf the result more than the given value, then exit with code 1, else exit 0.\n"    printf "Usage: \n\t$(basename ${BASH_SOURCE[0]}) " ; for key in "${CLParams_keys[@]}" ; do printf "<$key> "; done ; printf "\n"    printf "Example: \n\t$(basename ${BASH_SOURCE[0]}) \"/www/ab.cd/bitrix/php_interface/dbconn.php\" \"select count(id) from b_event where SUCCESS_EXEC<>'Y'\" 5\n"     exit 10fi# выход если конфиг пустой или его нет if ! [ -s "${CLParams[path_to_bxdb_config]}" ] ; then     printf "File ${CLParams[path_to_bxdb_config]} doesn't exist or empty.\n"    exit 20fi# выход если параметр запроса к БД содержит команду из "запретного списка"echo ${CLParams[single_num_value_query]} | grep -i -q -E 'delete|update|insert|drop' && printf "query \n${CLParams[single_num_value_query]}\nisn't allowed\n" && exit 30# получение значений переменных из php-config -n -- без php.ini , -r -- выполнить код без тэгов <?...?>for key in "${DBSettings_keys[@]}" ; do     DBSettings[$key]="$($php -n -r 'include("'${CLParams[path_to_bxdb_config]}'"); print $'$key';')"done# экспорт пароля mysql в переменную окружения (по соображениям безопасности)export MYSQL_PWD=${DBSettings[DBPassword]}# получение одиночного значения из БД: -N -- без названий колонок ; -B - (batch) - результаты без "бокса" вокруг значений; -e выполнить запросnum_value=`${mysql} -u ${DBSettings[DBLogin]} -N -B -e "use ${DBSettings[DBName]}; ${CLParams[single_num_value_query]}"`# отображение значенийecho "Result of the query (from DB ${DBSettings[DBName]}): ${num_value}, ${CLParams_keys[2]}: ${CLParams[max_num_value]}"# сравнение значенийif [ $num_value -gt ${CLParams[max_num_value]} ]; then    exit 1fi


Пример вызова скрипта
#!/bin/bashcheck_bx_db_value.sh \/www/ab.cd/bitrix/php_interface/dbconn.php \select count(id) from b_event where SUCCESS_EXEC<>'Y' \2



Проверка результатов выполнения с кодами завершения скрипта
запускаем:

$ ./ab_cd_unsent_check.sh && echo "success" || echo "failure"

получаем:

Result of the query (from DB ab_cd): 0, max_num_value: 2success

Добавим неотправленных сообщений и запустим снова.

$ ./ab_cd_unsent_check.sh && echo "success" || echo "failure"

а теперь:

Result of the query (from DB ab_cd): 4, max_num_value: 2failure


Все работает как нужно! В первом случае значение в БД не превышает заданного, скрипт выдает код 0 по завершении. Во втором значение в БД превышает заданное, скрипт завершается с кодом ошибки.

Настройка конфигурации monit


В данной статье я приведу пример конфигурации monit для отправки сообщений на e-mail.
Подразумевается, что на машине уже установлен и настроен monit.

Для повышения надёжности, в конфиге monitrc следует указывать аккаунт на почтовом сервере, отличном от используемого Битриксом для отправки писем. Кроме того, путем настройки и запуска дополнительных скриптов в конфигурации monit можно выполнять отправку сообщений в мессенджеры и соцсети.

Пример конфигурации monit
check program ab_cd_unsent_check with path /home/bitrix/scripts/ab_cd_unsent_check.shevery 2 cycles    group mailif status != 0 then alert

Файл конфигурации следует поместить в /etc/monit.d/.

Для упрощения демонстрации, в этом примере установлено 2 цикла (здесь 1 цикл = 30 сек).
Чтобы monit не отправлял ложные алерты, в реальных условиях, нужно ставить столько циклов, чтобы письма успевали уходить подбирается опытным путём. Здесь следует учитывать среднее количество сообщений, генерируемых сайтом, и скорость(время) их обработки почтовым сервером.

Для проверки работы конфигурации в терминале выполняем:
# systemctl restart monit# monit status

получаем:
Без неотправленных сообщений


С неотправленными сообщениями


Так выглядят уведомления от monit на почту:
Появились неотправленные сообщения!


Все сообщения отправлены (теперь всё норм.).


Отправка уведомлений из monit работает корректно.

Заключение


Надеюсь решение получилось достаточно универсальным и подойдёт для других задач.

Вот и всё! Исходники для статьи можно скачать здесь.

P.S. Кому не сложно прошу поделиться в комментариях возникают ли у вас подобные задачи?
Если да, то каким образом они решаются?
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru