Немного предновогодней рефлексии

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


Рисунок Рины Зенюк

Хабр

В этом году на Хабре опять были изменения, причем с точки зрения ведения блога они оказались наиболее серьезными. Если кто не помнит, шесть лет назад я решил вести блог в минимум еженедельном режиме. Так вот, научно-популярные хабы перевели в непрофильные, в каком-то смысле снова, как и в 2014, выделив гиктаймс, но теперь уже без отдельного домена. Тогда я решил ничего не менять, но сейчас пошел другим путем. Вместе с разрешением репоста изменения, на мой взгляд, выглядели как достаточно прозрачный совет «сначала попробуй опубликовать это где-нибудь в другом месте, а потом уже можешь постить у нас», соответственно, таким образом я и решил поступить. За прошедшие месяцы я писал материалы для: портала N+1, журнала «Вселенная, пространство, время», журнала Роскосмоса «Русский космос» и даже рекламировал Объединенную ракетно-космическую корпорацию в «Медузе» (интересная была задача, приятно вспомнить). Но, к сожалению, это имело и серьезный минус — длившийся уже пять с половиной лет эксперимент был завершен, и публикаций стало выходить меньше.

Также блог стал шире по охвату тем — благодаря Росатому я побывал на Атомэкспо-2019 и посетил Кольскую АЭС в рамках Нефорума Арктика. На некоторые события я не смог не отреагировать, и так были написаны обзор сериала «Чернобыль» (после него стоит почитать про проатомных экологических активистов) и исторические аналогии катастрофы «Суперджета» в Шереметьево. Живой журнал неоднократно приглашал посмотреть новые фильмы, и у кого-то могло сложиться впечатление, что я «забил» на космос. Это не совсем так, скорее нынешнее соотношение различных тем является результатом попыток впихнуть все интересное в недостаточное количество доступных часов. Я часто шучу про то, что хотел бы разорваться на пять частей, но это, в общем, не шутка, каждая грань жизни для сколько-нибудь заметного результата требует на порядок большего времени, чем выделяется на нее сейчас.

Проблемы слишком редко выходящих материалов и некоторого размывания тематики я вижу, и в 2020 году необходимо будет подумать над вариантами их решения.


Количество публикаций на Хабре по годам


Количество просмотров и рейтинги материалов по годам


И относительно предыдущего года

В дальнейшем, скорее всего, изменение режима публикации материалов сделает новые данные несравнимыми с существующими.

ЖЖ

В ЖЖ в целом заметен тот же эффект уменьшения количества просмотров из-за реже выходящих материалов.

Нельзя не похвалить редакцию Живого Журнала за активность — проводятся разнообразные мероприятия, и мне даже довелось поработать в жюри проекта «Эпоха просвещения», отбирая научно-популярные журналы. Прошедшие отбор получали гранты — показы в ЖЖ для раскрутки. Это был интересный опыт, и приятно видеть людей, которые искренне интересуются просвещением.

GTD

Остановка эксперимента показала, насколько полезны (по крайней мере для меня) действия по расписанию, даже если они в чем-то и иррациональны. Расписание и режим дисциплинируют, структурируют время и помогают стабильно двигаться вперед. С июня же я мог оказаться в ситуации «и что теперь делать?» — если нет «срочного и важного», какое из множества возможных занятий стоит выбрать именно сейчас? Также не получается ответить на вопрос «куда у меня подевался позапрошлый месяц?» — памяти на меняющиеся дела не хватает. И, закончив один эксперимент, я решил начать другой. С июня месяца я логирую в Toggl все свои активности по типам. Итоговая статистика за год выглядит примерно так:

Т.е. в усредненный день я 6,7 часа спал, 6,5 часа работал (субботы и воскресенья уменьшают среднее значение), 2,2 часа бездельничал (сюда идут компьютерные игры, чтение развлекательных книжек, тупление в интернете и т.п.), 2,1 часа занимался популяризацией, 1,7 часа ехал (здесь и дорога дом-работа и поездки в другие города по разным делам), 1,3 часа проводил время с семьей под кино и обед/ужин, 0,8 часа тратил на хозяйственные дела (магазины, починка сломанной розетки и т.п.) и так далее.

Вести лог несложно — активности объединяются по имеющим значение группам, например, я выделил отдельную «сборы на работу», потому что долго собираюсь и отвлекаюсь на всякую ерунду, расход времени на это мне бы хотелось сократить. При смене рода деятельности в смартфоне в приложении переключается таймер. Если относиться к себе честно и фиксировать, чем именно ты занимаешься, то можно получить достаточно любопытные результаты — сколько времени у тебя тратится на то, на что ты не хотел бы его тратить. Не могу сказать, что это дисциплинирует само по себе, но собирать такую статистику интересно и кажется полезным.

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

О сделанном

В целом год прошел достаточно активно — я несколько раз летал в Сочи, впервые посетил Питер, (выступил на SQADays-25 и прочитал лекцию «Байки программы Apollo»), слетал в Мурманск на Нефорум Арктика, второй раз навестил Минск и выступил на SQADays-26 (видео еще недоступно, и лекцию в планетарии также пока не обработали), посетил с лекцией Муром, читал лекции в уфимском планетарии и сделал несколько видео (реконструкция полета Гагарина в Orbiter, попытка смоделировать полет гиперзвуковой ракеты «Авангард», с Антоном Громовым провели стрим-реконструкцию посадки «Аполлона-11» к ее юбилею). Очень хочется надеяться, что следующий год будет не менее насыщенным.

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

Спасибо, что читали меня в этом году. Идеи, комментарии, вопросы, благодарности, проклятия и прочая обратная связь принимаются любым удобным вам способом — в комментариях, в личку, Вконтакте или ФБ.


ссылка на оригинал статьи https://habr.com/ru/post/482588/

Тимлид — Таков путь

В преддверии нового года хочется всем тимлидам пожелать расслабиться, отпустить все трудности на работе и готовиться ĸ празднованию с семьей, с друзьями.

Чтобы направить вас на веселую и праздничную ноту, хочу поделиться с вами примерами тернистого пути тимлида с отсылкой ĸ сериалу Мандалорец.

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


