Евгений Третьяков

Евгений Третьяков 

Flutter Developer, Full Stack

3subscribers

11posts

goals3
$0 of $1 920 raised
На макбук
$0 of $1 408 raised
Полгода аренды сервера со 128 Гб ОЗУ для реализации проекта собственного узла дешевого бэкэнда для проектов на Dart\Flutter
$0 of $640 raised
Тестирование новых коммерческих сервисов в связке с Flutter (оплата лицензий, абонентской платы и т.п.)

Бесплатный backend своими руками: Joomla CMS

Традиционно бэкэнд - это комплекс серверных технологий, работающих над тем, чтобы обеспечить обмен данными с приложением, организовать авторизацию (обеспечить безопасность данных), уведомить приложение об изменениях и так далее. 
Практически любая CMS (content management system, система управления содержимым) обладает определенным набором функций, свойственных бэкэнду для приложения, но не любая сможет работать с мобильным приложением.
Для того, чтобы приложение могло "общаться" с CMS надо, чтобы CMS поддерживала такие технологии, как REST API для обмена данных и современные протоколы авторизации.  
На момент написания статьи, впрочем, как и десять лет назад, наиболее популярные CMS это WordPress, Joomla и Drupal. 
Мы остановились на Joomla по той причине, что с точки зрения бэкэнда она наиболее адекватная: обеспечивает лучшую безопасность и больший потенциал для расширения, чем WordPress, но не такая сложная в настройке и работе, как Drupal. Мне же, как разработчику, Joomla нравится своей внутренней архитектурой, в частности, системой плагинов, которая может реагировать на события, генерируемые мобильным приложением.
Преимущества CMS-бэкэнда перед остальными вариантами:
1. Для небольшой нагрузки можно взять обычный веб-хостинг. Подойдут даже слабые бесплатные тарифы. Можно разместить рядом с вашим действующим сайтом (если он, конечно, не на конструкторе вроде тильды)
2. В случае "переезда" на более мощный тариф или к другому провайдеру сложность работ будет равна сложности переноса обычного сайта
3. Можно использовать как хостинг, так и VPS
4. Открытый исходный код и популярность позволят легко найти недорогого исполнителя на доработку функционала в случае необходимости
5. Легко соответствовать всем ФЗ, поскольку никто не мешает размещаться на отечественных серверах
6. Большинство вопросов безопасности закрывается разработчиком CMS и фирмой, у которой вы арендуете хостинг, нужно лишь вовремя обновляться
7. Снимается проблема вывода информации - при желании результат работы приложения можно легко встроить в любое место на сайте
Недостатки такого подхода:
1. Крайне сложно масштабировать проект - есть определенный предел нагрузки, зависящий от хостинга, выше которого вас может ждать отказ в работе сервиса
2. Предел нагрузки в каждом конкретном случае придется вычислять самостоятельно, нельзя сразу сказать что "вот на этом тарифе он выдержит 100 одновременных подключений, а на вот этом - 1000", плюс провайдеры любят капризничать и вытеснять такие проекты без реальных причин на более дорогие тарифы
3. CMS по определению более уязвимая категория серверного программного обеспечения - в основном из-за своей популярности, их не пытается взломать только ленивый
У нас сейчас стоит задача получить данные о заказе от нашего приложения и вывести их в какое-то подобие таблички с тем расчетом, что с ними можно было бы еще как-то работать, ну и когда-нибудь мы же добавим в наше приложение новые функции по работе с заказами.

