<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Ilya</title><author><name>Ilya</name></author><id>https://teletype.in/atom/ilznv</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/ilznv?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@ilznv?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=ilznv"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/ilznv?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-05-27T19:33:46.527Z</updated><entry><id>ilznv:iyd6pBg1wvf</id><link rel="alternate" type="text/html" href="https://teletype.in/@ilznv/iyd6pBg1wvf?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=ilznv"></link><title>Как заставить формы Tilda всегда прокидывать utm-метки в лиды в стандартной интеграции Битрикс24</title><published>2025-01-21T11:33:47.726Z</published><updated>2025-01-21T11:52:30.313Z</updated><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/d1/ee/d1eed0de-4dcd-4949-b6f2-bdbd724a8364.png&quot;&gt;Проблема: когда клиенты переходят на ваш сайт на Tilda из различных площадок, часто в URL можно увидеть так называемые utm-метки, например
https://example.com/page?utm_source=google&amp;utm_medium=cpc&amp;utm_campaign=search&amp;utm_content=shower&amp;utm_term=1</summary><content type="html">
  &lt;p id=&quot;goF2&quot;&gt;Проблема: когда клиенты переходят на ваш сайт на Tilda из различных площадок, часто в URL можно увидеть так называемые utm-метки, например&lt;br /&gt;https://example.com/page?utm_source=google&amp;amp;utm_medium=cpc&amp;amp;utm_campaign=search&amp;amp;utm_content=shower&amp;amp;utm_term=1&lt;/p&gt;
  &lt;p id=&quot;Bbrm&quot;&gt;если клиент,находясь на этой же странице, откроет стандартную тильдовскую форму обратной связи и отправит заявку(при условии что вы уже сделали стандартное подключение к Битрикс24 по инструкции &lt;a href=&quot;https://help-ru.tilda.cc/forms/bitrix&quot; target=&quot;_blank&quot;&gt;https://help-ru.tilda.cc/forms/bitrix&lt;/a&gt;) то UTM-метки передадутся в лид&lt;/p&gt;
  &lt;figure id=&quot;VTHO&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/d1/ee/d1eed0de-4dcd-4949-b6f2-bdbd724a8364.png&quot; width=&quot;418&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;K0zC&quot;&gt;&lt;strong&gt;Но!&lt;/strong&gt; если клиент вздумает побродить по вашему сайту, переходя по различным страницам и только после этого действия заполнит форму обратной связи то никаких UTM меток в лиде не будет&lt;/p&gt;
  &lt;figure id=&quot;xbVU&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/b5/ae/b5ae8ea0-73e2-4b10-a2bd-2ea0dc7a35e6.png&quot; width=&quot;243&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;ysCu&quot;&gt;Причина кроется в том, что в этом случае изначальные utm-метки в адресной строке браузера не сохраняются и не уходят в Тильду и соответственно Тильда не отправит их дальше в ваш Битрикс24&lt;/p&gt;
  &lt;p id=&quot;ysCu&quot;&gt;Решение:&lt;br /&gt;1) при первом посещении сайта проверять url на наличие utm-меток и записывать их в куки, тогда utm-метки сохранятся между переходами по страницам&lt;br /&gt;2) повесить обработчик который будет следить за тем, открыли ли вы любую форму и начали ли вводить в поле ввода свои данные. именно в этот момент автоматически подставлять в адресную строку браузера сохраненные в куки utm-метки&lt;br /&gt;3) готово! теперь utm-метки всегда передаются в лид в Битрикс24 и вы спокойно можете отслеживать сквозную аналитику&lt;/p&gt;
  &lt;p id=&quot;ysCu&quot;&gt;Ниже скрипт который необходимо разместить в админке тильды(Настройка сайта-&amp;gt;Еще-&amp;gt;Html-код для вставки внутрь HEAD), после этого он будет работать на всех страницах сайта&lt;/p&gt;
  &lt;figure id=&quot;IS3f&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img3.teletype.in/files/2a/08/2a0801ac-f631-43f8-89f6-fde92ae0d2fd.png&quot; width=&quot;1251&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;jFlx&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;br /&gt;// Функция для установки куки&lt;br /&gt;function setCookie(name, value, days) {&lt;br /&gt;    const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString();&lt;br /&gt;    document.cookie = &amp;#x60;${name}=${value}; expires=${expires}; path=/;&amp;#x60;;&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;YRFi&quot;&gt;&lt;code&gt;// Получение значения куки по имени&lt;br /&gt;function getCookie(name) {&lt;br /&gt;    const match = document.cookie.match(new RegExp(&amp;#x60;(^| )${name}=([^;]+)&amp;#x60;));&lt;br /&gt;    return match ? match[2] : null;&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;2G3u&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;tuAN&quot;&gt;&lt;code&gt;// 1. Запись utm-меток из URL&lt;br /&gt;const urlParams = new URLSearchParams(window.location.search);&lt;br /&gt;const utmParams = [&amp;#x27;utm_medium&amp;#x27;, &amp;#x27;utm_source&amp;#x27;, &amp;#x27;utm_term&amp;#x27;, &amp;#x27;utm_content&amp;#x27;, &amp;#x27;utm_campaign&amp;#x27;];&lt;br /&gt;utmParams.forEach(param =&amp;gt; {&lt;br /&gt;    const value = urlParams.get(param);&lt;br /&gt;    const existingCookie = getCookie(param);&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;e5ou&quot;&gt;&lt;code&gt;    // Перезаписываем куки, если utm-метка есть в URL&lt;br /&gt;    if (value) {&lt;br /&gt;        setCookie(param, value, 30);&lt;br /&gt;    } else if (!value &amp;amp;&amp;amp; existingCookie) {&lt;br /&gt;        // Не перезаписываем, если метки нет в URL, но есть в куках&lt;br /&gt;        return;&lt;br /&gt;    }&lt;br /&gt;});&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;WOFg&quot;&gt;&lt;code&gt;// 2. Обработка случая отсутствия utm_source и utm_medium&lt;br /&gt;const referrer = document.referrer;&lt;br /&gt;if (!urlParams.get(&amp;#x27;utm_source&amp;#x27;) &amp;amp;&amp;amp; !urlParams.get(&amp;#x27;utm_medium&amp;#x27;)) {&lt;br /&gt;    const existingUtmSource = getCookie(&amp;#x27;utm_source&amp;#x27;);&lt;br /&gt;    const existingUtmMedium = getCookie(&amp;#x27;utm_medium&amp;#x27;);&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;5OaU&quot;&gt;&lt;code&gt;    let utmSource = existingUtmSource || &amp;#x27;(direct)&amp;#x27;;&lt;br /&gt;    let utmMedium = existingUtmMedium || &amp;#x27;(none)&amp;#x27;;&lt;br /&gt;    &lt;br /&gt;    const currentDomain = window.location.hostname;&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;sVif&quot;&gt;&lt;code&gt;if (referrer &amp;amp;&amp;amp; new URL(referrer).hostname !== currentDomain){&lt;br /&gt;    if ((utmSource === &amp;#x27;(direct)&amp;#x27; &amp;amp;&amp;amp; utmMedium === &amp;#x27;(none)&amp;#x27;)) {&lt;br /&gt;        const referrerDomain = new URL(referrer).hostname;&lt;br /&gt;        const sourceMapping = {&lt;br /&gt;            &amp;#x27;google.com&amp;#x27;: &amp;#x27;google&amp;#x27;,&lt;br /&gt;            &amp;#x27;www.google.com&amp;#x27;: &amp;#x27;google&amp;#x27;,&lt;br /&gt;            &amp;#x27;google.ru&amp;#x27;: &amp;#x27;google&amp;#x27;,&lt;br /&gt;            &amp;#x27;www.google.ru&amp;#x27;: &amp;#x27;google&amp;#x27;,&lt;br /&gt;            &amp;#x27;google.kz&amp;#x27;: &amp;#x27;google&amp;#x27;,&lt;br /&gt;            &amp;#x27;www.google.kz&amp;#x27;: &amp;#x27;google&amp;#x27;,&lt;br /&gt;            &amp;#x27;www.google.co.uk&amp;#x27;: &amp;#x27;google&amp;#x27;,&lt;br /&gt;            &amp;#x27;bing.com&amp;#x27;: &amp;#x27;bing&amp;#x27;,&lt;br /&gt;            &amp;#x27;www.bing.com&amp;#x27;: &amp;#x27;bing&amp;#x27;,&lt;br /&gt;            &amp;#x27;duckduckgo.com&amp;#x27;: &amp;#x27;duckduckgo&amp;#x27;,&lt;br /&gt;            &amp;#x27;yandex.ru&amp;#x27;: &amp;#x27;yandex&amp;#x27;,&lt;br /&gt;            &amp;#x27;yandex.ru&amp;#x27;: &amp;#x27;yandex&amp;#x27;,&lt;br /&gt;            &amp;#x27;www.yandex.ru&amp;#x27;: &amp;#x27;yandex&amp;#x27;,&lt;br /&gt;            &amp;#x27;yandex.kz&amp;#x27;: &amp;#x27;yandex&amp;#x27;,&lt;br /&gt;            &amp;#x27;www.yandex.kz&amp;#x27;: &amp;#x27;yandex&amp;#x27;,&lt;br /&gt;            &amp;#x27;ya.ru&amp;#x27;: &amp;#x27;yandex&amp;#x27;,&lt;br /&gt;            &amp;#x27;www.ya.ru&amp;#x27;: &amp;#x27;yandex&amp;#x27;,&lt;br /&gt;            &amp;#x27;ya.kz&amp;#x27;: &amp;#x27;yandex&amp;#x27;,&lt;br /&gt;            &amp;#x27;www.ya.kz&amp;#x27;: &amp;#x27;yandex&amp;#x27;,&lt;br /&gt;            &amp;#x27;www.yahoo.com&amp;#x27;: &amp;#x27;yahoo&amp;#x27;,&lt;br /&gt;            &amp;#x27;yahoo.cn&amp;#x27;: &amp;#x27;yahoo&amp;#x27;,&lt;br /&gt;            &amp;#x27;m.yahoo.com&amp;#x27;: &amp;#x27;yahoo&amp;#x27;&lt;br /&gt;        };&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;90iU&quot;&gt;&lt;code&gt;        const organicSources = Object.keys(sourceMapping);&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;U7Hf&quot;&gt;&lt;code&gt;        if (organicSources.some(domain =&amp;gt; referrerDomain.includes(domain))) {&lt;br /&gt;            utmSource = sourceMapping[organicSources.find(domain =&amp;gt; referrerDomain.includes(domain))] || referrer;&lt;br /&gt;            utmMedium = &amp;#x27;organic&amp;#x27;;&lt;br /&gt;        } else {&lt;br /&gt;            utmSource = referrer;&lt;br /&gt;            utmMedium = &amp;#x27;referral&amp;#x27;;&lt;br /&gt;        }&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;99CO&quot;&gt;&lt;code&gt;        setCookie(&amp;#x27;utm_source&amp;#x27;, utmSource, 30);&lt;br /&gt;        setCookie(&amp;#x27;utm_medium&amp;#x27;, utmMedium, 30);&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;    if (!existingUtmSource &amp;amp;&amp;amp; !existingUtmMedium) {&lt;br /&gt;        setCookie(&amp;#x27;utm_source&amp;#x27;, utmSource, 30);&lt;br /&gt;        setCookie(&amp;#x27;utm_medium&amp;#x27;, utmMedium, 30);&lt;br /&gt;    }&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;0R1P&quot;&gt;&lt;code&gt;}&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;xDXr&quot;&gt;&lt;code&gt;// 3. Подстановка значений utm-меток из куков в URL при вводе в любой input&lt;br /&gt;function addUtmParamsToUrl() {&lt;br /&gt;    const utmParams = [&amp;#x27;utm_medium&amp;#x27;, &amp;#x27;utm_source&amp;#x27;, &amp;#x27;utm_term&amp;#x27;, &amp;#x27;utm_content&amp;#x27;, &amp;#x27;utm_campaign&amp;#x27;, &amp;#x27;client_id&amp;#x27;];&lt;br /&gt;    const currentUrl = new URL(window.location.href);&lt;br /&gt;    let urlModified = false;&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;bg2n&quot;&gt;&lt;code&gt;    utmParams.forEach(param =&amp;gt; {&lt;br /&gt;        const cookieValue = getCookie(param);&lt;br /&gt;        if (cookieValue &amp;amp;&amp;amp; !currentUrl.searchParams.has(param)) {&lt;br /&gt;            currentUrl.searchParams.set(param, cookieValue);&lt;br /&gt;            urlModified = true;&lt;br /&gt;        }&lt;br /&gt;    });&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;bafD&quot;&gt;&lt;code&gt;    if (urlModified) {&lt;br /&gt;        window.history.replaceState({}, &amp;#x27;&amp;#x27;, currentUrl);&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;vwkl&quot;&gt;&lt;code&gt;// Слушатель событий для ввода в любой input на странице&lt;br /&gt;document.addEventListener(&amp;#x27;input&amp;#x27;, (event) =&amp;gt; {&lt;br /&gt;    if (event.target.tagName === &amp;#x27;INPUT&amp;#x27;) {&lt;br /&gt;        addUtmParamsToUrl();&lt;br /&gt;    }&lt;br /&gt;});&lt;br /&gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;br /&gt;&lt;/p&gt;
  &lt;p id=&quot;3F5z&quot;&gt;После размещения скрипта не забудьте заново опубликовать все страницы&lt;/p&gt;