Итак, немного о рабочих буднях тимлида:

  1. Берешь на работу джуна, учишь его, лелеешь. Вкладываешь в него силы и время, а через год он расправляет крылья и уходит искать лучшее место с зарплатой в три раза выше.
    Таков путь.
  2. Полгода отĸрыта ваĸансия на разработчиĸа без результата. На собеседования приходят мидлы, просят зарплату больше твоей и говорят, что уже есть офферы на эту сумму в других ĸомпаниях.
    Таков путь.
  3. Руĸоводство решило, что стоит урезать ĸосты и повысить прибыльность. А значит, урезать и бюджет на печеньĸи в офис, ĸонференции и всё то, о чём просила твоя команда. Твои сотрудниĸи и таĸ были на грани, а сейчас считают тебя виновным в этом.
    Таков путь.
  4. К ĸонцу года разработчиĸи расслабляются, ĸто-то уехал в отпусĸ, ĸто-то ездит по магазинам и поĸупает подарĸи для друзей и семьи. А ты работаешь до последнего, таĸ ĸаĸ руĸоводство требует успеть доделать проеĸт до ĸонца года. А если не успеешь, то придётся ещё и в начале января поработать.
    Таков путь.
  5. Когда ты был простым разработчиĸом все было просто. Получаешь фиĸсированную зарплату и живёшь споĸойно. Когда ты стал лидом, то теперь большую часть твоего дохода составляют разные премии: месячная, ĸвартальная, годовая, опционы с вестингом и таĸ далее. Таĸ что получается, что большую часть своих заработанных денег ты увидишь в конце года. Или позже.
    Таков путь.
  6. Позвали на встречу с новым бизнес заĸазчиĸом. Оформил однодневную ĸомандировĸу в другой город, подготовил развернутую презентацию, продумал ĸаĸ отвечать на ĸаверзные вопросы. А на месте бизнес обсуждал свои вопросы полтора часа. Ну и в ĸонце просто представил тебя — это Коля, он тех лид и будет отвечать за ваш проеĸт.
    Таков путь.
  7. Когда разговаривал со своим сотрудником краем глаза заметил входящее сообщение из приватной группы с названием «Чат команды». Тебя в этом чате нет.
    Таков путь.
  8. Запусĸаем новый проеĸт. Ты составил списоĸ тех долга, пм сĸазал что ресурсов нет на 90% пунĸтов и вообще он их считает не ĸритичными и подумает о выделении техничесĸого спринта после второго релиза через полгода. Долго с ним спорил, но не смог переубедить. Через два месяца все посыпалось (ĸаĸ ты предупреждал), но теперь встала вся разработĸа и менять уже в два раза сложнее
    Таков путь.
  9. В субботу вечером звонят инженеры поддержки и просят помочь решить проблему на проде. А это даже не баг твоей ĸоманды, но Вася на даче, а у Ивана не доступен телефон… Отменяешь свои планы и пол ночи ищешь в чем проблема, ты же всегда готов помочь.
    Таков путь.
  10. Растишь заместителя, вĸладываешь ĸучу своего времени. Два месяца назад отправлял его на ĸрутую ĸонференцию, в прошлом месяце было перформанс ревью, на ĸотором ему повысили зарплату на 20%. В офисе гибĸий графиĸ, интересные задачи и печеньĸи. Но, сегодня он сĸазал, что хочет переехать в другую страну и ему нужно набирать опыт английсĸого. Поэтому он уходит в ĸомпанию, где все процессы на английсĸом.
    Таков путь.
  11. В компании принято поздравлять сотрудников на их день рождения и скидываться. Для всех своих сотрудников стараешься выбрать лучший подарок, часто добавляешь своих денег. Про твой день рождения все забыли и не поздравили тебя вообще.
    Таков путь.

  12. Ты решился перейти на новую релизную версию библиотеки, обновил зависимости, починил все зависимые проекты, описал на нескольких страницах как этим пользоваться, провёл внутренний митап и с чувством выполненного долга решил почитать новости. И там ты узнаешь, что сегодня вышла новая версия этой библиотеки и там всё по-новому.
    Таков путь.
  13. В прошлом месяце было много багов и регрессии, ты решил что ты больше не пойдёшь на поводу пм-а и не прогнешься, пытаешься заставить вычистить все замечания перед релизом, не мержишь пул реквест, но пм пожаловался руководству и сказали мержить что есть сейчас, так как сегодня запланирован релиз и уже закомитились по срокам перед бизнесом.
    Таков путь.
  14. На стендапе по scrum пришла твоя очередь рассказывать о том, что ты делал вчера, судорожно пытаешься вспомнить что было вчера, митинги, ответы на вопросы в слаке, собеседования, ответы на вопросы в почте, митинги, в пять вечера сбегал купил бутерброд и перекусил, срочная задача из другого проекта которую попросил решить лично руководитель и понимаешь что сказать тебе нечего.
    Таков путь.
  15. Нанимают разработчика, и он очень ценен сейчас, на проекте сейчас срочно нужны ресурсы, руководство готово идти на все его просьбы, когда начинается обсуждения уровня зп директор с рекрутером переглядываются и идут обсуждать зарплату отдельно без тебя. Вероятно, хотят предложить зарплату больше, чем у тебя.
    Таков путь.
  16. В вашей компании нет HR, поэтому приходится самому вести вакансии для своего отдела. А разработчики сейчас народ избалованный, с ними надо быть вежливым, перезванивать несколько раз, напоминать про себя раз за разом и только тогда будет результат. Но сотрудники нужны, поэтому делаешь всё это.
    Таков путь.
  17. Руководство выделило бюджет на обновление рабочих ноутбуков. Начали с самых ценных и опытных сотрудников. Им купили новенькие 6-ядерные макбуки с тачбаром и 32gb оперативки. А на тебя бюджета не хватило, ты сидишь за своим любимым, но довольно стареньким и тормозным ноутом.
    Таков путь.
  18. Для нового проекта нужны опытные и сильные разработчики, для того чтобы их побыстрее привлечь указываешь зарплатную вилку на hh. Среди команды сразу начинаются перешептывания и переглядывания. Несколько человек написали о том, что хотят поговорить о повышении, и что они видели сколько в компании получают другие сотрудники на hh (смотрят, конечно, на верхнюю планку).
    Таков путь.
  19. Во время отпуска получил сообщение от подчиненного что ему предложили оффер в другой компании. Весь отпуск приходится перестраивать, обсуждать с ним и руководством варианты его оставить. Семья или друзья, конечно, не рады таким изменениям.
    Таков путь.
  20. Раньше регулярно ходил с коллегами на обед, часто ездили на ланчи в интересные места. На обеде шутили и промывали всем косточки. После того как тебя повысили зовут всё реже, да и сам часто не успеваешь так как заказчик может написать о проблеме и тебе надо вот прямо сейчас с ним созвониться.
    Таков путь.
  21. Сходил на тимлидский митап, один лид рассказал, как они ввели автоматизированную систему анонимных 360 review, а другой что его команда выпустила в opensource систему логирования, и она уже набрала 350 звёздочек на github. Загрустил, так как в твоей компании opensource не поощряется, а свободного времени нет от слова совсем.
    Таков путь.
  22. Наняли сеньор разработчиĸа, который переписал часть системы на новый модный языĸ kotlin. Через два месяца он уволился и тебе приходится учить этот языĸ, изучать новую технологию. А фронтендеры из твоей команды давно просились использовать современные подходы. Ты решился им это разрешить. Они использовали хуки и саги. Но выяснилось, что на проде есть критичная проблема и тебе надо срочно её исправить самому, приходится разбираться что значат эти чертовы звездочки перед именем функции.
    Таков путь.
  23. Руководство решило перевести пм-а с одного твоего проекта в целях оптимизации процессов и ресурсов, теперь ты там сам общаешься напрямую с бизнесом, занимаешься планированием и контролем сроков, участвуешь во всех созвонах, но технические задачи и управление с тебя никто не снимал, приходится задерживаться по вечерам чтобы хоть что-то успевать.
    Таков путь.
  24. Сообщил руководителю что проектов стало больше и тяжело успевать выполнять все задачи, он предложил нанять еще троих. Сначала ты тратил время на собеседования, теперь тратишь на onboarding и передачу знаний, а все задачи пока всё равно делаешь сам.
    Таков путь.
  25. Твой карьерный рост был довольно быстрым, пару лет нужно было чтобы стать нормальным разработчиком, еще несколько лет чтобы стать сеньором и расти горизонтально в разных областях. Потом пару лет и ты уже тимлид. Но сейчас пропасть. Ты уже 6 лет тимлид и тебе не понятно, как и куда дальше расти. Приходишь на собеседование, проходишь все 5 этапов собеседований и тестирований. И в конце получаешь ответ «Вы нам не подходите, так как у нас только рутинные или миддл ваĸансии сейчас и вам будет сĸучно»
    Таков путь.

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

