Вы находитесь на странице: 1из 63

Молдавский государственный университет

Факультет математики и информатики


Департамент Информатики

Дипломная работа
„Создание системы управление веб-сайтом”

Специальность Информатика

Выполнена студентом III курса,


специальности Информатика
Можаровским Станиславом

Руководитель, преподаватель
Департамента Информатики
Анатолий Гладей

Кишинэу – 2021
Оглавление

Оглавление 2
Введение 5
Глава I 6
История развития и текущее состояние CMS 6
Определение CMS 6
История развития CMS 7
Первые веб-сайты 7
Динамическое изменение контента 7
Системы управления контентом 7
Классификация CMS 8
Популярные системы 8
Глава II 9
Задача дипломной работы и инструменты 9
Функционал системы 9
Выбор языка программирования 10
Выбор базы данных и СУБД 14
Выбор инструментов языка 18
Используемый софт 19
Глава III 20
Проект системы 20
Структура базы данных: 21
Веб сервер 22
Структура пользовательского интерфейса 23
Области применения 24
Преимущества и недостатки проекта 25
Преимущества 25
Недостатки 25
Глава IV 26
Реализация системы 26
Основные модули сервера 26
Пользовательский интерфейс 35
Заключение 37
Литература и ссылки 38
Приложения 39
АННОТАЦИЯ

На дипломную работу

„Разработка системы управления контентом”

Ключевые слова: Система управления контентом, CMS, Веб, веб движок, графический
интерфейс, язык программирования Java, Spring, Hibernate, Thyeamleaf.

Целью работы является изучение работы систем управления контентом, их архитектуру и


способы работы, и создание собственной системы управления контентом, полностью
управление которой будет реализовано полностью с помощью пользовательского интерфейса,
без необходимости писать код. Актуальность создания эффективных систем управления
контентом не вызывает сомнения. Количество веб сайтов растет, а количество контента,
публикуемого на них, растёт еще быстрее и для администрирования такого большого числа
информации, необходима надежная, качественная и интуитивно понятная система управления
контентом.
В дипломной работе рассматриваются история появления систем управления
содержимым, их архитектура, инструменты для реализации собственной системы. В качестве
конечного продукта была написана система управления контентом на языке программировании
Java, с большим количеством передовых инструментов.
В работе представлен проект системы и сама программа системы. Программа является
кроссплатформенной и может быть запущена практически на любом компьютере.
Дипломная работа состоит из введения, 4 глав и приложения. В первой главе
рассматриваются история и классификация систем управления контентом. Во второй главе
рассмотрен инструментарий, который был выбран для реализации собственной CMS на языке
Java. Третья и четвертая главы посвящены проектированию и разработке приложения.
Введение
В современном мире количество веб сайтов растет в геометрическом прогрессе, что требует
контроля над данными хранящимися на этих сайтах.
Для решения этих задач были разработаны системы управления контентом. Меня заинтересовал
данный вопрос, и я решил узнать, как это функционирует с технической точки зрения.

В данной дипломной работе, я рассмотрел текущий рынок систем управления контентом,


выявил их сильные и слабые стороны, и попытался создать свою вариацию.
Глава I

История развития и текущее состояние CMS

Определение CMS
CMS (Content Management System) — программное обеспечение, позволяющее управлять
контентом на сайта. Создавать новые записи, редактировать существующие, добавлять
пользователей, новые разделы. Редактировать дизайн сайта, добавлять или убирать некоторый
функционал.

Назначение CMS — основной целью CMS, является упрощение работы с сайтом, для
администратора и модератора. Он полностью избавляет владельца сайта от необходимости
писать какой-либо код. Весь функционал реализован с помощью пользовательского интерфейса
и является интуитивно понятным.

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

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

Динамическое изменение контента


С развитием и сайтов и объемом информации, потребовались инструменты для управления
содержимым. На этом этапе появились технологий для динамической генерации страниц на
стороне сервера, которые позволили управлять контентом и html-кодом с сервера:

● Personal Home;
● Page (PHP);
● Active Server, Pages (ASP);
● JavaServer Pages (JSP).

В это же время появились CSS, каскадные таблицы стилей и FrontPage, инструмент от MicroSoft
для создания и управления сайтами. Изначально он присутствовал в Office 2007, и обладал
широким спектром возможностей: мог автоматически отправлять изменения, внесенные
разработчиком сайта в исходные тексты в режиме реального времени.

Системы управления контентом


В начале 2000-х годов появляются специализированные приложения для управления контентом,
первые из них были написаны на языке php, которые продолжают свое развитие по
сегодняшний день.
На сегодняшний день, большинство веб-хостингов поддерживают все самые популярные CMS.
Классификация CMS
По принципу работы CMS различают:
● C открытым кодом. Исходный код опубликован в открытых источниках и может быть
использован кем угодно.
● с закрытым кодом. Проприетарные решения, как правило платные, исходный код
которых, есть только у их разработчиков.
По типу разработки различают CMS:
● модульные. Построены на системе модулей, которые можно подключать и отключать по
необходимости. Это позволяет гибко настроить CMS под любой спектр задач.
● специализированные. Созданные специально для решения определенных задач.

Популярные системы
● WordPress
Бесплатный движок и самый популярный на сегодняшний день CMS движок. Изначально
позволял создавать только блоги, однако с учетом своей модульной архитектуры и
открытого исходного кода, был быстро дополнен всевозможными модулями, для
создания сайтов под любой спектр задач. Блоги, интернет-магазины, корпоративные
сайты, информационные порталы.
● «1С-Битрикс»
Коммерческий , платный движок. В сравнении с Wordpress проигрывает, по спектру
применения, однако выигрывает в коммерческих задачах, под которые и был создан.
Использование является платным.
● Joomla
Близкий аналог WordPress. Бесплатная, простая и легкая CMS.
Глава II

Задача дипломной работы и инструменты


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

Функционал системы
Система должна обладать следующими функционалом:
● Регистрации и авторизации пользователей
● Возможность динамически создавать контент
● Редактировать существующий контент
● Управление персональными данными пользователей
● Пользователи имеют возможность комментировать контент
● Редактировать основные детали страниц сайта
● Система ролей доступа к содержимому
● Панель управления администратора
● Администратор имеет полный контроль над пользователями сайта
● Администратор имеет возможность редактировать любые посты пользователей.
● Система пользовательских компонентов
● Система визуального создания страниц
● API - инструментарий для взаимодействия с внешними программами
Выбор языка программирования
Для реализации данного функционала я изучил существующие CMS на сегодняшний день и
выявил для себя их основные достоинства и недостатки.
Основной недостаток большинства современных CMS - они медленные.

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


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

Интерпретируемый язык программирования — язык программирования, исходный код


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

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


случае такой язык можно реализовать только при помощи интерпретаторов.

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