</content></entry><entry><id>ilznv:m1SGoMlQ9E1</id><link rel="alternate" type="text/html" href="https://teletype.in/@ilznv/m1SGoMlQ9E1?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=ilznv"></link><title>Связь Many-to-Many в Symfony</title><published>2025-01-07T11:33:55.456Z</published><updated>2025-01-07T11:33:55.456Z</updated><summary type="html">При проектировании баз данных и работе с фреймворком Symfony, одной из самых распространённых задач является создание связи типа &quot;многие ко многим&quot; (Many-to-Many) между сущностями. Рассмотрим сценарий, где у нас есть две сущности: User (пользователь) и Group (группа). Каждый пользователь может принадлежать к нескольким группам, а каждая группа может содержать несколько пользователей. Это классический пример связи Many-to-Many.</summary><content type="html">
  &lt;p id=&quot;G21G&quot;&gt;При проектировании баз данных и работе с фреймворком Symfony, одной из самых распространённых задач является создание связи типа &lt;strong&gt;&amp;quot;многие ко многим&amp;quot; (Many-to-Many)&lt;/strong&gt; между сущностями. Рассмотрим сценарий, где у нас есть две сущности: &lt;code&gt;User&lt;/code&gt; (пользователь) и &lt;code&gt;Group&lt;/code&gt; (группа). Каждый пользователь может принадлежать к нескольким группам, а каждая группа может содержать несколько пользователей. Это классический пример связи Many-to-Many.&lt;/p&gt;
  &lt;h4 id=&quot;Как-реализовать-связь-Many-to-Many-в-Symfony&quot;&gt;Как реализовать связь Many-to-Many в Symfony&lt;/h4&gt;
  &lt;p id=&quot;NOOK&quot;&gt;В Symfony связь Many-to-Many реализуется с использованием аннотаций Doctrine ORM. Давайте рассмотрим, как можно настроить такую связь и вывести данные из базы.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;1.-Создание-сущностей&quot;&gt;1. Создание сущностей&lt;/h3&gt;
  &lt;h4 id=&quot;Сущность-User&quot;&gt;Сущность &lt;code&gt;User&lt;/code&gt;&lt;/h4&gt;
  &lt;pre id=&quot;ldfP&quot;&gt;&amp;lt;?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: &amp;#x27;integer&amp;#x27;)]
    private int $id;

    #[ORM\Column(type: &amp;#x27;string&amp;#x27;, length: 255)]
    private string $name;

    #[ORM\ManyToMany(targetEntity: Group::class, inversedBy: &amp;#x27;users&amp;#x27;)]
    #[ORM\JoinTable(name: &amp;#x27;user_group&amp;#x27;)]
    private Collection $groups;

    public function __construct()
    {
        $this-&amp;gt;groups = new ArrayCollection();
    }

    // Геттеры и сеттеры

    public function getId(): int
    {
        return $this-&amp;gt;id;
    }

    public function getName(): string
    {
        return $this-&amp;gt;name;
    }

    public function setName(string $name): self
    {
        $this-&amp;gt;name = $name;

        return $this;
    }

    public function getGroups(): Collection
    {
        return $this-&amp;gt;groups;
    }

    public function addGroup(Group $group): self
    {
        if (!$this-&amp;gt;groups-&amp;gt;contains($group)) {
            $this-&amp;gt;groups[] = $group;
            $group-&amp;gt;addUser($this);
        }

        return $this;
    }

    public function removeGroup(Group $group): self
    {
        if ($this-&amp;gt;groups-&amp;gt;removeElement($group)) {
            $group-&amp;gt;removeUser($this);
        }

        return $this;
    }
}
&lt;/pre&gt;
  &lt;h4 id=&quot;Сущность-Group&quot;&gt;Сущность &lt;code&gt;Group&lt;/code&gt;&lt;/h4&gt;
  &lt;pre id=&quot;DETj&quot;&gt;&amp;lt;?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Group
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: &amp;#x27;integer&amp;#x27;)]
    private int $id;

    #[ORM\Column(type: &amp;#x27;string&amp;#x27;, length: 255)]
    private string $name;

    #[ORM\ManyToMany(targetEntity: User::class, mappedBy: &amp;#x27;groups&amp;#x27;)]
    private Collection $users;

    public function __construct()
    {
        $this-&amp;gt;users = new ArrayCollection();
    }

    // Геттеры и сеттеры

    public function getId(): int
    {
        return $this-&amp;gt;id;
    }

    public function getName(): string
    {
        return $this-&amp;gt;name;
    }

    public function setName(string $name): self
    {
        $this-&amp;gt;name = $name;

        return $this;
    }

    public function getUsers(): Collection
    {
        return $this-&amp;gt;users;
    }

    public function addUser(User $user): self
    {
        if (!$this-&amp;gt;users-&amp;gt;contains($user)) {
            $this-&amp;gt;users[] = $user;
        }

        return $this;
    }

    public function removeUser(User $user): self
    {
        $this-&amp;gt;users-&amp;gt;removeElement($user);

        return $this;
    }
}
&lt;/pre&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;2.-Миграции&quot;&gt;2. Миграции&lt;/h3&gt;
  &lt;p id=&quot;Vj9Y&quot;&gt;После того как мы создали сущности, необходимо сгенерировать и выполнить миграцию базы данных:&lt;/p&gt;
  &lt;pre id=&quot;MKvW&quot;&gt;php bin/console make:migration