Как говорил главный герой сериала Мандалорец про себя — мандалорцы это не раса, это кредо. Так же можно сказать и про тимлидов. Никто не может заставить вас стать и быть тимлидом если вам самим этого не хочется. Это должно основываться на личных убеждениях.

За заглавный рисунок спасибо SlightlyMad


ссылка на оригинал статьи https://habr.com/ru/post/482522/

Подарок всем под Ёлочку

Или всё-таки под Елочку?

Нет, под Ёлочку! Теперь точно с Ё, потому что это статья про ёфикацию!

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

image
ссылка на сайт — пользуйтесь!

Как работает программа?

Она загружает файл с ёфицированными словами, потом деёфицирует их все.
(база взята вот отсюда) — http://python.anabar.ru/yo.htm
потом ищет каждое из этих слов в исходном тексте и если они там есть, запоминает где это слово.

Это происходит довольно быстро.

Дальше то что происходит медленно…

Программа ищет, что бы заменить — заменяет все Е где нужно на Ё (большие на большие, маленькие на маленькие).

Всё это происходит через Parallel.For в столько потоков сколько у вас есть.

Дальше программа предлагает сохранить файл куда вы хотите (на компе), или в папку Documents/Yo на телефоне. Оттуда его можно переместить куда угодго и открывать в любой читалке.

Поддерживаемые форматы:
txt, fb2, fb2.zip

Сохраняет в таком же формате, в каком и открывает. Если надо запаковать в zip то запаковывает. fb2 разбирает на элементы и не пытается просматривать binary для ускорения работы

Самое интересное то, что программа работает одинаково на всех платформах, алгоритм полностью портируемый! За это слава Microsoft, петя Core 3.1 и вася .net standard 2.1
Теперь у меня будет единая кодовая база и для мобильников, и для сервера и для десктопа. Чему я несказанно рад! Надо ещё научиться шарить картинки и ресурсы между проектами, чтобы не копировать вручную логотип и словарь.

Почему я так рад? Потому что теперь не буду читать искалеченные книги, которые скачиваю из интернета. Никаких больше ежиков, ее, еще и е-мое. Только Ё, только хардкор!

Торопился сделать всем подарок на новый год, поэтому пока есть некоторые ограничения:

  • пока нет поддержки epub
  • иногда не показывает куда сохранила (на телефоне)
  • пока работает только с кодировкой UTF-8
  • если txt то не показывает progress bar, но работает
  • пока ёфицирует только однозначно определяемые слова (все от всё не отличает, но я буду работать над этим)

Планы:

  • кроме улучшения интерфейсов и декоративных вещей, планирую сделать поддержку docx, epub, progressbar для текста, сервис по ёфикации на сайте, API для подключения сторонних сервисов, выложить в открытый доступ исходники, редактор ё-словаря (сейчас там не все ругательства, их надо обязательно добавить!), редактор исходных и ёфицированных текстов, автоматическое обучение сложным случаям, анализ контекста, вылаживание apk в PlayMarket

А теперь — Ёфицируй это!

Всех с новый годом!

P.S.… И в лесу родилась, наконец-то, Ёлочка!

(пока не качается apk, жду ответа от службы поддержки. Кому для мобильных, пока качайте архив)


ссылка на оригинал статьи https://habr.com/ru/post/482576/

Регистрация и авторизация с помощью Spring Security на примере простого приложения

Добрый день!

В этой статье будет рассмотрено создание простого веб приложения с использованием Spring Boot и Spring Security. В приложении будет реализована регистрация новых пользователей и авторизация, ограничение доступа к страницам сайта в зависимости от роли пользователя.
Главная цель статьи показать как можно ограничить доступ к различным страницам сайта для пользователей с разными ролями.

Что будет представлять из себя приложение

Сайт со следующими страницам:

  • страницы доступные всем пользователям: главная, регистрации и логина;
  • страница доступная для зарегистрированных пользователей: новости;
  • страница доступная для администратора.

