Первая часть: https://habr.com/ru/articles/883818/

Поговорим о текущем состоянии моей CRM, сравним с текущим релизом, на каком этапе сейчас код и какие планы. Разберем ключевые этапы. первый из них и один из самых важных:
Авторизация, сессии
В текущем релизе ПО не предусмотрена возможность одновременной работы пользователей с разных устройств. Куки хранится сразу в таблице с сотрудниками. Теперь, в новом релизе интегрирована поддержка нескольких сессии. Создана доп. таблица со след. параметрами: IP, cookie, время жизни. При любом запросе к веб странице — выполняется проверка необходимой записи в данной таблице. Так, как я говорил ранее о необходимости использовать ООП, происходит инициализация объекта Admin. В инициализации происходят 3 процедуры:
-
Проверка значения куки в браузере
-
Проверка данного ip адреса в черном списке
-
Проверка сессии (checkAuth)
function __construct(){ $this->cookie = $this->checkCookie(); $this->ban = $this->checkBan(); if($this->ban > 0) { exit(http_response_code(403)); } if($this->cookie == 0) { $url = explode('/',$_SERVER['REQUEST_URI']); header('Location:/?url='.$url[1]); exit; } } function checkBan(){ global $connect; $result = $connect->query("SELECT `id` FROM `***` WHERE ip = ?", [ip()], 's'); $row = $connect->fetch_array($result); if(sizeof($row) > 0){ $connect->query("INSERT INTO `***` (`time`,`type`,`message`,`ip`,`example`) VALUES (?, ?, ?, ?, ?)", [time(), 'ACCESS', 'BANLIST', ip(), 'TRY ACCESS FROM BANLIST'], 'issss'); } return sizeof($row); } function checkCookie(){ if((empty($_COOKIE['tmpsid'])) OR (!isset($_COOKIE['tmpsid'])) OR (strlen($_COOKIE['tmpsid']) != 32)){ if((strlen($_COOKIE['tmpsid']) != 32) AND (!empty($_COOKIE['tmpsid']))){ $this->ban('MODIFY COOKIE', $_COOKIE['tmpsid'], 'COOKIE `tmpsid`',); } $hash = md5(ip().time()); setcookie( 'tmpsid', $hash, [ 'expires'=>time() + (86400 * 350), //'secure'=> true, 'httponly'=> true, 'samesite'=> 'Lax', 'path'=>'/' ] ); $menus = ['fullscrenn','hidden']; if(empty($_COOKIE['menu']) OR !in_array($_COOKIE['menu'],$menus)){ setcookie( 'menu', 'fullscreen', [ 'expires'=>time() + (86400 * 350), //'secure'=> true, 'httponly'=> true, 'samesite'=> 'Lax', 'path'=>'/' ] ); } $url = explode('/',$_SERVER['REQUEST_URI']); header('Location:/?url='.$url[1]); exit; } else { return 1; } } function checkAuth(){ global $connect; $result = $connect->query("SELECT t1.admin,t2.* FROM *** t1 LEFT JOIN *** t2 ON t1.admin = t2.id WHERE `ip` = ? AND `cookie` = ? AND `time_end` >= ?", [ip(), $_COOKIE['tmpsid'], time()], 'ssi'); $row = $connect->fetch_array($result); if(sizeof($row) == 1){ $this->admin = $row[0]; if((!empty($_SESSION['admin'])) || (isset($_SESSION['admin']))){ $this->check_session = 1; if($row[0]['admin'] == $_SESSION['admin']){ $this->check_session = 1; return 1; } else { $this->check_session = 0; $this->logout(); return 0; }; } else { $_SESSION['admin'] = $row[0]['admin']; $this->check_session = 1; return 1; } } else { $this->check_session = 0; return 0; } }
Добавлено условие: если длинна cookie не равна 32 символам, то данный ip адрес блокируется и при любом запросе, crm будет отвечать 403. Далее думаю о ограничении неудачных попытках авторизации — сейчас не работал над этим, но к завершению проекта — обязательно допишу. Так же — открыт вопрос о скорости запросах. Ближе к завершению, так же как и попытки неудачной авторизации — буду думать в этом направлении о количестве и времени между запросами. К примеру, если между предидущем запросе прошло не более 1-2 секунды — кидать ip в бан лист.
Структура проекта

Ключевая директория — public_html, которая содержит в себе все директории и файлы, принимающие в себя запросы пользователя. Так же в данной папке содержится файл ajax.php , который вызывает файлы из папки engine/ajax — это различные окна редактирования, списки и обработчики запросов к БД.
require dirname($_SERVER['DOCUMENT_ROOT']).'/engine/include.php'; $filename = dirname($_SERVER['DOCUMENT_ROOT']).'/engine/'.$_GET['file']; $files = [ 'Массив файлов, доступных для вызова' ]; $admin = new Admin; $auth = $admin->checkAuth(); if((($auth == 1) AND ($_GET['file'] != 'ajax/auth.php') OR ($_GET['file'] == 'ajax/auth.php'))){ if(in_array($_GET['file'], $files)){ if(file_exists($filename)){ if(in_array($_GET['file'], $files)){ if($filename != 'ajax/auth.php' AND $auth == 1){ require $filename; } elseif($filename = 'ajax/auth.php' AND $auth == 0) { require $filename; } else { exit(http_response_code(403)); } } } else { exit(http_response_code(403)); } } else { echo 'nofile'; //$admin->ban('AJAX', 'NO_FILE', $_GET['file']); //$admin->ban(); } } else { exit(http_response_code(403)); }
Для безопасности — файл может вызывать скрипты, которые находятся в массиве с наименованиями файлов. В случае, если будет вызван иной файл — ip отправляется в бан лист. Ну и проверка сесси при вызове ajax. Соответственно со стороны JS , если в ответе прилетает 403 — пользователя перенаправляет на страницу с авторизацией.
Текущий релиз: в предыдущем релизе поддержка только 1 сессии пользователя. Соответственно так же, при каждом обращении к страницам — выполнялась проверка сессии и перенаправление на авторизации в случае за неимение ее в базе. jQuery не был никак интегрирован, по этому — проверка выполнялась сразу в загружаемом файле .php.
Обработчики
Думаю, разбирать селекты из обработчиков — смысла нету, поговорим сразу о INSERT и UPDATE. Решил не делать для каждого INSERT или UPDATE отдельные обработки — а собрать их в кучу. Все наименование полей и таблиц собираются из формы, а так же: тип данных, поле с уникальным значеним, обязательно поле к заполнению. Это все собирается в json и отправляется в обработчики update.php или insert.php. Те в свою очередь выдают ответ. К примеру, если поле должно быть уникальным — перед добавлением записи или ее обновлением — выполняется запрос с введенными данными. Опять же, проверяя авторизацию пользователя.
<div class="darkmode-window-header"> <h3>Добавить новую учебную группу</h3> <a onclick="modalClose()">×</a> </div> <div class="darkmode-window-body"> <form id="add"> <input type="hidden" name="table" value="1"> // Ключ массива с таблицами <input type="hidden" name="admin" value="123" data-type="i"> <div class="darkmode-window-input"><span><b>Основная информация</b></span><span><hr></span></div> <div class="darkmode-window-input"> <span>Школа</span> <span> <select name="school" data-type="i"><option value="2">ЧОУ ДПО «Автошкола Максимум»</option><option value="5">ЧОУ ДПО «Автошкола Максимум»(Филиалы)</option></select> </span> </div> <div class="darkmode-window-input"> <span>Наименование</span> <span><input type="text" name="title" data-empty="false" data-unikey="true" data-type="s"></span> </div> </div> <div class="darkmode-window-footer"> <button class="btn" onclick="add()"><i class="fa fa-plus"></i> Добавить</button> </form> </div>
К примеру — наименование. data-empty=»false» — указывает на то, что поле не должно быть пустым. data-unikey=»true» — указывает на то, что поле должно иметь уникальное значение. data-type=»s» — тип данных в базе «строка». Но для сложных записей в таблицах, где с нескольких input формируется одно значение — ввожу data-field и по нему уже задаю произвольную обработку.
Текущий релиз: Имеется реализация открывания окон, но все окна добавления записей в бд — были уже загружены на странице соответствующим файлом. А js только задавал ему свойство — display. Так же, этот же файл html кодом и содержал бэк часть с обработкой этих запросов.
Контент
К примеру, разберем запрос к списку учебных групп: /groups/ . Запрос идет к файлу index.php, который находится в данной директории. Он в свою очередь подгружает необходимые инклуды для работы ПО: подключение к бд, объекты, файл с функциями, autoload.php
require dirname($_SERVER['DOCUMENT_ROOT']).'/engine/include.php'; $admin = new Admin; if($admin->checkAuth() == 1){ if($admin->check_session == 0){ header('Location:/'); exit; } else { require 'header.php'; require dirname($_SERVER['DOCUMENT_ROOT']).'/engine/template/main.php'; if(isset($_REQUEST['id'])){ ?> <script> let form = {}; getItem('ajax/groups/item.php',<?php echo $_REQUEST['id']; ?>); document.querySelector('.panel-tools').addEventListener('change', function(event) { if (event.target.tagName === 'SELECT') { getItem('ajax/groups/item.php',<?php echo $_REQUEST['id']; ?>); } }); </script> <?php } else { ?> <script> let form = {}; getList('ajax/groups/list.php'); document.querySelector('.panel-tools').addEventListener('change', function(event) { if (event.target.tagName === 'SELECT') { getList('ajax/groups/list.php'); } }); </script> <?php } } } else { $url = explode('/',$_SERVER['REQUEST_URI']); header('Location:/?url='.$url[1]); exit; }
После подключения необходимых для работы файлов — инициализация объекта Admin -> проверка сессии, и подключение файла header.php, который находится вместе с index.php. Данный файл хранит в себе html код, который будет отображен в шаблоне, title страницы, запрос к базе, если указан ID в запросе:
if(isset($_REQUEST['id'])){ $_REQUEST['id'] = preg_replace('/[^\d]/', '', $_REQUEST['id']); $result = $connect->query("SELECT t1.title AS group_title, t2.title FROM auto_users_groups t1 JOIN auto_categories t2 ON t1.category = t2.id WHERE t1.id = ? LIMIT 1", [$_REQUEST['id']],'i'); $group = $connect->fetch($result); $title = 'Группа: '.$group['group_title'].', категория: '.$group['title'].' | '.$_ENV['HOME']; $content = '<div class="schedule-header"> <h3> <i class="fa fa-users"></i> Учебная группа '.$group['group_title'].', Категория обучения: '.$group['title'].' </h3> </div> <div class="panel-tools"> <a onclick="modal(\'add-user-group\','.$_REQUEST['id'].')" class="btn"><i class="fa fa-user-plus"></i> Ученик(и)</a> <a onclick="modal(\'edit-group\','.$_REQUEST['id'].')" class="btn empty"><i class="fa fa-pencil"></i> Редактировать</a> <div style="position:relative;"> <div class="drop-down-list" data-link="docs_all"> <a href="/src/docs/order_start_b.php?group=550" style="margin-bottom:2px;"><i class="fa fa-print"></i> Приказ о создании группы</a> <a href="/src/docs/protocol.php?group=550" style="margin-bottom:2px;"><i class="fa fa-print"></i> Протокол группы</a> <a href="/src/docs/journal.php?group=550" style="margin-bottom:2px;"><i class="fa fa-print"></i> Журнал группы</a> <a href="/src/docs/order_2022.php?group=550" style="margin-bottom:2px;"><i class="fa fa-print"></i> Приказ группы 2022</a> <a href="/src/docs/order_finish_b.php?group=550" style="margin-bottom:2px;"><i class="fa fa-print"></i> Приказ об отчислении</a> </div> </div> <a onclick="dropdown(\'docs_all\')" class="btn empty drop"> <i class="fa fa-file-text-o"></i> Документы <i class="fa fa-caret-down"></i> </a> <input type="text" name="search-list" id="search-f" onkeyup="tableSearch(\'contract-list\',\'search-f\')" placeholder="Поиск по таблице"> <label style="flex-grow:1;"></label> <label> Статус ученика <select name="block"> <option value="" selected>- Все</option> <option value="1">Заблокирован</option> <option value="0">Не заблокирован</option> </select> </label> <label> Документы <select name="docs"> <option value="" selected>- </option> <option value="problems">Неактуальные</option> <option value="false">Отсутсвует</option> <option value="true">В порядке</option> </select> </label> <label> Баланс <select name="balance"> <option value="" selected>- Все</option> <option value="false">Должники</option> <option value="problems">Баланс минус</option> </select> </label> </div> <div class="table-scrolled-x"> </div>'; } else { $title = 'Учебные группы | '.$_ENV['HOME']; $content = '<div class="schedule-header"> <h3> <i class="fa fa-users"></i> Список учебных групп </h3> </div> <div class="panel-tools"> <a onclick="modal(\'add-group\')" class="btn"><i class="fa fa-user-plus"></i> Новая группа</a> <input type="text" name="search-list" placeholder="Поиск по таблице"> <label style="flex-grow:1;"></label> <label> Категория <select name="category"> <option value="">- Все</option> <option value="1">A</option> <option value="2">B</option> <option value="3">C</option> <option value="4">D</option> </select> </label> <label> Актуальность <select name="date"> <option value="">- Все</option> <option value="true" selected>Актуальные</option> <option value="false">Неактуальные</option> </select> </label> </div> <div class="table-scrolled-x"> </div>'; }
И так же в index.php — подключается файл с основным html содержимым страницы (шаблон). Все это собирается в 1 кучу и получается контент. Да, возможно метод не самый оптимальный и в комментариях будет куча учителей — и это хорошо. На этапе разработки будет полезно почитать для себя какую-то информацию, и правильно ее применить на фронте работ.
Текущий релиз: 2 фала в директории, которая открывается пользователем. index.php — содержит в себе шаблон и переменную $content, в которую вносит html код и логику из файла в той же директории — functions.php. Фрон и бэк в куче.
На какой стадии и какой план
Стадия — сырой скилет. Ключевые моменты реализованы — сейчас рутина с формированием таблиц, окон редактирования и т.д. Пока занимаюсь этим. После перейду к детальным связкой учеников — договоров, договоров — прочим (графиком вождения, финансовым операциям и т.д.).
Я не считаю, себя крутым разработчиком и не беру за данную работу 6-ти значную сумму, и понимаю то, что есть много вещей и нюансов, которые я не знаю. Все мы учимся. Иногда обучение происходин на своих ошибках, иногда на чужих. Но это код, и он работает — сделать лучше можно в любой момент(сел, открыл. переписал, перезаписал). На любого программиста — всегда найдется программист по лучше. Прошу сильно не критиковать, а подсказать совет — всегда пожалуйста!
ссылка на оригинал статьи https://habr.com/ru/articles/883940/
Добавить комментарий