php bin/console doctrine:migrations:migrate
&lt;/pre&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;3.-Пример-с-таблицами-и-тестовыми-данными&quot;&gt;3. Пример с таблицами и тестовыми данными&lt;/h3&gt;
  &lt;p id=&quot;qx55&quot;&gt;Для того чтобы продемонстрировать работу с сущностями, давайте создадим таблицы и заполним их тестовыми данными. В базе данных будут следующие таблицы:&lt;/p&gt;
  &lt;figure id=&quot;nGx1&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/30/f6/30f604d3-ce67-4770-80de-6034de69003e.png&quot; width=&quot;508&quot; /&gt;
  &lt;/figure&gt;
  &lt;h4 id=&quot;4.-Пример-работы-с-данными&quot;&gt;4. Пример работы с данными&lt;/h4&gt;
  &lt;p id=&quot;Sg5q&quot;&gt;Теперь давайте выведем группы для пользователя с ID = 1. Выполнив следующий код, мы получим:&lt;/p&gt;
  &lt;pre id=&quot;oS6y&quot;&gt;$userRepository = $entityManager-&amp;gt;getRepository(User::class);
$user = $userRepository-&amp;gt;find(1);

// Выводим группы пользователя
foreach ($user-&amp;gt;getGroups() as $group) {
    echo $group-&amp;gt;getName();
}
&lt;/pre&gt;
  &lt;h4 id=&quot;Ожидаемый-вывод-для-пользователя-с-ID-=-1-(Иван-Иванов):&quot;&gt;Ожидаемый вывод для пользователя с ID = 1 (Иван Иванов):&lt;/h4&gt;
  &lt;pre id=&quot;OfKG&quot;&gt;Администраторы