Что будем использовать

  • JDK 8+;
  • Intellij Idea;
  • Spring (Spring Boot, Spring MVC, Spring Security);
  • Hibernate;
  • JSP;
  • PostgreSQL.

Содержание

  1. Описание основных используемых аннотаций.
  2. Создание нового проекта в IDE.
  3. Создание структуры проекта (пакетов).
  4. Добавление сущностей, контроллеров, сервисов, репозиториев и представлений.
  5. Запуск приложения.

1. Описание основных используемых аннотаций

Controller – специальный тип класса, применяемый в MVC приложениях. Похож на обычный сервлет HttpServlet, работающий с объектами HttpServletRequest и HttpServletResponse, но с расширенными возможностями от Spring Framework.
Repository – указывает, что класс используется для задания перечня
необходимых работ по поиску, получению и сохранению данных. Аннотация может использоваться для реализации шаблона DAO.
Service – указывает, что класс является сервисом для реализации бизнес логики.
Configuration – эта аннотация используется для классов, которые определяют bean-компоненты.
Autowired – аннотация позволяет автоматически установить значение поля. Функциональность этой аннотации заключается в том, что нам не нужно заботиться о том, как лучше всего Bean’у передать экземпляр другого Bean’a. Spring сам найдет нужный Bean и подставит его значение в свойство, которое отмечено аннотацией.

Немного информации о Spring Security

Самым фундаментальным объектом является SecurityContextHolder. В нем хранится информация о текущем контексте безопасности приложения, который включает в себя подробную информацию о пользователе (принципале), работающим с приложением. Spring Security использует объект Authentication, пользователя авторизованной сессии.

«Пользователь» – это просто Object. В большинстве случаев он может быть
приведен к классу UserDetails. UserDetails можно представить, как адаптер между БД пользователей и тем что требуется Spring Security внутри SecurityContextHolder.

Для создания UserDetails используется интерфейс UserDetailsService, с единственным методом:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException  

2. Создание нового проекта в IDE

Мы будем использовать систему сборки Maven.

Под GroupId подразумевается уникальный идентификатор компании (или ваше личное доменное имя), которая выпускает проект. ArtefactId – это просто название нашего проекта.

После завершения создания проекта отроется файл pom.xml, Idea предложит включить автоимпорт – не отказывайтесь. В этом файле будут содержаться все зависимости (библиотеки), используемые в проекте.

3. Создание структуры проекта (пакетов)

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

Теперь коротко о том, что будет храниться в каждом пакете:

  • src\main\java\com\boots\config — классы с конфигурациями для MVC (MvcConfig) и безопасности (WebSecurityConfig);
  • src\main\java\com\boots\controller — классы с контроллерами;
  • src\main\java\com\boots\entity — классы с моделями;
  • src\main\java\com\boots\repository — интерфейсы репозиториев;
  • src\main\java\com\boots\service — классы c сервисами для моделей;
  • src\main\webapp\resources — статические объекты: js, css, img;
  • src\main\webapp\WEB-INF\jsp — представления в виде файлов .jsp.

Рассмотрим файл pom.xml. В этом файле нужно указать ссылку на родительский файл с помощью тега parent, т.о. все свойства и зависимости родителя будут добавлены в этот дочерний файл.

<parent>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-parent</artifactId>     <version>2.1.9.RELEASE</version> </parent> 

Далее добавляем зависимости для работы модулей Spring, драйвер БД PostgreSQL, сервера Tomcat, JSTL.

<properties>     <java.version>1.8</java.version> </properties> 

По умолчанию maven будет использовать старую версию java 1.6, чтобы это исправить указываем версию явно.

<dependencies>     <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-web</artifactId>     </dependency>     <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-security</artifactId>     </dependency>     <dependency>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-data-jpa</artifactId>     </dependency>     <dependency>         <groupId>org.postgresql</groupId>         <artifactId>postgresql</artifactId>         <version>42.2.8</version>         <scope>runtime</scope>     </dependency>     <dependency>         <groupId>javax.servlet</groupId>         <artifactId>jstl</artifactId>         <version>1.2</version>     </dependency>     <dependency>         <groupId>org.apache.tomcat.embed</groupId>         <artifactId>tomcat-embed-jasper</artifactId>         <version>9.0.27</version>     </dependency>     <dependency>         <groupId>org.springframework.security</groupId>         <artifactId>spring-security-taglibs</artifactId>         <version>5.2.0.RELEASE</version>     </dependency> </dependencies> 

Также добавляем плагин, позволяющий упаковывать архивы jar или war и запускать их «на месте»:

<build>     <plugins>         <plugin>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-maven-plugin</artifactId>         </plugin>     </plugins> </build> 

Полный pom.xml

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>      <groupId>ark</groupId>     <artifactId>spring</artifactId>     <version>1.0-SNAPSHOT</version>      <parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>2.1.6.RELEASE</version>     </parent>      <dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-security</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-jpa</artifactId>         </dependency>         <dependency>             <groupId>org.postgresql</groupId>             <artifactId>postgresql</artifactId>             <scope>runtime</scope>         </dependency>         <dependency>             <groupId>javax.servlet</groupId>             <artifactId>jstl</artifactId>             <version>1.2</version>         </dependency>         <dependency>             <groupId>org.apache.tomcat.embed</groupId>             <artifactId>tomcat-embed-jasper</artifactId>             <version>9.0.27</version>         </dependency>         <dependency>             <groupId>org.springframework.security</groupId>             <artifactId>spring-security-taglibs</artifactId>             <version>5.2.0.RELEASE</version>         </dependency>           <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-devtools</artifactId>         </dependency>     </dependencies>      <properties>         <java.version>1.8</java.version>     </properties>      <build>         <plugins>             <plugin>                 <groupId>org.springframework.boot</groupId>                 <artifactId>spring-boot-maven-plugin</artifactId>             </plugin>         </plugins>     </build>  </project> 

Заполним файл application.properties. Первые 3 строки содержат данные для подключения к БД (имя БД – «spring», логин и пароль). Последний 2 строки указывают путь к .jsp файлам:

spring.datasource.url=jdbc:postgresql://localhost/spring spring.datasource.username=postgres spring.datasource.password=password spring.jpa.show-sql=true spring.jpa.generate-ddl=false spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true  spring.mvc.view.prefix = /WEB-INF/jsp/ spring.mvc.view.suffix = .jsp 