Большое количество языков, включая BASIC, C, Lisp, Pascal и Python, имеют обе реализации. В
Java используется JIT-компиляция для генерации машинного кода, хотя изначально он
переводится в интерпретируемую форму. Языки Microsoft .NET Framework компилируются в
Common Intermediate Language (CIL), который во время выполнения компилируется в нативный
код. Большинство реализаций Lisp позволяют смешивать оба вида кода.

К интерпретируемым языкам можно отнести большинство скриптовых языков.

Компилируемый язык программирования — язык программирования, исходный код


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

Классификация языков программирования на компилируемые и интерпретируемые является


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

Кроме того, существуют реализации языков, которые компилируют исходный текст программы
в байт-код, который затем либо интерпретируется, либо выполняется JIT-компилятором (или
виртуальной машиной).

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


типичных различий:

● скорость выполнения программы, скомпилированной в машинный код, превосходит


скорость интерпретируемой программы, как правило, в десятки и сотни раз;
● в случае использования компилятора, при внесении изменений в исходный код
программы, прежде чем эти изменения можно будет увидеть в работе программы,
необходимо выполнить компиляцию исходного текста.
Большинство современных CMS написаны на языке программирования PHP

PHP - (Personal Home Page) скриптовый язык общего назначения, интенсивно применяемый для
разработки веб-приложений. В настоящее время поддерживается подавляющим большинством
хостинг-провайдеров и является одним из лидеров среди языков, применяющихся для создания
динамических веб-сайтов.

Исходя из этого, я решил остановить свой выбор на языке программирования Java, так как он
обладает JIT компиляцией.

JIT-компиляция (Just-in-time compilation) — технология позволяющая компилировать


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

Так как JIT-компиляция является, по сути, одной из форм динамической компиляции, она
позволяет применять такие технологии, как адаптивная оптимизация и динамическая
рекомпиляция. Благодаря этому JIT-компиляция может показывать лучшие результаты в плане
производительности, чем статическая компиляция. Интерпретация и JIT-компиляция особенно
хорошо подходят для динамических языков программирования, при этом среда исполнения
справляется с поздним связыванием типов и гарантирует безопасность исполнения.
JIT компиляция работает быстрее интерпретированных языков, но синтаксис и инструменты
Java является проще в освоение чем, полностью компилированный C или C++.
Выбор базы данных и СУБД
Следующим шагом моей работы стал выбор базы данных и СУБД.
База данных - это хранение информации в упорядоченном виде, по заранее известным
стандартам.

Выделяются следующие виды баз данных по структуре:

● иерархические;
● сетевые;
● реляционные;

Иерархическая база данных

Иерархическая база данных - хранение данных и их структурирование представлено в виде


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

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

Сетевая база данных

Сетевая база данных - модифицированная иерархическая база данных, основным отличием


является то, что дочерний элемент может иметь множество предков, в нарушении иерархии.

Реляционная база данных


Реляционная база данных - база данных, данные в которой хранятся в связных между собой
таблицах. Таблицы представлены в виде строк и столбцов, которые хранят данные
определенных форматов.

Таблица является способом хранения данных и способна реагировать на любые обращения со


стороны СУБД. Основным требование к такому типу баз данных, это их правильное
проектирование.

Основные факторы, которые следует учесть при проектировании базы данных:

1. База данных должна быть компактной и не хранить избыточные данные;


2. База данных должна находится, как минимум в третьей нормальной форме.

Третья нормальная форма - представляет собой, что каждый столбец таблицы, зависит только
от столбца, который является ключом.
СУБД

Система управления базами данных - встраиваемый модуль или полноценная программа,


основной задачей которой является выполнение операций с базой данных.

Существует две модели СУБД - реляционная и бессистемная. Без Схемные СУБД основанные
на принципах не структурированного подхода, что позволяет программисту, не заботиться о
правильном проектировании реляционной базы данных, однако минусами такой системы
является низкая производительность и трудное расширяемость базы данных в будущем.
Одним из примеров не структурированных баз данных является NoSQL.

К реляционным базам данных относятся:

● SQLite;
● MySQL;
● PostgreSQL.

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

Сравнение SQL и NoSQL

1. SQL- строгом представлении данных, NoSQL- свободное представление.


2. SQL-системы стандартизированы, запросы с использованием языка SQL. NoSQL- язык
специфичен для каждой реализации.
3. Масштабируемость. Обе СУБД способны обеспечить вертикальное масштабирование.
При этом NoSQL, позволяет применять простые методы горизонтального
масштабирования.
4. SQL является надежнее, чем NoSQL.
В связи с этим, для проекта дипломной работы была выбрана система управления базами
данных SQL, в частности MySQL, надежность и скорость выполнения которой была проверена
временем.
Выбор инструментов языка
Для выбранного мною языка программирования Java, существует огромное множество
framewor’ов для разработки любых программных решений.

Фре́ймворк — программная платформа, определяющая структуру программной системы;


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

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


● Spring framework
● Hibernate
● Thymeleaf
● Bootstrap
● JQuery

Spring Framework — универсальный Фреймворк с открытым исходным кодом для Java-


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

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

Hibernate — библиотека для языка программирования Java, основной задачей которой является
представление Java объектов в виде объектов реляционной базы данных. Является самой
популярной реализации JPA.
Данный подход позволяет сократить число низкоуровневых операции работы с базой данных,
что увеличивает скорость разработки и уменьшает число ошибок.

Thymeleaf — серверный механизм для создания Java шаблонах, для создания веб страниц.
Способен обрабатывать HTML, XML, JavaScript, CSS на стороне Java приложения.
Thymeleaf разработан с учетом стандартов Web, и технологии HTML5, что позволяет создавать
полностью соответствующие стандарту шаблоны.

Bootstrap - свободный набор инструментов для создания веб сайтов. Имеет готовые и
стилизованные HTML и CSS шаблоны для оформления страниц, кнопок, веб-форм, меню,
блоков навигации. Также имеет встроенные JavaScript расширения.

JQuery - набор функций JavaScript, оптимизированный для кроссбраузерной работы. Упрощает


работу со стандартными JavaScript функциями, что позволяет быстрее и проще работать с веб
элементами.

Используемый софт
Для сборки проекта была выбрана система сборки проектов Apache Maven.

Apache Maven — система для автоматизации сборки Java проектов. Имеет строгую структуру,
основанную на XML. Позволяет разработчикам легко подключать новые библиотеки и собирать
проект в jar или war пакет.

Git - Система управления версиями. Позволяет хранить все версии файлов на определенном
моменте их жизненного цикла. Позволяет легко отслеживать изменения файлов и при
необходимости, возвращаться к предыдущим версиям.
Имеет систему веток, что позволяет развивать проект независимо от других версии, что
востребовано в корпоративных решениях.
Глава III

Проект системы
Для необходимой структуры проект был разделен на три части:
● База данных - в которой хранятся пользователи, роли, посты, комментарии и т.д.
● Веб сервер - ядро проекта, отвечающее за обработку действий пользователей сайта,
редактирования контента, переход по ссылкам, отрисовку страниц, взаимодействие с
базой данных.
● Пользовательский интерфейс - все страницы сайта, компоненты страницы, стили,
скрипты и библиотеки JavaScript.
● REST API - инструментарий для доступа внешних программ.