Между прочим вместо CMS можно использовать CRM - тот же битрикс24 позволяет работать по REST API, но только если у вас самый дорогой тариф. Если же вдруг ваша текущая CRM на текущем тарифе позволяет это - то, скорее всего, вы сможете самостоятельно на базовом уровне интегрироваться в нее.
1. Установка CMS Joomla на бесплатном хостинге
Наш выбор пал на beget.ru - https://beget.com/ru/hosting/free
Можете выбрать другой, но он должен позволять устанавливать Joomla 4 и не прятать информацию о токенах пользователя (столкнулся с такой проблемой на одном).
Открываем сайт, регистрируемся:
После регистрации нас будет интересовать кнопка с CMS
Где мы выбираем Joomla
Вам скажут ваш технический домен - запишите его, он вам пригодится, и попросят ввести необходимый минимум в виде названия сайта и данных администратора. Можете вводить сюда что вздумается, главное - не забудьте потом, что писали
После этого ждем установки
Пока ждем, можно вверху нажать на кнопку меню (там написано Хостинг) и выбрать пункт CMS
Здесь мы увидим информацию о наших сайтах. Минимальная версия PHP для работы Joomla 4 - 7.4, хостинг установил ее автоматически (кое-где приходится делать это вручную)
Если мы поторопимся и откроем свой новый сайт до того, как провайдер закончит установку - увидим вот такое зрелище:
Рекомендую в таком случае подождать еще пару минут и очистить кэш браузера. В каждом браузере эта функция находится в своем уникальном месте, поэтому попробуйте нажать на клавиатуре сочетание Ctrl-Shift-Del, оно работает почти везде. Удаляем данные за последний час:
И обновляем страницу. Если все получилось правильно - вы увидите пустой Joomla сайт
Мы не будем заниматься украшательством, не стоит такой задачи, нам надо настроить взаимодействие с приложением.
Переходим в панель управления сайтом - для этого допишите после адреса своего сайта слово /administrator, как на картинке.
Как видите, моя опечатка в названии сайта будет нас преследовать теперь до конца )
После того, как мы ввели логин и пароль, попадаем в панель управления. Уведомление о том, что у вас устаревшая версия PHP будет преследовать постоянно, его можно просто закрывать крестиком, а от предложения посылать анонимные данные лучше отказаться:
Сразу после этого обновляем Joomla до актуальной версии. Этого можно не делать, но от этого обычно зависит безопасность работы
На странице обновления есть кнопка Обновить, которая сделает все автоматически. Обращаю внимание на то, что на этой странице все проверки пройдены - все галочки зеленые, на некоторых бесплатных хостингах система может жаловаться на поврежденную базу данных и исправить ее будет нельзя. В таком случае просто меняйте провайдера.
Появится окошко, где вас попросят подтвердить, что вы готовы обновиться
....но для России она не сработает. Сервера, на которых лежать обновления Joomla на момент написания этого материала недоступны из-за санкций.
Поэтому придется вручную. Пролистываем ниже и нажимаем "загрузить и установить"
На следующей странице вам подскажут, где скачать обновление
Опять же, скачать без VPN не получится. Проблема решается с помощью github - https://github.com/joomla/joomla-cms/releases
Берем отсюда архив из последнего релиза, нам нужен тот, в чьем имени есть слово update и скачиваем
...и загружаем его в панели управления Joomla
Вас попросят еще раз ввести логин и пароль администратора
и закончится это все успехом:
2. Добавляем пользователя и добываем его токен
Нажимаем слева на "пользователи" и на кнопку с плюсом
Давайте назовем его robot )
В момент создания у нас уже будет заветная вкладка с токеном API, но сейчас там пусто...
А после нажатия на кнопку "сохранить" она пропадет. Это связано с тем, что авторизация по токену по умолчанию доступна только супер-администратору
Неразумно давать полный доступ к нашему сайту (то есть бэкэнду) по одному лишь токену, который мы собираемся хранить в самом же приложении. Я предлагаю сделать так, чтобы наш "робот" умел только публиковать заказы и все. На всякий случай, пусть он даже не может редактировать и удалять то, что сам же и отправил, это идеальный вариант. К счастью, Joomla это позволяет.
Для этого надо сначала создать отдельную группу безопасности пользователя:
Назовем ее так же, robot
Потом сделаем уровень доступа с таким же именем
Когда будем создавать, надо будет ее связать с группой robot. Для этого переходим в соответствующую вкладку
И находим недавно созданную группу, отмечаем ее галочкой. Остальные галочки убираем
Сохраняем. 
Важный момент здесь то, что мы не можем посмотреть токен пользователя, если не зайдем в его профиль на сайте через панель управления. Для этого нам надо получить специальные разрешения.
Для этого здесь же, в уровнях доступа открываем уровень special
В ней переходим на вкладку с группами и отмечаем нашего "робота". Остальные галочки не трогаем!
На этом настройка безопасности еще не закончена. После того, как сохранили и закрыли, нажимаем кнопку "Настройки" вверху
Затем слева выбираем "Общие настройки", потом вкладку "Права доступа" и нажимаем на нашего "robot"
Далее разрешаем ему вход на сайт, вход в панель управления и доступ к веб-службам
Все, группу настроили, теперь добавляем пользователя robot в нее. Для этого слева нажимаем "пользователи", потом на пользователя robot
Переходим во вкладку "Группы пользователей" и ставим нужную галочку
Теперь разрешаем ему пользоваться токенами в принципе. 
Нажимаем слева "Система", затем, по центру, "Плагины"
Ищем слово Токен - обязательно с большой буквы, система с какого-то перепуга чувствительна к регистру. Если не получится - можете пролистать список вручную, там не очень много. Нам надо "Пользователь - Токен API Joomla".
В открывшихся настройках добавляем группу Robot и сохраняем.
Сейчас, если вы вдруг решите снова открыть настройки пользователя robot, то увидите, что там появилась вкладка Токен API и что посмотреть ее может только сам robot
Что же, открываем новое окно браузера в приватном режиме (чтобы не выходить из панели управления администратором), вводим данные robot
Увидите что-то наподобие этого:
Это нормально, поскольку мы умышленно ничего не разрешали этому пользователю. Переходим в профиль - кнопка "Мой профиль" справа вверху
И наконец-то копируем токен куда-нибудь в текстовый файл, нам он пригодится
На этом настройка пользователя закончена, можно закрыть приватное окно, мы возвращаемся в основную панель управления, где работали от имени администратора.
3. Настраиваем место, куда будут отправляться заказы
Один из главных недостатков Joomla - то, что каждый отдельный материал (статья) может принадлежать только одной категории. Такая строгая типизация контента сильно не нравится пользователям других систем, потому что нельзя одну и ту же статью разместить и в "новости" и в "документы". 
Хотя и существует около десятка разных способов обойти это, нам же, с точки зрения работы приложения, такое ограничение скорее на пользу. Потому что именно такая система позволяет очень точно назначить права доступа и отображения.
Для начала создадим категорию. Для этого слева нажмем на контент, а потом на плюс рядом с пунктом "категории"
Категорию назовем "заказы", выставим уровень доступа "robot", и сохраним, не закрывая
После сохранения переходим во вкладку "права доступа" и разрешаем пользователю "robot" только создание и изменение состояния
Сохраняем и закрываем, после чего записываем себе ID категории - в моем случае это 8
Теперь надо создать пункт меню на сайте для показа материалов из этой категории. 
Нажимаем слева на Меню и на плюс около 'Main Menu' 
Придумываем название и нажимаем на кнопку выбора типа пункта меню
Выбираем материалы - материалы
Далее выбираем, из какой категории материалы будут показываться
В нашем случае это "Заказы"
И меняем уровень доступа. Сам пункт меню и все, что внутри должно быть доступно только вам, администратору, т.е. Super User
Нажимаем "сохранить" и открываем главную страницу сайта, можно это сделать нажав по кнопке слева вверху
Вводим имя админа и пароль в форму и входим
Как видите, появился пункт Заказы
Только там нам пока ничего не покажут. Дело в том, что материалы-то принадлежат пользователям группы robot, нам надо бы разрешить пользователям группы Super User тоже их смотреть. 
Возвращаемся в панель управления, нажимаем слева Пользователи - Уровни доступа и открываем robot
Во вкладке "группы пользователей" ставим галочку напротив "Super Users" и сохраняем
Все, теперь заказы видны
Казалось бы, такой длинный путь чтобы получить простую вещь. Но это вынужденная плата за использование универсального инструмента.
4. Добавляем код в приложение
У нас уже есть приложение с кнопкой, которая проверяет правильность заполненных полей и если все хорошо, то выводит сообщение.
Теперь нам надо, чтобы по нажатию этой кнопки также уходило сообщение в "Заказы" нашего только что созданного сайта.
Для этого рекомендую воспользоваться вот этим классом, который оформлен в виде отдельного .dart файла:
dart
save_to_joomla.dart1.54 Kb
Один из простых вариантов сделать это - просто перетащить файл мышкой из проводника прямо в нужную папку внутри Android Studio. Например, в папку "data" проекта, потому что технически этот файл не имеет непосредственного отношения к какому-то конкретному экрану
Подтверждаем наши намерения (осторожно, он именно переместит файл - то есть удалит из того места, куда вы его распаковывали)
После добавления наблюдаем, что добавленный нами код требует пакета dio (секция import), поэтому открываем pubspec.yaml
Памятуя прошлый конфликт с версией пакета intl, оставим Flutter'у решать, какую версию устанавливать. Просто добавим dio: в раздел dependencies и нажмем на желтую надпись pub get для скачивания библиотек
В этом файле у нас один класс - Joomla с двумя методами, saveArticle и randomId.
Кстати, если ваше любопытство еще не завело вас сюда раньше, нажмите на кнопку Structure слева внизу - она позволит вам легко ориентироваться в любой dart файле - отобразит список всего, что есть значимого внутри и позволит быстро перемещаться по тексту. Попробуйте с другими файлами проекта, если еще не пробовали
Конечно, я мог бы показать вам эту функцию Android Studio и раньше, но вам все равно надо было научиться ориентироваться в коде разными способами )
Итак, нам надо вставить код отправки статьи на сайт. Непосредственно эта кнопка у нас в файле edit_profile_view.dart, открываем его и находим кнопку
Как видим, их тут две - одна уводит пользователя назад, на предыдущий экран, а другая обращается к какому-то методу в своем контроллере. Очевидно, что нам туда. Переходим с помощью левой кнопки мыши и зажатой кнопки Ctrl
Путем недолгих раздумий приходим к выводу, что код надо вставлять куда-то сюда
Но какой код?
Правила тут просты: прежде чем начать работать с каким-то классом в нашем коде, надо сначала создать его экземпляр. То есть непосредственно внутрь мы залезть не можем, это не по правилам (чаще всего, бывают исключения). 
Общая схема такова:
Тип_класса переменная = Конструктор_класса(параметры);
Чаще всего Тип_класса и Конструктор_класса это одно и то же, параметров может не быть, в общем... 
Класс у нас называется Joomla, давайте начнем писать:
Как видите - подчеркнуто красным, потому что в этом файле про этот класс ничего не слышали. Игнорируем это, придумываем имя переменной - например, newOrder и ставим знак "=" 
То есть мы здесь объявили переменную newOrder типа Joomla, который у нас класс. Надо конструктор - вот он, если забыли:
Пишем его - и система сразу пытается угадать, что мы имеем в виду:
Если мы сейчас нажмем на Enter, то автоматически будет добавлена строка в секцию import и перечислены все необходимые поля конструктора:
И нам остается только заменить значения на правильные.
Если не нажать на Enter а продолжить печатать, например, то автоматически все это сделано не будет. Тогда придется нажать правой кнопкой мыши, выбрать Show Context Actions и добавить оттуда, как мы раньше делали. Параметры тоже придется писать вручную, правда, Android Studio все равно будет вам подсказывать.
Итак, вносима данные, которые мы заботливо сохраняли в отдельном текстовом файле, пока настраивали Joomla (я перенес каждый отдельный параметр на новую строку, чтобы красивее было):
Адрес сайта должен быть без символа "/" в конце.
Также не забываем, что все команды должны заканчиваться точкой с запятой (";")
Все, у нас есть переменная newOrder, с помощью которой мы можем обратиться к методам класса. Нам нужен тот, который сохраняет материал на сайт. 
Для этого с новой строки пишем newOrder, ставим точку и смотрим на варианты, выбираем нужный и нажимаем Enter:
Готово! Осталось заполнить значения:
Title - заголовок статьи, articleText- ее содержимое. 
Пусть title у нас будет  'Заказ от ${DateTime.now()}' - знак доллара и фигурные скобки ${} обозначают, что то, что внутри них надо посчитать, если это считается, и превратить в строку. Функция DateTime.now() возвращает текущую дату и время с высокой точностью. 
Если мы в яндексе напишем "flutter как узнать текущее время" то он нам покажет примерно вот это - 
Поскольку использование конструкции внутри строки ${} и так подразумевает, что результат будет вычислен, то нам не надо писать что-то вроде DateTime now = DateTime.now(); - достаточно самой функции, т.е. DateTime.now() без лишних действий.
Ладно, с заголовком разобрались.
Теперь сам текст. 
Нам надо передать туда введенные данные и информацию по заказу.
В самом контроллере у нас есть ряд переменных типа TextEditingController, в которые сохраняются данные при заполнении формы, их мы используем. Надо только заметить, что TextEditingController это тоже класс, а само значение хранится в параметре text, так что выглядеть это будет так:
У вас может возникнуть вопрос: как так, newOrder.saveArticle вроде хотел две строки, а тут их целая пачка? На самом деле здесь две строки, я подчеркнул запятую, которая их отделяет. А то, что ниже запятой - это допустимый формат записи длинных строк. Если строка не вмещается в экран по длине, ее можно перенести на следующую таким вот способом - как новая строка, только без запятой. A <br> внутри строки - это из HTML, обозначает "с новой строки". Дело в том, что мы же выводим статью на сайт, а он понимает HTML, так что если мы хотим форматирование то можем сделать его так.
Вот только откуда нам взять информацию о заказе? Дело в том, что мы абсолютно ничего дополнительно не передаем ни в метод submit, ни в свойства контроллера, и это надо исправить.
Возвращаемся к коду нашей кнопки в edit_profile_view.dart и вспоминаем, что экран-то все знает о заказе! Вот она, эта переменная, слева
Поэтому просто как есть пишем ее в скобках метода submit, чтобы превратить это в параметр
IDE, конечно же, по началу будет против - в описании к ошибке говорится, что ожидается ноль параметров, а мы отправляем аж целый один
Если нажать на recipe правой кнопкой мыши и выбрать Show Context Actions, то нам предложат создать обязательный или необязательный параметр:
Выбираем обязательный, и нас сразу отправит к нужному методу класса
И нам останется лишь добавить продолжение в тексте вашего заказа. Переменная recipe теперь доступна, только не забудьте, опять же, что это класс, а нам, наверное, в заказ надо будет отправить значение поля title
Проверяем. Работает!
Есть, правда, одно "но": а если не получится отправить, что тогда? Например, сайт упал. 
Если мы посмотрим на объявление метода saveArticle, то увидим, что он возвращает логическую переменную, которая принимает значение false если отправка не удалась:
Слово Future обозначает, что "результат надо будет подождать" - для этого есть специальное служебное слово await, которое приостанавливает работу метода до тех пор, пока не вернется значение. 
То есть нам надо "подождать" результат, и если он положительный - то выводим одно сообщение, если не удалось - то другое.
Тут нам поможет условный оператор if .. else.
Давайте выделим выделим мышкой часть кода, отвечающего за показ успешного диалога, нажмем на нем правой кнопкой мыши и "обернем" его оператором if через пункт меню Show context actions:
Получилось вот что:
Condition в переводе обозначает "условие". Такой переменной у нас нет, поэтому подчеркнуто красным. Давайте вместо нее напишем, например, isSent (былоОтправлено) и с помощью Show COntext Actions (которое по нажатию правой кнопки мыши, верхний пункт меню) создадим локальную переменную:
Получилось так:
Пока что все еще не правильно, потому что переменную надо инициализировать - присвоить ей сразу какое-то значение. 
Логические переменные бывают либо true - истина, либо false - ложь. Как раз то, что возвращает нам newOrder.saveArticle! Так что давайте сразу и присвоим, не забыв, что надо "подождать":
Последний шаг - у нас условие срабатывает только если isSent = true, то есть, отправка была успешна.
Надо добавить код, показывающий, что произошла ошибка. Для этого надо дописать после if слово else ("иначе"), и поставить фигурные скобки, вот так:
Выше в этом же файле у нас есть код, показывающий всплывающее сообщение об ошибке если мы неправильно ввели данные в форму. Давайте его скопируем сюда 
Осталось только заменить первых два позиционных параметра - если навести курсор мышки на слово snackbar, то нам покажут, что это за переменные
То есть первая это title, то есть заголовок, а вторая - это message, то есть текст. 
Так что смело удаляем слова ProfileEditStrings.snackbarErrorHeader и warnings и пишем вместо них свою текстовку, примерно так:
Вот теперь точно все )
Мы настроили бэкэнд, организовали безопасную отправку данных, защитили вывод конфиденциальной информации от посторонних глаз, добавили новый код в приложение и написали несколько строк кода самостоятельно. 
В итоге у нас рабочее приложение, которым уже можно пользоваться.
Поздравляю!
Subscription levels3

Внешний вид, интерфейс

$2.56 per month
Коллекции элементов интерфейса, готовые экраны и отдельные элементы

Сниппеты

$6.4 per month
Функциональные блоки кода, решающие определенные задачи. Включает доступ к материалам из подписки "внешний вид, интерфейсы"

Интегрированные экраны

$12.8 per month
Готовые к интеграции экраны с работающей логикой. Включает доступ к материалам из подписки "внешний вид, интерфейсы", "сниппеты"
Go up