Свойство spring.jpa.show-sql выводит тела запросов к БД в консоль.
spring.jpa.hibernate.ddl-auto позволяет задать стратегию формирования БД на основе наших моделей, имеет разные значения (none, create, update и др.). update в данном случае значит, что таблицы БД и поля будут созданы на основе наших моделей и буду изменяться вместе с ними.

Забегая вперед можно отметить, что нам нужно будет только создать БД с именем spring, а таблицы пользователей, ролей и их связующая таблица вместе с внешними ключами будут сформированы автоматически на основе моделей (пакет entity), к созданию которых мы сейчас перейдем.

4. Добавление сущностей, контроллеров, сервисов, репозиториев и представлений

4.1. Добавление сущностей (моделей)

Обязательное требование для всех сущностей: приватные поля, геттеры и сеттеры для всех полей и пустой конструктор (в примерах не представлены). Их не нужно писать вручную, нажмите Alt+Insert и Idea сделает это за вас.

Для импорта необходимых классов и библиотек используем комбинацию клавиш Alt+Enter.

User

@Entity @Table(name = "t_user") public class User implements UserDetails {     @Id     @GeneratedValue(strategy = GenerationType.IDENTITY)     private Long id;     @Size(min=2, message = "Не меньше 5 знаков")     private String username;     @Size(min=2, message = "Не меньше 5 знаков")     private String password;     @Transient     private String passwordConfirm;     @ManyToMany(fetch = FetchType.EAGER)     private Set<Role> roles;      public User() {     }      public Long getId() {         return id;     }      public void setId(Long id) {         this.id = id;     }      @Override     public String getUsername() {         return username;     }      @Override     public boolean isAccountNonExpired() {         return true;     }      @Override     public boolean isAccountNonLocked() {         return true;     }      @Override     public boolean isCredentialsNonExpired() {         return true;     }      @Override     public boolean isEnabled() {         return true;     }      public void setUsername(String username) {         this.username = username;     }      @Override     public Collection<? extends GrantedAuthority> getAuthorities() {         return getRoles();     }      @Override     public String getPassword() {         return password;     }      public void setPassword(String password) {         this.password = password;     }      public String getPasswordConfirm() {         return passwordConfirm;     }      public void setPasswordConfirm(String passwordConfirm) {         this.passwordConfirm = passwordConfirm;     }      public Set<Role> getRoles() {         return roles;     }      public void setRoles(Set<Role> roles) {         this.roles = roles;     } } 

User. В начале об аннотациях: Entity говорит о том, что поля класса имеют отображение в БД, Table(name = «t_user») указывает с какой именно таблицей.

GenerationType.IDENTITY параметр IDENTITY значит, что генерацией id будет заниматься БД. Существует другие стратегии. SEQUENCE – использует встроенный в базы данных, такие как PostgreSQL или Oracle, механизм генерации последовательных значений (sequence). TABLE – используется отдельная таблица с проинициализированным значениями ключей. Еще один вариант – AUTO, hibernate сам выберет из одну вышеописанных стратегий, но рекомендуется указывать стратегию явно.

Поле, находящееся под аннотацией Transient, не имеет отображения в БД. Список ролей связан с пользователем отношением многие ко многим (один пользователь может иметь несколько ролей с одной стороны и у одной роли может быть несколько пользователей с другой);FetchType.EAGER – «жадная» загрузка, т.е. список ролей загружается вместе с пользователем сразу (не ждет пока к нему обратятся).

Для того, чтобы в дальнейшим использовать класс Userв Spring Security, он должен реализовывать интерфейс UserDetails. Для этого нужно переопределить все его методы. Но в нашем примере мы будем использовать только метод getAuthorities(), он возвращает список ролей пользователя. Поэтому для остальных методов измените возвращаемое значение на true.

Role

@Entity @Table(name = "t_role") public class Role implements GrantedAuthority {     @Id     private Long id;     private String name;     @Transient     @ManyToMany(mappedBy = "roles")     private Set<User> users;     public Role() {     }      public Role(Long id) {         this.id = id;     }      public Role(Long id, String name) {         this.id = id;         this.name = name;     }      public Long getId() {         return id;     }      public void setId(Long id) {         this.id = id;     }      public String getName() {         return name;     }      public void setName(String name) {         this.name = name;     }      public Set<User> getUsers() {         return users;     }      public void setUsers(Set<User> users) {         this.users = users;     }      @Override     public String getAuthority() {         return getName();     } } 

Role. Этот класс должен реализовывать интерфейс GrantedAuthority, в котором необходимо переопределить только один метод getAuthority() (возвращает имя роли). Имя роли должно соответствовать шаблону: «ROLE_ИМЯ», например, ROLE_USER. Кроме конструктора по умолчанию необходимо добавить еще пару публичных конструкторов: первый принимает только id, второй id и name.

Здесь же можно добавить ограничения на поля. Size(min=2) – значит, что минимальная длина поля 2, в случае если ограничение нарушено будет выведено сообщение.

4.2. Реализация слоя доступа к данным и сервисного слоя

Spring Data предоставляет набор готовых реализаций для создания слоя, обеспечивающего доступ к БД. Интерфейс JpaRepository предоставляет набор стандартных методов (findBy, save, deleteById и др.) для работы с БД.

UserRepository. Создаем интерфейс для пользователя в пакете repository и наследуем JpaRepository<User, Long>, указываем класс User и тип его id — Long.

public interface UserRepository extends JpaRepository<User, Long> {     User findByUsername(String username); } 

Т.о. просто создав интерфейс и унаследовав JpaRepository можно выполнять стандартные запросы к БД. Если понадобиться специфичный метод просто добавляем его в интерфейс, опираясь на подсказки Idea. Например, нам нужен метод поиска пользователя в БД по имени. Пишем тип возвращаемого объекта, а затем IDE предлагает возможные варианты. Т.е. в данном случае имя метода определяет, тело запроса.

При необходимости можно использовать аннотацию Query над методом и писать запросы на HQL или SQL (нужно добавить nativeQuery = true).

@Query(value = "SELECT nextval(pg_get_serial_sequence('t_user', 'id'))", nativeQuery = true)     Long getNextId(); 

RoleRepository. Создаем аналогично, собственные методы тут не понадобятся.

public interface RoleRepository extends JpaRepository<Role, Long> { } 