В качестве серверной архитектуры была выбрана гибридная структура.


Сервер способен отдавать как уже готовые страницы в формате HTML, так и может работать в
режиме REST(рис.1) API сервера.

REST (от англ. Representational State Transfer — “передача состояния представления”) —


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

В данном проекте архитектура REST была реализована, для предоставления API, для сторонних
программ. Что позволило бы расширять функционала основной системы.
Структура базы данных:
База данных состоит из следующих таблиц:
● User - таблица хранящая все данные о пользователях. Их данные для аутентификации,
персональные данные и ссылки на другие таблицы
● Post - таблица хранящая все посты всех пользователей сайта, дату и время публикации.
● Roles - таблица ролей пользователей, разграничивающая из полномочия.
● Post_comments - таблица хранящая комментарии для каждого поста
● Api_data - таблица для дополнительной информации.

Схема базы данных


Веб сервер
Основная логическая единица CMS, имеет следующие папки :
● Config - классы конфигурации безопасности и настройки ролей пользователей
● Controller - хранит классы отвечающие за обработку данных. Переход по ссылкам,
обработка GET и POST запросов, возвращение соответствующих страниц.
● Model - хранит модели(представления) основных единиц проекта, таких как
пользователь, пост и т.д
● Service - хранит классы для обработки прикладной логики.
● Repository - хранит классы для работы с базой данных. Извлечение, удаление,
модификация данных в базе данных.
● Utils - вспомогательные классы. Константы, врапперы и т.д.

Схема проекта
Структура пользовательского интерфейса
● Common - вспомогательный файлы
● Js - библиотеки и файлы скриптов Javascript.
● View - содержит все страницы и компоненты страницы
● adminPanel - содержит страницы и компоненты панели управления сайта
● Site - содержит страницы и компоненты основного сайта
Области применения
Данная система управления контентом построена, на модульном (компонентном) подходе, что
позволяет ее легко расширять для любых нужд.
Данная система управления контентом изначально нацелена, на использования для создания и
управления блогом.
Однако есть возможность создавать пользовательские компоненты, что позволит
переквалифицировать систему под интернет магазин, новостной портал, сайт визитку.
Также есть возможность взаимодействовать с системой посредством REST API, что позволяет
как добавлять в систему новый функционал, так и интегрировать ее в другое приложение.
Список ролей позволяет разграничивать полномочия пользователей, благодаря этому можно
построить иерархию пользователей и разграничить их возможности.
Преимущества и недостатки проекта

Преимущества

● Скорость работы - из-за выбранного языка программирования.


● Надежность - используемые инструменты безопасности, гарантируют безопасность и
бесперебойную работу приложения. А строгая типизация сокращает спектр возможных
ошибок программиста
● Модульность - проект поддерживает легкое внедрение новой логики, без необходимости
делать изменения в основных компонентах системы
● Расширяемость - наличие REST API , позволяет взаимодействовать с функционалом
системы и изменять ее.

Недостатки

● Дороговизна хостинга - не все хостинг провайдеры предоставляют услуги размещения


Java на виртуальных серверах
● Увеличенное потребление ресурсов - java требует большее количество ресурсов для
минимальной работы.
Глава IV

Реализация системы

Основные модули сервера

Папка Controller :
AdminPanelController - класс отвечающий за управление контентом сайта со страницы
AdminPanel.Отвечает за навигацию по панели администратора, и все основные функции.Это
основная страница управления контентом сайта, доступная только администратору.
Его основные функции это роутинг по панели администратора и получения данных для каждой
из страниц панели.

@GetMapping("")
public String adminPage(Principal principal, Model model) {
if (!isAdmin(principal)) {
return "redirect: /403";
}
model.addAttribute("links",
menuItemManger.getMenuItemDtoList(Constants.MENU_TYPE.ADMIN));
model.addAttribute("title", "Latte Dashboard");
model.addAttribute("posts",postManager.getAll());
return "/view/adminPanel/main";
}

@GetMapping("/pageList")
public String getPages(Model model, Principal principal) {
if (!isAdmin(principal)) {
return "redirect: /403";
}
File componentsDir = FileUtil.readFile(Constants.SITE_VIEW_PATH + "/elements");
File templatesDir = FileUtil.readFile(Constants.SITE_VIEW_PATH + "/templates");
List<String> components = new LinkedList<>();
List<String> templates = new LinkedList<>();
if (componentsDir.isDirectory()) {
Arrays.stream(componentsDir.listFiles())
.forEach(f -> components.add(f.getName()));
}
if (templatesDir.isDirectory()) {
Arrays.stream(templatesDir.listFiles())
.forEach(f -> templates.add(f.getName()));
}
model.addAttribute("components", components);
model.addAttribute("templates", templates);
return "/view/adminPanel/templates/pageList";
}

ApiController - контроллер используемый для предоставления инструментов для взаимодействия


с внешними приложениями.
@RequestMapping("{pageName}")
public String linkResolver(Model model,
@PathVariable("pageName") String pageName){
model.addAttribute("userManager",userManager);
model.addAttribute("postManager",postManager);
model.addAttribute("roleManager",roleManager);
model.addAttribute("menuItemManager",menuItemManger);
model.addAttribute("categoryManager",categoryManager);
return "/view/site/components/"+pageName;
}
CategoryController - контроллер отвечающий за добавление,удаления и вывода списка
доступных категорий.

● DispatcherController - контроллер отвечающий за переадресацию, по специфическим


страницами
@GetMapping
public String feed(){return "redirect:/feed";}
@GetMapping("/about")
public String about(){return "/view/site/templates/about";}

● PageConstructorController - контроллер, отвечающий за создания страниц с помощью


конструктора страниц.
@PostMapping("/save")
public String savePage(@RequestParam Map<String,String> params) {
JSONObject jsonObject = new JSONObject((String) params.keySet().toArray()[0]);
String title = jsonObject.getString("title");
Map<String, List<String>> data =new HashMap<>();
data.put("page_body",jsonArrayToList(jsonObject.getJSONArray("page_body")));
data.put("side_bar",jsonArrayToList(jsonObject.getJSONArray("side_bar")));
data.put("page_footer",jsonArrayToList(jsonObject.getJSONArray("page_footer")));
data.put("page_header",jsonArrayToList(jsonObject.getJSONArray("page_header")));
PageCreator pageCreator = new PageCreator(title,data);
pageCreator.createPage();
return "/view/adminPanel/main";
}

● PostCommentsController - контроллер отвечающий, за добавление комментариев


пользователей к определенным постам.
public void addComment(Principal principal,
@PathVariable Long id,
@RequestParam String text){
User user = userManager.getUserByUsername(principal.getName());
PostComment postComment = new PostComment();
postComment.setCommentPost(postManager.getPostById(id));
postComment.setText(text);
postComment.setCreateUser(user);
postComment.setUpdateUser(user);
postComment.setDate(new Date());
postCommentsManager.addComment(postComment);
}

