Введение
Технологии используются
Анализ требований
Модель данных
Архитектура приложения
Уровень данных
Контроллеры
Обработчики действий
Виды
Фильтры
Результаты
Веб -приложение на базе Java для действий TODO. Благодаря этому веб -приложению можно создавать, читать и обновлять свои Todos через современный веб -браузер. Приложение также реализует AAA, что означает, что у каждого пользователя есть своя учетная запись, а также список TODO и т. Д.
Этот документ не для новичков. Вы должны обладать хорошими знаниями о технологиях ниже, чтобы понять этот документ и связанное приложение:
Ява
Сервлет, JSP
Apache Tomcat
Mysql
HTML
Apache Netbeans IDE
Firefox
Это полностью внутренний проект. Таким образом, передние технологии, такие как CSS, JavaScript не используются. Целью проекта является эффективное изучение и продемонстрировать, как работают разные части API Java Servlet.
Мы разработаем веб -приложение, начиная с анализа требований. Затем мы перейдем к дизайну базы данных. Данные являются центральными для любого веб -приложения. Почти все варианты использования касаются данных. После того, как модель данных веб -приложения будет готова, мы перейдем к разработке архитектуры приложения. На этом этапе мы увидим, как наше приложение ведет себя с различными действиями HTTP. Потому что все действия, выполняемые пользователями приложения, находятся через HTTP. Мы подумаем обо всех возможных действиях пользователя и четко определим их. Затем мы перейдем к проектированию интерфейсов и классов.
Для нашего приложения мы начинаем с определения того, что такое Todo для нас. Тодо - это задача, которая должна быть выполнена. Мы создаем список таких задач, чтобы помочь нам жить в жизни. Мы продолжаем отслеживать список, когда мы выполняем задачи один за другим. Тодо для нас имеет ниже свойства:
Первоначально задача будет иметь статус «todo». Когда мы начинаем работать над этим, мы меняем его на «в процессе». Как только задача будет выполнена, мы отмечаем его статус как «сделанный».
Мы хотим, чтобы наше приложение также поддерживало нескольких пользователей. И у каждого пользователя должен быть собственный личный список. Таким образом, пользователи не могут видеть список других TODO. Пользователь должен быть идентифицирован их именем пользователя, которое является их действительным адресом электронной почты для нас. Пользователям дают учетные записи в нашем приложении. Таким образом, учетная запись имеет ниже свойства:
Мы хотим, чтобы учетная запись «администратора» управляла только учетными записями. Учетная запись администратора должна использовать имя пользователя «администратор». Пользователь администратора может:
Последние два предмета стоит наблюдать. Обычно считается, что пользователь с правами администратора имеет доступ к информации каждого. Мы не хотим этого. Кроме того, мы уже определили, что учетная запись «администратора» для нас предназначена только для управления учетными записями. Это не для управления списками пользователей TODO. Учетная запись пользователя Admin не часто используется. Это подразумевается только для особых целей. Для нашего приложения мы ожидаем, что одна учетная запись пользователя также обрабатывает учетную запись «администратора». Таким образом, это будет тот же человек, который входит в систему, используя учетные данные «администратора» только при необходимости. Поскольку это существующая учетная запись пользователя, использующая учетную запись «администратора» только для управления всеми учетными записями, мы не хотим отдельный список задач для учетной записи администратора. Это не служит какой -либо цели.
Мы хотим, чтобы списки TODO всегда сохранялись. Это означает, что после того, как элемент Todo будет успешно создан пользователем, его никогда не может быть удалено. Точно так же мы также не хотим удалять учетную запись пользователя. В заключение мы не хотим поддерживать операции «удалить» в нашем приложении. Таким образом, мы поддерживаем CRU только из CRUD.
Поскольку мы хотим, чтобы наше приложение поддерживало частные списки TODO, мы хотим, чтобы приложение предоставило входные и входные механизмы. Это называется «аутентификация». Каждый пользователь, включая «администратор», должен сначала аутентифицировать себя. После успешной аутентификации пользователь будет перенаправлен на свое рабочее пространство. Поскольку мы обсуждаем два типа пользователей (один администратор, а другой нормальный), у нас будет два типа рабочей области в нашем приложении. Пользователь администратора должен работать только с рабочей области управления учетной записью пользователя. Обычный пользователь должен работать только с рабочей области управления списками TODO. Оба являются эксклюзивными. Обычный пользователь не может видеть рабочее пространство администратора. И пользователь администратора не может видеть обычное рабочее пространство пользователя. Это называется «авторизация».
В дополнение к вышеуказанным требованиям, мы хотим, чтобы наше приложение сохранило подробную информацию о входе в систему пользователей и временных метках. Благодаря этому мы отслеживаем деятельность пользователей в нашем приложении. Это не совсем «бухгалтерский учет» от AAA, но для нашего приложения это служит целью назвать его «бухгалтерским учетом».
Основываясь на требованиях, которые мы собрали до сих пор, мы понимаем, что мы должны хранить данные для ниже объектов приложения:
Пример данных для нескольких учетных записей:
| Идентификатор учетной записи | Имя пользователя | Имя | Фамилия | Пароль | Создан в | Статус |
|---|---|---|---|---|---|---|
| 1 | администратор | Администратор | Пользователь | пароль | 2020-05-06 17:34:04 | включено |
| 2 | [email protected] | Джон | Джонссон | одно слово | 2020-05-07 12:34:04 | неполноценный |
| 3 | [email protected] | Эрик | Эрикссон | TwoWord | 2020-05-08 13:34:04 | включено |
| 4 | [email protected] | Ана | Мэри | три слова | 2020-05-09 11:34:04 | включено |
Мы видим, что статусы учетной записи повторяются по всей таблице. Таким образом, как часть нормализации базы данных, лучше поместить повторные данные в отдельную таблицу. Есть какая -то веская причина. Допустим, у нас есть 100 пользователей. И мы хотим заменить слова, включенные и отключены на 1 и 2 соответственно. Мы должны изменить столбец статуса всех рядов таблицы. Представьте, как громоздко будет такая модификация для таблицы с тысячами строк! К счастью, нормализация базы данных при спасении!
После нормализации у нас будет две таблицы - Account_Statuses and Accounts:
Account_statuses
| ИДЕНТИФИКАТОР | Статус |
|---|---|
| 1 | включено |
| 2 | неполноценный |
Счета
| Идентификатор учетной записи | Имя пользователя | Имя | Фамилия | Пароль | Идентификатор статуса |
|---|---|---|---|---|---|
| 1 | администратор | Администратор | Пользователь | пароль | 1 |
| 2 | [email protected] | Джон | Джонссон | одно слово | 2 |
| 3 | [email protected] | Эрик | Эрикссон | TwoWord | 1 |
| 4 | [email protected] | Ана | Мэри | три слова | 2 |
Точно так же у нас будет три таблицы для задач - task_statuses, task_priorities и задачи:
Task_statuses
| ИДЕНТИФИКАТОР | Статус |
|---|---|
| 1 | Тодо |
| 2 | в ходе выполнения |
| 3 | сделанный |
Task_priorities
| ИДЕНТИФИКАТОР | Приоритет |
|---|---|
| 1 | Важно и срочно |
| 2 | Важный, но не срочный |
| 3 | Не важно, но срочно |
| 4 | Не важно и не срочно |
Задачи
| Идентификатор задачи | Идентификатор учетной записи | Подробности | Создан в | Крайний срок | Последнее обновлено | Идентификатор статуса | Приоритетный идентификатор |
|---|---|---|---|---|---|---|---|
| 1 | 2 | Купить карандаши. | 2019-05-06 17:40:03 | 2019-05-07 17:40:03 | 2 | 1 | |
| 2 | 3 | Купить книги. | 2019-05-07 7:40:03 | 2019-05-07 17:40:03 | 2019-05-07 23:40:03 | 2 | 1 |
Наконец, у нас также есть еще одно требование для хранения данных сеанса учетной записи. Мы храним его, как показано в таблице ниже:
Account_sessions
| Идентификатор сессии | Идентификатор учетной записи | Сессия создана | Конец сеанса |
|---|---|---|---|
| ASD1GH | 1 | 2019-05-06 17:40:03 | 2019-05-06 18:00:03 |
Обычно в корпоративных приложениях идентификаторы не хранятся в качестве целых чисел. Потому что кому -то будет легче запросить информацию о других, просто используя целое число! В реальных приложениях идентификаторы не являются числовыми, а буквенно -цифровыми, с 100 символами. Таким образом, сделает невозможным для кого -то угадать другой идентификатор!
Мы будем называть нашу базу данных как «Todo» в MySQL. А вот модель данных, основанная на основе вышеуказанной информации:

Мы разработаем это приложение после знаменитого и широко используемого MVC 2 Desgin Pattern. Ниже показано, как мы собираемся реализовать MVC для нашего приложения: 
Наше приложение будет основано на действии. Когда пользователь отправляет HTTP -запрос в нашу приложение, мы переводим его в соответствующее действие в нашем приложении. Действия, которые мы поддерживаем, - это создание, чтение и обновление (CRU). Наше приложение, по сути, представляет собой данные. Это облегчает действия в базе данных. Это помогает пользователям безопасно и безопасно хранить и управлять своими данными в удаленной базе данных с помощью механизмов аутентификации и авторизации. Он действует как интерфейс на основе HTML и HTTP для базы данных.
Когда пользователь делает HTTP -запрос в нашем приложении, мы отправляем запрошенные данные в форме HTML. HTML поддерживает ссылки и формы, чтобы помочь пользователям взаимодействовать с веб -приложениями. Ссылки используются для извлечения/получения информации (http get), в то время как формы используются для публикации данных (http post) в веб -приложение.
Итак, вот как мы собираемся перевести HTTP -запросы на действия:
| HTML -элемент | HTTP Метод | Действие подачи заявления |
|---|---|---|
| Гиперссылка | Http Get | Прочитайте детали |
| Форма | Http post | Создать или обновить |
HTTP GET отправляет данные в качестве параметров запроса на URL. В то время как HTTP Post отправляет данные в HTTP -образном теле. HTTP Post не раскрывает данные через URL, тогда как HTTP GET DO. Таким образом, http get не подходит для отправки учетных данных для входа в систему. Никто не хочет, чтобы их имя пользователя и пароль были добавлены к URL -адресу HTTP -запроса! Мы будем использовать HTTP Post для отправки учетных данных пользователя во время входа в систему.
Итак, мы решили, как мы собираемся использовать HTTP, HTML и базу данных. Есть еще одна концепция HTTP, которая необходима для нас, чтобы понять архитектуру нашего приложения на основе HTTP. Это URL - унифицированный локатор ресурсов. Вот пример URL -адреса веб -приложения под названием «WebApp», размещенный на сервере примера.com:
http://www.example.com/webapp/details?id=12
В приведенном выше примере URL, «http» - это протокол, www.example.com - это имя домена или имя сервера, «WebApp» - это контекст приложения, развернутый на сервере, а «Подробности» - это приложение, которое мы отправляем нашу HTTP -запрос. «ID» - это параметр запроса, который мы передаем в «детали» со значением «12». Параметры запроса предоставляют нам механизм передачи параметров в веб -приложение и получение соответствующего контента в ответ на веб -приложение. Например, представьте себе приложение погоды, работающее на веб -сервере. Вместо того, чтобы предоставлять нам список отчетов о погоде во всех местах, мы можем отправить наш выбор местоположения в качестве параметра запроса в веб -приложение. Затем заявление отправит в ответ на последовательность погоды о нашем выборе местоположения.
Веб -приложение работает на веб -сервере, в отличие от приложений, которые запускаются на наших ПК. Приложение Java, работающее на веб -сервере, вызывается в качестве сервлета. Сервлеты эмулируют веб -приложения. Они бегают в контейнер. Apache Tomcat является популярным примером такого контейнера. Программное обеспечение для контейнеров переводит необработанные HTTP -запросы и ответы в объекты Java и предоставляет их для сервлетов. Статический веб -сайт обслуживает один и тот же контент для каждого HTTP -запроса. Но сервлет может генерировать динамический контент, который отличается для каждого выполненного HTTP -запроса.
Веб -приложение TODO, которое мы создаем, будет содержать несколько частей - сервлетов, фильтров, файлов JSP, классов базы данных, POJOS и т. Д. Мы будем соединить их все вместе (например, их объединение) в одном контексте приложения (или среде приложения) в контейнер (TOMCAT). Мы будем называть этот контекст приложения как «Тодо». Итак, если мы запускаем Tomcat на нашем компьютере на порте 8080, то через URL можно получить доступ к нашему контексту приложения «Todo»:
http://localhost:8080/todo/
Наш уровень данных состоит из реализации шаблона DAO и заводской шаблона поверх данных JDBC. Мы выбираем DATASOURCE JDBC вместо DriverManager, потому что мы хотим пожинать преимущества объединения соединений.
У нас просто один сервис, который служит контроллером , называется «Main». HTTP -запрос пользователя является для нас действием. Таким образом, цель нашего сервлета контроллера состоит в том, чтобы просто выбрать соответствующее действие для выполненного HTTP -запроса. Сервтель контроллера выбирает обработчик действий и вручает его по запросу, сделанному пользователем. Мы не пишем шаги выполнения действия в нашем контроллере. Мы держим его в чистоте и наклоняемся. Его цель состоит в том, чтобы «выбрать» обработчик действий. Не для того, чтобы «выполнять» действие само по себе. После того, как обработчик действий выполняет запрошенное действие, контроллер получает «следующий шаг», который будет выполнен в качестве ответа от обработчика действия. Задача контроллера состоит в том, чтобы просто выбрать ресурс, который выполняет запрошенный ответ. В заключение мы удерживаем наш контроллер от всей бизнес -логики.
Мы сопоставляем наш сервенс контроллера с шаблоном URI /app/* . Таким образом, наш сервенс контроллера будет обрабатывать каждый URI, который следует за шаблоном /app/ .
Действие, запрашиваемое пользователем, является бизнес -логикой для нашего приложения. Обработчики действий являются моделью в нашей реализации MVC. Каждое действие должно реализовать интерфейс действий:
public interface Action {
/*
An action is supposed to execute and return results. ActionResponse represents the response.
*/
public abstract ActionResponse execute ( HttpServletRequest request , HttpServletResponse response )
throws Exception ;
}Предполагается, что действие должно выполнить и вернуть результаты. Мы создаем специальный класс для такого результата - ActionResponse. Обработчик действий может выбрать «вперед» или «перенаправить».
public class ActionResponse {
private String method ;
private String viewPath ;
public ActionResponse () {
this . method = "" ;
this . viewPath = "" ;
}
public void setMethod ( String method ) {
this . method = method ;
}
public String getMethod () {
return this . method ;
}
public void setViewPath ( String viewPath ) {
this . viewPath = viewPath ;
}
public String getViewPath () {
return this . viewPath ;
}
@ Override
public String toString () {
return this . getClass (). getName ()+ "[" + this . method + ":" + this . viewPath + "]" ;
}
}Мы реализуем заводской шаблон проектирования. Мы создаем фабричный класс - ActionFactory - чтобы дать нам класс обработчика действий, который нам требуется:
public class ActionFactory {
private static Map < String , Action > actions = new HashMap < String , Action >() {
{
put ( new String ( "POST/login" ), new LoginAction ());
put ( new String ( "GET/login" ), new LoginAction ());
put ( new String ( "GET/logout" ), new LogoutAction ());
put ( new String ( "GET/admin/accounts/dashboard" ), new AdminAccountsDashboardAction ());
put ( new String ( "GET/admin/accounts/new" ), new AdminNewAccountFormAction ());
put ( new String ( "POST/admin/accounts/create" ), new AdminCreateAccountAction ());
put ( new String ( "GET/admin/accounts/details" ), new AdminReadAccountDetailsAction ());
put ( new String ( "POST/admin/accounts/update" ), new AdminUpdateAccountAction ());
put ( new String ( "GET/tasks/dashboard" ), new UserTasksDashboardAction ());
put ( new String ( "GET/tasks/new" ), new UserNewTaskFormAction ());
put ( new String ( "GET/tasks/details" ), new UserReadTaskDetailsAction ());
put ( new String ( "POST/tasks/create" ), new UserCreateTaskAction ());
put ( new String ( "POST/tasks/update" ), new UserUpdateTaskAction ());
put ( new String ( "GET/users/profile" ), new UserReadProfileAction ());
put ( new String ( "POST/users/update" ), new UserUpdateProfileAction ());
}
;
};
public static Action getAction ( HttpServletRequest request ) {
Action action = actions . get ( request . getMethod () + request . getPathInfo ());
if ( action == null ) {
return new UnknownAction ();
} else {
return action ;
}
}
}Цель нашего сервлета контроллера -:
protected void processRequest ( HttpServletRequest request , HttpServletResponse response )
throws ServletException , IOException {
Action action = ActionFactory . getAction ( request );
try {
ActionResponse actionResponse = action . execute ( request , response );
if ( actionResponse . getMethod (). equalsIgnoreCase ( "forward" )) {
System . out . println ( this . getClass (). getCanonicalName () + ":forward:" + actionResponse );
this . getServletContext (). getRequestDispatcher ( actionResponse . getViewPath ()). forward ( request , response );
} else if ( actionResponse . getMethod (). equalsIgnoreCase ( "redirect" )) {
System . out . println ( this . getClass (). getCanonicalName () + ":redirect:" + actionResponse );
if ( actionResponse . getViewPath (). equals ( request . getContextPath ())) {
response . setHeader ( "Cache-Control" , "no-cache, no-store, must-revalidate" );
response . setHeader ( "Pragma" , "no-cache" );
response . setDateHeader ( "Expires" , 0 );
}
response . sendRedirect ( actionResponse . getViewPath ());
} else if ( actionResponse . getMethod (). equalsIgnoreCase ( "error" )) {
System . out . println ( this . getClass (). getCanonicalName () + ":error:" + actionResponse );
response . sendError ( 401 );
} else {
System . out . println ( this . getClass (). getCanonicalName () + ":" + actionResponse );
response . sendRedirect ( request . getContextPath ());
}
} catch ( Exception e ) {
e . printStackTrace ();
}
} В таблице ниже показан список HTTP -запросов, на которые отвечает наше приложение и связанные с ними обработчики действий:
| Предполагаемое действие пользователя | Http -запрос URI | Обработчик действий |
|---|---|---|
| Отправить пустые учетные данные для входа в систему | GET /app/login | Логин |
| Отправить учетные данные для входа | POST /app/login | Логин |
| Получите учетные записи | GET /app/admin/accounts/dashboard | Adminaccountsdashboardaction |
| Получите новую форму учетной записи | GET /app/admin/accounts/new | Adminnewaccountformaction |
| Отправить новые данные учетной записи | POST /app/admin/accounts/create | AdmincreateAccountaction |
| Получите подробную информацию о учетной записи | GET /app/admin/accounts/details?id=xx | AdminReadAccountDetailSaction |
| Обновить подробности учетной записи | POST /app/admin/accounts/update | Adminupdateaccountaction |
| Получите задачи приборной панели | GET /app/tasks/dashboard | Usertasksdashboardaction |
| Получите новую форму задачи | GET /app/tasks/new | Usernewtaskformaction |
| Отправить новые данные задачи | POST /app/tasks/create | UsercreateTaskAction |
| Получить подробности задачи | GET /app/tasks/details?id=xx | UserReadTaskDetailSaction |
| Обновить детали задачи | POST /app/tasks/update | UserupdateTaskAction |
| Получите подробности моего профиля | GET /app/users/profile | UserReadProfileAction |
| Обновите данные моего профиля | POST /app/users/update | UserUpdateProfileAction |
| Выход | GET /app/logout | LogoutAction |
Задача обработчика действий состоит в том, чтобы выполнить бизнес -логику и выбрать соответствующий компонент представления в качестве ответа на запрос, сделанный пользователем. В таблице ниже показаны все обработчики действий и их компоненты представления:
| Обработчик действий | Просмотреть компонент |
|---|---|
| Логин | /WEB-INF/pages/admin/accounts/dashboard.jsp/WEB-INF/pages/tasks/dashboard.jsp |
| Adminaccountsdashboardaction | /WEB-INF/pages/admin/accounts/dashboard.jsp |
| Adminnewaccountformaction | /WEB-INF/pages/admin/accounts/newAccount.jsp |
| AdmincreateAccountaction | /WEB-INF/pages/admin/accounts/createAccountResult.jsp |
| AdminReadAccountDetailSaction | /WEB-INF/pages/admin/accounts/accountDetails.jsp |
| Adminupdateaccountaction | /WEB-INF/pages/admin/accounts/updateAccountResult.jsp |
| Usertasksdashboardaction | /WEB-INF/pages/tasks/dashboard.jsp |
| Usernewtaskformaction | /WEB-INF/pages/tasks/newTask.jsp |
| UsercreateTaskAction | /WEB-INF/pages/tasks/createTaskResult.jsp |
| UserReadTaskDetailSaction | /WEB-INF/pages/tasks/taskDetails.jsp |
| UserupdateTaskAction | /WEB-INF/pages/tasks/updateTaskResult.jsp |
| UserReadProfileAction | /WEB-INF/pages/users/viewProfile.jsp |
| UserUpdateProfileAction | /WEB-INF/pages/users/updateProfileResult.jsp |
| Неизвестное | /WEB-INF/pages/users/unknownAction.jsp |
Компонент View создает требуемый HTML -ответ, который будет отправлен пользователю. Просмотр компонента считывает сообщения, установленные обработчиком действий, и показывает их пользователю.
Мы используем фильтры для перехвата входящих HTTP -запросов. Все фильтры будут использоваться до того, как запрос будет передан в сервлет контроллера. Любой входящий HTTP -запрос будет сначала обработан фильтром аутентификации. Через этот фильтр мы проверяем, входит ли пользователь в систему или нет. Если не войти в систему, мы перенаправляем пользователя на страницу входа в систему. После успешного прохождения через фильтр аутентификации HTTP -запрос будет перехвачен еще двумя фильтрами. В этих фильтрах мы проверяем путь URI, а пользователь - «администратор» или обычный пользователь. Если нормальный пользователь пытается получить доступ к путям URI Admin ', мы предотвращаем такой доступ. Если пользователь «администратора» пытается получить доступ к задачам, связанным с пути URI, мы предотвращаем такой доступ.
Только «администратор» может получить доступ к URI, начинающемуся с /app/admin/* , и только нормальный пользователь может получить доступ к URI, начиная с /app/tasks/* . Другое URIS /app/login , /app/logout , /app/users/* может быть доступен обоими.
Мы не перехватываем ответы, которые мы посылаем.
Итак, вот как выглядит наше приложение. Я сохранил обычную равнину для простоты. Там нет CSS или JavaScript.