UserService. Содержит методы для бизнес-логики приложения. Этот класс реализует интерфейс UserDetailsService (необходим для Spring Security), в котором нужно переопределить один метод loadUserByUsername().

В этом классе можно увидеть еще один способ выполнения SQL запроса — с помощью EntityManager.

UserService

@Service public class UserService implements UserDetailsService {     @PersistenceContext     private EntityManager em;     @Autowired     UserRepository userRepository;     @Autowired     RoleRepository roleRepository;     @Autowired     BCryptPasswordEncoder bCryptPasswordEncoder;      @Override     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {         User user = userRepository.findByUsername(username);          if (user == null) {             throw new UsernameNotFoundException("User not found");         }          return user;     }      public User findUserById(Long userId) {         Optional<User> userFromDb = userRepository.findById(userId);         return userFromDb.orElse(new User());     }      public List<User> allUsers() {         return userRepository.findAll();     }      public boolean saveUser(User user) {         User userFromDB = userRepository.findByUsername(user.getUsername());          if (userFromDB != null) {             return false;         }          user.setRoles(Collections.singleton(new Role(1L, "ROLE_USER")));         user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));         userRepository.save(user);         return true;     }      public boolean deleteUser(Long userId) {         if (userRepository.findById(userId).isPresent()) {             userRepository.deleteById(userId);             return true;         }         return false;     }      public List<User> usergtList(Long idMin) {         return em.createQuery("SELECT u FROM User u WHERE u.id > :paramId", User.class)                 .setParameter("paramId", idMin).getResultList();     } } 

Рассмотрим метод saveUser(User user).

public boolean saveUser(User user) {         User userFromDB = userRepository.findByUsername(user.getUsername());          if (userFromDB != null) {             return false;         }          user.setRoles(Collections.singleton(new Role(1L, "ROLE_USER")));         user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));         userRepository.save(user);         return true;     } 

Сначала происходит поиск в БД по имени пользователя, если пользователь с таким именем уже существует метод заканчивает работу. Если имя пользователя не занято, добавляется роль ROLE_USER. Чтобы не хранить пароль в «сыром» виде он предварительно хэшируется с помощью bCryptPasswordEncoder. Затем новый пользователь сохраняется в БД.

4.3. Добавление контроллеров

Для страниц, которые никак не обрабатываются сервером, а просто возвращают страницу, маппинг можно настроить в конфигурации. Страница login обрабатывается Spring Security контроллером по умолчанию, поэтому для неё отдельный контроллер не требуется.

@Configuration public class MvcConfig implements WebMvcConfigurer {      @Override     public void addViewControllers(ViewControllerRegistry registry) {         registry.addViewController("/login").setViewName("login");         registry.addViewController("/news").setViewName("news");     } } 

RegistrationController. Отдельный контроллер нужен для страницы регистрации. Для обработки GET запроса используется аннотация @GetMapping("/registration"), для POST – @PostMapping("/registration").

RegistrationController

@Controller public class RegistrationController {      @Autowired     private UserService userService;      @GetMapping("/registration")     public String registration(Model model) {         model.addAttribute("userForm", new User());          return "registration";     }      @PostMapping("/registration")     public String addUser(@ModelAttribute("userForm") @Valid User userForm, BindingResult bindingResult, Model model) {          if (bindingResult.hasErrors()) {             return "registration";         }         if (!userForm.getPassword().equals(userForm.getPasswordConfirm())){             model.addAttribute("passwordError", "Пароли не совпадают");             return "registration";         }         if (!userService.saveUser(userForm)){             model.addAttribute("usernameError", "Пользователь с таким именем уже существует");             return "registration";         }          return "redirect:/";     } } 

Чтобы что-то добавить или получить со страницы мы обращаемся к model. В GET запросе на страницу добавляется новый пустой объект класса User. Это сделано для того, чтобы при POST запросе не доставать данные из формы регистрации по одному (username, password, passwordComfirm), а сразу получить заполненный объект userForm.

Метод addUser() в качестве параметров ожидает объект пользователя (userForm), который был добавлен при GET запросе. Аннотация Valid проверяет выполняются ли ограничения, установленные на поля, в данном случае длина не меньше 2 символов. Если ограничения не были выполнены, то bindingResult будет содержать ошибки.

Если пароль и его подтверждение не совпадают добавляем сообщение на страницу и возвращаем её. В конце пробуем сохранить добавить пользователя в БД.

Метод saveUser() возвращает false, если пользователь с таким именем уже существует и true, если пользователь сохранен в БД. При неудачной попытке сохранения – добавляем сообщение об ошибке и возвращаем страницу. При удачном сохранении пользователя – переходим на главную страницу.

AdminController. Доступ к странице admin имеют только пользователи с ролью администратора. В методе userList() нет ничего нового, он получает данные всех пользователей и добавляет их на страницу.

AdminController

@Controller public class AdminController {     @Autowired     private UserService userService;      @GetMapping("/admin")     public String userList(Model model) {         model.addAttribute("allUsers", userService.allUsers());         return "admin";     }      @PostMapping("/admin")     public String  deleteUser(@RequestParam(required = true, defaultValue = "" ) Long userId,                               @RequestParam(required = true, defaultValue = "" ) String action,                               Model model) {         if (action.equals("delete")){             userService.deleteUser(userId);         }         return "redirect:/admin";     }      @GetMapping("/admin/gt/{userId}")     public String  gtUser(@PathVariable("userId") Long userId, Model model) {         model.addAttribute("allUsers", userService.usergtList(userId));         return "admin";     } } 

Метод deleteUser() использует аннотацию RequestParam т.е. в представлении будет форма, которая должная передать два параметра – userId и action. Ссылка будет иметь вид http://localhost:8080/admin?userId=24&action=delete при выполнении такого запроса будет удален пользователь с id=24.

Еще один вариант передать параметры в URL – использовать PathVariable. С помощью этой аннотации мы получаем отдельные части URL, для метод getUser() URL будет выглядеть следующим образом: http://localhost:8080/admin/gt/24, после перехода выведется список всех пользователей с id>24.

Настройки безопасности

WebSecurityConfig. Содержит 2 бина BCryptPasswordEncoder и AuthenticationManager, которые, уже встречались ранее в классе userService.
Кроме этого в методе configure() настраивается доступ к различным ресурсам сайта. В качестве параметров метода antMatchers() передаем пути, для которых хотим установить ограничение. Затем указываем, пользователям с какой ролью будет доступна эта страница/страницы.