● PostController - контроллер отвечающий за добавление, удаления и редактирования


постов сайта. А также вывода списка постов.
@RequestMapping(value = {"/save", "/save/{id}"}, method = RequestMethod.POST)
public String savePost(Principal principal,
@RequestParam("content") String content,
@RequestParam("title") String title,
@RequestParam("category_id") Long categoryId,
@PathVariable(required = false) Long id) {
Post post = null;
if (id != null) {
post = postManager.getPostById(id);
} else {
post = new Post();
}
post.setContent(content);
post.setTitle(title);
User user=userManager.getUserByUsername(principal.getName());
post.setCreateUser(user);
post.setUpdateUser(user);
post.setCategory(categoryManager.getCategoryById(categoryId));
post.setDate(new Date());
postManager.save(post);
return "redirect:/feed";}

● RegistrationController - контроллер отвечающий за регистрацию и авторизацию


пользователей сайта.
@PostMapping("/registration")
public String addUser(@ModelAttribute("userForm") User userForm, BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
return "/view/site/templates/registration";}
if (!userManager.saveUser(userForm)){
model.addAttribute("usernameError", "Пользователь с таким именем уже существует");
return "/view/site/templates/registration";}
return "redirect:/feed";}

● UserProfileController- контроллер отвечающий за вывод информации об определенном


пользователи.
public String userProfile(Principal principal, Model model){ User
user=userManager.getUserByUsername(principal.getName());
model.addAttribute("user",user);
model.addAttribute("currUser",true);
model.addAttribute("userPosts",postManager.getUserPosts(user.getId()));
return "/view/site/elements/userCard";}

● UtilsController - контроллер отвечающий за дополнительный функционал, такой как


возвращение списка ссылок, которые должны быть рутированы с помощью router.js.
@GetMapping("/ajaxLinks")
public String ajaxLinks(){
List<String> links = menuItemManger.getMenuLinks(Constants.MENU_TYPE.ADMIN);
links.addAll(menuItemManger.getMenuLinks(Constants.MENU_TYPE.SITE));
links.remove("/feed");
links.remove("/feed/add");
links.remove("/adminPanel");
links.remove("/adminPanel/pageConstructor");
links.remove("/save");
links.remove("#");
String json = new JSONArray(links).toString();
return json;
}

Папка Model:
Хранит в себе все модели POJO, классы, для работы приложения.

● BaseObject - абстрактный класс , являющийся базовый объект от которого наследуются


другие модели. Он содержит в себе базовые данные, для хранения в базе данных, такие
как id, дату создания, пользователя создавшего и изменившего данный объект.
● Post - представляет собой модель для представления информации поста на странице
сайта. Имеет такие поля как, заголовок, контент, список комментариев и категория.
● PostComment - модель хранящая в себе комментарий и связанный с ним пост.
● Role - модель отвечающая за роли пользователей.
● User - модель пользователя сайта. Хранит в себе креденциалы пользователя, личные
данные и список ролей доступных пользователю

Папка Repository :
Хранит в себе классы, которые предоставляют методы для работы с базой данных.Такие
методы, как : получения объектов, по id, имени, удаление, обновление и создание нового
объекта.
● CategoryCrudRepository - клас, для извлечения объектов модели Category.
● PostCommentsRepository - класс для извлечения комментариев. Из нестандартных
методов, имеет поиск всех комментариев по id поста.
● PostCrudRepository - класс для извлечения постов. Из нестандартных методов, имеет
метод для получения постов по id пользователя и поиск постов по категории.
● RoleCrudRepository - класс для извлечения ролей.
● UserCrudRepository - класс для извлечения пользователей.

Папка Service:
Хранит в себе классы, отвечающие за логику выполнения операций с базой данных и являются
связующим звеном контроллеров и репозитория.
Каждый класс имеет интерфейс и одну реализацию, которая находится в каталоге impl, таковы
требования фреймворка Spring.
● CategoryManager - позволяет работать с категориями. Имеет методы для получения
списка категорий, получения категории по имени или id. Методы для добавления и
удаления категории по id.
public void addCategory(String name, Long roleId) {
try {
Role role = roleCrudRepository.findById(roleId).get();
Category category = new Category();
category.setName(name);
if (role != null) {
category.setRole(role);
repository.save(category);}
} catch (Exception e) {
System.err.println("Cant add category!");
e.printStackTrace();}}
public void deleteCategory(Long categoryId) {
if (repository.findById(categoryId).isPresent()) {
repository.delete(repository.findById(categoryId).get());
}else{
System.err.println("No category with such ID !");}
}
● MenuItemManager - считывает список ссылок и оборачивает их в DTO, для передачи в
Router.js
public List<MenuItemDto> getMenuItemDtoList(int menuType) {
switch (menuType) {
case Constants.MENU_TYPE.SITE:
return getSiteMenuLinkList();
case Constants.MENU_TYPE.ADMIN:
return getAdminMenuLinkList();
default:
System.err.println("MenuItemManger(2) -> No such option ");
return null;}}

● PostCommentsManager - реализует функционал получения всех комментариев связанных


с постом и функционал для добавления новых комментариев
● PostManager - отвечает за все операции с постами сайта. Обладает функционалом
получения всех постов, получения определенного поста по его id, получения новых
постов, сохранение нового поста, удаления поста, получения постов по категориям и
удаление списка постов.
● RoleManager - позволяет получить список всех ролей.
● UserManager - отвечает за все операции с пользователями. Получение списка всех
пользователей, получение определенного пользователя по id, получение пользователя по
username, сохранение пользователя, получения списка пользователей по ролям.
public boolean saveUser(User user) {
User userFromDb = userCrudRepository.findByUsername(user.getUsername());
if (userFromDb != null) {return false;}
Role defaultRole = roleCrudRepository.findById(1L).get();
user.setRole(Collections.singleton(defaultRole));
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
user.setFirstName("anonymous");
user.setLastName("anonymous");
userCrudRepository.save(user);
return true;}

● PageCreator - специальный класс, отвечающий за создания новых страниц сайта, на


основе компонентов, собранных в конструкторе страниц.
private String importComponents(List<String> componentNames) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL url = loader.getResource("static/view/site/components");
String path = url.getPath();
File dir = new File(path);
String importStrTemplate = "<th:block th:include='view/site/elements/%s :: %s'></th:block>\n";
StringBuilder importElement = new StringBuilder();
if (dir.isDirectory()) {
for (File file : dir.listFiles()){
if(componentNames.contains(file.getName())){
String compName = StringUtils.splitCamelCase(file.getName().split("\\.")[0],"-");
String res = String.format(importStrTemplate,file.getName(),compName);
importElement.append(res);
}
}
}
return importElement.toString()

Папка Utils:
● Constants - хранит все константы проекта.
● FileUtil - хранит в себе статические методы для удобной работы с файлами.
● StringUtils - хранит в себе методы для удобной работы со строками.

