Conversation
…e with interfaces.
…enticationHandler for sending cookie. Added REST controllers for Product and Review.
…s adjusted. dev(test): Added IT tests for UserRestController.
dev(test): Added and fixed IT tests for controllers
Общий вывод по проектуПредставленный код демонстрирует хорошее владение экосистемой Spring Boot (Security, Data JPA, MVC) и инструментами тестирования (MockMvc, Testcontainers). Архитектура построена по классической многослойной модели, что упрощает поддержку. Сильные стороны: Грамотное использование Testcontainers для интеграционного тестирования. Разделение ответственности между контроллерами, сервисами и репозиториями. Использование Lombok для уменьшения шаблонного кода. Области для роста: Безопасность и конфигурация: Стоит уделить внимание защите чувствительных данных (секреты JWT) и валидации входящих DTO. Производительность JPA: Необходимо контролировать типы загрузки (FetchType) и избегать лишних запросов к БД внутри циклов или мапперов. Чистота кода: Переход от System.out к логированию и строгое соблюдение модификаторов доступа (private final) сделают код более профессиональным. Проект выглядит очень достойно для продвинутого уровня обучения. Видно стремление к использованию современных подходов Java 21. Устранение мелких недочетов по SOLID и Code Convention позволит автору претендовать на позицию Middle-разработчика. Поставил A |
| public ReviewTo save(ReviewTo reviewTo) { | ||
| ReviewTo saved = super.save(reviewTo); | ||
| ProductTo updatedProduct = updateProduct(saved.getProductId()); | ||
| return saved; |
There was a problem hiding this comment.
При пересчете рейтинга используется метод findAll(), что может привести к проблемам с производительностью при большом количестве отзывов. Рекомендуется использовать агрегатные функции SQL (AVG) через репозиторий.
| return saved; | ||
| } | ||
|
|
||
| private ProductTo updateProduct(Long productId) { |
There was a problem hiding this comment.
Использование Stream API для вычисления среднего значения — отличная практика Java 8+, но здесь лучше применить метод DoubleStream.average() для чистоты кода.
| public UserService(UserRepository repository, UserMapper mapper, PasswordEncoder passwordEncoder) { | ||
| super(repository, mapper); | ||
| this.passwordEncoder = passwordEncoder; | ||
| } |
There was a problem hiding this comment.
Поле passwordEncoder должно быть помечено как final для обеспечения иммутабельности и следования принципам внедрения зависимостей через конструктор.
| .map(mapper::mapToDto) | ||
| .orElseThrow(() -> new UserNotFoundException("User not found with username: " + username)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Метод поиска пользователя бросает RuntimeException без описания. На собеседовании это сочтут плохим тоном; лучше использовать кастомное исключение или EntityNotFoundException.
|
|
||
| @Service | ||
| public class ProductService extends AbstractBaseService<Product, ProductTo, ProductRepository, ProductMapper> { | ||
| public ProductService(ProductRepository repository, ProductMapper mapper) { |
There was a problem hiding this comment.
Отсутствие модификатора доступа у полей сервиса (package-private по умолчанию). Согласно Java Code Convention, поля должны быть private.
| @Getter | ||
| private long expiration; | ||
|
|
||
| public String generateToken(CustomUserDetails userDetails, List<String> roles) { |
There was a problem hiding this comment.
Секретный ключ захардкожен прямо в коде. В продвинутой разработке такие данные выносятся в переменные окружения или конфигурационные файлы.
|
|
||
| @GetMapping("/{id}") | ||
| public CategoryTo getCategory(@PathVariable Long id) { | ||
| return categoryService.getById(id); |
There was a problem hiding this comment.
В контроллере используется System.out.println для логирования. В Spring Boot проектах необходимо использовать логгеры (например, Slf4j), чтобы управлять уровнями вывода данных.
| } | ||
|
|
||
| @DeleteMapping("/{id}") | ||
| @PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')") |
There was a problem hiding this comment.
Аннотация @PreAuthorize использует роли напрямую. Хорошей практикой считается вынос названий ролей в константы для предотвращения опечаток.
| import jakarta.persistence.Enumerated; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; |
|
|
||
| @Test | ||
| @WithMockUser // Обычный пользователь | ||
| void should_getAllCategories() throws Exception { |
There was a problem hiding this comment.
Название теста should_getAllCategories нарушает стандарт camelCase для Java. В тестах это допустимо, но лучше придерживаться единого стиля в проекте.
|
|
||
| // Настраиваем поведение базового сохранения (super.save) | ||
| when(mapper.mapToEntity(inputDto)).thenReturn(reviewEntity); | ||
| when(repository.save(reviewEntity)).thenReturn(reviewEntity); |
There was a problem hiding this comment.
Тест перегружен настройками Mockito (when/thenReturn). Это признак того, что тестируемый метод делает слишком много (нарушение Single Responsibility).
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| @Entity |
There was a problem hiding this comment.
Поле category помечено как @manytoone, но не указан FetchType. По умолчанию это Eager, что может вызвать проблему N+1. Рекомендуется всегда ставить LAZY.
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Data; | ||
| import lombok.NoArgsConstructor; |
There was a problem hiding this comment.
Класс для DTO не является финальным и не использует преимущества неизменяемости (final поля).
| import java.util.List; | ||
|
|
||
| @Mapper(componentModel = "spring") | ||
| public interface ReviewMapper extends BaseMapper<Review, ReviewTo> { |
There was a problem hiding this comment.
Логика преобразования userId в сущность User находится внутри маппера и требует обращения к репозиторию. Это делает маппер 'тяжелым' и затрудняет тестирование.
| throw new IllegalStateException("Utility class"); | ||
| } | ||
| // ============ URL PATHS ============= | ||
| public static final class Path { |
There was a problem hiding this comment.
Интерфейс для констант — это антипаттерн (Constant Interface). Следует использовать финальный класс с приватным конструктором.
| @Setter | ||
| @NoArgsConstructor | ||
| @SuperBuilder | ||
| public class ReviewViewTo extends ReviewTo { |
There was a problem hiding this comment.
Наследование DTO (ReviewViewTo extends ReviewTo) усложняет структуру данных. Часто лучше использовать композицию или плоские структуры для API.
| import net.minidev.json.annotate.JsonIgnore; | ||
|
|
||
| @Getter | ||
| @Setter |
There was a problem hiding this comment.
Отсутствует валидация полей (например, @Email, @notblank). В Spring Boot важно проверять входные данные на уровне контроллера.
Финальный проект 5 модуля.
Данные для аутентификации в приложение:
Администратор:
login=admin, password=admin
Менеджер:
login=manager, password=manager
Пользователь:
login=cyber_user, password=cyber_user