WebSecurityConfig

@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter {     @Autowired     UserService userService;      @Bean     public BCryptPasswordEncoder bCryptPasswordEncoder() {         return new BCryptPasswordEncoder();     }      @Override     protected void configure(HttpSecurity httpSecurity) throws Exception {         httpSecurity                 .csrf()                     .disable()                 .authorizeRequests()                     //Доступ только для не зарегистрированных пользователей                     .antMatchers("/registration").not().fullyAuthenticated()                     //Доступ только для пользователей с ролью Администратор                     .antMatchers("/admin/**").hasRole("ADMIN")                     .antMatchers("/news").hasRole("USER")                     //Доступ разрешен всем пользователей                     .antMatchers("/", "/resources/**").permitAll()                 //Все остальные страницы требуют аутентификации                 .anyRequest().authenticated()                 .and()                     //Настройка для входа в систему                     .formLogin()                     .loginPage("/login")                     //Перенарпавление на главную страницу после успешного входа                     .defaultSuccessUrl("/")                     .permitAll()                 .and()                     .logout()                     .permitAll()                     .logoutSuccessUrl("/");     }      @Autowired     protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {         auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder());     } } 

4.4. Добавление представлений

index.jsp Главная страница, ниже представлены 2 варианта — для гостя и для авторизованного пользователя.

index.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>  <!DOCTYPE HTML> <html> <head>   <title>Главная</title>   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>   <link rel="stylesheet" type="text/css" href="${contextPath}/resources/css/style.css"> </head> <body> <div>   <h3>${pageContext.request.userPrincipal.name}</h3>   <sec:authorize access="!isAuthenticated()">     <h4><a href="/login">Войти</a></h4>     <h4><a href="/registration">Зарегистрироваться</a></h4>   </sec:authorize>   <sec:authorize access="isAuthenticated()">     <h4><a href="/logout">Выйти</a></h4>   </sec:authorize>   <h4><a href="/news">Новости (только пользователь)</a></h4>   <h4><a href="/admin">Пользователи (только админ)</a></h4> </div> </body> </html> 

Для скрытия части контента на странице для авторизованных пользователей (ссылка на страницу регистрации и авторизации) можно использовать тег authorize из библиотеки тегов Spring Security. Параметр access принимает несколько выражений, можно, например, установить ограничение в зависимости от роли пользователя hasRole(‘ADMIN’).

registration.jsp Страница регистрации.

registration.jsp

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>  <!DOCTYPE html> <html> <head>   <meta charset="utf-8">   <title>Регистрация</title> </head>  <body> <div>   <form:form method="POST" modelAttribute="userForm">     <h2>Регистрация</h2>     <div>       <form:input type="text" path="username" placeholder="Username"                   autofocus="true"></form:input>       <form:errors path="username"></form:errors>         ${usernameError}     </div>     <div>       <form:input type="password" path="password" placeholder="Password"></form:input>     </div>     <div>       <form:input type="password" path="passwordConfirm"                   placeholder="Confirm your password"></form:input>       <form:errors path="password"></form:errors>         ${passwordError}     </div>     <button type="submit">Зарегистрироваться</button>   </form:form>   <a href="/">Главная</a> </div> </body> </html>  

На этой странице используется тег form из библиотеки тегов, с помощью него осуществляется связка атрибута модели userForm (мы добавили его на страницу при GET запросе в контроллере) и формы:

<form:form method="POST" modelAttribute="userForm"> 

Также необходимо указать путь для привязки свойств userForm:

<form:input type="text" path="username" placeholder="Username"></form:input> 

login.jsp Страница авторизации.

login.jsp

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>  <!DOCTYPE html> <html> <head>   <meta charset="utf-8">   <title>Log in with your account</title> </head>  <body> <sec:authorize access="isAuthenticated()">   <% response.sendRedirect("/"); %> </sec:authorize> <div>   <form method="POST" action="/login">     <h2>Вход в систему</h2>     <div>       <input name="username" type="text" placeholder="Username"              autofocus="true"/>       <input name="password" type="password" placeholder="Password"/>       <button type="submit">Log In</button>       <h4><a href="/registration">Зарегистрироваться</a></h4>     </div>   </form> </div>  </body> </html> 

Эту страницу, как уже говорилось, обрабатывает контроллер Spring’а по умолчанию. Важно указать действие: action="/login" и name инпутов.

admin.jsp Страница администратора.

admin.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>  <!DOCTYPE html> <html> <head>   <meta charset="utf-8">   <title>Log in with your account</title>   <link rel="stylesheet" type="text/css" href="${contextPath}/resources/css/style.css"> </head>  <body> <div>   <table>     <thead>     <th>ID</th>     <th>UserName</th>     <th>Password</th>     <th>Roles</th>     </thead>     <c:forEach items="${allUsers}" var="user">       <tr>         <td>${user.id}</td>         <td>${user.username}</td>         <td>${user.password}</td>         <td>           <c:forEach items="${user.roles}" var="role">${role.name}; </c:forEach>         </td>         <td>           <form action="${pageContext.request.contextPath}/admin" method="post">             <input type="hidden" name="userId" value="${user.id}"/>             <input type="hidden" name="action" value="delete"/>             <button type="submit">Delete</button>           </form>         </td>       </tr>     </c:forEach>   </table>   <a href="/">Главная</a> </div> </body> </html>  

news.jsp Страница новостей статическая. Используется только для демонстрации прав пользователей, поэтому содержание на ваш выбор.

news.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>  <!DOCTYPE html> <html> <head>     <meta charset="utf-8">     <title>Новости</title> </head> <body> <div>     <h2>Новости <br> Только для залогинившихся пользователей.</h2>     <a href="/">Главная</a> </div> </body> </html> 

5. Запуск приложения

В main класс Application нужно добавить следующее:

@SpringBootApplication public class Application {     public static void main(String[] args) {         SpringApplication.run(Application.class, args);     } } 

Перед тем, как переходить к следующему шагу, убедитесь в том, что структура вашего проекта соответствует представленной в начале.

Пришло время создать пустую БД с именем spring, это нужно сделать перед первым запуском приложения и только один раз.

Можете запустить приложение и посмотреть, как измениться БД – в ней создадутся 3 пустые таблицы. Нужно добавить роли пользователей в таблицу t_role:

SQL запрос добавление ролей

INSERT INTO public.t_role(id, name)   VALUES (1, 'ROLE_USER'), (2, 'ROLE_ADMIN');  

Теперь можно попробовать зарегистрироваться. В приложение не предусмотрено метода для регистрации пользователя-администратора, но он нужен для демонстрации. Поэтому после регистрации нового пользователя, добавьте в таблицу пользователь-роли запись, дающую эту роль:

SQL запрос добавление роли админа

INSERT INTO public.t_user_roles(user_id, roles_id)   VALUES (1, 2); 

Если после добавления прав администратора вы не можете зайти на страницу администратора (ошибка 403) – перезайдите на сайт.

К странице http://localhost:8080/admin доступ имеет только пользователь с ролью администратора. http://localhost:8080/news увидит любой зарегистрированный пользователь. Также можно попробовать перейти на страницы регистрации и логина, будучи авторизированным на сайте.

Заключение

В итоге было создано веб приложение, в котором мы можем управлять доступом пользователей к страницам сайта применяя роли. Можно назначать несколько ролей одному пользователю. Например, при регистрации нового пользователя добавляем базовую для всех роль User, а затем при запросе дополнительных привилегий пользователем можем дать ему допустим роль Writer, которая позволит ему добавлять новые новости на сайт.

Файлы css и js были созданы, но их содержимое не было представлено. При желании можно добавить дизайн, например, используя Bootstrap и интерактивности с помощью js.

Скачать готовый проект.

Используемые источники

  1. Registration and Login with Spring Boot, Spring Security, Spring Data JPA, Hibernate, MySQL, JSP, Bootstrap and Docker Compose
  2. Обратная сторона Spring
  3. Spring Security/Технический обзор Spring Security
  4. Официальная документация Spring
  5. Учимся готовить: Spring 3 MVC + Spring Security + Hibernate


ссылка на оригинал статьи https://habr.com/ru/post/482552/

Наш FunCode, или Как мы проводили конкурс для iOS-разработчиков

В ноябре мы запустили конкурс для разработчиков FunCode iOS Сhallenge. Теперь, когда он завершился, эмоции утихли, а победители получили свои награды, мы рассказываем, как всё проходило.

Зачем мы вообще всё это затеяли

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

Так родилась идея онлайн-челленджа в виде конкурса с призовыми местами. Формат совсем не новый, но мы в FunCorp затеяли такое впервые.

Как придумывали задание

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

Мысли крутились вокруг разработки игр. Steam, Dota, CS, VIPER, Swift, Xcode. Какой коктейль можно из этого смешать?

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

Александр Колобанов, тимлид мобильной разработки FunCorp:
«В таком челлендже есть несколько интересных моментов. Прежде всего, это разработка задания — нужно найти тему, интересную многим. Желательно, чтобы небанальным было и само приложение. И уж точно это не про объём работы. А если у решения будет ещё и практическое применение, то и писать его гораздо интереснее»
.

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

И тут нас ожидал первый сюрприз. Чат довольно быстро наполнился людьми, посыпались уточняющие вопросы. Общение переросло в бурное обсуждение деталей, ребята делились своими достижениями и даже проводили опросы, кто насколько продвинулся, что использует, сколько времени уже потратил. Мы же продолжали отвечать и с интересом наблюдали за происходящим.

Александр Колобанов, тимлид мобильной разработки FunCorp:
«Если говорить с точки зрения жюри, то мы получили возможность посмотреть, как одну задачу решает множество талантливых людей. С разными подходами к архитектуре и написанию кода. Можно, например, взять и сравнить разные архитектурные подходы, изучить интересные и зачастую необычные решения. В общем, неплохой такой поток идей. И это очень круто для личной прокачки».

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

Всего мы получили 27 выполненных заданий.

Помня о том, что у нас в запасе всего 3 дня, мы с головой окунулись в проверку.

Как мы оценивали решения

Нам нужно было оценить архитектуру и продуктовую часть. Команде пришлось даже поиграть в CS:GO и Dota 2, чтобы набить реальных данных и посмотреть, как будут работать приложения.

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

  • продуктовое решение по отображению данных и навигации;
  • техническое решение по архитектуре приложения;
  • отсутствие визуальных и логических ошибок;
  • God mode решение (дополнительные требования, дающие преимущество при их реализации).

Андрей Мухаметов, тимлид мобильной разработки:
«Мы провели код ревью, оценили используемые участниками архитектуры и фреймворки. За 3 дня у нас получилось разобрать десятки тысяч строк кода абсолютно новых проектов. Кроме того, каждый участник iOS-команды смог побывать продакт-менеджером, когда оценивал продуктовую составляющую работ на предмет удобного UI и полноты решения».

После жарких дискуссий мы всё-таки это сделали: выбрали трёх победителей.

Первое место заслуженно получил Антон Лагутин (Хабаровск) за решение по обработке и отображению матчей Dota 2 с несколькими параметрами в статистике, VIPER в качестве архитектуры модуля и хорошим разделением приложения на слои.

Вторым победителем стал Федотов Станислав (Москва), который сделал приложение с использованием данных по CS:GO и предложил хорошее решение с хранением данных в CoreData и нетворкингом на Rx.

Третий приз отдали Рычкову Андрею (Санкт-Петербург). Он сделал красивое приложение по Dota 2 с архитектурой MVVM, модулями в Pods и записью данных в CoreData.

Но было ещё 12 работ, которые нам очень понравились. Мы связались с авторами, сделали разбор их приложений и пригласили в офис. Так совпало, что в этот же день была новогодняя вечеринка FunCorp, и некоторые из ребят присоединились к нам и пообщались с командой в неформальной обстановке.

Всем участникам, не попавшим в этот список, мы отправим разборы решений и сувениры от FunCorp в ближайшее время.

Что мы получили

Конечно, нам было интересно узнать мнение участников о самом конкурсе с точки зрения организации.

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

Ещё раз благодарим всех участников за интерес и старания, а некоторых даже за 10 часов полета для личного знакомства 🙂

Одним словом, нам понравилось. И мы будем проводить челленджи ещё и ещё, так что следите за новостями в следующем году.

Cчастливого всем Нового года!


ссылка на оригинал статьи https://habr.com/ru/company/funcorp/blog/482456/