Папка Common:
Menu.json - файл хранящий в себе все ссылки сайта, и их названия.
AdminMenu.json - файл храня в себе все ссылки панели администратора , и их названия
Пользовательский интерфейс
Модуль js - содержит все скрипты используемые на сайте.

Папка Core:
● Router.js - скрипт перехватывающий все переходы по ссылкам и подгружающий из в
виде AJAX запросов.
function ajaxLoad(link) {
fetch(link)
.then(response => response.text())
.then(data => {
console.log(data);
var doc = parser.parseFromString(data, 'text/html');
console.log(doc);
document.getElementById('content').innerHTML = doc.body.innerHTML;
});
}

● App.js - содержит набор функций используемых на всех страницах сайта. Функции для
ajaxLogin, loadComments, addComment, ajaxSubmit.

Папка View:
Данный каталог имеет два подкаталога : aminPanel и site. Каждый из них имеет одинаковую
структуру, но хранят в себе данные для панели администратора и для основного сайта.
Пример main.html :

● Templates - шаблоны страниц, состоящие из компонентов и элементов


● Elements - базовые единицы страницы, не могут быть использованы самостоятельно,
только в связки с каким-либо компонентом.
● Components - компоненты , которые являются частями шаблонов страниц. Они могут
быть стандартными или сделанные и загруженные пользователям. С помощью данных
компонентов можно строить странице в конструкторе.
Компонентный подход позволяет легко расширять и дополнять сайт сторонними компонентами.
Для создания пользовательского компонента, пользователь должен следовать заранее
установленным правилам и иметь знания HTML и языка шаблонизатора Thymleaf.

Правила создания компонента


1. Каждый компонент должен подключать библиотеку thymeleaf в тег html.
2. Каждый компонент должен иметь секцию head, в которой должен находится тег title и
опционально div с id ‘_description’, с описанием компонента. Это будет являться именем
компонента, оно будет отображаться на странице конструктора.
3. Содержимое каждого компонента, все содержимое тега body, должно быть обернуто в
тег th:fragment и иметь аналогичное имя имени файла компонента.
4. Компонент должен строго следовать синтаксису thymeleaf, если он его использует.

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

После того как компонент был создан, он должен быть загружен на сайт, на странице Page
editor.
После этого компонент будет доступен в конструкторе страниц.
Заключение
В результате выполнения данной дипломной работы, я изучил техническое устройство и
архитектуру систем управления контентом, что позволило мне создать свою систему
управления контентом.
Мною были изучены технологии обработки страниц, динамической от рисовки страниц, основы
роутинга по страницам и ORM модель базы данных.
Я попытался избежать в ней недостатков, которые допустили авторы других операционных
система, и создать свою, максимально расширяемую систему управления контентом.
Литература и ссылки
https://club.cnews.ru/blogs/entry/istoriya_razvitiya_cms_evolyutsiya_ili_revolyutsiya_
https://ru.wikipedia.org/wiki/
https://dic.academic.ru/dic.nsf/ruwiki/522058
https://blog.ingate.ru/seo-wikipedia/cms/
https://webshake.ru/post/bazy-dannyh
https://opensource.com/article/20/7/history-content-management-system
https://docs.oracle.com/en/java/
https://spring.io/projects/spring-boot
https://www.thymeleaf.org/
https://jquery.com/
https://hibernate.org/
Приложения
Примеры пользовательского интерфейса :

Форма логина

Форма регистрации
Основная страница сайта
Страница профиля
Страница редактирования проста:

Список постов на панели администратора:


Страница редактирования пользователей:

Страница категорий:
Форма добавления категорий

Страница конструктора страниц

Код всех JavaScript модулей:

Router.js
let mainDivId;
let ajaxLinks = [];