Модераторы
&lt;/pre&gt;
  &lt;p id=&quot;d4QI&quot;&gt;Для пользователя с ID = 4 (Мария Смирнова) вывод будет:&lt;/p&gt;
  &lt;pre id=&quot;xXVC&quot;&gt;Модераторы
Разработчики
Сотрудники
&lt;/pre&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;5.-Заключение&quot;&gt;5. Заключение&lt;/h3&gt;
  &lt;p id=&quot;pTVz&quot;&gt;Связь Many-to-Many в Symfony — это мощный инструмент, который позволяет организовать отношения между сущностями&lt;/p&gt;
  &lt;hr /&gt;

</content></entry><entry><id>ilznv:k9SlCG9o15p</id><link rel="alternate" type="text/html" href="https://teletype.in/@ilznv/k9SlCG9o15p?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=ilznv"></link><title>Простой скрипт для отслеживания доступности сайта с уведомлением в Telegram </title><published>2024-12-21T15:04:24.664Z</published><updated>2024-12-21T15:04:24.664Z</updated><summary type="html">Современные системы мониторинга позволяют гибко настроить мониторинг сайта и уведомления к нему, но что если вам нужно просто знать доступен ли ваш сайт на данный момент и посылать уведомление в телеграм-чат если что-то не так? Для этого можно использовать данный php-скрипт который можно разместить на сервере и запускать его по крону</summary><content type="html">
  &lt;p id=&quot;BYSt&quot;&gt;Современные системы мониторинга позволяют гибко настроить мониторинг сайта и уведомления к нему, но что если вам нужно просто знать доступен ли ваш сайт на данный момент и посылать уведомление в телеграм-чат если что-то не так? Для этого можно использовать данный php-скрипт который можно разместить на сервере и запускать его по крону&lt;/p&gt;
  &lt;p id=&quot;Q89g&quot;&gt;Шаг 1. Устанавливаем необходимые зависимости&lt;/p&gt;
  &lt;blockquote id=&quot;xvLF&quot;&gt;composer require guzzlehttp/guzzle telegram-bot/api&lt;/blockquote&gt;
  &lt;p id=&quot;QQaE&quot;&gt;Шаг 2. Создаем файл site_health_checker.php&lt;/p&gt;
  &lt;pre id=&quot;y8ZY&quot;&gt;&amp;lt;?php

