Делаем свой генератор CRUD для asp.net mvc (часть 1 — получаем данные) / Хабр | Веб-студия Nat.od.ua
Делаем свой генератор CRUD для asp.net mvc (часть 1 — получаем данные) / Хабр
Типичный вывод набора данных в опрятном дизайне
Во имя чего?
Недавно проходил очередное собеседование, попросили написать CRUD для данных из 5 таблиц с максимальной глубиной связей 3 (таблица 1->таблица 2->таблица 3).
В принципе, это по количеству работы аналог какого-нибудь форума (users, files, themes, groups, messages). Сделайте нам пожалуйста phphbb :). И слышать “Ой, а ваше решение плохо поддерживается” в ответ на труд более двух дней не хотелось.
Встроенный в VisualStudio генератор CRUD по EntityFramework тоже оказался почти бессилен.
Плюс, довольно известный генератор CRUD EasyData(ссылка) не поддерживает валидацию данных через атрибуты (по крайней мере на стороне сервера).
Часть 1 – Система типов
Сначала построим все классы для хранения результатов
DbSetInfo.cs – Информация о контексте
///
internal class DbSetInfo
{
///
///
public string Name;
///
///
public List
}
ModelInfo.cs – Информация о таблице
///
internal class ModelInfo
{
///
///
public string ClassName;
///
///
public string TableName
{
get
{
if (ClassName.EndsWith(“Model”))
return ClassName.Substring(0, ClassName.Length – 5);
else
return ClassName;
}
}
///
///
public List
///
///
public List
}
ItemInfo.cs – Информация о полях таблицы
///
internal class ItemInfo
{
///
///
public string Name { get; set; }
///
///
public eItemTypeInfo Type { get; set; }
///
///
public List
}
eItemTypeInfo.cs – Типы данных в полях таблицы
///
internal enum eItemTypeInfo
{
//Строка
@string,
//GUID
Guid
}
ValidationAttributeInfo.cs – Информация об атрибутах полей (возможно я разделю атрибуты по типам на этапе рисования шаблонов)
///
public class ValidationAttributeInfo
{
///
///
public Attribute Attribute;
}
RelativeElementInfo.cs – ссылка 1 к 1 на другую таблицу
///
internal class SingleRelativeElementInfo
{
///
///
public string PropertyName;
///
///
public string TableName
{
get
{
if (@Type.Name.EndsWith(“Model”))
return @Type.Name.Substring(0, @Type.Name.Length – 5);
else
return @Type.Name;
}
}
///
///
public Type @Type;
}
Как видим, ничего интересного, у контекста есть таблицы, у таблицы есть поля, у полей есть атрибуты. Есть ссылочные поля (ссылка 1 к 1).
Часть 2 – Основная часть парсера
Естественно, данные которые мы получаем от Helper-а тоже заносятся в отдельный класс
RawTableInfo.cs – Класс для хранения данных парсера
///
internal class RawTableInfo
{
///
///
public string ClassName;
///
///
public string TableName;
///
///
public string FieldName;
///
///
public Type TableType;
///
///
public Type ModelType;
}
Так же обычно помогают Extention-методы для object, которые получают почти любую информацию о типе (без дублирования typeofGetType).
ObjectExtentions.cs – Методы расширения для объектов
///
internal static class ObjectExtentions
{
public static List
{
var t = obj.GetType();
return t.GetRuntimeProperties().ToList();
}
public static List
{
var t = obj.GetType();
return t.GetRuntimeFields().ToList();
}
public static List
{
var t = obj.GetType();
return t.GetCustomAttributes().ToList();
}
public static List
{
return obj.GetRuntimeProperties().ToList();
}
public static List
{
return obj.GetRuntimeFields().ToList();
}
public static List
{
return obj.GetCustomAttributes().ToList();
}
}
Теперь перейдем к нашему основному классу, который получает данные о DbContext и возвращает класс DbSetInfo
DbSetParser.cs – Парсер информации DbContext
В основном (кроме вызова говорящих методов GetRuntimeFields() GetRuntimeProperties()) встречаются 2 проблемы:
Как реализовать is/as
Очень просто, использовать IsAssignableFrom(). Метод вернет true если аргумент можно привести к базовому типу.
typeof(baseClassType).IsAssignableFrom(DerivedClassType)
Как определить что тип унаследован от Generic-а
Тоже довольно просто. Приводить все к GetGenericTypeDefinition(). Следующий код вернет true для всех объектов, унаследованных от DbSet
. item.PropertyType.GetGenericTypeDefinition() != typeof(DbSet).GetGenericTypeDefinition()
Как получить параметры Generic типов (string из List
) Специальная коллекция GenericTypeArguments со ссылками на типы параметров
GenericTypeArguments – В случае DbSet
Ну и немного про сам DbSetParser. В принципе, все написано в стиле SOLID и мне ничто не помешает добавить ссылки 1 к N либо новые типы данных. Я просто добавлю поля в ModelInfo и метод наподобие process1toNReferences не нарушая структуры кода.
Заключение
Вот так, за час-два мы получили через рефлексию почти исчерпывающую информацию о таблицах в нашей БД, их полях, атрибутах полей, связях и всем остальном.
Атрибуты можно использовать в cshtml.
Во второй части сформулируем чем похожи все исходные коды на всех языках, построим модели данных и сделаем пару простых шаблонов (Controller/CrudViews (Index, Add, Edit, Delete).
четыре способа коммуникации между frontend-приложениями / Хабр | Веб-студия Nat.od.ua
четыре способа коммуникации между frontend-приложениями / Хабр
Микрофронтенды «выросли» из модного веяния web-разработки в технологию, которую активно используют на больших проектах. Микрофронтенд гибче монолита, может быстрее реагировать на непредвиденные ситуации и дорабатываться под запрос. Этот подход позволяет быстро расширять приложение и разрабатывать на разных фреймворках. Тем не менее один из ключевых недостатков технологии — сложность во взаимодействии между frontend-приложениями.
Меня зовут Игорь, я frontend-разработчик SimbirSoft. Ранее мы с коллегами рассматривали вопрос построения архитектуры микрофронтендов. А в этой статье я разберу основные способы коммуникации между отдельными приложениями, которые можно применять в коммерческой разработке. Материал будет полезен разработчикам и архитекторам frontend-приложений.
iFrame
Для коммуникации между абсолютно не несвязанными приложениями можно использовать iFrame. Кто-то удивится его применению в коммерческом проекте — многие считают эту технологию устаревшей. Но при передаче данных между frontend-приложениями, которые находятся на разных доменах, у iFrame практически нет альтернатив.
Принцип работы довольно прост. Он состоит в использовании одного приложения как хранилища данных и запрашивании/передаче данных из других приложений. У iFrame в этом контексте почти нет ограничений — мы можем передавать данные вне зависимости от используемых фреймворков и доменов.
Метод PostMessage позволяет безопасно отправлять кросс-доменные запросы.
Отправка данных выглядит примерно так:
const win = document.getElementById(“iframe”).contentWindow; // объект iframe
win.postMessage(“Сообщение отправлено “, “*”); // отправляем сообщение в iframe
В приложение, которое обрабатывает это сообщение, оно попадает в событие message и прослушивается так:
window.addEventListener(“message”, listener, false);
Послать ответ в родительское окно можно с помощью window.parent:
window.parent.postMessage(“Какое-нибудь произвольное сообщение”, “*”);
В целом, этих функций достаточно для коммуникации между приложениями и даже построения EventEmmiter через событие message. Но стоит помнить о безопасности — послать запрос в это хранилище может почти любой пользователь. Чтобы этого избежать, проверяйте event.origin у всех, кто отправляет сообщения.
Важно понимать, что тег iFrame хранит в себе целый документ и загружается вместе с основной страницей, поэтому лучше не использовать в нем никаких тяжелых фреймворков — которые, по большому счету, там и не нужны.
Схема работы iFrame
Подведем итоги метода коммуникации по iFrame.
Достоинства:
позволяет передавать данные между приложениями на разных доменах;
поддерживается всеми современными браузерами.
Недостатки:
при разработке могут выявиться особенности, которые влияют на безопасность системы;
имеет не лучшую репутацию среди разработчиков;
замедляет загрузку приложения.
CustomEvent
CustomEvent API позволяет разработчикам не только создавать кастомные события, но и инициировать их на элементах DOM, передавая данные по цепочке. При этом API довольно прост.
Все, что нам нужно – создать событие с помощью конструктора. Например:
var myEvent = new CustomEvent(“event “, {
data: {
username: “igor”
}
});
Далее — подписаться на него:
myElement.addEventListener(“event”, function(e) {
console.info(“Event is: “, e);
})
И вызвать его с помощью метода dispatch:
myElement.dispatchEvent(myEvent);
Этот подход позволяет писать красивый событийно-ориентированный код, который свяжет работу приложения. Но стоит понимать, что работает этот метод только внутри одного window. Простыми словами — не получится передать событие в другую вкладку браузера. На практике этот метод можно использовать, если внутри одной страницы (благодаря новому Module Federation) контентом управляют разные приложения.
Схема работы CustomEvent
Достоинства:
Недостатки:
имеет очень ограниченную сферу применения, так как работает только в одном окне;
частично поддерживается в IE 9-11 (другой синтаксис) и не поддерживается в IE 6-8.
Storage event
Когда обновляются данные в localStorage или sessionStorage, генерируется событие storage. Его можно использовать для оповещения приложения о новых данных. Обработка этого события выглядит примерно так:
// срабатывает при обновлениях, сделанных в том же хранилище из других документов
window.onstorage = event => { // можно также использовать window.addEventListener(“storage”, event => {
if (event.key != “now”) return;
alert(event.key + “:” + event.newValue + ” at ” + event.url);
};
localStorage.setItem(“now”, Date.now());
Важно понимать, что при использовании localStorage мы можем оповещать все window, которые имеют доступ к этому хранилищу, в том числе разные вкладки. В случае session storage мы оповещаем только текущую вкладу (схоже с CustomEvent).
Схема работы Storage event
Достоинства:
не замедляет загрузку страницы;
может передавать данные как между вкладками, так и внутри одной из них;
поддерживается всеми современными браузерами.
Недостатки:
не может передавать данные между доменами (исходя из ограничений localStorage);
изначально не предназначен для передачи событий.
BroadcastChannel API
Интерфейс BroadcastChannel представляет собой именованный канал, на который можно подписаться из любого контекста просмотра этого источника. Это позволяет настроить коммуникацию между документами — в разных окнах, вкладках, фреймах и так далее. Основное ограничение — в целях безопасности контексты, которые обмениваются данными, должны принадлежать одному источнику (same origin), что подразумевает одинаковый протокол, домен и порт.
Пример передачи данных:
const broadcastChannel = new BroadcastChannel(“channel_identifier”);
broadcastChannel.postMessage(“Example message”);
Слушать этот канал можно точно так же, как и iframe — с помощью события message.
Схема работы BroadcastChannel API
Достоинства:
не замедляет загрузку страницы;
может передавать данные между вкладками;
имеет простой и удобный синтаксис.
Недостатки:
не может передавать данные между разными доменами;
не поддерживается IE 11, Opera Mini и старыми версиями других браузеров (приблизительно, старше 2016).
Итоги
Все описанные выше методы можно использовать в коммерческом приложении для связи между микрофронтендами в зависимости от текущей ситуации:
Если нужно максимально просто передать события внутри текущего окна без хранения дополнительного стейта в сторейдже, стоит использовать CustomEvent.
Если же стоит задача передать данные между вкладками только одного домена, лучше остановиться на StorageEvent или BroadcastChannel. Выбор стоит только в том случае, если не требуется поддерживать старые версии браузеров. Если ваш фронтенд их поддерживает — избегайте использования BroadcastChannel. В остальных случаях делайте выбор в зависимости от того, как долго вам нужно хранить эти данные. Если их требуется хранить и после закрытия вкладки — используйте Storage, если нет — BroadcastChannel станет более подходящим инструментом.
При кросс-доменной передачи данных у iFrame почти нет конкурентов. Но в других случаях эта технология избыточна.
Спасибо за внимание!
Полезные материалы для frontend-разработчиков мы также публикуем в наших соцсетях – ВК и Telegram.
7 ошибок глобального рефакторинга и как их избежать / Хабр | Веб-студия Nat.od.ua
7 ошибок глобального рефакторинга и как их избежать / Хабр
«Все снести и переписать нормально» — нередко именно такое желание возникает у разработчиков, когда дело касается legacy кода. Причины на то могут быть разными: сервис плохо написан, технологии устарели, либо в коде накопилось такое количество архитектурных ошибок, что развивать продукт, а то и просто поддерживать, стало невозможно. Но стоит помнить, что далеко не всегда это верное решение. Очень часто попытки переписать код заново заканчиваются провалом — мне довелось видеть немало таких примеров.
В этой статье я отвечу на вопрос, почему попытки переписать код с нуля оказываются неудачными, опишу 7 самых распространенных ошибок глобального рефакторинга и расскажу, как работать с legacy кодом, чтобы их избежать.
Ошибка №1. Переписывать с нуля сразу весь сервис
Да, это очень привлекательный и самый очевидный вариант. Зачем тратить время и ресурсы на поддержку legacy сервиса и интеграцию нового в старое, если можно сразу написать новый код, а потом перевезти сервис на него? Это же гораздо эффективнее! К тому же, новая архитектура совсем не совместима со старой.
К сожалению, если ваш продукт существует не 1-2 месяца, а дольше, скорее всего, это план не сработает. Те решения, которые вам кажутся плохими, вероятно были необходимы, чтобы решить какую-то конкретную проблему, о которой вы еще не знаете. В итоге, пока вы пишете код заново, вы узнаете новые вводные и требования, процесс затягивается, а старый продукт, тем временем, продолжает обрастать новыми фичами.
Как избежать:
Придумайте и создайте механизм совместной работы для старой и новой версий, а также разделите приложение на логические элементы которые можно перенести независимо друг от друга. Чем меньше будет такой элемент, тем лучше. Например, можно переносить сервис постранично, переносить по одной модели, и т.д.
Ошибка №2. Начинать переписывать продукт как только вы пришли в компанию
Итак, вы начали работать над новым продуктом месяц назад, и видите, что архитектура сервиса никуда не годится. Знакомо? У вас, само собой, возникает желание переделать все с нуля, ведь поддерживать такой продукт очень сложно.
Проблема в том, что, скорее всего, вы не видите всю картину целиком и не знаете причины таких решений. Да, есть шанс, что разработчики до вас допустили ошибки по незнанию или были сильно ограничены во времени. Но часто оказывается, что те патчи и костыли, которые вы видите, были «необходимым злом», чтобы реализовать тот или иной функционал.
Как избежать:
Попробуйте какое-то время поработать с текущей версией продукта, даже если вам кажется что она «так себе». Так вы сможете лучше понять контекст и уберечь себя от будущих ошибок. Также рекомендую с самого начала привлекать к процессу продакта — он может знать какие-то неочевидные особенности сервиса и важные детали.
Ошибка №3. Думать, что новый язык или фреймворк решит все проблемы
Раз уж мы решили переписывать сервис — нужно использовать самые современные инструменты. А тут как раз вышел новый мега-крутой фреймворк, где много классных возможностей, которые отлично впишутся в наш продукт.
В самой идее переписывать сервис на современных технологиях нет ничего плохого, но не нужно гнаться за хайпом. Переход на ультра новую технологию сам по себе — эксперимент, где вам придется столкнуться со множеством сложностей, проблем и сюрпризов. Рефакторинг — не менее масштабный проект. Не стоит пытаться делать все и сразу.
Как избежать:
Для рефакторинга советую использовать современный, но при этом проверенный стек, стабильный и поддерживаемый, с которым вы и ваша команда уже работали. Еще лучше, если этот стек полностью или частично совпадает с текущим — это значительно упростить вашу работу.
Ошибка №4. Исправлять только код
Когда вы видите некачественно написанный продукт, проще всего решить, что проблема в технологиях и неверных инженерных решениях. А значит, нужно просто все переделать, как надо, и сервис заработает. Но, возможно, причины плохого кода — в неэффективной организации внутренних процессов.
Как избежать:
Прежде чем приступить к рефакторингу, стоит ответить на важный вопрос — как так получилось, что нам приходится тратить столько времени на переписывание? Не возникнет ли проблема снова после того, как мы выкатим новый код?
Важно не только исправлять плохой код, но и устранять причины, которые привели к его появлению. Например, перестроить внутренние процессы, как инженерные, так и продуктовые, а также поднять общий уровень hard skills разработчиков. Словом, убедиться, что если проблема и возникнет снова, то уже не в том масштабе.
Ошибка №5. Переписывать legacy и одновременно внедрять новый функционал
Раз уж мы и так переписываем продукт, а новая архитектура как раз поддерживает весь тот функционал, который так давно от нас ждут, почему бы не внедрить все новые фичи сразу? Такой подход часто встречает поддержку со стороны менеджмента, поэтому многие команды идут этим путем.
Но что же получается в итоге? Рефакторинг, который до этого шел хорошими темпами, начинает затягиваться до бесконечности: сначала вы тратите время на разработку долгожданной фичи, а затем ждете обратную связь и внедряете улучшения. Кроме того, оказывается, что выкатывать ее пока нельзя — отдел поддержки не знает, как с ней работать, и не готов к релизу.
Как избежать:
Безусловно, рефакторинг должен поддерживать новый функционал, ради которого он затевался. Но стоит разделить весь этот процесс на два этапа: сначала вы переносите и выкатываете существующий функционал, и только потом начинаете делать новый. Разделение процессов позволяет отследить риски и при необходимости отложить реализацию фичи, если она требует больших затрат или настройки сложных процессов в компании.
Ошибка №6. Проводить рефакторинг без участия PM’а или PO
Рефакторинг — технический процесс. Мы и так знаем, что и как делать, а Product Owner будет только мешать — долго разбираться в проблеме, делать кучу ненужной работы и требовать внимания. Размышляя таким образом, команда берется решать все самостоятельно. В процессе рефакторинга, столкнувшись с проблемами, они решают отказаться от поддержки небольшой части функционала, чтобы ускорить процесс, но остальное кропотливо переносят.
В итоге оказывается, что фича, от которой команда отказалась, была критически важной для значительной части пользователей, зато половину всего остального можно было и не поддерживать – этим почти никто не пользуется.
Как избежать:
В legacy продукте многие фичи уже давно никем не используются и остаются там в силу исторических причин. Подключив грамотного PO, вы сможете уменьшить количество фичей, которые необходимо перенести, выпилить на время какой-то менее важный функционал, и тем самым значительно сократить время поставки.
Ошибка №7. Пытаться создать идеальную архитектуру
Сейчас наш продукт написан плохо, но мы знаем, как создать правильную, качественную архитектуру, которая учтет все текущие требования и решит все проблемы.
К сожалению, данный подход обычно приводит к тому, что вы создаете примерно такую же по качеству и надежности архитектуру — при новых сильных вызовах она снова не справляется и отправляется на помойку.
Вызовы, к слову, могут появиться довольно быстро, так как многие смелые идеи не доходили до разработки именно потому, что в прошлой версии продукта сделать их было чрезвычайно дорого и сложно, и никто даже и не пытался начинать.
Как избежать:
Не пытайтесь создать «эталонную» архитектуру, вместо этого сфокусируйтесь на ее гибкости. Мир изменчив, бизнесу приходится реагировать на вызовы и лучшая архитектура — та, что сможет быстро адаптироваться к изменениям и не потребует нового большого рефакторинга.
Вывод
Опыт научил меня, что на переписывание приложения с нуля всегда уходит больше времени и сил, чем кажется на первый взгляд. К тому же, приоритетом для любого сервиса являются пользователи, а рефакторинг часто откладывает выпуск важного для них функционала на неопределенный срок. Поэтому прежде чем браться переписывать приложение, нужно дважды оценить, действительно ли это необходимо.
С другой стороны бояться и избегать рефакторинга тоже не стоит. Иногда переписать все с нуля — единственно верное решение. Если не допускать перечисленных мной ошибок, весь процесс можно эффективно и контролируемо довести до конца.
цифровой завод в браузере или 3D в контексте клиент-серверных web приложений / Хабр | Веб-студия Nat.od.ua
цифровой завод в браузере или 3D в контексте клиент-серверных web приложений / Хабр
Всем привет! Меня зовут Евгений, я backend‑разработчик в компании Bimeister. Сегодня я хочу рассказать о нашем 3D движке Spatium для рендеринга сводных моделей масштаба промышленного предприятия в браузере.
Материал может представлять интерес для разработчиков и пользователей BIM-систем, а также для инженеров и конструкторов, которые выполняют работы по проектированию 3D моделей, получению фотограмметрических моделей и облаков точек для таких систем.
Контекст
Не будет секретом тот факт, что работа с графикой в целом и с 3D-графикой в частности предполагает использование значительных аппаратных ресурсов. И чем выше наши ожидания от качества изображения, от глубины детализации и плавности смены кадров, тем больше этих ресурсов потребуется. И ладно бы, просто картинку отрисовать – хватило бы и видеокарты, так нам интерактив подавай, да подинамичнее. И хранить эту самую графику тоже где-то нужно – в общем, целый набор требований к аппаратной части. И это – первое ограничение. А высокая стоимость таких ресурсов – неприятное следствие из него.
Решить эту проблему можно разными способами, и самый популярный – за счет разделения обязанностей, то есть, архитектурно. Пусть одна часть приложения – клиент – показывает элементы интерфейса, отправляет запросы, принимает данные, помещает их в видеокарту и рисует картинку. А всю логику подготовки, хранения и обработки данных возлагаем на другую часть – производительный сервер. Требования к аппаратной части клиента снижаются, но сразу появляются два недостатка такого подхода – зависимость от наличия подключения к сети и потенциальные ограничения по объему трафика. И это наши ограничения под номерами два и три.
Допустим, что мы построили клиент-серверную архитектуру (подробнее про нее можно прочитать на Хабре или в Википедии, мне лично понравилась вот эта статья) и обеспечили себя безлимитным стабильным подключением к сети. Так давайте поприветствуем разные форматы данных! Часть из них – относительно универсальны, часть – довольно специфичны. Возникает новая проблема – необходимость наличия различных клиентов-приложений для работы с различными форматами. Неприятно. И дорого! И снова вырастают требования на аппаратную часть – эти приложения нужно хранить, и иногда запускать одновременно. И спасением становится неизменный спутник современного человека – веб-браузер. Достаточно универсальная среда выполнения кода, написанного на популярном языке программирования – JavaScript. И самое главное, браузеры можно встретить повсюду: почти любое устройство под управлением наиболее распространенных операционных систем имеет в своем составе браузер – Google Chrome, Safari, Edge и т.д.
В задачах информационного моделирования (BIM) могут быть задействованы – в большей или меньшей степени – 3D-технологии, такие как проектирование в САПР, лазерное сканирование (облака точек), фотограмметрия (подробнее об этом – статья в Википедии и целая книга). Если артефакты этих технологий разместить в едином цифровом пространстве, то результатом будет так называемая сводная модель, которую уже удобно использовать в задачах BIM.
Рисунок 1 – Визуализация сводной 3D-модели (САПР-модель + облако точек)
Так как же получить интерактивную сводную 3D модель завода с миллионами элементов в браузере? Ответом будет динамическая сцена. Прежде чем рассмотреть механизм ее работы, остановимся на некоторых определениях.
WebGL
WebGL – кроссплатформенный API для 3D-графики в браузере. WebGL исполняется как элемент HTML5 и поэтому является полноценной частью объектной модели документа (DOM) браузера. Может использоваться с любыми языками программирования, которые умеют работать с DOM (компилируются в WASM) – JS, Rust, C++, C# и Go и т.д.
WebGL – это контекст элемента canvas HTML. Познакомиться с технологией подробнее можно здесь, здесь или здесь.
WebGL выполняется на графическом процессоре компьютера (GPU), и простейшем случае представляет собой набор двух функций: вершинного и фрагментного шейдеров. WebGL использует язык программирования шейдеров GLSL (GL Shader Language).
Шейдер (shader) – программа, выполняемая графическим процессором. Особенность архитектуры GPU – множество параллельных ядер, поэтому шейдеры могут выполняться сразу по несколько тысяч параллельно. Задача вершинного шейдера – вычислять положения вершин. Задача фрагментного шейдера – вычислять цвет для каждого пикселя примитива, который в данный момент отрисовывается.
Frustum
Фрустум – это пространство отсечения или ограниченный объем просмотра. Каждая координата, попавшая во фрустум, окажется на экране, а все, что во фрустум не попало – будет отсечено. Для перспективной проекции используется фрустум в виде усеченной пирамиды, для ортографической проекции – в виде параллелепипеда.
Рисунок 2 – Фрустум для перспективной проекции
Динамическая сцена
Поскольку клиент ограничен аппаратно, может иметь слабое интернет соединение или не иметь его вовсе (вспоминаем про ограничения из раздела Контекст), то нам потребуются следующие решения:
Поддержка автономного режима.
Оптимизация использования трафика.
Оптимизация использования геометрий.
Алгоритм поиска одинаковых геометрий решает задачи с оптимизацией трафика и переиспользования геометрий. А как организовать автономный режим – вопрос достаточно тривиальный. Поэтому давайте рассмотрим непосредственно механизм формирования динамической сцены:
По положению камеры и направлению “взгляда” формируется фрустум на стороне клиента.
На сервер отправляется запрос объектов, находящихся в данном фрустуме.
На сервере осуществляется поиск по пространственному индексу всех 3D-объектов, попадающих во фрустум из запроса с учетом углового размера и установленного лимита, после чего вычисляется разница между предыдущим запросом и текущим, которая записывается в кэш, а результат возвращается клиенту.
Клиент на основании полученной разницы между предыдущим и текущим состоянием сцены:
Удаляет из памяти геометрию “лишних” объектов. Лишние объекты – это объекты вне области видимости и превысившие установленный лимит.
Запрашивает геометрию объектов, которой не хватает для отображения объектов из текущей области видимости. С сервера геометрия передается в удобном для WebGL и GPU виде.
По мере загрузки геометрий, формируются коллекции одинаковых геометрий, которые могут быть отрисованы за один кадр. Одиночные геометрии и коллекции геометрий отправляются на рендеринг в видеокарту.
На каждую смену положения камеры формируется новый фрустум и цикл повторяется.
Таким образом, пользователь видит только те объекты, которые находятся перед камерой с учетом ее положения и настроек. Фрустум не передается целиком, а кэшируется. Для экономии траффика и оптимизации работы с памятью применяется переиспользование геометрий.
Spatium
Итак, Spatium — это инструмент для работы с 3D-графикой в контексте клиент-серверных web-приложений, ориентированный на сводные модели масштаба промышленного предприятия. Физически представляет собой совокупность docker образов с приложениями обработки 3D данных, а также набор npm и nuget пакетов для интеграции с ними.
Движок разработан с использованием открытого ПО и поддерживает парсинг (импорт) следующих форматов:
САПР – ifc, rvm+att, nwd, ipt
Фотограмметрия – obj + mtl + png/jpg/jpeg
Лазерное сканирование – pts, e57, las, laz
Динамическая сцена в Spatium может одновременно отрисовывать до 150 тысяч геометрий и до 30 миллионов точек лазерного сканирования, при этом в сводной модели количество объектов может исчисляться миллионами. Производительность при этом будет ограничена аппаратными средствами клиента.
Рисунок 3 – Визуализация облака точек
Вместо заключения
Технология WebGL не нова и широко применяется для создания 3D контента и игр в вебе. Если поверх этой технологии организовать динамическую сцену, снабдить ее алгоритмами для оптимизации памяти и трафика, то появится возможность побывать в огромном виртуальном заводе без использования специализированных программ-клиентов – а это открывает широкий простор для автоматизации промышленности.
Разработка движка Spatium была выполнена ООО “Бимэйстер Холдинг” при поддержки Фонда содействия развитию малых форм предприятий в научно-технической сфере, в рамках реализации инновационного проекта «Исследование алгоритмов оптимизации 3D-моделей для отображения высокодетальных цифровых двойников в информационных системах управления предприятием».
Shopify Editions 2023 — крупное обновление в Шопифай / Хабр | Веб-студия Nat.od.ua
Shopify Editions 2023 — крупное обновление в Шопифай / Хабр
В этой статье вы узнаете, что добавилось в последнем крупном Shopify обновление под названием Shopify Edition Winter ’23.
Шопифай периодически выпускает большую пачку обновлений и они обычно называются Shopify Edition «Сезон» «Год».
Так как обновлений очень много, в этой статье будут затронуты основные изменения в зимнем обновление 2023 года. На случай если вы хотите почитать полный список обновлений — ссылка.
Будут затронуты следующие темы:
Обновление версии темы с изменённым исходным кодом
Кастомный CSS динамических секций
Переводите и адаптируйте
Метаобъекты
Грядущие обновления
Обновление тем
Если вы захотите обновить версию темы и в её коде есть изменения, то теперь Шопифай не переносит изменения кода в обновленную версию темы. Это довольно серьёзное обновление, ведь существует очень много тем в которых так или иначе был изменён код.
Сообщение об обновлении версии темы с измененным кодом
Да что говорить, в каждом магазине в котором я работал приходилось менять код.
И что тогда делать? Шопифай рекомендует создать копию темы которую вы хотите обновить и после обновления просто перенести изменённые участки кода. На словах звучит легко, но на деле сложнее.
Custom CSS
У каждой динамической секции появилась новая возможность. Теперь можно добавлять кастомные стили прямо в кастомайзере или добро пожаловать в WordPress. Как по мне, это нововведение связано с первым обновлением. То есть, если вы поменяли какие‑то стили в коде, то при обновление темы они затрутся, но если вы поменяли стили в секции через админку, то при обновление темы они останутся. Но есть нюанс, стили применяются лишь к секции в которой вы их пишите.
Чтобы найти это нововведение откройте любую секцию через кастомайзер, пролистайте в самый низ и после настроек темы увидите выпадающее меню “Custom CSS”.
Это нововведение бесполезно, по крайней мере, для меня. С момента обновления прошло пол года и я ни разу не воспользовался этой функцией. Скорее всего, оно создано для обычных пользователей. Но я не могу себе представить, если в каждую секцию будут добавлены какие‑то стили и спустя какое‑то время нужно их поменять. Это как минимум нужно помнить, где они находятся, ведь поиска там нет.
Переводите и адаптируйте
Появилось официальное приложение для мультиязычности магазина которое позволяет автоматически переводить сайт на разные языки. В бесплатной версии можно перевести максимум два языка.
Полезное обновление, ведь раньше приходилось вручную обновлять каждое слово на сайте, конечно, если у вас не установлено какое‑то приложение. Но в то время я работал с клиентами которые не хотели платить $20 в месяц за приложение которое будет переводить их магазин, поэтому приходилось работать с тем, что есть.
Метаобъекты
А вот это уже очень интересное обновление. Теперь можно создавать свои метаобъекты, а не как раньше только метафилды (далее метаполя).
При создание метаобъекта вы сами настраиваете какие поля (филды) в нём будут. Раньше можно было создать поля только в заготовленных объектах, которые представлены ниже на скриншоте:
Их количество потихоньку увеличивается, что радует. В любом случае теперь вы можете создавать свои объекты.
Пример использования метаобъектов
В вашем магазине представлены бренды одежды и вы сотрудничаете с разными дизайнерами. Вам нужно отображать дизайнера на каждой странице товара со следующими данными: фотография дизайнера, имя, описание, и ссылка на соц. сеть. Также требуется создать страницу со всеми дизайнерами.
Для того чтобы не создавать разные секции с похожим контентом, вы можете создать метаобъект с полями, которые указаны выше.
Создаём секцию с дизайнером на странице продукта
Я создал метаобъект Designers и добавил туда нужные мне метаполя. Теперь нужно добавить первого дизайнера:
Добавление нового дизайнера через метаобъект
После добавления дизайнера, нужно связать метаобъект с каким-то метаполем. Так как я хочу отображать дизайнеров на странице продукта, мне нужен метаобъект продукта.
Для того чтобы открыть метаполя переходим по следующему пути:
Settings → Custom Data → Products
Страница метаполей Шопифай магазина
Теперь нужно открыть объект Products и создать новое определение (definition)
Связываем кастомный метаобъект с метаполем товара
Отлично! Теперь мы можем добавлять разных дизайнеров на разные товары.
Давайте назначим нашего дизайнера на какой‑то товар. Для этого открываем любой продукт через админку и листаем в самый низ. В поле Designer добавляем желаемого дизайнера:
Выбираем какого дизайнера отображать на странице этого товара
Теперь открываем страницу товара через кастомайзер, создаём новую секцию. Я создам секцию Image with text, так как она содержит всё, что мне нужно для текущего объекта.
Создание секции Image with text
После того как секция создана, нужно добавить значения к картинке, названию, описанию, и ссылке. Чтобы они подтягивались из метаполя нашего дизайнера.
Подключаем динамические значения в секцию
Теперь на странице продукта появилась новая секция с нашим дизайнером:
Секция дизайнера с данными из метаобъекта
Но если на товаре метаполе дизайнера не заполнено, то будут пустые значения:
В этом товаре дизайнер через админку не добавлен
Создаём страницу всех дизайнеров
Также метаобъекты можно вывести через код. Например, в нашем случае можно создать отдельную страницу со всеми дизайнерами. Давайте попробуем.
Создаём новую страницу, меняем у неё шаблон потому‑что я хочу показывать список дизайнеров только на странице дизайнеров.
Создаём новую секцию, называем её designers.liquid. В первой строчке подключаем CSS, можете писать стили прям внутри файла, я предпочитаю иметь отдельный файл.
Создаем контейнер‑обёртку для всех дизайнеров, внутри обёртки используем liquid объект for для того чтобы перебрать всех дизайнеров внутри метаобъекта. На данном этапе создаём вёрстку и подставляем данные из объекта.
В итоге должно получиться что‑то такое:
{{ ‘component-designers.css’ | asset_url | stylesheet_tag }}
{{ designers.name }}
{{ designers.description }}
Visit
{% endfor %}
{% schema %}
{
“name”: “Designers”,
“settings”: [],
“presets”:
}
{% endschema %}
Теперь нужно добавить стилей, чтобы страница выглядела посимпатичнее, потому‑что сейчас она выглядит как‑то так:
Страница дизайнеров, без стилей
Добавляем стилей и ещё одного дизайнера:
Страница дизайнеров, со стилями
component-designers.css
.designers {
display: flex;
flex-direction: column;
gap: 20px;
}
.designer {
display: flex;
align-items: center;
border-radius: 10px;
color: #fff;
padding: 20px;
transition: transform 0.2s;
border: 2px solid #1d1d1d;
width: 80%;
margin: 0 auto;
}
.designer:hover {
transform: translateY(-5px);
}
.designer__img img {
width: 200px;
margin-right: 20px;
}
.designer__body h2 {
margin: 0;
}
@media screen and (max-width: 430px) {
.designer {
flex-direction: column;
width: 95%;
}
.designer__img {
text-align: left;
width: 100%;
}
.designer__img img {
width: 150px;
}
}
Вот и всё! Теперь мы умеем работать с метаобъектами.
Искусственный интеллект для описаний товаров
Шопифай представили искусственный интеллект который по ключевым словам пишет описание товаров:
Обновление крутое! Ставлю лайк.
Грядущие обновления – Shopify Bundles
Шопифай анонсировали, что выпустят свои бандлы для продуктов. В Шопифай коммьюнити уже давно обсуждают бандлы и жалуются, что Шопифай их не включает в магазин бесплатно. Сейчас чтобы подключить бандлы, владельцам магазинов нужно ставить плагин. Плагины обычно платные и цена разнится от 5 до 20+ долларов в месяц.
Заключение
В этой статье я постарался рассказать о самом важном из зимнего Шопифай обновления и ещё мы создали динамический объект с дизайнерами. Обновления крутые, весь список тут.
Как я написал удобную оболочку над электронным дневником / Хабр | Веб-студия Nat.od.ua
Как я написал удобную оболочку над электронным дневником / Хабр
Статья о том, как я в 15 лет облегчил работу с электронным дневником, представленным правительством Кировской области в 2022 году.
Что получилось сделать и как это было реализовано.
Предыстория
Долгое время во всех школах моей области использовался устаревший АИАС АВЕРС. В 2022 году сообщили, что все школы переходят на новый электронный дневник. Я надеялся, что представят нечто вроде дневник.ру с приложением, уведомлениями, мобильной адаптацией и удобными фичами, а на деле мы получили устаревший сайт, в котором посмотреть расписание на телефоне – огромное испытание. Дневник был настолько сырой, что в начале учебного года для просмотра оценок было необходимо скачивать excel файл, благо это починили спустя половину учебного года.
Моей задачей стало разработать сайт, ускоряющей процесс обучения и помогающий в различных его сферах.
Обзор сайта
Я хотел сделать прежде всего удобную замену, из-за чего в приоритете стояли:
Мобильная адаптация
Большинство учащихся пользуются дневником на телефонах. В моем дневнике на мобильных устройствах элементы выстраиваются в 1 столбик, а оценки изменяют свой вид.
Оценки на телефоне
Темная тема
С появлением темной темы во всех мессенджерах и большинстве приложений, люди привыкли к ней. Я решил не обходить это стороной и добавил ее на сайт. Переключаются темы при помощи кнопки в левом нижнем углу. По умолчанию тема соответствует теме браузера.
Дополнительные функции, ускоряющие работу с дневником.
Если о первых двух пунктах достаточно написать пару предложений, то на следующем стоит остановиться.
Домашняя страница
На главной странице расположено окно с текущим днем, последними оценками и ботом вк, отправляющим изменения в расписании, когда они приходят на сайт школы.
Домашняя страница
Модальное окно урока
Если нажать на оценку или что-либо, связанное с уроком, открывается модальное окно. В нем можно просмотреть тему, домашнее задание с прошлого и данного урока.
Рядом с домашним заданием есть две кнопки:
AI – отправляет запрос с данным домашним заданием в perplexity.ai
Перейти – отправляет пользователя в гугл с этим запросом
Модальное окно урока
Live режим оценок
Представим ситуацию: в итоговых оценках появилась двойка, поставленная еще в начале четверти. Старый дневник пришлось бы долго листать и искать оценку, но я упростил это.
Теперь достаточно перейти к оценкам, в четвертях выбрать “Live” и дневник подгрузит все оценки вместе с датами. Если нажать на оценку, появится модальное окно с уроком, когда она была поставлена.
Live режим оценок
Фильтрация предметов
Рассмотрим еще одну ситуацию: ученик отсутствовал по болезни и в последний день болезни ему надо сделать все накопившиеся домашние задания. В старом дневнике пришлось бы листать и искать эти уроки, я предлагаю более простое решение.
При нажатии на урок, появляется модальное окно. В нем можно нажать кнопку “Показать уроки”, что откроет следующую страницу:
Фильтрация предметов
На данной странице показаны последние уроки заданного типа.
Расчет оценок
Ну и куда без третьей ситуации? Допустим, началась последняя неделя четверти и нам срочно надо исправлять оценки. Но сколько оценок надо исправить, сколько получить пятерок? В этом поможет дневник.
Если нажать на предмет в итоговых оценках, откроется модальное окно расчета оценок. В нем написано сколько пятерок осталось до повышения балла и как изменится балл при получении/удалении оценки.
Расчет балла
Сессия
Последним нововведением является продление сессии. В оригинальном дневнике необходимо авторизовываться по несколько раз за месяц, в моем же дневнике сессия продлена до года.
Немного технических деталей
Сайт был написан на фреймворке flask, так как он наиболее прост в освоении.
Api
Получение данных идет через RESTful оригинального дневника. Методы api я узнал из инструментом разработчика chrome.
Методы api, которые я использовал
https://one.43edu.ru/edv/index/diary/GUID?date=DATE – получение дневника. Возвращает дни с заданного до конца недели. Также в ответе есть периоды (четверти)
Оценки возможно получить только в виде excel файла, поэтому я их скачиваю, а позже читаю как csv. Получение оценок:
https://one.43edu.ru/edv/index/report/period/GUID?format=xls – годовые оценки
https://one.43edu.ru/edv/index/report/marks/GUID?format=xls&begin=BEGIN_DATE&end=END_DATE – четвертные оценки
POST https://passport.43edu.ru/auth/login – авторизация
При авторизации я сохраняю в базу данных данные о пользователе и session cookie с сайта. При повторном входе на сайт, я пытаюсь зайти с использованием этого куки и если не получается, авторизуюсь при помощи логина и пароля. Данный подход значительно ускоряет вход на сайт.
Сайт дневника стал блокировать запросы с заголовком python requests, поэтому для каждой сессии создаются случайные заголовки.
Frontend
Для верстки использовался Bootstrap 5, тема меняется при помощи его цветовых режимов.
Diary class
Так как запросы выполняются достаточно долго, я создал класс Diary, реализующий кэширование данных. В нем содержатся датаклассы Week (в Week – Day, а в Day – Subject), которые сохраняются при получении с сервера. Для работы с этим классом я реализовал декоратор login_required, который передает в маршрутизатор дневник первым аргументом.
Но если класс Diary будет храниться долгий срок, данные станут не актуальны. Поэтому нужно пересоздавать класс достаточно редко, чтобы не получать одни данные много раз и достаточно часто, чтобы данные были актуальны.
Я придумал следующее решение этой проблемы: при переходе в разные вкладки дневника, страница не обновляется и если пользователь обновит ее, дневник пересоздастся.
Javascript запросы
Переход между страницами реализован при помощи javascript. При нажатии на кнопку клиент отправляет запрос серверу и обновляет содержимое контейнера на сайте.
На следующих иллюстрациях показан процесс формирования домашней страницы.
Обозначения:
Схема запросов
Соответствие зон сайта
Возможно это решение не лучшее, но оно позволяет обновлять части сайта без обновления остальных.
Заключение
Это был один из первых моих flask проектов, благодаря ему я получил большой опыт. Надеюсь на предложения/критику/вопросы или просто оценку работы в комментариях.
Если кому-то интересно, с исходным кодом можно ознакомиться в GitHub проекте.
Это моя первая статья и оформление в некоторых моментах не совсем красивое, а текст не очень складный. Надеюсь, что мои навыки в данной сфере будут улучшаться с каждой статьей.
Удачного дня!
Качественно — значит долго? Как быстро создавать жизнеспособные MVP / Хабр | Веб-студия Nat.od.ua
Качественно — значит долго? Как быстро создавать жизнеспособные MVP / Хабр
Вопрос скорости и качества стоит в разработке особенно остро. Мы привыкли думать, что чем больше времени было потрачено на разработку продукта, тем лучше результат, и наоборот. Но так ли это на самом деле?
С одной стороны, кажется логичным, что скорость достигается путем отказа от тестирования, использования костылей, часто приходится жертвовать чистотой архитектуры. Пусть сервис и будет написан с ошибками, зато быстро.
С другой стороны, это возможный, но далеко не единственный способ добиться скорости в разработке. В этой статье я предложу несколько вариантов работы над MVP, которые позволят ускорить процесс и получить продукт который впоследствии можно будет развивать многие годы.
Способ 1. Сокращаем возможности продукта
Главная задача MVP, да и практически любой фичи — решить какую-то проблему клиента. Подумайте, так уж ли важен весь тот функционал, который вы запланировали? От чего можно отказаться, чтобы сократить время разработки? Что все же придется воплотить в MVP?
Причем речь здесь не только об очевидных излишествах. Отказаться можно и от возможностей, которые считаются стандартными. Например, вместо поддержки пользовательских картинок профиля можно подключить Gravatar или заменить регистрацию пользователя на сайте авторизацией через Google.
Способ 2. Сокращаем количество поддерживаемых платформ и вариантов
Например, на первых порах вы можете выпустить только desktop приложение для Windows, или отказаться от поддержки изображений в любых форматах — ограничьтесь JPEG. Подчеркну, что здесь я имею в виду не продуктовые фичи как таковые (о них речь шла выше), а скорее их техническую реализацию.
При этом закладывайте поддержку для будущего функционала, просто чаще возвращайте NotImplemented — в этом случае вы потратите меньше усилий на разработку. Например:
def normalize_image(filename):
if not filename.endswith(“.jpg”):
raise NotImplementedError()
…Способ 3. Упрощаем тестирование
Самый очевидный вариант — сократить время на тестирование. Да, тщательно протестировать продукт в этом случае не получится. Но, возможно, для MVP будет достаточно провести лишь необходимый минимум тестов — проверить код локально и в staging среде перед релизом.
Я очень рекомендую описать unit тесты, но не запускать их. Даже если вы просто опишите тест-кейсы, вы сможете увидеть недоработки и получить неочевидные инсайты для последующей проверки вручную. Это прекрасный пример того, как можно получить 80% результата выполнив 20% работы, особенно в краткосрочной перспективе. Например так это выглядит в Jest:
test.todo(“form shows error messaage for invalid credentials”)
test.todo(“form redirects on success login”)
test.todo(“redirect supports _next query param”)
test.todo(“_next query param validates url”)Способ 4. Используем более простую архитектуру
Почти всегда можно отказаться от использования трех разных баз данных, микросервисов или очередей без каких либо потерь. НО: важно разделить логику в слабо связанных частях приложения. Не стоит слишком усложнять — чаще всего, будет достаточно простого разделения на функции или модули.
Применительно к архитектуре можно обозначить два основных правила:
Не так важна правильность самих архитектурных решений, как возможность впоследствии их поменять
Чем более базовым является компонент, тем важнее его качество, и наоборот
И еще три важных совета:
Не усложняйте — большинство продуктовых идей не будут реализованы вообще, а среди тех, что будут, немалая часть окажутся нерабочими.
Производительностью можно пренебречь — безусловно, не стоит закладывать явно неэффективные решения в архитектуру, но разница между одной секундой и пятью, хоть и заметна, зачастую абсолютно не важна.
Не пытайтесь решать проблемы с помощью библиотек — они создадут вам значительно больше проблем в последствии. Хуже всего, если вы пытаетесь компенсировать недостаток знаний путем установки библиотеки (например, для графов). Библиотеки — не магия, а инструмент. Если вы не знаете хотя бы базовых принципов их работы, очень легко попасть в тупик.
Вывод
Помните, что MVP является как проверкой гипотезы, так часто и первоначальной версией продукта. Поэтому при его создании важно соблюдать баланс между техническими задачами и ожиданиями бизнеса и избегать крайностей. Не нужно слишком сокращать функционал или релизить код, на 100% покрытый тестами.
Если не допускать грубых ошибок и грамотно балансировать, то вы сможете создать MVP, который сэкономит вам большое количество ресурсов как в процессе разработки, так и в дальнейшей поддержке.
localStorage для авторизации / Хабр | Веб-студия Nat.od.ua
localStorage для авторизации / Хабр
Данный набор решений реализует логику разграничения прав доступ к определённым действиям и/или блокам интерфейса, в зависимости от конкретной роли (в качестве примера будет бинарное разделение, но ничего не мешает сделать разраганичение на множество ролей).
Предисловие
Использование localStorage, наряду с использованием httpOnly cookie, является одним из наиболее популярных подходов к хранению токенов доступа.
Мы не будем заниматься сравнением подоходов, об этом сказано уже много. Например в этой статье).
Здесь мы всего лишь зафиксируем, что localStorage чуть более уязвим для XSS атак, но позволяет работать с большим набором сценариев.
В этой статье мы рассмотрим как можно организовать работу с localStorage и при этом постараться обезопасить себя от XSS атак.
Так же важно понимать разницу между access token и refresh token. Как следует из названия, первый токен нужен для разграничения прав доступа к ресурсам, в то время как второй только для обновления первого).
В данной фиче мы будем говорить только о первом, об access token и в рамках нашего скоупа обсуждения будем считать, что refresh token у нас стабильный и валидный.
Итак, поехали!
План действий
Для реализации фичи, нам понадобится пройти несколько шагов, а именно:
понять, как и где безопасно хранить токен
научится получать доступ к приватному апи (согласно нашей роли)
выяснить, как мы можем ограничить доступ пользователя к части интерфейса и/или целым страницам
Безопасно хранить токен
Итак, после того как мы получили от бэка авторизационный токен, мы должны где-то его сохранить.
Выбираем для этого localStorage. Это достаточно безопасно, если следить за временем жизни этого самого токена и вовремя его обновлять.
Подробнее можно почитать тут: как использовать localStorage для хранения токена.
Наш модуль должен уметь в стандартные CRUD операции:
устаналивать/обновлять его значение (CREAT/UPDATE)
возвращать наш токен, по первому требованию (READ)
а так же удалять его (DELETE)
А ещё:
type StoredToken = {
value: string;
timeStamp: number;
};
const TOKEN_KEY = ‘auth_token’;
/**
* Токен имеет фиксированное время жизни.
* Важно, так как храним в localStorage и уменьшаем риск в случае xss.
* Время жизни токена ставим в 23 часа 59 минут
*/
const TOKEN_TTL_MS = 86340000;
const isExpired = (timeStamp?: number): boolean => {
if (!timeStamp) return false;
const now = new Date().getTime();
const diff = now – timeStamp;
return diff > TOKEN_TTL_MS;
};
const setToken = (access_token: string): void => {
localStorage.setItem(
TOKEN_KEY,
JSON.stringify({
value: access_token,
timeStamp: new Date().getTime(),
})
);
};
const removeToken = (): void => {
localStorage.removeItem(TOKEN_KEY);
};
const getToken = (): StoredToken | null => {
let result = null;
const storedToken = localStorage.getItem(TOKEN_KEY);
storedToken && (result = JSON.parse(storedToken));
return result;
};
export { getToken, setToken, removeToken, isExpired };
Сейчас есть весь функционал, который нам может понадобится.
Сама же логика установки токена будет на уровне компонента, так как триггером обновления access token должен быть пользователь.
Получить доступ к приватному апи
В рамках решения данной задачи нам всего лишь нужно взять наш access token и подмешать его в отправляемый запрос (поместить значение в признак, который понимает наш бэкенд). В подавляющем большинстве случаев этот признак будет в виде какого-либо http заголовка.
Предположим, что мы договорились с нашим бэком о следующей конвенции: “Authorization: Bearer ${access_token}“
А, и ещё мы лучше не будем в каждый fetch руками что-то подмешивать. А напишем простенький authFetch с блэкджеком. И сделаем его таким, чтобы сигнатура его использования была точно такая же, как и у обычного, всем привычного fetch. Т.е. наш небольшой модуль сделает всё за нас:
сам посмотрит, есть ли токен в хранилище (через внешние методы tokenProvider)
сам подмешает нужный заголовок
сам заботливо вернёт ответ без каких-либо манипуляций над данными
А нам останется всего лишь использовать authFetch вместо fetch для всех защищённых эндпойнтов.
const authFetch = async (
input: RequestInfo,
init: RequestInit | undefined = {},
token?: string
): Promise
const access_token = token || getToken()?.value || ‘no_token’;
if (access_token === ‘no_token’) {
// eslint-disable-next-line no-console
console.warn(‘Making secure API call without an auth token’);
}
const options = { …init };
options.headers = {
…init.headers,
Authorization: `Bearer ${access_token}`,
};
return fetch(input, options);
};
export default authFetch;
Вроде бы пока что не сложно. Идём дальше
Ограничить доступ пользователя к части интерфейса
А для решения этой задачки нам достаточно лишь написать стандартный react компонент, который будет либо рендерить предоставленных ему children, либо отправлять на страницу авторизации.
Правилом хорошего тона, при редиректе на страницу авторизации, будет передать в явном виде информацию о том, откуда мы пришли на страницу логина. Мы будем использовать простой query parametr под названием from
Но мы сделаем наш компонент немного уменее. Он сам сможет инициировать попытку обновления access token, сам его установить в случае успешного обновления или же перенаправить на страницу авторизации в случае фейла.
Алгоритм работы будет следующий:
import React, { useState, useEffect } from ‘react’;
import Preloader from ‘some-ui-kit-library/Preloader’;
import { getToken, setToken, removeToken, isExpired } from ‘./index’;
import api from ‘../../api’;
type Props = {
children: React.ReactNode;
};
const WithAuth = (props: Props) => {
const { children } = props;
const =
useState
const = useState
useEffect(() => {
const fetchToken = async () => {
try {
removeToken();
const access_token = await api.get.token();
setToken(access_token);
setIsAuthenticated(true);
setTokenFetchingStatus(false);
} catch (err) {
const msg =
err instanceof Error ? err.message : ‘Unknown Error: api.get.token’;
// реализуем утилитарное предупреждение для пользователя
// eslint-disable-next-line no-alert
alert(
`Неудалось загрузить токен доступа. Сейчас вы будете перенаправлены на страницу авторизации. Details: ${msg}`
);
window.location.assign(
`/your-login-page/?from=${window.location.pathname}`
);
}
};
if (isTokenFetchingActive) {
const token = getToken();
if (token && !isExpired(token.timeStamp)) {
setIsAuthenticated(true);
setTokenFetchingStatus(false);
} else {
fetchToken();
}
}
}, );
const renderContent = () => {
return isAuthenticated ? children : null;
};
return
;
};
export default WithAuth;
Применение WithAuth:
Итого
Имея всего 2 модуля и 1 компонет мы можем полностью, абсолютно как захотим, разграничить на зоны доступа наше приложение.
tokenProvider инкапсулирует в себе всю “низкоуровневую” работу с обслуживанием токена и торчит наружу свой незамысловатый интерфейс
WithAuth позволит нам зарендерить только доступную страницу (а при небольших доработках и кусочек интерфейса в рамках одной страницы)
authFetch позволит нам не парится и просто бомбить нужное нам апи запросами.
Спасибо за чтение и удачи в реализаици фичи авторизации)
PS: cсылки из статьи:
Распознавание банковских карт в видеопотоке в браузере с помощью SmartEngines и WebAssembly | Веб-студия Nat.od.ua
Распознавание банковских карт в видеопотоке в браузере с помощью SmartEngines и WebAssembly
С активным развитием и распространением технологии WebAssembly (или сокращённо WASM) появилась возможность создавать веб-модули, которые можно загружать с сервера и исполнять их прямо в браузере! Мы не смогли пройти мимо такой возможности, и, после долгих оптимизаций, представили свой модуль Smart Code Engine, умеющий распознавать банковские карты, баркоды, машиночитаемые зоны, номера телефонов и документы прямо в браузере.
Сегодня мы расскажем, как с помощью wasm-модуля от Smart Engines распознать номер банковской карты, просто поднеся её к веб-камере ноутбука.
Итак, наша цель – распознавать данные с банковской карты, поднесенной к камере ноутбука, с помощью wasm-модуля от SmartEngines, и заполнять готовую форму (в рамках этой статьи – просто выводить на экран). Механизм действия такой:
Выводим изображение от видеокамеры на экран.
По готовности забираем изображения и передаём на распознавание в wasm-модуль до тех пор, пока модуль не вернёт флаг терминальности (не скажет, что ему достаточно картинок для уверенного распознавания).
Забрать результат, распарсить его и вывести на экран.
Делается это очень просто: для этого нужен сам модуль распознавания, код интеграции модуля, а также механизм взаимодействия с ним. Давайте пройдёмся по порядку по всем указанным компонентам.
Технически WASM-модули представляют собой некоторый набор инструкций в промежуточном представлении, который, попав в браузер, “компилируется” в исполняемый браузерным движком код. Результат компиляции – обычный js-объект в памяти браузера со своим списком методов. Это позволяет работать с модулем обычным front-end разработчикам, без необходимости разбираться в новых технологиях или языках программирования.
Наш модуль поставляется в виде двух файлов – .js в качестве служебного файла для загрузки wasm-файла в память, компиляции в js-объект и создания подключаемого модуля, и самого .wasm файла. Собственно, благодаря js-файлу с вспомогательным кодом, компиляция и инициализация модуля занимают всего пару строк кода:
importScripts(`./bin/idengine_wasm.js`);
let wasmFilePath = {
mainScriptUrlOrBlob: `./bin/idengine_wasm.js`,
};
const SE = await SmartIDEngine(wasmFilePath);
Модуль мы будем исполнять в отдельном потоке, для этого в js есть механизм Web Workers – это позволит разделить работу модуля и взаимодействие с ним, а так же не блокировать исполнение основного потока, отвечающего за отрисовку графики.
Дальше переходим непосредственно к интеграции модуля – необходимо инициализировать инструменты распознавания, создать настройки и сессию распознавания.
// Инициализируем набор инструментов для распознавания
let engine;
try {
engine = new SE.seIdEngine(‘false’, 1);
} catch (e) {
throw ‘Create engine ‘ + SE.printExceptionMessage(e);
}
// Создаём объект с настройками для распознавания:
// с его помощью мы можем настраивать поведение библиотеки при распознавании (например, тип распознаваемого баркода)
let sessionSettings;
try {
sessionSettings = engine.CreateSessionSettings();
} catch (e) {
console.error(SE.printExceptionMessage(e));
}
// Задаём тип объекта, который будет искаться библиотекой на картинке
sessionSettings.AddEnabledDocumentTypes(“card.*”);
// Создаём сессию распознавания – объект, который выбирает и использует нужные инструменты распознавания, а также хранит все промежуточные данные
let spawnedSession;
try {
spawnedSession = engine.SpawnSession(sessionSettings, signature);
} catch (e) {
console.error(SE.printExceptionMessage(e));
}
Для передачи изображений в модуль и для возвращения результата реализуем простенький протокол на основе механизма передачи сообщений между воркерами:
Со стороны основного потока:
SEWorker.onmessage = function (msg) {
switch (msg.data.requestType) {
// Вернулся готовый результат распознавания
case ‘result’:
let result = msg.data;
printResult(result);
// Cбрасываем сессию распознавания
SEWorker.postMessage({ requestType: ‘reset’ });
break;
// нужны новые изображения для поднятия качества распознавания
case ‘FeedMeMore’:
console.log(‘Need new frame’);
SEWorker.postMessage(requestFrame());
break;
}
};
// так мы забираем изображение с canvas и отдаём на распознавание
function requestFrame() {
return {
requestType: ‘frame’,
imageData: canvas.getContext(‘2d’).getImageData(0, 0, canvas.width, canvas.height),
width: canvas.width,
height: canvas.height,
};
}
Со стороны worker’a:
onmessage = async function (msg) {
switch (msg.data.requestType) {
case ‘frame’:
const resultFrame = recognizerFrame(msg.data.imageData);
postMessage(resultFrame);
break;
case ‘reset’:
spawnedSession.Reset();
postMessage({ requestType: ‘wasmEvent’, data: { type: ‘reset’ } });
break;
// no default
}
Распознаётся изображение с помощью функции:
function recognizerFrame(canvas) {
const width = canvas.width;
const height = canvas.height;
const rawData = canvas.data.buffer;
const channels = rawData.byteLength / (height * width); // считаем количество каналов
const stride = channels >= 3 ? rawData.byteLength / height : width; // Высчитываем stride
// Создаём объект, хранящий изображение
const imgSrc = new SE.seImageFromBuffer(rawData, width, height, stride, channels);
// Распознаём изображение
const result = spawnedSession.Process(imgSrc);
if (!result.GetIsTerminal()) { // Если нужны дополнительные кадры
return {
requestType: ‘FeedMeMore’
};
}
// Тут парсим объект
const resultMessage = resultObject(result);
imgSrc.delete();
result.delete();
return resultMessage;
}
В итоге распознавания нескольких кадров нам вернётся объект с результатом, который нужно распарсить для получения текстовых строк:
function resultObject(result) {
return {
requestType: ‘result’,
docType: result.GetDocumentType(),
data: getTextFields(result)
};
}
// Парсим текстовые поля
function getTextFields(result) {
const data = {};
const tf = result.TextFieldsBegin();
for (; !tf.Equals(result.TextFieldsEnd()); tf.Advance()) {
const key = tf.GetKey();
const field = tf.GetValue();
let value = field.GetValue().GetFirstString();
data = {
name: key,
value: value,
isAccepted: field.GetBaseFieldInfo().GetIsAccepted()
};
}
return data;
}
Итак, модуль встроен, давайте посмотрим, как это работает!
При использовании всех доступных оптимизаций и картинки хорошего качества время уверенного распознавания банковской карты занимает от одной до двух секунд. Подобного качества удалось добиться потому, что мы владеем всеми исходными кодами библиотеки, это же позволяет нам добиться минимального размера модуля – сжатый стандартным для серверов шифрованием gzip, модуль весит примерно 2,5 мегабайта!
WebAssembly – удобный инструмент, позволяющий создавать легковесные и простые в интеграции продукты. WASM-модули “берут на себя” все проблемы, связанные с кроссплатформенностью и интеграцией новых продуктов за счет выполнения в браузере. Модули имеют простой и понятный каждому front-end разработчику js-интерфейс, и все современные браузеры поддерживают их исполнение. Мы верим в большое будущее этой технологии, поэтому продолжим оптимизировать наши продукты для работы в браузере, делая их ещё быстрее и компактнее!
UI-библиотеки для React / Хабр | Веб-студия Nat.od.ua
UI-библиотеки для React / Хабр
Современные фреймворки для разработки веб-приложений породили за собой разработку библиотек для них. Одним из самых популярных представителей является React.
React — библиотека JavaScript с открытым кодом для фронтенда веб-приложений. Данный фреймворк отличается компонентной моделью, которая позволяет сохранять состояние и генерировать новые элементы пользовательского интерфейса.
Мы собрали пул UI-библиотек для React-проектов.
1. Ant Design
Ant Design — продукт компании Alibaba Group, который представляет собой удобную библиотеку компонентов пользовательского интерфейса: кнопки, формы, таблицы, панели и т.д. Каждый компонент имеет множество опций и настроек, что делает их адаптивными.
Преимущества данной библиотеки:
1. Богатый выбор компонентов
Ant Design включает в себя не только базовые компоненты, но и расширенные, которые позволяют создавать сложные и красивые пользовательские интерфейсы.
2. Простота в использовании
Ant Design достаточно просто поддается освоению даже начинающим разработчикам. Это всё благодаря интуитивно понятным API и документации.
3. Приятный и стильный дизайн
Компоненты библиотеки – стильные и элегантные, которые также хорошо интегрируются с другими библиотеками дизайна.
4. Гибкость
Все компоненты библиотеки содержат множество настроек и опций, что делает их адаптивными под конкретные нужды проекта.
5. Постоянное развитие библиотеки
У Ant Design большое сообщество разработчиков, которые развивают и поддерживают библиотеку.
Как и у всего, Ant Design обладает рядом недостатков, о которых стоит сказать:
1. Объемная библиотека
Большое количество компонентов = большой объем библиотеки и возможные проблемы с производительностью. (Проблему можно решить импортом модулей).
2. Ограниченное использование
Ant Design создана только для React, работа с другими фреймворками может быть ограничена.
2. Mantine
Mantine — новая, малоизвестная библиотека с рядом преимуществ. Содержит в себе не только компоненты, но и React Hooks, работу с формами и Date pickers.
Преимущества данной библиотеки
Указание номера версии
Если версия сырая, то это указывается, чтобы пользователи могли видеть, что библиотека готова не полностью и содержит много багов.
Открытый исходный код
Универсальность
Mantine можно использовать под множество React-фреймворков.
Частота обновлений
Mantine постоянно обновляется и отличается активностью на Github.
Кастомизируемость
Компоненты библиотеки можно адаптировать под проект. Еще одним плюсом является возможность замены стилей по-умолчанию с помощью настройки глобальной темы.
3. Material UI
Material UI — библиотека компонентов и набор утилит для создания интерфейсов на Material Design System от Google. Содержит в себе компоненты пользовательского интерфейса: кнопки, текстовые поля, диалоговые окна и т.д.
Преимущества данной библиотеки:
Адаптивность
Material UI позволяет создать адаптивные интерфейсы под разные разрешения экрана.
Кроссбраузерность
Material UI поддерживает все современные браузеры, представленные на рынке.
Стилизация
Библиотека предоставляет готовые компоненты с настроенным дизайном.
Модульность
Material UI позволяет импортировать только необходимые компоненты, что снижает вес приложения.
Material UI обладает рядом недостатков, о них ниже:
Большой размер библиотеки
Material UI содержит в себе большое количество компонентов и стилей, что снижает скорость загрузки приложения.
Сложность
Material UI довольно сложная библиотека для пользования, особенно для начинающих разработчиков.
Интеграция с другими библиотеками
Данная библиотека далеко не всегда успешно интегрируется с другими React-библиотеками.
Ограничение в кастомизации
Material Design строго ограничивают кастомизацию компонентов MUI.
4. Tailwind CSS
Tailwind CSS —CSS-библиотека на основе утилиты, содержащий такие классы как flex, pt-4, text-center и rotate-90. Она упрощает стилизацию HTML, добавляя большое количество разнообразных классов.
Преимущества данной библиотеки
Адаптивность
Tailwind CSS позволяет создавать сложные адаптивные макеты под мобильные устройства.
Наличие служебных шаблонов
Tailwind CSS имеет собственные служебные шаблоны, которые избавляют от необходимости определения, организации и нейминга классов.
Легкая настройка
Библиотека имеет файл с конфигурацией по умолчанию, в котором можно настроить необходимые элементы: стили, темы, цветовые палитры и т.д.
Интеграция с PurgeCSS
Tailwind CSS позволяет оптимизировать себя с помощью PurgeCSS, который помогает уменьшить размер файла (сканирует HTML и удаляет неиспользуемые классы).
Недостатки Tailwind CSS
Практика написание кода
В работе с Tailwind CSS приходится писать стили inline.
Огромное количество классов
Как бы странно не звучало, но большое количество классов тоже плохо.
Многочисленные классы и стили увеличивают продолжительность выбора необходимых элементов. Особенно это минус для начинающих разработчиков, ведь выбор будет очень сложным.