$(document).ready(()=>{
fetch("/utils/ajaxLinks").then(response => response.json()).then(data => {
data.forEach(link => ajaxLinks.push(link));
console.log(ajaxLinks);
});
setListener();
})
//Link listener
function setListener() {
$('a').on('click',function (e){
e.preventDefault();
const origin = e.target.closest("a");
let link = origin.getAttribute('href');
let flag = false;
ajaxLinks.forEach(ajaxLink => {
if (link.includes(ajaxLink)) {
ajaxLoad(link);
flag = true;
}
})
if (!flag) {
window.location.href = link;
}
});

let parser = new DOMParser();

function ajaxLoad(link) {
fetch(link)
.then(response => response.text())
.then(data => {
console.log(data);
var doc = parser.parseFromString(data, 'text/html');
document.getElementById('content').innerHTML = doc.body.innerHTML;
});
}

app.js
let devMode = true;

function ajaxLogin(data) {
$.ajax({
type: "POST",
url: "/login",
data: data,
success: function (data) {
// console.log(data);
},
error: function (err) {
console.log(err);
alert("Err");
}
});
}

function loadComments(postId, toggle = true) {


$.get("/comments/" + postId, function (data) {
let post = $('#postComments_' + postId);
$(post).html(data);
if (toggle) {
$(post).collapse('toggle');
setListener();
}
});
}

function addComment(commForm) {
$.ajax({
type: "GET",
url: $(commForm).attr('action'),
data: $(commForm).serialize(),
success: function (data) {
console.log();
let updatePost = $(commForm).attr('action').split("/")[3];
loadComments(updatePost, false);
setListener();
},
error: function (err) {
console.log(err);
}
})
}

function ajaxSubmit(form, callBack) {


$.ajax({
type: $(form).attr("method"),
url: $(form).attr("action"),
data: $(form).serialize(),
success: function (data) {
if (callBack) {
callBack(data);
}
},
error: function (err) {
if (devMode) {
console.log(err);
}
}
})
}

Код основных модулей Java:

AdminPanelController
package com.latte.demolatte.controller;

import com.latte.demolatte.model.Role;
import com.latte.demolatte.model.User;
import com.latte.demolatte.service.MenuItemManger;
import com.latte.demolatte.service.PostManager;
import com.latte.demolatte.service.UserManager;
import com.latte.demolatte.utils.Constants;
import com.latte.demolatte.utils.FileUtil;
import com.latte.demolatte.utils.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.io.File;
import java.io.FileWriter;
import java.security.Principal;
import java.util.*;

@Controller
@RequestMapping("adminPanel")
public class AdminPanelController {

private MenuItemManger menuItemManger;


private UserManager userManager;
private PostManager postManager;

public AdminPanelController(MenuItemManger menuItemManger, UserManager


userManager, PostManager postManager) {
this.menuItemManger = menuItemManger;
this.userManager = userManager;
this.postManager = postManager;
}

@GetMapping("")
public String adminPage(Principal principal, Model model) {
if (!isAdmin(principal)) {
return "redirect: /403";
}
model.addAttribute("links",
menuItemManger.getMenuItemDtoList(Constants.MENU_TYPE.ADMIN));
model.addAttribute("title", "Latte Dashboard");
model.addAttribute("posts",postManager.getAll());
return "/view/adminPanel/main";
}

@GetMapping("/pageList")
public String getPages(Model model, Principal principal) {
if (!isAdmin(principal)) {
return "redirect: /403";
}
File componentsDir = FileUtil.readFile(Constants.SITE_VIEW_PATH +
"/elements");
File templatesDir = FileUtil.readFile(Constants.SITE_VIEW_PATH +
"/templates");
List<String> components = new LinkedList<>();
List<String> templates = new LinkedList<>();
if (componentsDir.isDirectory()) {
Arrays.stream(componentsDir.listFiles())
.forEach(f -> components.add(f.getName()));
}
if (templatesDir.isDirectory()) {
Arrays.stream(templatesDir.listFiles())
.forEach(f -> templates.add(f.getName()));
}
model.addAttribute("components", components);
model.addAttribute("templates", templates);
return "/view/adminPanel/templates/pageList";
}

@GetMapping("/editComponent/{type}/{name}")
public String getComponent(Model model,
Principal principal,
@PathVariable String type,
@PathVariable String name) {
if (!isAdmin(principal)) {
return "redirect: /403";
}
try {
String fileContent = FileUtil.readFileByTypeAndName(type, name);
if (fileContent == null) {
throw new Exception("Can't read file");
}
model.addAttribute("code", type.equalsIgnoreCase("component"));
model.addAttribute("editPostContent", fileContent);
model.addAttribute("saveAction", "/adminPanel/editComponent/" + type +
"/" + name);
return "/view/adminPanel/templates/codeEditor";
} catch (Exception e) {
e.printStackTrace();
}
return "redirect: /feed";
}

@PostMapping("/editComponent/{type}/{name}")
public String saveComponent(@PathVariable String type,
Principal principal,
@PathVariable String name,
@RequestParam("content") String content) {
if (!isAdmin(principal)) {
return "redirect: /403";
}
try {

FileWriter writer = new FileWriter(FileUtil.getFileByTypeAndName(type,


name));
writer.write(content);
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
return "redirect:/adminPanel";
}

@GetMapping("/userList")
public String userList(Model model) {
model.addAttribute("columns", User.columnsNames());
model.addAttribute("userList", userManager.getAll());
return "/view/adminPanel/components/userList";
}

@PostMapping("/update/user/")
@ResponseStatus(value = HttpStatus.OK)
public void updateUser(Principal principal,
@RequestParam Map<String, String> editedUser) {
if (!isAdmin(principal)) {
return;
}
if (editedUser.get("action").equals("delete")) {
userManager.deleteUser(editedUser.get("username"));
return;
}
User user = userManager.getUserByUsername(editedUser.get("username"));
user.setUsername(editedUser.get("username"));
user.setFirstName(editedUser.get("firstName"));
user.setLastName(editedUser.get("lastName"));
userManager.updateUser(user);
}

private boolean isAdmin(Principal principal) {


User user = userManager.getUserByUsername(principal.getName());
for (Role role : user.getRoles()) {
if (role.getName().equalsIgnoreCase("ADMIN")) {
return true;
}
}
return false;
}
}

ApiController
package com.latte.demolatte.controller;

import com.latte.demolatte.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/api")
public class ApiController {
private CategoryManager categoryManager;
private MenuItemManger menuItemManger;
private PostManager postManager;
private RoleManager roleManager;
private UserManager userManager;

@Autowired
public ApiController(CategoryManager categoryManager,
MenuItemManger menuItemManger,
PostManager postManager,
RoleManager roleManager,
UserManager userManager) {
this.categoryManager = categoryManager;
this.menuItemManger = menuItemManger;
this.postManager = postManager;
this.roleManager = roleManager;
this.userManager = userManager;
}

public CategoryManager getCategoryManager() {


return categoryManager;
}

public MenuItemManger getMenuItemManger() {


return menuItemManger;
}

public PostManager getPostManager() {


return postManager;
}

public RoleManager getRoleManager() {


return roleManager;
}

public UserManager getUserManager() {


return userManager;
}

@RequestMapping("{pageName}")
public String linkResolver(Model model,
@PathVariable("pageName") String pageName){
System.out.println("Page name = "+pageName);
userManager.getAll().forEach(System.out::println);
model.addAttribute("userManager",userManager);
model.addAttribute("postManager",postManager);
model.addAttribute("roleManager",roleManager);
model.addAttribute("menuItemManager",menuItemManger);
model.addAttribute("categoryManager",categoryManager);
return "/view/site/components/"+pageName;
}
}

CategoryController
package com.latte.demolatte.controller;

import com.latte.demolatte.model.Post;
import com.latte.demolatte.service.CategoryManager;
import com.latte.demolatte.service.PostManager;
import com.latte.demolatte.service.RoleManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequestMapping
@Controller
public class CategoryController {
private CategoryManager categoryManager;
private PostManager postManager;
private RoleManager roleManager;

@Autowired
public void setCategoryManager(CategoryManager categoryManager) {
this.categoryManager = categoryManager;
}

@Autowired
public void setPostManager(PostManager postManager) {
this.postManager = postManager;
}

@Autowired
public void setRoleManager(RoleManager roleManager) {
this.roleManager = roleManager;
}

@PostMapping("/adminPanel/category/add")
@ResponseStatus(value = HttpStatus.OK)
public void addCategory(@RequestParam("name") String name,
@RequestParam("role_id") Long roleId) {
categoryManager.addCategory(name, roleId);
}

@GetMapping("/adminPanel/category/{id}")
public void deleteCategory(@PathVariable(name = "id") Long categoryId){
List<Post> posts = postManager.getPostsByCategory(categoryId);
postManager.deletePosts(posts);
categoryManager.deleteCategory(categoryId);
}

@GetMapping("/adminPanel/categories")
public String categoryList(Model model){
model.addAttribute("categoryList",categoryManager.getCategoryList());
model.addAttribute("roleList",roleManager.getAllRoles());
return "/view/adminPanel/components/categoryList";
}
}

PageConstructController
package com.latte.demolatte.controller;

import com.latte.demolatte.service.ComponentService;
import com.latte.demolatte.service.PageCreator;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping("/adminPanel/pageConstructor")
public class PageConstructorController {

@GetMapping("")
public String page(Model model){
ComponentService componentService = new ComponentService();
model.addAttribute("components",componentService.getComponents());
return "/view/adminPanel/templates/pageConstructor";
}

@PostMapping("/save")
public String savePage(@RequestParam Map<String,String> params) {
JSONObject jsonObject = new JSONObject((String) params.keySet().toArray()
[0]);
String title = jsonObject.getString("title");
Map<String, List<String>> data =new HashMap<>();

data.put("page_body",jsonArrayToList(jsonObject.getJSONArray("page_body")));
data.put("side_bar",jsonArrayToList(jsonObject.getJSONArray("side_bar")));

data.put("page_footer",jsonArrayToList(jsonObject.getJSONArray("page_footer")));

data.put("page_header",jsonArrayToList(jsonObject.getJSONArray("page_header")));
PageCreator pageCreator = new PageCreator(title,data);
pageCreator.createPage();
return "/view/adminPanel/main";
}

private List<String> jsonArrayToList(JSONArray array){


List<String> res = new LinkedList<>();
array.forEach(el -> res.add((String) el));
return res;
}
}

PostCommentsController
package com.latte.demolatte.controller;

import com.latte.demolatte.model.PostComment;
import com.latte.demolatte.model.User;
import com.latte.demolatte.service.PostCommentsManager;
import com.latte.demolatte.service.PostManager;
import com.latte.demolatte.service.UserManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.Date;
@Controller
@RequestMapping("/comments")
public class PostCommentsController {

private PostCommentsManager postCommentsManager;


private UserManager userManager;
private PostManager postManager;

@Autowired
public void setPostManager(PostCommentsManager postCommentsManager) {
this.postCommentsManager = postCommentsManager;
}

@Autowired
public void setUserManager(UserManager userManager) {
this.userManager = userManager;
}

@Autowired
public void setPostManager(PostManager postManager) {
this.postManager = postManager;
}

@GetMapping("{id}")
public String postComments(@PathVariable Long id, Model model) {
model.addAttribute("comments", postCommentsManager.getPostComments(id));
model.addAttribute("postId",id);
return "/view/site/elements/comments";
}

@GetMapping("/add/{id}")
@ResponseStatus(value = HttpStatus.OK)
public void addComment(Principal principal,
@PathVariable Long id,
@RequestParam String text){
User user = userManager.getUserByUsername(principal.getName());
PostComment postComment = new PostComment();
postComment.setCommentPost(postManager.getPostById(id));
postComment.setText(text);
postComment.setCreateUser(user);
postComment.setUpdateUser(user);
postComment.setDate(new Date());
postCommentsManager.addComment(postComment);
}
}

PostController
package com.latte.demolatte.controller;

import com.latte.demolatte.model.Category;
import com.latte.demolatte.model.Post;
import com.latte.demolatte.model.User;
import com.latte.demolatte.service.CategoryManager;
import com.latte.demolatte.service.MenuItemManger;
import com.latte.demolatte.service.PostManager;
import com.latte.demolatte.service.UserManager;
import com.latte.demolatte.utils.Constants;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

@Controller
@RequestMapping("/feed")
public class PostController {

private PostManager postManager;


private MenuItemManger menuItemManger;
private UserManager userManager;
private CategoryManager categoryManager;

public PostController(PostManager postManager, MenuItemManger menuItemManger,


UserManager userManager, CategoryManager categoryManager)
{
this.postManager = postManager;
this.menuItemManger = menuItemManger;
this.userManager = userManager;
this.categoryManager = categoryManager;
}

@GetMapping()
public String lastPosts(Model model,
@RequestParam(value = "category", required = false)
Long categoryId) {
List<Post> postList = null;
if (categoryId != null) {
postList = postManager.getPostsByCategory(categoryId);
}else{
postList = postManager.getNewPosts();
}
if (postList.size() > 1) {
model.addAttribute("lastPost", postList.get(0));
}
model.addAttribute("links",
menuItemManger.getMenuItemDtoList(Constants.MENU_TYPE.SITE));
model.addAttribute(Constants.CONTEXT_VARIABLES.SITE_TITLE, "Test title");
model.addAttribute("categories",categoryManager.getCategoryList());
model.addAttribute("posts", postList);
return "/view/site/main";
}

@GetMapping("add")
public String addPost(Model model) {
model.addAttribute("saveAction", "/feed/save");
model.addAttribute("categoryList", categoryManager.getCategoryList());
return "/view/site/templates/editorPage";
}

@GetMapping("edit/{id}")
public String editPost(Model model, @PathVariable Long id) {
Post post = postManager.getPostById(id);
model.addAttribute("saveAction", "/feed/save/" + id);
model.addAttribute("editPostContent", (post != null ? post.getContent() :
""));
model.addAttribute("postTitle", post != null ? post.getTitle() : "");
model.addAttribute("categoryList", categoryManager.getCategoryList());
return "/view/site/templates/editorPage";
}

@RequestMapping(value = {"/save", "/save/{id}"}, method = RequestMethod.POST)


public String savePost(Principal principal,
@RequestParam("content") String content,
@RequestParam("title") String title,
@RequestParam("category_id") Long categoryId,
@PathVariable(required = false) Long id) {
Post post = null;
if (id != null) {
post = postManager.getPostById(id);
} else {
post = new Post();
}
post.setContent(content);
post.setTitle(title);
User user = userManager.getUserByUsername(principal.getName());
post.setCreateUser(user);
post.setUpdateUser(user);
post.setCategory(categoryManager.getCategoryById(categoryId));
post.setDate(new Date());
postManager.save(post);
return "redirect:/feed";
}

@GetMapping("delete/{id}")
public String deletePost(@PathVariable Long id) {
postManager.deletePostById(id);
return "redirect:/feed";
}
}

RegistrationController
package com.latte.demolatte.controller;

import com.latte.demolatte.model.User;
import com.latte.demolatte.service.UserManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class RegistrationController {

private final UserManager userManager;

public RegistrationController(UserManager userManager) {


this.userManager = userManager;
}

@GetMapping("/registration")
public String registration(Model model) {
model.addAttribute("userForm", new User());

return "/view/site/templates/registration";
}

@PostMapping("/registration")
public String addUser(@ModelAttribute("userForm") User userForm, BindingResult
bindingResult, Model model) {

if (bindingResult.hasErrors()) {
return "/view/site/templates/registration";
}
if (!userManager.saveUser(userForm)){
model.addAttribute("usernameError", "Пользователь с таким именем уже
существует");
return "/view/site/templates/registration";
}

return "redirect:/feed";
}

@GetMapping("/login")
public String loginPage(){
return "/view/login";
}
}

UserProfileController
package com.latte.demolatte.controller;

import com.latte.demolatte.model.User;
import com.latte.demolatte.service.PostManager;
import com.latte.demolatte.service.UserManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.Map;

@Controller
@RequestMapping("/profile")
public class UserProfileController {
private UserManager userManager;
private PostManager postManager;

@Autowired
public void setUserManager(UserManager userManager) {
this.userManager = userManager;
}

@Autowired
public void setPostManager(PostManager postManager) {
this.postManager = postManager;
}

@GetMapping
public String userProfile(Principal principal, Model model) {
User user = userManager.getUserByUsername(principal.getName());
model.addAttribute("user",user);
model.addAttribute("currUser",true);
model.addAttribute("userPosts",postManager.getUserPosts(user.getId()));
return "/view/site/elements/userCard";
}

@GetMapping("/{id}")
public String anotherUserInfo(@PathVariable Long id,
Model model){
User user = userManager.getUserById(id);
model.addAttribute("user",user);
model.addAttribute("currUser",false);
model.addAttribute("userPosts",postManager.getUserPosts(user.getId()));
return "/view/site/elements/userCard";
}

@PostMapping("/save")
public String saveUser(Principal principal,
@RequestParam Map<String,String> editedUser){
User user = userManager.getUserByUsername(principal.getName());
user.setInfo(editedUser.get("info"));
user.setLastName(editedUser.get("lastName"));
user.setFirstName(editedUser.get("firstName"));
userManager.updateUserInfo(user);
return "redirect:/feed";
}
}

CategoryManagerImpl
package com.latte.demolatte.service.impl;

import com.latte.demolatte.model.Category;
import com.latte.demolatte.model.Role;
import com.latte.demolatte.repository.CategoryCrudRepository;
import com.latte.demolatte.repository.RoleCrudRepository;
import com.latte.demolatte.service.CategoryManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
@Component
public class CategoryManagerImpl implements CategoryManager {

private CategoryCrudRepository repository;


private RoleCrudRepository roleCrudRepository;

@Autowired
public void setRepository(CategoryCrudRepository repository) {
this.repository = repository;
}

@Autowired
public void setRoleCrudRepository(RoleCrudRepository roleCrudRepository) {
this.roleCrudRepository = roleCrudRepository;
}

@Override
public List<Category> getCategoryList() {
return repository.findAll();
}

@Override
public Category getCategoryByName(String name) {
return repository.findByName(name);
}

@Override
public Category getCategoryById(Long id) {
return repository.findById(id).get();
}

@Override
public void addCategory(String name, Long roleId) {
try {
Role role = roleCrudRepository.findById(roleId).get();
Category category = new Category();
category.setName(name);
if (role != null) {
category.setRole(role);
repository.save(category);
}
} catch (Exception e) {
System.err.println("Cant add category!");
e.printStackTrace();
}
}

@Override
public void deleteCategory(Long categoryId) {
if (repository.findById(categoryId).isPresent()) {
repository.delete(repository.findById(categoryId).get());
}else{
System.err.println("No category with such ID !");
}
}

}
MenuItemManagerImpl
package com.latte.demolatte.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.latte.demolatte.model.dto.MenuItemDto;
import com.latte.demolatte.service.MenuItemManger;
import com.latte.demolatte.utils.Constants;
import com.latte.demolatte.utils.FileUtil;
import org.springframework.stereotype.Component;

import java.io.FileNotFoundException;
import java.util.LinkedList;
import java.util.List;

@Component
public class MenuItemManagerImpl implements MenuItemManger {
private List<MenuItemDto> adminMenuLinkList;
private List<MenuItemDto> siteMenuLinkList;

@Override
public List<MenuItemDto> getMenuItemDtoList(int menuType) {
switch (menuType) {
case Constants.MENU_TYPE.SITE:
return getSiteMenuLinkList();
case Constants.MENU_TYPE.ADMIN:
return getAdminMenuLinkList();
default:
System.err.println("MenuItemManger(2) -> No such option ");
return null;
}
}

@Override
public List<String> getMenuLinks(int menuType) {
List<String> res = new LinkedList<>();
getMenuItemDtoList(menuType).forEach(el -> res.add(el.getUrl()));
return res;
}

public List<MenuItemDto> initMenuItemDtoList(int menuType) {


try {
String menuJson = null;
switch (menuType) {
case Constants.MENU_TYPE.SITE:
menuJson =
FileUtil.JsonFromFile(FileUtil.readFile("/static/common/menu.json"));
break;
case Constants.MENU_TYPE.ADMIN:
menuJson =
FileUtil.JsonFromFile(FileUtil.readFile("/static/common/adminMenu.json"));
break;
default:
System.err.println("MenuItemManger(2) -> No such option ");
return null;
}
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(menuJson, new
TypeReference<List<MenuItemDto>>() {
});
} catch (JsonProcessingException | FileNotFoundException e) {
e.printStackTrace();
}
return null;
}

public List<MenuItemDto> getAdminMenuLinkList() {


if (adminMenuLinkList == null) {
adminMenuLinkList = initMenuItemDtoList(Constants.MENU_TYPE.ADMIN);
}
return adminMenuLinkList;
}

public List<MenuItemDto> getSiteMenuLinkList() {


if (siteMenuLinkList == null) {
siteMenuLinkList = initMenuItemDtoList(Constants.MENU_TYPE.SITE);
}
return siteMenuLinkList;
}
}

UserManagerImpl
package com.latte.demolatte.service.impl;

import com.latte.demolatte.model.Role;
import com.latte.demolatte.model.User;
import com.latte.demolatte.repository.RoleCrudRepository;
import com.latte.demolatte.repository.UserCrudRepository;
import com.latte.demolatte.service.UserManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
public class UserManagerImpl implements UserManager {
private UserCrudRepository userCrudRepository;
private RoleCrudRepository roleCrudRepository;
private BCryptPasswordEncoder bCryptPasswordEncoder;

public UserManagerImpl(UserCrudRepository userCrudRepository,


BCryptPasswordEncoder bCryptPasswordEncoder, RoleCrudRepository roleCrudRepository)
{
this.userCrudRepository = userCrudRepository;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
this.roleCrudRepository = roleCrudRepository;
}
@Override
public List<User> getAll() {
return userCrudRepository.findAll();
}

@Override
public User getUserById(Long id) {
Optional<User> user = userCrudRepository.findById(id);
if (user.isPresent()) {
return user.get();
}
return new User();
}

@Override
public User getUserByUsername(String username) {
return userCrudRepository.findByUsername(username);
}

@Override
public boolean saveUser(User user) {
User userFromDb = userCrudRepository.findByUsername(user.getUsername());
if (userFromDb != null) {
return false;
}
Role defaultRole = roleCrudRepository.findById(1L).get();
user.setRole(Collections.singleton(defaultRole));
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
user.setFirstName(user.getFirstName() == null ? "anonymous" :
user.getFirstName());
user.setLastName(user.getLastName() == null ? "anonymous" :
user.getLastName());
user.setInfo(user.getInfo() == null ? "" : user.getInfo());
userCrudRepository.save(user);
return true;
}

@Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
User user = userCrudRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User '" + username + "' not
found");
}
return user;
}

@Override
public void createUser(UserDetails userDetails) {

@Override
public void updateUser(UserDetails userDetails) {
if (userDetails instanceof User) {
User user = (User) userDetails;
userCrudRepository.save(user);
}
}

@Override
public void deleteUser(String username) {
User user = userCrudRepository.findByUsername(username);
if(user != null){
userCrudRepository.delete(user);
}
}

@Override
public void changePassword(String s, String s1) {

@Override
public boolean userExists(String s) {
return false;
}

public void updateUserInfo(User user) {


userCrudRepository.save(user);
}

@Override
public List<User> getUsersByRole(String roleName) {
Role role = roleCrudRepository.findByName(roleName);
if (role != null) {
return userCrudRepository.getAllUsersByRoleId(role.getId());
}
return new LinkedList<>();
}
}

Вам также может понравиться