require &amp;#x27;vendor/autoload.php&amp;#x27;;

use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use TelegramBot\Api\BotApi;
use GuzzleHttp\Client;

function checkSite($url)
{
    $client = new Client([
        &amp;#x27;timeout&amp;#x27;         =&amp;gt; 30, //время ожидания ответа 30 секунд
        &amp;#x27;connect_timeout&amp;#x27; =&amp;gt; 30,
    ]);

    $start = microtime(true);

    try {
        $response = $client-&amp;gt;request(&amp;#x27;HEAD&amp;#x27;, $url);
        $httpCode = $response-&amp;gt;getStatusCode();
    } catch (RequestException $e) {
        $httpCode = $e-&amp;gt;getCode() ?: 0;
    } catch (ConnectException $e) {
        $httpCode = 0;
    }

    $totalTime = microtime(true) - $start;

    return [
        &amp;#x27;httpCode&amp;#x27;     =&amp;gt; $httpCode,
        &amp;#x27;responseTime&amp;#x27; =&amp;gt; $totalTime,
    ];
}

function sendTelegramNotification($token, $chatId, $message)
{
    $bot = new BotApi($token);
    $bot-&amp;gt;sendMessage($chatId, $message);
}


$telegramToken  = &amp;#x27;ваш телеграм токен&amp;#x27;;
$telegramChatId = &amp;#x27;id чата телеграмм&amp;#x27;;
$sites          = [
    &amp;#x27;https://example.ru&amp;#x27;,
    &amp;#x27;https://google.com&amp;#x27;,
//ваш список сайтов
];

foreach ($sites as $site) {
    $result       = checkSite($site);
    $httpCode     = $result[&amp;#x27;httpCode&amp;#x27;];
    $responseTime = $result[&amp;#x27;responseTime&amp;#x27;];

//если код ответа отличается от успешного  (200) или время ответа больше 30 секунд посылаем уведомление в телеграмм чат
    if ($httpCode != 200 || $responseTime &amp;gt; 30) {
        $message = &amp;quot;❗ Проблема с сайтом: $site\n&amp;quot;;
        $message .= &amp;quot;HTTP-код: $httpCode\n&amp;quot;;
        $message .= sprintf(&amp;quot;Время ответа: %.2f секунд\n&amp;quot;, $responseTime);
        sendTelegramNotification($telegramToken, $telegramChatId, $message);
    } else {
        echo &amp;quot;✅ Сайт $site работает нормально (HTTP $httpCode, время ответа: &amp;quot; . sprintf(&amp;quot;%.2f&amp;quot;,
                $responseTime) . &amp;quot; секунд).\n&amp;quot;;
    }
}

&lt;/pre&gt;
  &lt;p id=&quot;iqv2&quot;&gt;Шаг 3. Настраиваем задачу cron (команда в консоли crontab -e) чтобы ваш скрипт запускался, к примеру, раз в 3 минуты&lt;/p&gt;
  &lt;blockquote id=&quot;GX7L&quot;&gt;*/3 * * * * php site_health_checker.php&lt;/blockquote&gt;
  &lt;p id=&quot;ApBF&quot;&gt;Готово! Теперь в случае неполадок вы будете получать подобные сообщения в свой телеграмм-чат&lt;br /&gt;&lt;strong&gt;❗ Проблема с сайтом: https://example.ru &lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;Lzqf&quot;&gt;&lt;strong&gt; HTTP-код: 503&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;amSi&quot;&gt;&lt;strong&gt; Время ответа: 45.23 секунд &lt;/strong&gt;&lt;/p&gt;

</content></entry><entry><id>ilznv:fnSt_yVUYVm</id><link rel="alternate" type="text/html" href="https://teletype.in/@ilznv/fnSt_yVUYVm?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=ilznv"></link><title>Обработка десятков тысяч заказов и смена статуса с ограничением по запросам API </title><published>2024-12-17T16:21:36.608Z</published><updated>2024-12-17T16:26:30.030Z</updated><summary type="html">🚀 Задача: Обработка десятков тысяч заказов и смена статуса с ограничением по запросам API 📦</summary><content type="html">
  &lt;p id=&quot;wL7C&quot;&gt;&lt;br /&gt;🚀 Задача: Обработка десятков тысяч заказов и смена статуса с ограничением по запросам API 📦&lt;/p&gt;
  &lt;p id=&quot;neeX&quot;&gt;Представьте, что перед вами стоит задача обновить статус для нескольких десятков тысяч заказов в внешней системе, но API ограничивает количество запросов до 50 в секунду. Как эффективно решить такую задачу? 🤔&lt;/p&gt;
  &lt;p id=&quot;VrqW&quot;&gt;Давайте разберемся, как с помощью генераторов, пакетных запросов и параллельных запросов через Guzzle можно справиться с этой задачей без перегрузки системы! 😎&lt;/p&gt;
  &lt;p id=&quot;Kycx&quot;&gt;&lt;br /&gt;🔧 Решение проблемы с обработкой заказов:&lt;/p&gt;
  &lt;p id=&quot;aNVf&quot;&gt;1️⃣ Используем генераторы:&lt;br /&gt;Вместо того, чтобы сразу загружать все заказы в память, мы применяем генератор, который будет извлекать данные &amp;quot;по частям&amp;quot;. Это помогает эффективно работать с большими объемами данных без переполнения памяти.&lt;/p&gt;
  &lt;p id=&quot;8TbS&quot;&gt;2️⃣ Пакетная обработка данных:&lt;br /&gt;Каждую порцию данных (например, 50 заказов) извлекаем и отправляем на API. Это позволяет избежать перегрузки БД и контролировать количество запросов, отправляемых на сервер.&lt;/p&gt;
  &lt;p id=&quot;LJNi&quot;&gt;3️⃣ Параллельные запросы с Guzzle:&lt;br /&gt;API ограничивает нас 50 запросами в секунду, но это не значит, что мы не можем отправлять их параллельно! Мы используем асинхронные запросы через Guzzle, отправляя сразу 50 запросов за раз. Это позволяет значительно ускорить процесс обработки, не нарушая лимит.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;BrIp&quot;&gt;&lt;code&gt;use GuzzleHttp\Client;&lt;br /&gt;use GuzzleHttp\Promise;&lt;br /&gt;use GuzzleHttp\Exception\RequestException;&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;7q2W&quot;&gt;&lt;code&gt;&lt;br /&gt;// Генератор для выборки заказов из базы данных&lt;br /&gt;function getOrdersFromDatabase($batchSize = 50) {&lt;br /&gt;    &lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;HRG8&quot;&gt;&lt;code&gt;    $offset = 0;&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;V9KP&quot;&gt;&lt;code&gt;    while (true) {&lt;br /&gt;        // Выполнение SQL-запроса для выборки заказов&lt;br /&gt;        $stmt = $db-&amp;gt;prepare(&amp;quot;SELECT * FROM orders WHERE status = &amp;#x27;pending&amp;#x27; LIMIT :batchSize OFFSET :offset&amp;quot;);&lt;br /&gt;        $stmt-&amp;gt;bindParam(&amp;#x27;:batchSize&amp;#x27;, $batchSize, PDO::PARAM_INT);&lt;br /&gt;        $stmt-&amp;gt;bindParam(&amp;#x27;:offset&amp;#x27;, $offset, PDO::PARAM_INT);&lt;br /&gt;        $stmt-&amp;gt;execute();&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;IZLd&quot;&gt;&lt;code&gt;        $data = $stmt-&amp;gt;fetchAll();&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;lfGt&quot;&gt;&lt;code&gt;        // Если данных больше нет, выходим из цикла&lt;br /&gt;        if (empty($data)) {&lt;br /&gt;            break;&lt;br /&gt;        }&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;iwsx&quot;&gt;&lt;code&gt;        // Возвращаем порцию заказов&lt;br /&gt;        yield $data;&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;JWbH&quot;&gt;&lt;code&gt;        // Переходим к следующей порции&lt;br /&gt;        $offset += $batchSize;&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;sLNl&quot;&gt;&lt;code&gt;// Функция для отправки статуса заказов на API&lt;br /&gt;function updateOrderStatus(Client $client, $orders) {&lt;br /&gt;    $requests = [];&lt;br /&gt;    &lt;br /&gt;    foreach ($orders as $order) {&lt;br /&gt;        // Создаем асинхронный запрос для обновления статуса&lt;br /&gt;        $requests[] = $client-&amp;gt;postAsync(&amp;#x27;https://api.example.com/update-status&amp;#x27;, [&lt;br /&gt;            &amp;#x27;json&amp;#x27; =&amp;gt; [&lt;br /&gt;                &amp;#x27;order_id&amp;#x27; =&amp;gt; $order[&amp;#x27;id&amp;#x27;],&lt;br /&gt;                &amp;#x27;status&amp;#x27; =&amp;gt; &amp;#x27;completed&amp;#x27;,  // Пример статуса&lt;br /&gt;            ]&lt;br /&gt;        ]);&lt;br /&gt;    }&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;z71b&quot;&gt;&lt;code&gt;    // Возвращаем массив промисов&lt;br /&gt;    return Promise\settle($requests);&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;hNdh&quot;&gt;&lt;code&gt;// Основная логика обработки заказов&lt;br /&gt;function processOrders() {&lt;br /&gt;    $client = new Client();  // Инициализация Guzzle клиента&lt;br /&gt;    $batchSize = 50;         // Размер пачки заказов&lt;br /&gt;    $maxRequestsPerSecond = 50; // Лимит API запросов в секунду&lt;br /&gt;    $delayBetweenBatches = 1;  // Задержка между батчами (сек)&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;gIw8&quot;&gt;&lt;code&gt;    // Процесс обработки заказов по батчам&lt;br /&gt;    foreach (getOrdersFromDatabase($batchSize) as $batch) {&lt;br /&gt;        try {&lt;br /&gt;            // Отправляем 50 параллельных запросов на API&lt;br /&gt;            $results = updateOrderStatus($client, $batch)-&amp;gt;wait(); &lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;78Jy&quot;&gt;&lt;code&gt;            // Логируем успешные обновления (по желанию)&lt;br /&gt;            foreach ($results as $result) {&lt;br /&gt;                if ($result[&amp;#x27;state&amp;#x27;] === &amp;#x27;fulfilled&amp;#x27;) {&lt;br /&gt;                    echo &amp;quot;Order {$batch[0][&amp;#x27;id&amp;#x27;]} successfully updated\n&amp;quot;; // Пример логирования&lt;br /&gt;                } else {&lt;br /&gt;                    echo &amp;quot;Failed to update order {$batch[0][&amp;#x27;id&amp;#x27;]}: {$result[&amp;#x27;reason&amp;#x27;]-&amp;gt;getMessage()}\n&amp;quot;;&lt;br /&gt;                }&lt;br /&gt;            }&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;ihx4&quot;&gt;&lt;code&gt;        } catch (RequestException $e) {&lt;br /&gt;            // Обработка ошибок API или подключения&lt;br /&gt;            echo &amp;quot;Request failed: &amp;quot; . $e-&amp;gt;getMessage() . &amp;quot;\n&amp;quot;;&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        // Задержка между батчами для соблюдения лимита запросов в секунду&lt;br /&gt;        sleep($delayBetweenBatches);&lt;br /&gt;    }&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;
    &lt;p id=&quot;Qsa8&quot;&gt;&lt;code&gt;// Вызов основной функции&lt;br /&gt;processOrders();&lt;/code&gt;&lt;/p&gt;
  &lt;/section&gt;

</content></entry><entry><id>ilznv:yirXjZ5T-sm</id><link rel="alternate" type="text/html" href="https://teletype.in/@ilznv/yirXjZ5T-sm?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=ilznv"></link><title>Как использовать Puppeteer с NestJS для парсинга рейтинга</title><published>2024-12-17T16:13:38.669Z</published><updated>2024-12-17T16:13:38.669Z</updated><summary type="html">В этом посте покажу, как с помощью библиотеки Puppeteer спарсить рейтинг с сайта с использованием фреймворка NestJS.</summary><content type="html">
  &lt;p id=&quot;Ev17&quot;&gt;В этом посте покажу, как с помощью библиотеки &lt;strong&gt;Puppeteer&lt;/strong&gt; спарсить рейтинг с сайта с использованием фреймворка &lt;strong&gt;NestJS&lt;/strong&gt;.&lt;/p&gt;
  &lt;p id=&quot;Umul&quot;&gt;📌 &lt;strong&gt;Шаг 1: Установка зависимостей&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;8u6U&quot;&gt;Для начала установим необходимые пакеты:&lt;/p&gt;
  &lt;pre id=&quot;yWff&quot;&gt;npm install puppeteer
npm install @nestjs/axios
npm install --save-dev @types/puppeteer
&lt;/pre&gt;
  &lt;p id=&quot;aMZh&quot;&gt;📌 &lt;strong&gt;Шаг 2: Создаем сервис для парсинга&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;8TBU&quot;&gt;Теперь создадим сервис в NestJS, который будет парсить данные с сайта. В нашем примере мы будем извлекать рейтинг из элемента с определенным классом.&lt;/p&gt;
  &lt;pre id=&quot;O6c9&quot;&gt;rating.service.ts
import { Injectable } from &amp;#x27;@nestjs/common&amp;#x27;;
import * as puppeteer from &amp;#x27;puppeteer&amp;#x27;;

@Injectable()
export class RatingService {
  async getRating(url: string): Promise&amp;lt;number&amp;gt; {
    const browser = await puppeteer.launch({ headless: true });
    const page = await browser.newPage();

    await page.goto(url, { waitUntil: &amp;#x27;domcontentloaded&amp;#x27; });

    // Пример: извлекаем рейтинг с элемента с классом &amp;#x27;.rating&amp;#x27;
    const rating = await page.$eval(&amp;#x27;.rating&amp;#x27;, (el) =&amp;gt; {
      return parseFloat(el.textContent.trim());
    });

    await browser.close();

    return rating;
  }
}
&lt;/pre&gt;
  &lt;p id=&quot;PcrW&quot;&gt;📌 &lt;strong&gt;Шаг 3: Создаем контроллер для API&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;vraL&quot;&gt;Теперь создадим контроллер, который будет обрабатывать запросы и отдавать рейтинг в формате JSON.&lt;/p&gt;
  &lt;pre id=&quot;t11d&quot;&gt;rating.controller.ts
import { Controller, Get, Query } from &amp;#x27;@nestjs/common&amp;#x27;;
import { RatingService } from &amp;#x27;./rating.service&amp;#x27;;

@Controller(&amp;#x27;rating&amp;#x27;)
export class RatingController {
  constructor(private readonly ratingService: RatingService) {}

  @Get()
  async getRating(@Query(&amp;#x27;url&amp;#x27;) url: string) {
    const rating = await this.ratingService.getRating(url);
    return { rating };
  }
}
&lt;/pre&gt;
  &lt;p id=&quot;5yoR&quot;&gt;📌 &lt;strong&gt;Шаг 4: Добавляем сервис и контроллер в модуль&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;WzkV&quot;&gt;Не забываем добавить сервис и контроллер в наш модуль:&lt;/p&gt;
  &lt;pre id=&quot;vafw&quot;&gt;app.module.ts
import { Module } from &amp;#x27;@nestjs/common&amp;#x27;;
import { RatingService } from &amp;#x27;./rating.service&amp;#x27;;
import { RatingController } from &amp;#x27;./rating.controller&amp;#x27;;

@Module({
  imports: [],
  controllers: [RatingController],
  providers: [RatingService],
})
export class AppModule {}
&lt;/pre&gt;
  &lt;p id=&quot;OkFa&quot;&gt;📌 &lt;strong&gt;Шаг 5: Запуск проекта&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;nLss&quot;&gt;Теперь запускаем приложение:&lt;/p&gt;
  &lt;pre id=&quot;Ab9C&quot;&gt;npm run start
&lt;/pre&gt;
  &lt;p id=&quot;T5zB&quot;&gt;🔗 &lt;strong&gt;Шаг 6: Тестируем&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;sd4B&quot;&gt;Отправляем запрос к API, передав URL сайта:&lt;/p&gt;
  &lt;pre id=&quot;EL7n&quot;&gt;GET /rating?url=https://example.com
&lt;/pre&gt;
  &lt;p id=&quot;5Xvt&quot;&gt;В ответе получим рейтинг:&lt;/p&gt;
  &lt;pre id=&quot;yy8e&quot;&gt;{
  &amp;quot;rating&amp;quot;: 4.5
}
&lt;/pre&gt;
  &lt;p id=&quot;E1SR&quot;&gt;🎉 &lt;strong&gt;Поздравляем!&lt;/strong&gt; Теперь у вас есть сервис, который парсит рейтинг с сайтов с помощью Puppeteer и NestJS!&lt;/p&gt;

</content></entry></feed>