Skip to content

Commit 6bc534c

Browse files
committed
Part 18: Add UserController.java
1 parent 9a21489 commit 6bc534c

File tree

1 file changed

+253
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)