Skip to content

Gimaldinov - Final Spring Project#1

Open
DEEXORT wants to merge 24 commits intobasefrom
feature/full-project
Open

Gimaldinov - Final Spring Project#1
DEEXORT wants to merge 24 commits intobasefrom
feature/full-project

Conversation

@DEEXORT
Copy link
Copy Markdown
Owner

@DEEXORT DEEXORT commented Jan 28, 2026

Финальный проект 5 модуля.
Данные для аутентификации в приложение:
Администратор:
login=admin, password=admin
Менеджер:
login=manager, password=manager
Пользователь:
login=cyber_user, password=cyber_user

…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
@DEEXORT DEEXORT changed the title Final Spring Project Gimaldinov - Final Spring Project Jan 28, 2026
@demologin
Copy link
Copy Markdown

Общий вывод по проекту

Представленный код демонстрирует хорошее владение экосистемой 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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

При пересчете рейтинга используется метод findAll(), что может привести к проблемам с производительностью при большом количестве отзывов. Рекомендуется использовать агрегатные функции SQL (AVG) через репозиторий.

return saved;
}

private ProductTo updateProduct(Long productId) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Использование Stream API для вычисления среднего значения — отличная практика Java 8+, но здесь лучше применить метод DoubleStream.average() для чистоты кода.

public UserService(UserRepository repository, UserMapper mapper, PasswordEncoder passwordEncoder) {
super(repository, mapper);
this.passwordEncoder = passwordEncoder;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поле passwordEncoder должно быть помечено как final для обеспечения иммутабельности и следования принципам внедрения зависимостей через конструктор.

.map(mapper::mapToDto)
.orElseThrow(() -> new UserNotFoundException("User not found with username: " + username));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Метод поиска пользователя бросает RuntimeException без описания. На собеседовании это сочтут плохим тоном; лучше использовать кастомное исключение или EntityNotFoundException.


@Service
public class ProductService extends AbstractBaseService<Product, ProductTo, ProductRepository, ProductMapper> {
public ProductService(ProductRepository repository, ProductMapper mapper) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Отсутствие модификатора доступа у полей сервиса (package-private по умолчанию). Согласно Java Code Convention, поля должны быть private.

@Getter
private long expiration;

public String generateToken(CustomUserDetails userDetails, List<String> roles) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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


@GetMapping("/{id}")
public CategoryTo getCategory(@PathVariable Long id) {
return categoryService.getById(id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В контроллере используется System.out.println для логирования. В Spring Boot проектах необходимо использовать логгеры (например, Slf4j), чтобы управлять уровнями вывода данных.

}

@DeleteMapping("/{id}")
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Аннотация @PreAuthorize использует роли напрямую. Хорошей практикой считается вынос названий ролей в константы для предотвращения опечаток.

import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Использование @DaTa из Lombok в JPA сущностях может привести к проблемам с производительностью и бесконечной рекурсии в методах hashCode/equals. Лучше использовать @Getter и @Setter.


@Test
@WithMockUser // Обычный пользователь
void should_getAllCategories() throws Exception {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Название теста should_getAllCategories нарушает стандарт camelCase для Java. В тестах это допустимо, но лучше придерживаться единого стиля в проекте.


// Настраиваем поведение базового сохранения (super.save)
when(mapper.mapToEntity(inputDto)).thenReturn(reviewEntity);
when(repository.save(reviewEntity)).thenReturn(reviewEntity);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тест перегружен настройками Mockito (when/thenReturn). Это признак того, что тестируемый метод делает слишком много (нарушение Single Responsibility).

import java.util.ArrayList;
import java.util.List;

@Entity
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поле category помечено как @manytoone, но не указан FetchType. По умолчанию это Eager, что может вызвать проблему N+1. Рекомендуется всегда ставить LAZY.


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Класс для DTO не является финальным и не использует преимущества неизменяемости (final поля).

import java.util.List;

@Mapper(componentModel = "spring")
public interface ReviewMapper extends BaseMapper<Review, ReviewTo> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Логика преобразования userId в сущность User находится внутри маппера и требует обращения к репозиторию. Это делает маппер 'тяжелым' и затрудняет тестирование.

throw new IllegalStateException("Utility class");
}
// ============ URL PATHS =============
public static final class Path {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Интерфейс для констант — это антипаттерн (Constant Interface). Следует использовать финальный класс с приватным конструктором.

@Setter
@NoArgsConstructor
@SuperBuilder
public class ReviewViewTo extends ReviewTo {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Наследование DTO (ReviewViewTo extends ReviewTo) усложняет структуру данных. Часто лучше использовать композицию или плоские структуры для API.

import net.minidev.json.annotate.JsonIgnore;

@Getter
@Setter
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Отсутствует валидация полей (например, @Email, @notblank). В Spring Boot важно проверять входные данные на уровне контроллера.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants