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

Ajax

Пишем комментарии для сайта на чистом PHP MySQL Ajax

05.08.2020 02:22:35 | Автор: admin
Привет всем! Недавно столкнулся с проблемой написания комментариев на сайте. Просмотрел весь интернет в поиске нормальных статей, по поводу написания комментариев на чистом PHP 7. Статьи были, но в основном либо устаревшие, либо не работающие уже совсем. Для написания комментариев Вам необходимо знать PHP 7, MySQL, JavaScript и Ajax. Приступим!



Front-End


Структура


Для начало создаем все нужные файлы и папки.


Далее создаем самую простую форму заполнения комментария.
В comments.php:
<!DOCTYPE html><html lang="ru"><head>  <title>Комментарии</title>  <link rel="stylesheet" href="http://personeltest.ru/aways/habr.com/css/style.css">  <meta http-equiv="content-type" content="text/html; charset=utf-8">  <script type="text/javascript" src="js/jquery-1.5.1.min.js"></script></head><body>  <form action="sendMessage.php" method="post" name="form">    <p class="is-h">Автор:<br> <input name="author" type="text" class="is-input" id="author"></p>    <p class="is-h">Текст сообщения:<br><textarea name="message" rows="5" cols="50" id="message"></textarea></p>    <input name="js" type="hidden" value="no" id="js">    <button type="submit" id='click' name="button" class="is-button">Отправить</button>  </form>  <div class="clear">  </div>  <p>Комментарии к статье</p>  <div id="commentBlock"><!-- Здесь будут высвечиваться комментарии -->  </div></body></html>


Работаем с БД


Подготовка Базы Данных


Сначала создадим нужную нам Базу Данных и таблицу:
Базу данных назовем test.
Далее создаем таблицу:
CREATE TABLE `test`.`messages` ( `id` INT(255) UNSIGNED NOT NULL AUTO_INCREMENT , `author` VARCHAR(30) NOT NULL , `message` TEXT NOT NULL , `date` VARCHAR(25) NOT NULL , PRIMARY KEY (`id`)) ENGINE = InnoDB CHARSET=utf8 COLLATE utf8_general_ci;

Она должна быть такой структуры:


Подключение к Базе Данных


Теперь подключимся к самой базе данных. Подключаться будем в отдельном файле, чтобы в дальнейшем каждый раз не подключаться.
В файле connect.php пишем:
<?php$mysql = new mysqli('localhost', 'login', 'password', 'test');?>

Вместо login и password вставляете свои значения (логин и пароль соответственно). Например у меня login root, и пароль root:
<?php$mysql = new mysqli('localhost', 'root', 'root', 'test');?>

test название нашей Базы Данных.

Пишем сам скрипт


Получение и обработка данных из полей


Теперь нам нужно получить данные из полей. Но для начала сделаем нашу форму немного симпатичнее, чтобы было приятнее с ней работать. В style.css пропишем:
* {  max-width: 800px;  margin: 0 auto;}textarea {  resize: none;}.clear {  margin-top: 50px;}#author {  width: 100%;  height: 4%;  font-size: 1.3em;}.is-h {  font-weight: bold;  font-family: cursive;  margin-top: 2%;}#message {  width: 100%;  font-size: 1.5em;}.is-button {  cursor: pointer;  color: white;  background-color: green;  width: 25%;  height: 50px;  margin-top: 1%;  outline: none; /* Убираем линию вокруг кнопки при нажатии */  font-weight: bold;  font-family: cursive;  font-size: 1.2em;  border: none;  transition: all 0.3s ease-out;}.is-button:hover {  color: black;  background: rgb(48, 184, 66);}


Теперь наша форма должна выглядить так:


Обработка данных с помощью AJAX


Теперь получим данные из полей, обработаем их и отправим в PHP на доработку:
В файле comments.php, в теге script пропишем:
$(function() {    $("#send").click(function(){ // При нажатии на кнопку      var author = $("#author").val(); // Получаем имя автора комментария      var message = $("#message").val(); // Получаем само сообщение      $.ajax({ // Аякс        type: "POST", // Тип отправки "POST"        url: "sendMessage.php", // Куда отправляем(в какой файл)        data: {"author": author, "message": message}, // Что передаем и под каким значением         cache: false, // Убираем кеширование        success: function(response){ // Если все прошло успешно          var messageResp = new Array('Ваше сообщение отправлено','Сообщение не отправлено Ошибка базы данных','Нельзя отправлять пустые сообщения');          var resultStat = messageResp[Number(response)];          if(response == 0){             $("#author").val("");            $("#message").val("");            $("#commentBlock").append("<div class='comment'>Автор: <strong>"+author+"</strong><br>"+message+"</div>");}            $("#resp").text(resultStat).show().delay(1500).fadeOut(800);}});return false;});});

Обработка данных и запись их в БД


Теперь приступим к самому интересному. Для начала нам нужно провести самые примитивные проверки и словить данные с AJAX. В sendMessage.php пишем:
<?php include("connect.php"); // Подключаемся к БДheader("Content-type: text/html; charset=UTF-8"); // Устанавливаем кодировку//Если JS у пользователя включенif(empty($_POST['js'])){ if($_POST['message'] != '' && $_POST['author'] != ''){ // Если поля не пустые$author = @iconv("UTF-8", "windows-1251", $_POST['author']);$author = addslashes($author);$author = htmlspecialchars($author);$author = stripslashes($author);$author = mysql_real_escape_string($author); // Обрабатываем данные$message = @iconv("UTF-8", "windows-1251", $_POST['message']);$message = addslashes($message);$message = htmlspecialchars($message);$message = stripslashes($message);$message = mysql_real_escape_string($message); // Обрабатываем данные$date = date("d-m-Y в H:i:s"); // Получаем дату(фиксируем)$result = $mysql->query("INSERT INTO `messages` (`author`, `message`, `date`) VALUES ('$author', '$message', '$date')"); // Передаем в БД значенияif($result == true){echo 0; //Ваше сообшение успешно отправлено}else{echo 1; //Сообщение не отправлено. Ошибка базы данных}}else{echo 2; //Нельзя отправлять пустые сообщения}}// Если отключен JavaScript if($_POST['js'] == 'no'){if($_POST['message'] != '' && $_POST['author'] != ''){$author = $_POST['author'];$author = addslashes($author);$author = htmlspecialchars($author);$author = stripslashes($author);$message = $_POST['message'];$message = addslashes($message);$message = htmlspecialchars($message);$message = stripslashes($message);$date = date("d-m-Y в H:i:s");$result = $mysql->query("INSERT INTO `messages` (`author`, `message`, `date`) VALUES ('$author', '$message', '$date')");if($result == true){echo "Ваше сообшение успешно отправлено"; //Ваше сообшение успешно отправлено}else{echo "Сообщение не отправлено. Ошибка базы данных"; //Сообщение не отправлено. Ошибка базы данных}}else{echo "Нельзя отправлять пустые сообщения"; //Нельзя отправлять пустые сообщения}}?>

Вывод комментариев


Теперь дело за малым. Осталось всего лишь вывести эти комментарии в приятной для глаза форме.
Сначала необходимо подключиться к БД в comments.php. В самом верху пишем:
<?php require 'connect.php'; ?>

В div с id=commentBlock в файле comments.php пишем:
<?php            $result = $mysql->query("SELECT * FROM `messages`"); /*Получаем все данные из таблицы*/            $comment = $result->fetch_assoc(); /* В результирующий массив */            do{echo "<div class='comment' style='border: 1px solid gray; margin-top: 1%; border-radius: 5px; padding: 0.5%;'>Автор: <strong>".$comment['author']."</strong><br>".$comment['message']."</div>"; // Выводим          }while($comment = $result->fetch_assoc());          ?>

По комментариям должно быть все понятно. И теперь у нас должно получиться что-то вроде такого:

Всем спасибо за Внимание! Вот исходники:
comments.php
<?php require 'connect.php'; ?><!DOCTYPE html><html lang="ru"><head>  <title>Комментарии</title>  <link rel="stylesheet" href="http://personeltest.ru/aways/habr.com/css/style.css">  <meta http-equiv="content-type" content="text/html; charset=utf-8">  <script type="text/javascript" src="js/jquery-1.5.1.min.js"></script>  <script type="text/javascript">  $(function() {    $("#send").click(function(){      var author = $("#author").val();      var message = $("#message").val();      $.ajax({        type: "POST",        url: "sendMessage.php",        data: {"author": author, "message": message},        cache: false,        success: function(response){          var messageResp = new Array('Ваше сообщение отправлено','Сообщение не отправлено Ошибка базы данных','Нельзя отправлять пустые сообщения');          var resultStat = messageResp[Number(response)];          if(response == 0){            $("#author").val("");            $("#message").val("");            $("#commentBlock").append("<div class='comment'>Автор: <strong>"+author+"</strong><br>"+message+"</div>");}            $("#resp").text(resultStat).show().delay(1500).fadeOut(800);}});return false;});});            </script>          </head>                    <body>            <form action="sendMessage.php" method="post" name="form">              <p class="is-h">Автор:<br> <input name="author" type="text" class="is-input" id="author"></p>              <p class="is-h">Текст сообщения:<br><textarea name="message" rows="5" cols="50" id="message"></textarea></p>              <input name="js" type="hidden" value="no" id="js">              <button type="submit" id='click' name="button" class="is-button">Отправить</button>            </form>            <div class="clear">                          </div>                        <p>Комментарии к статье</p>                        <div id="commentBlock">              <?php              $result = $mysql->query("SELECT * FROM `messages`");              $comment = $result->fetch_assoc();              do{echo "<div class='comment' style='border: 1px solid gray; margin-top: 1%; border-radius: 5px; padding: 0.5%;'>Автор: <strong>".$comment['author']."</strong><br>".$comment['message']."</div>";              }while($comment = $result->fetch_assoc());              ?>            </div>          </body>          </html>


sendMessage.php:
<?php include("connect.php");header("Content-type: text/html; charset=UTF-8");//**********************************************if(empty($_POST['js'])){if($_POST['message'] != '' && $_POST['author'] != ''){$author = @iconv("UTF-8", "windows-1251", $_POST['author']);$author = addslashes($author);$author = htmlspecialchars($author);$author = stripslashes($author);$author = mysql_real_escape_string($author);$message = @iconv("UTF-8", "windows-1251", $_POST['message']);$message = addslashes($message);$message = htmlspecialchars($message);$message = stripslashes($message);$message = mysql_real_escape_string($message);$date = date("d-m-Y в H:i:s");$result = $mysql->query("INSERT INTO `messages` (`author`, `message`, `date`) VALUES ('$author', '$message', '$date')");if($result == true){echo 0; //Ваше сообшение успешно отправлено}else{echo 1; //Сообщение не отправлено. Ошибка базы данных}}else{echo 2; //Нельзя отправлять пустые сообщения}}//**************************************** Если отключен JavaScript ************************************if($_POST['js'] == 'no'){if($_POST['message'] != '' && $_POST['author'] != ''){$author = $_POST['author'];$author = addslashes($author);$author = htmlspecialchars($author);$author = stripslashes($author);$message = $_POST['message'];$message = addslashes($message);$message = htmlspecialchars($message);$message = stripslashes($message);$date = date("d-m-Y в H:i:s");$result = $mysql->query("INSERT INTO `messages` (`author`, `message`, `date`) VALUES ('$author', '$message', '$date')");if($result == true){echo "Ваше сообшение успешно отправлено"; //Ваше сообшение успешно отправлено}else{echo "Сообщение не отправлено. Ошибка базы данных"; //Сообщение не отправлено. Ошибка базы данных}}else{echo "Нельзя отправлять пустые сообщения"; //Нельзя отправлять пустые сообщения}}?>


connect.php:
<?php$mysql = new mysqli('localhost', 'root', 'root', 'test');?>


style.css:
* {  max-width: 800px;  margin: 0 auto;}textarea {  resize: none;}.clear {  margin-top: 50px;}#author {  width: 100%;  height: 4%;  font-size: 1.3em;}.is-h {  font-weight: bold;  font-family: cursive;  margin-top: 2%;}#message {  width: 100%;  font-size: 1.5em;}.is-button {  cursor: pointer;  color: white;  background-color: green;  width: 25%;  height: 50px;  margin-top: 1%;  outline: none; /* Убираем линию вокруг кнопки при нажатии */  font-weight: bold;  font-family: cursive;  font-size: 1.2em;  border: none;  transition: all 0.3s ease-out;}.is-button:hover {  color: black;  background: rgb(48, 184, 66);}

Подробнее..
Категории: Javascript , Php , Js , Mysql , Ajax , Jquery , Back-end , Comments

Моментальная загрузка с instant.page

18.09.2020 12:15:01 | Автор: admin


instant.page это небольшой скрипт, позволяющий ускорять навигацию по сайту с помощью just-in-time предзагрузки. Когда пользователь наводит курсор на ссылку, страница предзагружается в фоне, и при переходе по ссылке открывается моментально. По тому же принципу работает InstantClick, но он предоставляется уже как отдельная библиотека на pushState и Ajax, с дополнительными модулями вроде прогресс-бара предзагрузки.

Установка


Просто добавьте скрипт в конец body:

<script src="http://personeltest.ru/aways/instant.page/5.1.0" type="module" integrity="sha384-by67kQnR+pyfy8yWP4kPO12fHKRLHZPfEsiSXR8u2IKcTdxD805MGUXBzVPnkLHw"></script>

Может показаться, что в современном вебе бессмысленно распространять своё решение через скрипт вместо размещения в npm, но это не JQuery и весь код буквально умещается в чуть больше 200 строк, поэтому подключить его может себе позволить даже самое легковесное приложение. К тому же, в виде модуля уже есть сверхпопулярный но спорный quicklink от Google.

Десктоп


У разных исследований получаются разные значения, поэтому нельзя точно сказать, с какой вероятностью человек нажмёт на ссылку, на которую он навёл курсор, и как быстро это произойдёт. Автор скрипта утверждает, что если указатель уже наведён на ссылку более 65 миллисекунд, пользователь в половине случае перейдёт по ней.

Для понижения количества ложных срабатываний и в instant.page, и в InstantClick предлагают опционально триггерить предзагрузку в момент нажатия (mousedown), что в среднем должно ускорить загрузку на 80 миллисекунд. Правда, скрипт загружает только HTML, а другие жирные ресурсы всё равно будут загружаться в обычном режиме.

В instant.page есть и совсем безумный триггер для маньяков (и любителей quicklink), загружающий страницу, когда ссылка попадает в зону видимости. К счастью, в противовес ему есть и режим белого списка.

Телефон


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

Настройка


  • Белый список: предзагрузка работает только для ссылок с атрибутом data-instant (для этого в body надо добавить атрибут data-instant-whitelist)
  • Чёрный список: не будут загружаться ссылки с атрибутом data-no-instant
  • Внешние ссылки по умолчанию не загружаются, изменить это можно, добавив data-instant-allow-external-links в body
  • Ссылки с вопросительным знаком по умолчанию не загружаются, потому что могут вызывать нежелательные действия. Чтобы разрешить их, нужно добавить data-instant-allow-query-string в body


Проблемы


  • uBlock Origin и другие блокировщики, использующие список правил EasyPrivacy, блокируют скрипт как потенциальную угрозу приватности. Автор пытался убедить его мейтейнеров убрать instant.page из списка, но в итоге ему отказали и закрыли issue. При использовании скрипта на своём сайте можно просто захостить его у себя, что позволит обойти подобные блокировки. Однако у пользователей на Firefox с включённым uBlock Origin вырезается вообще любая предзагрузка, поэтому для них это решение не поможет.
  • В Safari 13 поддержка выключена по умолчанию. Должно быть исправлено в Safari 14.


Заключение


Меньше чем за год (а популярность пришла к instant.page даже меньше полугода назад) технология закрепилась на рынке, обзавелась крупными клиентами вроде Spotify и Pepsico и подбирается по количеству звёзд на GitHub к InstantClick, который там находится с 2014 года. Автор заявляет (со ссылкой на builtwith) о более чем 7000 сайтов, использующих instant.page, с суммарной аудиторией более 76 миллионов пользователей ежемесячно.



На правах рекламы


VDS для сайтов любых масштабов это про наши эпичные серверы! Они бесплатно защищены от DDoS-атак, скорость интернет-канала 500 Мегабит. Предоставляем возможность автоматически установить удобную панель управления VestaCP для размещения сайтов. Поспешите заказать!

Подробнее..

Из песочницы Как использовать Axios в React

03.10.2020 16:08:05 | Автор: admin


В этой статье вы узнаете как использовать библиотеку Axios в React.

Рассмотрим все на примере запросов на сервер, отобразим полученные данные в таблице, сдалаем компонент проверки загрузки и все это используя React Hooks.

Что такое Axios?


Axios это один из самых популярных HTTP клиентов для браузеров и node.js, основанный на промисах.

В Axios есть поддержка запросов, получение ответов от сервера, их трансформация и автоматическая конвертация в JSON.

Перед тем как начать, создадим новый React проект:

npx create-react-app react-axios-table

Зайдем в него:

cd react-axios-table

Данные для проекта


В качестве данных для нашего проекта будем использовать массив объектов:

[{id: 101,firstName: 'Sue',lastName: 'Corson',email: 'DWhalley@in.gov',phone: '(612)211-6296',address: {streetAddress: '9792 Mattis Ct',city: 'Waukesha',state: 'WI',zip: '22178'},description: 'et lacus magna dolor...',}]

Ссылка на данные

Настройка компонента для работы с Axios


Загружаем библиотеку Axios:

npm i axios

Импортируем axios в компонент из которого будем отправлять запросы на сервер:

import axios from 'axios'

Т.к. в проекте мы используем React Hooks, импортируем useState и useEffect (подробнее про хуки в реакте читаем по ссылке):

import React, { useEffect, useState } from 'react';

Далее в компонент добавляем следующий код:

function App() {    const [appState, setAppState] = useState();    useEffect(() => {    const apiUrl = 'http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}';    axios.get(apiUrl).then((resp) => {      const allPersons = resp.data;      setAppState(allPersons);    });  }, [setAppState]);   return (    <div className="app">        </div>  );}export default App;

Рассмотрим код поближе:

const [appState, setAppState] = useState();

Отвечает за состояние стейта в компоненте:

 useEffect(() => {}, [setAppState])

useEffect будет следить за изменением setAppState и производить ререндер если это необходимо

 const apiUrl=''

сюда подставляем нашу ссылку

axios.get(apiUrl).then((resp) => {      const allPersons = resp.data;      setAppState(allPersons);    });

отправляем get запрос на сервер, затем полученные JSON данные сохраняем в стейт

Компонент проверки загрузки


В src создадим папку components. В ней создаем компонент UserData.js и добавляем следующий код:
function UserData(props) {    const { persons } = props    if (!persons || persons.length === 0) return <p>Нет данных.</p>    return (        <div>            <table>                <thead>                    <tr>                        <th>id</th>                        <th>firstName</th>                        <th>lastName</th>                        <th>email</th>                        <th>phone</th>                    </tr>                </thead>                <tbody>                    {                        persons.map((person) =>                            <tr key={person.id}>                                <td>{person.id}</td>                                <td>{person.firstName}</td>                                <td>{person.lastName}</td>                                <td>{person.email}</td>                                <td>{person.phone}</td>                            </tr>                        )                    }                </tbody>            </table>      </div>    )}export default UserData

В пропсы компонента мы будем передавать данные полученные с сервера

 if (!persons || persons.length === 0) return <p>Нет данных.</p>

делаем проверку, есть ли данные с сервера

{                        persons.map((person) =>                            <tr key={person.id}>                                <td>{person.id}</td>                                <td>{person.firstName}</td>                                <td>{person.lastName}</td>                                <td>{person.email}</td>                                <td>{person.phone}</td>                            </tr>                        )                    }

Методом map проходим по массиву с людьми, для каждого человека создаем строку. Не забываем про key.

В папке components создаем LoadingPersonsData.js и вставляем следующий код:

function OnLoadingUserData(Component) {    return function LoadingPersonsData({ isLoading, ...props }) {        if (!isLoading) return <Component {...props} />        else return (            <div>                <h1>Подождите, данные загружаются!</h1>            </div>        )    }}export default LoadingPersonsData

Код выше это higher-order component в React. В качестве пропсов он принимает другой компонент и затем возвращает какую-либо логику. В нашем случае компонент проверяет isLoading. Пока данные загружаются, мы отображаем сообщение о загрузке, как только isLoading станет false мы возвращаем компонент с данными.

Внесем изменения в наш App.js для возможности проверки загрузки данных.

Импортируем наши компоненты в App.js:

import UserData from './components/UserData'import OnLoadingUserData from './components/OnLoadingUserData'

Добавляем следующий код:

function App() {  const DataLoading =  LoadingPersonsData(UserData);  const [appState, setAppState] = useState(    {      loading: false,      persons: null,    }  ) useEffect(() => {    setAppState({loading: true})    const apiUrl = 'http://www.filltext.com/?rows=32&id={number|1000}&firstName={firstName}&lastName={lastName}&email={email}&phone={phone|(xxx)xxx-xx-xx}&address={addressObject}&description={lorem|32}';    axios.get(apiUrl).then((resp) => {      const allPersons = resp.data;      setAppState({      loading: false,      persons: allPersons       });    });  }, [setAppState]);  return (    <div className="app">        <DataLoading isLoading={appState.loading} persons={appState.persons} />    </div>  );}export default App;

 const DataLoading =  LoadingPersonsData(UserData);

Мы создаем новый компонент, приравниваем его к нашему higher-order компоненту и обворачиваем им UserData (компонент отображения данных).

В стейт мы добавляем новое свойство loading: false, по нему мы будем определять загрузку данных с сервера.

<DataLoading isLoading={appState.loading} persons={appState.persons} />

Рендерим компонент и передаем пропсы в наш higher-order компонент.

Добавим немного css и при загрузки данных увидим следующее окно:

image

А теперь, когда get запрос на сервер завершен успешно, данные получены:

image

Теперь мы знаем как использовать Axios get с REST API.

Если у вас есть вопросы или пожелания, оставляйте их в комментариях. Буду рад ответить.
Подробнее..

Ajax, REST API OpenCart

21.01.2021 12:12:35 | Автор: admin

В статье рассмотрим как устроеныajax запросы в OpenCart, в том числе запросы черезapi OpenCart, познакомимся с новым понятиемfront controllerи немного коснемся темыajax REST API.

Клиент

Клиентская часть OpenCart работает с использованиемjquery, а значит можно использовать$.ajaxиз этой библиотеки.Ссылка на документацию. Примеры ajax запросов на клиентской части можно посмотреть вadmin/view/template/sale/order_form.tpl(.twig для OpenCart 3.0).

Сервер

Просматривая все тот же файлadmin/view/template/sale/order_form.tpl(для OpenCart 2.3)можно понять, чтов качестве адреса вызова используется классическая схема роутинга OpenCart. Посмотрим на один из запросов:

$.ajax({    url: 'index.php?route=customer/customer/autocomplete&token=<?php echo $token; ?>&filter_name=' +  encodeURIComponent(request),    ...

Все просто:url - путь до контроллера и, если надо,имя метода этого контроллера.

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

Создадим контроллер нашего нового тестового модуля по путиadmin/controller/extension/module/myajax.php:

class ControllerExtensionModuleMyAjax extends Controller{    public function index()    {        $this->response->addHeader('Content-Type: application/json');        $this->response->setOutput(json_encode(            [                "success" => true,                 "message" => "ok",                 "data" => []            ]        ));    }}

В классе контроллера есть объектresponse, это экземпляр классаResponse, который расположен по путиsystem/library/response.php. Он позволяет управлять ответом сервера. Нас интересуют только 2 метода:

  • addHeader($header)- добавить http заголовок,headerстроковый аргумент

  • setOutput($output)- установить данные для вывода,outputстроковый аргумент

Для формирования ответа на запрос в методе контроллера можно использовать-

$this->response

Так как OpenCart имеет 2режима доступа/контекста(admin, catalog), то передаваемые данные в запросах разные:

  • admin- требует токен вgetпараметре(получить можно из объекта класса контроллера):

    • для OpenCart 2.3token, который берется из$this->session->data['token']

    • для OpenCart 3.0C4C, который берется изC14CC5C

  • catalog- в общем случае не требует токена, но есть нюансы о которых позже

Теперь чтобы осуществитьajax запросдостаточно в файл представления(читай в html)подставить js(код для OpenCart 2.3):

$.ajax({    url: '<?php echo $admin; ?>index.php?route=extesion/module/myajax&token=<?php echo $token; ?>',    type: 'get',    dataType: 'json',    success: function(json) {        alert("success: "+json["success"]+"\n"+"message: "+json["message"]);    },    error: function(xhr, ajaxOptions, thrownError) {        alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);    }});

В этом коде в urladminэто путь указывающий контекст запроса(admin или catalog). Для контекста есть 2 дефайна, определенных вadmin/config:

  • HTTP_SERVERилиHTTP<b style="box-sizing: border-box;">S</b>_SERVER- путь до директорииadmin(проще - админка), где будет осуществлен поиск контроллера для выполнения запроса

  • HTTP_CATALOGилиC6CC15C- корень сайта, однако контроллеры будут браться из директорииC16CC7C

Ajax API

Просматривая файл представленияadmin/view/template/sale/order_form.tpl(OpenCart 2.3), можно увидеть что из админки осуществляются ajax запросы наcatalogконтекст, с использованием особого токена.

Сначала объявляется глобальная переменнаяtoken, затем ajax запрос на адрес/index.php?route=api/login, который отвечает json данными, в которых есть ключtoken:

var token = ''; // Login to the API$.ajax({    url: '<?php echo $catalog; ?>index.php?route=api/login',    type: 'post',    data: 'key=<?php echo $api_key; ?>',    dataType: 'json',    crossDomain: true,    success: function(json) {    //...         if (json['token']) {            token = json['token'];        }    },    error: function(xhr, ajaxOptions, thrownError) {        alert(thrownError + "\r\n" + xhr.statusText + "\r\n" + xhr.responseText);    }});

Контроллер этого запроса находится вcatalog/controller/api/login.php ControllerApiLogin::index. Он:

  • создает новую сессию

    (catalog/model/account/api.php - ModelAccountApi::addApiSession) и

  • генерирует для нее случайный токен(функцияtokenнаходится вsystem/helper/general.php),

который возвращается в json этого ajax запроса, если доступ по api(api_key)разрешен для текущего пользователя(Админка-Система-API).

Дальше разбирая представлениеadmin/view/template/sale/order_form.tplможно увидеть, что последующиеajax запросы, которые по адресуroute=api/...используют этот самыйtokenдля определения права доступа, таким образом(в каждом api файле, в каждом методе)C25C существует такой кусок кода для определения права осуществлять запрос:

if (!isset($this->session->data['api_id'])) {    $json['error']['warning'] = $this->language->get('error_permission');} else {    ...}

Ajax запросы черезcatalogконтекст можно осуществлять с использованиемtokenдля безопасного доступа

А теперь копнем глубже и выясним, как это происходит внутри движка, ведь можно отправлятьajax запросыи без токена.

Просматривая код файлаindex.phpотправляемся вsystem/startup.php, оттуда следуем вsystem/framework.phpв самый конец и видим такое вот:

// Front Controller$controller = new Front($registry); // Pre Actionsif ($config->has('action_pre_action')) {    foreach ($config->get('action_pre_action') as $value) {        $controller->addPreAction(new Action($value));    }}

Здесь видим новое понятиеfront controller, код которого находится вsystem/engine/front.phpв классеFront.

Ниже следует мое субъективное определение этого понятия :)

Подробных комментариев найти не удалось, но судя по кодуfront controllerэтоглавный/передний контроллер, онзапускает общий контроллерstartup/routerотносительно директорииcontrollerконтекста(admin/controllerилиcatalog/controller), которыйвыполняет первичные контроллеры,указанные в$_['action_pre_action'];в файлеsystem/config/catalog.php.

В коде выше происходит только добавление первичных контроллеров воfront controller, а их исполнение осуществляется кодом ниже, в методеdispatch(внутри метода перед выполнением action указанного в$config->get('action_router')):

// Dispatch$controller->dispatch(new Action($config->get('action_router')), new Action($config->get('action_error')));

Среди первичных контроллеров естьstartup/sessionотносительноcatalog/controller, где вControllerStartupSession::indexнаходится интересующий наскод для авторизации в api через токен. Вкратце:

  • происходит проверка обращения кapi/и наличияgetпараметраtoken

  • удаление старых api сессий

  • выборка актуальной api сессии на основании ip адреса запросившего и его токена

  • старт сессии с id из$_COOKIE["api"]

  • обновление времени модификации сессии, (чтобы она осталась жива, то есть не была устаревшей)

Теперь, когда исполнение кода дойдет до целевого контроллера,$this->session->data['api_id']уже будет инициализировано, если указана актуальная комбинация токена и ip адреса.

Ajax REST API

Данная глава описывает возможный вариант создания и встроенные средства реализации REST API в OpenCart.

Мы рассмотрели реализациюajax запросов OpenCartдляadminиcatalogконтекстов.

Если говорить обadmin, то предполагается более рациональным реализовывать контроллеры именно вadminконтексте. Однако, такое не всегда возможно. Иногда один и тот же код контроллера(возможно речь о методе контроллера)должен использоваться в обработчикеcatalogсобытия(например при изменении заказа), так и отдельно непосредственно при работе с заказом через админку. Чтобы устранить такие случаи можно реализовать контроллеры вcatalogконтексте и организовать для нихбезопасный доступ(о чем говорится в предыдущей главе).

Для реализацииREST API в OpenCartесть все необходимое:

  • объект для работы с ответом сервера в контроллере$this->response, а именно методыaddHeaderиsetOutput

  • безопасная работа с административным доступом черезcatalogC22Cконтекст

  • единая точка входа api черезC24CC10CC25C контекст, в директориюC26CC11CC27C, можно размещать свои файлы контроллеров и при помощи ajax осуществлять к ним запросы

На стороне сервера надосоздать контроллерывcatalog/controller/api/, а на стороне клиентадобавить ajax запросы в нужные файлы представленийс использованием токена, полученного в результате ajax запросаapi/login. Если в этих файлах нет такого ajax запроса, тогда необходимо добавить его, например, взяв изadmin/view/template/sale/order_form.tpl.Теперь чтобы сделать REST API достаточно изучить, что это такое, несколько ссылок:

Автор: Виталий Бутурлин

Подробнее..

Обзор разработки дополнений для amoCRM, с использованием webHook и виджетов

09.03.2021 18:08:33 | Автор: admin

Содержание

  1. WebHook

  2. Виджет

  3. Техническая поддержка

  4. Итог

Мы не использовали все возможности разработки под amoCRM, ограничились приватным виджетом и webHook, поэтому ниже речь пойдет именно об этом

WebHook

К каждому аккаунту(на пробном только в течении 14 дней)можно установитьwebHook,документация подробно описывает процесс. Разработка каких-либо интеграций при этом не нужна.

В нашем случае было достаточно информации одобавлении сделки.

На сервере по указанному url в файле(в данном случаеindex.php)первым делом необходимо сырые POST данные преобразовать из json в массив php:

//если в сырых POST данных первый символ { значит это jsonif(strlen($sRawPost) > 0 && $sRawPost[0] == "{"){    $sDecode = json_decode($sRawPost, true);    if($sDecode !== null)         $_POST = $sDecode;}

ВgetпараметрыwebHookпри создании новой сделки ничего не приходит, а вpostпримерно следующее:

{    "leads": {        "add": [            {                "id": 4564454,                "name": "Название товара",                "status_id": 7534534,                "price" => 0,                "responsible_user_id": 453453453,                "last_modified": 1612007407,                "modified_user_id": 0,                "created_user_id": 0,                "date_create": 1612007407,                "pipeline_id": 4546445,                "tags": [                    {                        "id": 7899                        "name": tilda                    }                ]            }        ],        "account_id": 19277260        "custom_fields": [            {                "id": 448797,                "name": "name_field",                "code": "code_field",                "values": [                    {                        "value": "string"                    }                ]            }        ],        "created_at": 1612007407,        "updated_at": 1612007407    },    "account": [        {            "subdomain": "subdomain",            "id": 19217260,            "_links": [                "self": "https://subdomain.amocrm.ru"            ]        }    ]}

Очевидно чтоидентифицировать аккаунтиз которого была отправка запроса можно по ключуaccount, аleads["add"][0]["account_id"] == account["id"].

Вleads["add"][0]["tags"]находятсяспециальные метки, которые можно присвоить сделке, и по которым на стороне принимающего сервера можно как-то идентифицировать, в нашем случае нужен был тег со значениемtilda.

Но больший интерес представляетleads["add"][0]["custom_fields"]- этомассив произвольных полей сделки.

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

Для редактирования полей сделки нужно зайти в любую сделку или в интерфейс создания новой сделки, затем перейти во вкладку "Настроить".

Редактирование полей сделкиРедактирование полей сделки

Новое поле сделки может быть скрыто из веб-интерфейса для редактирования и может быть доступно только на стороне API.

Для работы с полями сделки на стороне сервера принимающего запрос можно так:

$aAdd = $_POST['leads']['add'][0]; //извлекаем имена полей$aNameCustomFields = array_column($aAdd['custom_fields'], 'name'); //здесь пишем проверку наличия нужных полей //получаем значение поля$idOrder = $aAdd['custom_fields'][array_search('ORDERID', $aNameCustomFields)]['values'][0]['value'];
Добавление нового поля сделкиДобавление нового поля сделки

А дальше все зависит от целей использования webHook :)

Изначально мы делали эту интеграцию для клиента на Tilda, так как у клиента было настроено получение заявок из Tilda в amoCRM. Нам необходимо было отслеживать факт поступления оплаты, однако в amoCRM не поступают такие данные ифакт оплаты не удалось выявить, даже в самом интерфейсе CRM

Виджет

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

Для начала разработки виджета необходимозарегистрировать приложение, все просто.

Затем необходимо создатьструктуру виджетасостоящую из директорий и файлов.

Код виджета пишется наjavascript, шаблоны виджета наtwig, в js доступенjquery, есть возможность использованияcss

В директории виджета необходимо наличие файлаmanifest.json- файла конфигурации виджета,в документации есть подробное описание, аздесьописаны типы полей. Не забываем олокализацииi18n.

Стоит учесть что виджет подключается только к темобластям сайта, которые будут указаны вmanifest.json

В документации есть разделWEB SDKкоторый также посвящен созданию виджетов.

Не вижу смысла описывать процесс написания виджета, а также его возможности, так какдокументация исчерпывающая:)

Если виджет используетajax запросы со стороннего сервера(например как было у нас, виджет обращался к нашему серверу), то сервер должен отправлять заголовокAccess-Control-Allow-Origin: *:

header("Access-Control-Allow-Origin: *");

Разработка виджета осуществляется локально, на машине разработчика, тестирование виджета возможно только череззагрузку архива виджетана странице созданной интеграции.

Это не очень удобно, к тому же вmanifest.jsonкаждый разпри загрузке виджета нужно менять версиюwidget.version, иначе обновление виджета произойдет не сразу.

Техническая поддержка

Через чат amoCRM на всех страницах сайта CRM можно быстро получить ответы на многие вопросы. CRM платная для использования, но предоставляется бесплатный доступ на 14 дней. Однако,мы не собирались пользоваться самой CRM, а лишьпредоставлять нашу интеграцию. Возможность разработки виджета возможна только в течении 14 дней. После истечения периода, нам понадобилось продлить пробный период, обратившись в онлайн чат мы получили дополнительные 10 дней. Однако, позже через онлайн-чат удалось выяснить чтодля разработчиков публичных интеграций естьспециальный бесплатныйтехнический аккаунт. Также во время разработки нам потребовалось узнатьip адреса серверов amoCRM, с которых они присылают webHook на наш сервер, тех. поддержка через онлайн чат любезно их предоставила.На момент написания статьи, ip адреса серверов amoCRM не находятся в публичном доступе, узнать информацию о них можно через онлайн-чат на сайте.

Итог

В целом мне понравилось разработка для amoCRM, понятная и объемная документация с примерами, однако загрузка виджета доставляет определенные неудобства.

Подробнее..

Как работать с ошибками бизнес-логики через HTTP

09.03.2021 20:23:18 | Автор: admin

Почти все разработчики так или иначе постоянно работают с api по http, клиентские разработчики работают с api backend своего сайта или приложения, а бэкендеры "дергают" бэкенды других сервисов, как внутренних, так и внешних. И мне кажется, одна из самых главных вещей в хорошем API это формат передачи ошибок. Ведь если это сделано плохо/неудобно, то разработчик, использующий это API, скорее всего не обработает ошибки, а клиенты будут пользоваться молчаливо ломающимся продуктом.

За 7 лет я как поддерживал множество legacy API, так и разрабатывал c нуля. И я поработал, наверное, с большинством стратегий по возвращению ошибок, но каждая из них создавала дискомфорт в той или иной мере. В последнее время я нащупал оптимальный вариант, о котором и хочу рассказать, но с начала расскажу о двух наиболее популярных вариантах.

1: HTTP статусы

Если почитать апологетов REST, то для кодов ошибок надо использовать HTTP статусы, а текст ошибки отдавать в теле или в специальном заголовке. Например:

Success:

HTTP 200 GET /v1/user/1Body: { name: 'Вася' }

Error:

HTTP 404 GET /v1/user/1Body: 'Не найден пользователь'

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

Http статусы предназначались для описания ошибок при передаче данных, а про логику вашего приложения никто не думал. Статусов явно не хватает для описания всего разнообразия ошибок в вашем проекте, да они и не были для этого предназначены. И тут начинается натягивание "совы на глобус": все начинают спорить, какой статус ошибки дать в том или ином случае. Пример: Есть API для task manager. Какой статус надо вернуть в случае, если пользователь хочет взять задачу, а ее уже взял в работу другой пользователь? Ссылка на http статусы. И таких проблемных примеров можно придумать много.

REST скорее концепция, чем формат общения из чего следует неоднозначность использования статусов. Разработчики используют статусы как им заблагорассудится. Например, некоторые API при отсутствии сущности возвращают 404 и текст ошибки, а некоторые 200 и пустое тело.

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

Когда бизнес-логика приложения усложняется, начинают делать как-то так:

HTTP 400 PUT /v1/task/1 { status: 'doing' }Body: { error_code: '12', error_message: 'Задача уже взята другим исполнителем' } 

Из-за ограниченности http статусов разработчики начинают вводить свои коды ошибок для каждого статуса и передавать их в теле ответа. Другими словами, пользователю API приходиться писать нечто подобное:

if (status === 200) {  // Success} else if (status === 500) {  // some code} else if (status === 400) {  if (body.error_code === 1) {    // some code  } else if (body.error_code === 2) {    // some code  } else {    // some code  }} else if (status === 404) {  // some code} else {  // some code}

Из-за этого ветвление клиентского кода начинает стремительно расти: множество http статусов и множество кодов в самом сообщении. Для каждого ошибочного http статуса необходимо проверить наличие кодов ошибок в теле сообщения. От комбинаторного взрыва начинает конкретно пухнуть башка! А значит обработку ошибок скорее всего сведут к сообщению типа Произошла ошибка или к молчаливому некорректному поведению.

Многие системы мониторинга сервисов привязываются к http статусам, но это не помогает в мониторинге, если статусы используются для описания ошибок бизнес логики. Например, у нас резкий всплеск ошибок 429 на графике. Это началась DDOS атака, или кто-то из разработчиков выбрал неудачный статус?

Итог: Начать с таким подходом легко и просто и для простого API это вполне подойдет. Но если логика стала сложнее, то использование статусов для описания того, что не укладывается в заданные рамки протокола http приводит к неоднозначности использования и последующим костылям для работы с ошибками. Или что еще хуже к формализму, что ведет к неприятному пользовательскому опыту.

2: На все 200

Есть другой подход, даже более старый, чем REST, а именно: на все ошибки связанные с бизнес-логикой возвращать 200, а уже в теле ответа есть информация об ошибке. Например:

Вариант 1:

Success:HTTP 200 GET /v1/user/1Body: { ok: true, data: { name: 'Вася' } }Error:HTTP 200 GET /v1/user/1Body: { ok: false, error: { code: 1, msg: 'Не найден пользователь' } }

Вариант 2:

Success:HTTP 200 GET /v1/user/1Body: { data: { name: 'Вася' }, error: null }Error:HTTP 200 GET /v1/user/1Body: { data: null, error: { code: 1, msg: 'Не найден пользователь' } }

На самом деле формат зависит от вас или от выбранной библиотеки для реализации коммуникации, например JSON-API.

Звучит здорово, мы теперь отвязались от http статусов и можем спокойно ввести свои коды ошибок. У нас больше нет проблемы впихнуть невпихуемое. Выбор нового типа ошибки не вызывает споров, а сводится просто к введению нового числового номера (например, последовательно) или строковой константы. Например:

module.exports = {  NOT_FOUND: 1,  VALIDATION: 2, // .}module.exports = {  NOT_FOUND: NOT_AUTHORIZED,  VALIDATION: VALIDATION, // .}

Клиентские разработчики просто основываясь на кодах ошибок могут создать классы/типы ошибок и притом не бояться, что сервер вернет один и тот же код для разных типов ошибок (из-за бедности http статусов).

Обработка ошибок становится менее ветвящейся, множество http статусов превратились в два: 200 и все остальные (ошибки транспорта).

if (status === 200) {  if (body.error) {    var error = body.error;    if (error.code === 1) {      // some code    } else if (error.code === 2) {      // some code    } else {      // some code    }  } else {    // Success  }} else {  // transport erros}

В некоторых случаях, если есть библиотека десериализации данных, она может взять часть работы на себя. Писать SDK вокруг такого подхода проще нежели вокруг той или иной имплементации REST, ведь реализация зависит от того, как это видел автор. Кроме того, теперь никто не вызовет случайное срабатывание alert в мониторинге из-за того, что выбрал неудачный код ошибки.

Но неудобства тоже есть:

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

  • При использовании средств отладки (Chrome DevTools) или других подобных инструментов вы не сможете быстро найти ошибочные запросы бизнес логики, придется обязательно заглянуть в тело ответа (ведь всегда 200)

  • Мониторинг теперь точно будет срабатывать только на ошибки транспорта, а не бизнес-логики, но для мониторинга логики надо будет дописывать парсинг тела сообщения.

В некоторых случаях данный подход вырождается в RPC, то есть по сути вообще отказываются от использования url и шлют все на один url методом POST, а в теле сообщения передают все параметры. Мне кажется это не правильным, ведь url это прекрасный именованный namespace, зачем от этого отказываться, не понятно?! Кроме того, RPC создает проблемы:

  • нельзя кэшировать по http GET запросы, так как замешали чтение и запись в один метод POST

  • нельзя делать повторы для неудавшихся GET запросов (на backend) на реверс-прокси (например, nginx) по указанной выше причине

  • имеются проблемы с документированием swagger и ApiDoc не подходят, а удобных аналогов я не нашел

Итог: Для сложной бизнес-логики с большим количеством типов ошибок такой подход лучше, чем расплывчатый REST, не зря в проектах c разухабистой бизнес-логикой часто именно такой подход и используют.

3: Смешанный

Возьмем лучшее от двух миров. Мы выберем один http статус, например, 400 или 422 для всех ошибок бизнес-логики, а в теле ответа будем указывать код ошибки или строковую константу. Например:

Success:

HTTP 200 /v1/user/1Body: { name: 'Вася' }

Error:

HTTP 400 /v1/user/1Body: { error: { code: 1, msg: 'Не найден пользователь' } }

Коды:

  • 200 успех

  • 400 ошибка бизнес логики

  • остальное ошибки в транспорте

Тело ответа для удачного запроса у нас имеет произвольную структуру, а вот для ошибки есть четкая схема. Мы избавляемся от избыточности данных (поле ошибки/данных) благодаря использованию http статуса в сравнении со вторым вариантом. Клиентский код упрощается в плане обработки ошибки (в сравнении с первым вариантом). Также мы снижаем его вложенность за счет использования отдельного http статуса для ошибок бизнес логики (в сравнении со вторым вариантом).

if (status === 200) {  // Success} else if (status === 400) {  if (body.error.code === 1) {    // some code  } else if (body.error.code === 2) {    // some code  } else {    // some code  }} else {  // transport erros}

Мы можем расширять объект ошибки для детализации проблемы, если хотим. С мониторингом все как во втором варианте, дописывать парсинг придется, но и риска стрельбы некорректными alert нету. Для документирования можем спокойно использовать Swagger и ApiDoc. При этом сохраняется удобство использования инструментов разработчика, таких как Chrome DevTools, Postman, Talend API.

Итог: Использую данный подход уже в нескольких проектах, где множество типов ошибок и все крайне довольны, как клиентские разработчики, так и бэкендеры. Внедрение новой ошибки не вызывает споров, проблем и противоречий. Данный подход объединяет преимущества первого и второго варианта, при этом код более читабельный и структурированный.

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

P.S. Иногда ошибки любят передавать массивом

{ error: [{ code: 1, msg: 'Не найден пользователь' }] }

Но это актуально в основном в двух случаях:

  • Когда наш API выступает в роли сервиса без фронтенда (нет сайта/приложения). Например, сервис платежей.

  • Когда в API есть url для загрузки какого-нибудь длинного отчета в котором может быть ошибка в каждой строке/колонке. И тогда для пользователя удобнее, чтобы ошибки в приложении сразу показывались все, а не по одной.

В противном случае нет особого смысла закладываться сразу на массив ошибок, потому что базовая валидация данных должна происходить на клиенте, зато код упрощается как на сервере, так и на клиенте. А user-experience хакеров, лезущих напрямую в наше API, не должен нас волновать?HTTP

Подробнее..

Категории

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

  • Имя: Макс
    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