Max: Исключение повторных нажатий кнопок до ответа сервера

Как бы не ругались некоторые товарищи на Habr, что «разрабатывать под Max, себя не уважать», но тем не менее не все имеют этот выбор 😉 Так что проблемы решать таки нужно..

Итак, задача: между нажатием кнопки с кэлбеком и ответом сервера происходит некоторая пауза. Иногда в несколько секунд. Пользователи не всегда дожидаются ответа, и нажимают кнопки по нескольку раз.

Решение: т.к. на основной ответ сервера требуется некоторое время на его подготовку, то перед «настоящим» ответом сервера, отправим «промежуточный», который сообщит пользователю «Подождите ответа сервера», а заодно скроет все кнопки из предыдущего сообщения пользователя, чтоб не было соблазна их нажимать. Код получается примерно такой:

...
    if (isset($in->update_type)){
        if ($in->update_type=="message_callback"){
          $params=[];
          $params["callback_id"]=$in->callback->callback_id;
          $data=[];
          $data["message"]["attachments"]=[];
          $data["notification"]="Подождите ответа сервера...";
          $res=$Max->Reqwest("answers",$params,$data,"POST");    
        };
        if (($in->update_type=="message_created") or ($in->update_type=="message_callback") or ($in->update_type=="bot_started")){                       
          $res=$Api1c->reqwest("MaxCallback2",["body"=>$body]);  // пересылаем всё что пришло в 1С  и получаем ответ
          if ($res==null){
            $params=[];  
            $params["chat_id"]=$in->message->recipient->chat_id;
            $data=[];
            $data["text"]="Временная ошибка серверера. Попробуйте позже.";    
            $data["format"]= "html";            
                $buttons=[];                
                    $button=[];
                    $button["type"]="callback";
                    $button["text"]="Перейти в главное меню";
                    $button["payload"]="Go2MainMenu";                
                $buttons[]=$button;                                
                    $attachment=[];
                    $attachment["type"]="inline_keyboard";
                    $attachment["payload"]["buttons"]=[];
                    $attachment["payload"]["buttons"][]=$buttons;                                                     
            $data["attachments"]=[];    
            $data["attachments"][]=$attachment;
            $res=$Max->Reqwest("messages",$params,$data,"POST");    
            die();  
          };
    // если 1С вернула ошибку то её и показываем пользователю
          if ($res->error==true){
            $params=[];  
            if (isset($in->message)){$params["chat_id"]=$in->message->recipient->chat_id;};
            if (isset($in->chat_id)){$params["chat_id"]=$in->chat_id;};        
            $data=[];
            $data["text"]=$res->result;    
            $data["format"]= "html";            
                $buttons=[];                
                    $button=[];
                    $button["type"]="callback";
                    $button["text"]="Перейти в главное меню";
                    $button["payload"]="Go2MainMenu";                
                $buttons[]=$button;                                
                    $attachment=[];
                    $attachment["type"]="inline_keyboard";
                    $attachment["payload"]["buttons"]=[];
                    $attachment["payload"]["buttons"][]=$buttons;                                                     
            $data["attachments"]=[];    
            $data["attachments"][]=$attachment;
            
            $res=$Max->Reqwest("messages",$params,$data,"POST");          
            die();  
          };
          // Что пришло из 1С, то и отдаём в сообщения пользователю..
          $params=$res->result->params;
          $data=$res->result->data;
          $res=$Max->Reqwest("messages",$params,$data,"POST");          
          die();
...

Javascript: формирование csv файла

Задача: на чистом javascript сформировать и сохранить из браузера файл формата csv, адекватно открывающийся табличным редактором (например OnlyOffice)

Решение:

rows=[];
rows.push(["aa","bb"]);

let csvContent = "data:text/csv;charset=utf-8," + rows.map(e => e.join(",")).join("\n");
var encodedUri = encodeURI(csvContent);
var link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "download.csv");
document.body.appendChild(link); 
link.click();

Что тут происходит: заполняется некий массив, далее создаётся ссылка в DOM, и в элемент ссылки записываются данные массива. Далее эмулируется нажатие собственно на эту ссылку.

Nginx: настройка проксирования

Задача: есть локальный web сервис запущенный…ну например в докере. Не суть в общем где запущен. Может быть просто какой то локальный сервис типа web интерфейса к zigbee2mqtt: htpp://192.168.8.1:8080 Необходимо дать возможность открывать его в браузере, с нормальным ssl сертификатом ну и всё такое. Вот собственно для этого и нужно проксирование.

Решение: первым делом получим сертификат для домена. Для этого добавим в настройки nginx следующие строчки:

server {
        listen 80;
        server_name ваш_домен;
        root /var/www/m;
        index index.php;
}

После чего получим сертификат:

service nginx restart
certbot certonly --webroot -w /var/www/m -d ваш_домен

Далее настройки nginx изменим на:

server {
        listen 80;
        listen [::]:80;
        listen 443 ssl;
        server_name ваш_домен;
        if ($scheme = 'http') {return 301 https://$host$request_uri;}
        ssl_certificate     /etc/letsencrypt/live/ваш_домен/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/ваш_домен/privkey.pem;
        location / {
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
            proxy_pass http://192.168.8.1:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Real-IP $remote_addr;
        }
}

Что тут есть?

  1. Переадрессация с http на https
  2. Подсовывание сертификата
  3. Проксирование http://192.168.8.1:8080 на ваш_домен

Собственно и всё

Получение ExecutorGUID и orgPPAGUID

При работе с ГИС ЖКХ это первоочередные идентификаторы, которые в самой панели ГИС ЖКХ спрятаны так, что не каждый с первого раза и найдёт. Поэтому есть способ чуть попроще.

  1. Авторизуемся на ГИС ЖКХ https://portal.dom.gosuslugi.ru/
  2. В браузере нажимаем F12 и переходим на вкладку «Сеть»
  3. В правом верхнем углу нажимаем на название своей организации -> посмотреть информацию об организации, и смотрим содержимое запроса из вкладки «Сеть»

guid в «полезной нагрузке» это и есть orgPPAGUID

Для получения ExecutorGUID, точно так-же нажимаем на название своей организации -> Посмотреть информацию о представителе организации. Ничего не меняя нажимаем «Сохранить». В сетевом запросе updateinfo, видим требуемый guid:

API Map Yandex: Изменение содержимого метки «на лету»

Задача: разместить внутри метки поле ввода текста (комментарий) и сохранять его значение

Решение:

При формировании Json данных FeatureCollection, внутри каждой метки, в настройке balloonContentBody разместим теги <textarea style=’width: 100%;’></textarea>. И кнопку сохранения результата. Но! Сохранить результат в БД не сложно. Сложно добиться того, чтобы то что пользователь ввел в поле, сохранилось, после перевыбора метки мышкой. Содержимое поля скидывается каждый раз к изначальному, т.к. движек яндекса каждый раз перерисовывает отображение html на основании данных которые у него » в душе». Поэтому функция сохранения будет выглядеть как-то так:

function SaveComment(ls_id){
    console.log("-сохраняем комментарий "+ls_id);
    comment=$("#comment_"+ls_id).val();
    $.post("?r=receipts2/savecomment", {  
            ls_id:ls_id,
            comment:comment
    }).done(function(data) {                        
        console.log(data);
        balloonContentBody=objectManager.objects.getById(ls_id).properties.balloonContentBody;
        start_=balloonContentBody.slice(0,balloonContentBody.search("%;'")+4);
        end_=balloonContentBody.slice(balloonContentBody.search("</textarea>"),balloonContentBody.length);
        balloonContentBody=start_+comment+end_;
        objectManager.objects.setObjectProperties(ls_id,{balloonContentBody:balloonContentBody});
    });                      
}

Что тут происходит?

  1. Изменения сохраняются в базу данных при помощи вызова скрипта receipts2/savecomment
  2. Читаем данные которые яндекс хранит у себя в нутри
  3. Изменем эти данные, добавив комментарий
  4. Сохраняем данные «внутри»

В результате после перещелкивания по метке, комментарий сохраняется.

1 2 3 56