Skip to content

Commit 531c2ed

Browse files
committed
Part 17: Add UserController.java
1 parent 2f4a8f8 commit 531c2ed

File tree

1 file changed

+216
-0
lines changed

1 file changed

+216
-0
lines changed
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package spring.oldboy.http.controller;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.data.domain.Page;
5+
import org.springframework.data.domain.Pageable;
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.stereotype.Controller;
8+
import org.springframework.ui.Model;
9+
import org.springframework.web.bind.annotation.*;
10+
import org.springframework.web.server.ResponseStatusException;
11+
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
12+
import spring.oldboy.database.entity.Role;
13+
import spring.oldboy.dto.PagePaginationResponse;
14+
import spring.oldboy.dto.UserCreateEditDto;
15+
import spring.oldboy.dto.UserFilterDto;
16+
import spring.oldboy.dto.UserReadDto;
17+
import spring.oldboy.service.CompanyService;
18+
import spring.oldboy.service.UserService;
19+
20+
/*
21+
Наш класс это контроллер и мы его аннотируем соответственно,
22+
он будет обрабатывать запросы к нашему приложению на работу
23+
с БД, а точнее с записями user-ов.
24+
*/
25+
@Controller
26+
@RequestMapping("/users")
27+
/*
28+
@RequiredArgsConstructor - генерирует конструктор с 1 параметром для каждого поля,
29+
которое требует специальной обработки. Все неинициализированные final поля получают
30+
параметр, также как все остальные поля, помеченные @NonNull, которые не иницилизированы
31+
при объявлении. Для этих случаев также генерируется явная проверка на null. Конструктор
32+
бросает исключение NullPointerException, если какой-либо из параметров, предназначенный
33+
для полей, помеченных @NonNull содержит null. Порядок этих параметров совпадает с
34+
порядком появления полей в классе.
35+
36+
Добавляем аннотацию, чтобы иметь возможность внедрять соответствующие зависимости.
37+
*/
38+
@RequiredArgsConstructor
39+
public class UserController {
40+
private final UserService userService;
41+
private final CompanyService companyService;
42+
43+
/*
44+
Определили какой HTTP метод запроса будет использоваться. Данный
45+
метод будет активирован запросом: http://localhost:8080/users
46+
*/
47+
@GetMapping
48+
/* Методы самого Spring приложения, как и раньше мы именуем отглагольными названиями */
49+
public String findAll(Model model) {
50+
/* Используя метод UserService мы возвращаем всех запрошенных пользователей */
51+
model.addAttribute("users", userService.findAll());
52+
/*
53+
Возвращаем view соответствующий полученному запросу,
54+
у нас это - resources/templates/user/users.html
55+
*/
56+
return "user/users";
57+
}
58+
59+
/*
60+
Определили какой HTTP метод запроса будет использоваться. Данный
61+
метод будет активирован запросом: http://localhost:8080/users/filter
62+
*/
63+
@GetMapping("/filter")
64+
/* Методы самого Spring приложения, как и раньше мы именуем отглагольными названиями */
65+
public String findAll(Model model, UserFilterDto filter) {
66+
/* Используя метод UserService мы возвращаем всех запрошенных пользователей */
67+
model.addAttribute("users", userService.findAll(filter));
68+
/*
69+
Возвращаем view соответствующий полученному запросу, у
70+
нас это - resources/templates/user/users_with_filter.html
71+
*/
72+
return "user/users_with_filter";
73+
}
74+
75+
/*
76+
Определили какой HTTP метод запроса будет использоваться. Данный
77+
метод будет активирован запросом: http://localhost:8080/users/pagination
78+
*/
79+
@GetMapping("/pagination")
80+
public String findAll(Model model, UserFilterDto filter, Pageable pageable) {
81+
Page<UserReadDto> page = userService.findAll(filter, pageable);
82+
model.addAttribute("users", PagePaginationResponse.of(page));
83+
model.addAttribute("filter", filter);
84+
return "user/users_with_pagination";
85+
}
86+
87+
88+
@GetMapping("/{id}")
89+
public String findById(@PathVariable("id") Long id, Model model) {
90+
/*
91+
В данной ситуации мы должны вернуть страницу с данными по одному User-у,
92+
либо страницу с сообщением о том, что User с таким ID не найден.
93+
*/
94+
return userService.findById(id)
95+
.map(user -> {
96+
/*
97+
Добавляем атрибуты во View и возвращаем его. Теперь у нас есть доступ
98+
к конкретной записи user в БД, к списку всех roles и списку всех companies.
99+
На нашей странице отображения мы выведем role в виде радио-кнопок, а
100+
список companies в виде выпадающего списка, чтобы можно было только выбирать
101+
из существующих в БД.
102+
*/
103+
model.addAttribute("user", user);
104+
model.addAttribute("roles", Role.values());
105+
model.addAttribute("companies", companyService.findAll());
106+
return "user/user";
107+
})
108+
/*
109+
Если User не найден возвращаем страницу со статусом "не найдена", а если точнее,
110+
то такой страницы не существует. Т.к. ID в нашем случае часть URL страницы, нет
111+
User-a с ID - нет и страницы, а нет страницы - ошибка 404 или Not Found -
112+
стандартный код ответа HTTP о том, что клиент был в состоянии общаться с сервером,
113+
но сервер не может найти данные.
114+
115+
И тут мы пробрасываем исключение с требуемым статусом.
116+
*/
117+
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
118+
}
119+
120+
/*
121+
Основная идея такова, что мы вносим данные в БД, и если произошла ошибка валидации, мы не
122+
вбиваем заново все данные, а используем уже введенные, и исправляем ошибку во введенных
123+
данных перед следующим сохранением. Что бы реализовать этот функционал мы в модель добавляем
124+
атрибут user через model.addAttribute("user", user);
125+
126+
Еще раз повторим: После запуска приложения SpringAppRunner.java мы можем обратиться к нему,
127+
а точнее к данному методу *.registration() через браузер по адресу: 'http://localhost:8080/'
128+
(адрес сервера) + '/users' (общий RequestMapping для методов данного класса контроллера) +
129+
'/registration' (маппинг текущего метода) т.е. мы вбиваем:
130+
http://localhost:8080/users/registration и попадаем на форму регистрации напрямую, а не
131+
через кнопку Registration формы login.html (на которой GET запросом мы делаем то же самое).
132+
*/
133+
@GetMapping("/registration")
134+
public String registration(Model model, @ModelAttribute("user") UserCreateEditDto user) {
135+
model.addAttribute("user", user);
136+
model.addAttribute("roles", Role.values());
137+
model.addAttribute("companies", companyService.findAll());
138+
/*
139+
Снова повтор: Именно отсюда resources/templates/user/registration.html будет возвращена
140+
страница на запрос пользователя, только в return "..." она указана без префикса и суффикса.
141+
В общих чертах, так это работает со всеми методами в данном (и др.) классе.
142+
1 - запускается приложение (сервер TomCat);
143+
2 - мы обращаемся к нему с HTTP запросом через браузер (или другим способом);
144+
3 - согласно маппингу (карте методов) приложение обрабатывает запрос;
145+
4 - при наличии правильного запроса (верной карты, адреса) пользователь получает ответ;
146+
5 - ответ может быть 'условно ожидаемым' если запрос верный (имеет соответствие в карте)
147+
или будет возвращен один из вариантов ошибки 4** или 5**
148+
*/
149+
return "user/registration";
150+
}
151+
152+
/*
153+
Помним, что поиск, создание, обновление и удаление мы регулируем через
154+
тип HTTP запроса, это может быть, как POST, так или PUT вариант запроса.
155+
*/
156+
@PostMapping
157+
public String create(@ModelAttribute UserCreateEditDto user,
158+
RedirectAttributes redirectAttributes) {
159+
160+
/*
161+
Вариант с регистрацией:
162+
if (true) {
163+
redirectAttributes.addAttribute("username", user.getUsername());
164+
redirectAttributes.addAttribute("firstname", user.getFirstname());
165+
redirectAttributes.addFlashAttribute("user", user);
166+
return "redirect:/users/registration";
167+
}
168+
*/
169+
170+
/*
171+
Перенаправление на метод *.findById(), который возвращает заполненную
172+
стр. user.html, а та в свою очередь позволяет редактировать данные по
173+
выбранной записи user-a и вызвать метод *.update()
174+
*/
175+
return "redirect:/users/" + userService.create(user).getId();
176+
}
177+
178+
/*
179+
Для обновления мы можем использовать PUT и PATCH запросы, тут все
180+
зависит от полноты обновления - всю или часть записи мы обновляем.
181+
Однако тут есть небольшая загвоздка - мы не используем JavaScript
182+
при реализации наших форм в *.jsp, то можем использовать пока,
183+
только POST и GET запросы, поэтому нарушим принципы 'best practice'
184+
при разработке нашего API и не будем использовать PUT запрос.
185+
186+
Данный недочет мы исправим когда возьмемся за RESTful API.
187+
*/
188+
@PostMapping("/{id}/update")
189+
public String update(@PathVariable("id") Long id, @ModelAttribute UserCreateEditDto user) {
190+
return userService.update(id, user)
191+
.map(it -> "redirect:/users/{id}")
192+
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
193+
}
194+
195+
/*
196+
Тут мы должны были использовать HTTP метод DELETE, но,
197+
как и ранее в методе *.update() мы используем POST.
198+
*/
199+
@PostMapping("/{id}/delete")
200+
public String delete(@PathVariable("id") Long id) {
201+
/*
202+
Метод *.delete() на уровне сервисов возвращает булево значение. Мы реализовали его так,
203+
чтобы иметь возможность на уровне контроллеров вернуть view удаленного пользователя или
204+
вернуть страницу со статусом 'Не найден' см. код ниже.
205+
*/
206+
if (!userService.delete(id)) {
207+
/*
208+
Поскольку ID участвует в генерации URL страницы с результатами, то при отсутствии
209+
user-a с требуемым ID и такой страницы существовать не может, т.е. снова статус
210+
404 - NOT FOUND и генерация исключения, как в методе *.update() в схожей ситуации.
211+
*/
212+
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
213+
}
214+
return "redirect:/users";
215+
}
216+
}

0 commit comments

Comments
 (0)