Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ repositories {

dependencies {
implementation("org.springframework.boot:spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.projectlombok:lombok:1.18.30")

Choose a reason for hiding this comment

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

compileOnly

testImplementation("org.springframework.boot:spring-boot-starter-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
annotationProcessor("org.projectlombok:lombok")
}

tasks.withType<Test> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.javaspringcourse.controller;

import lombok.RequiredArgsConstructor;
import org.javaspringcourse.dto.ArticleIn;
import org.javaspringcourse.service.ArticleService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/article")
@RequiredArgsConstructor
public class ArticleController {
private final ArticleService service;

@PostMapping("/create")
@ResponseStatus(HttpStatus.CREATED)
public String register(@RequestBody ArticleIn article) {

Choose a reason for hiding this comment

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

Возвращать следует ДТО

return service.create(article);
}
}
18 changes: 18 additions & 0 deletions src/main/java/org/javaspringcourse/dto/ArticleIn.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.javaspringcourse.dto;

import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.javaspringcourse.validation.RussianFullName;
import org.javaspringcourse.validation.Title;

@Data
public class ArticleIn {

Choose a reason for hiding this comment

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

Начиная с java 17 для дто следует использовать record, когда это возможно. Или хотя бы использовать @Value из lombok вместо @Data, т.к. дто должны быть неизменяемыми

@Title private String title;
@RussianFullName private String author;
@NotBlank private String content;

@Override
public String toString() {
return title + " (" + author + ")";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.javaspringcourse.exception;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.stream.Collectors;

@RestControllerAdvice
public class ArticleExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleBadGatewayException(ConstraintViolationException ex) {
return new ErrorResponse("Неверно заполнены поля.",
ex.getConstraintViolations().stream()
.collect(Collectors.toMap(
violation -> violation.getPropertyPath().toString(),
ConstraintViolation::getMessage)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.javaspringcourse.exception;

import java.util.Map;

public record ErrorResponse(String message, Map<String, String> errors) {}
18 changes: 18 additions & 0 deletions src/main/java/org/javaspringcourse/service/ArticleService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.javaspringcourse.service;

import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.javaspringcourse.dto.ArticleIn;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

@Slf4j
@Service
@Validated
public class ArticleService {
public String create(@Valid ArticleIn article) {

Choose a reason for hiding this comment

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

Вообще такой подход имеет место (использовать аннотации валидации на уровне сервиса) и как правило применяется не к самому сервису, а к его интерфейсу. Но все же, на будущее, следует валидации выполнять на уровне контроллера. Как правило во всех проектах у тебя должны быть обработчики и на MethodArgumentNotValidException, и на ConstraintViolationException

var ans = "Creating a new article: " + article.toString();
log.info(ans);
return ans;
}
}
22 changes: 22 additions & 0 deletions src/main/java/org/javaspringcourse/validation/RussianFullName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.javaspringcourse.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import jakarta.validation.constraints.NotBlank;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@NotBlank
@Constraint(validatedBy = RussianFullNameConstraintValidator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface RussianFullName {
String message() default "Введённое ФИО не соответствует формату: \"Фамилия Имя Отчество\" (допускаются двойные фамилии).";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.javaspringcourse.validation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.util.Arrays;
import java.util.regex.Pattern;

/**Проверяет, соответствует ли введённая строка Фамилии-Имени-Отчеству в русском алфавите.*/
public class RussianFullNameConstraintValidator implements ConstraintValidator<RussianFullName, String> {
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
var name = s.split(" ");
if (name.length != 3) return false;
return Arrays.stream(name).allMatch(n -> Pattern.matches("^[А-Яа-я]+(-?[А-Яа-я]+)?$", n));
}
}
24 changes: 24 additions & 0 deletions src/main/java/org/javaspringcourse/validation/Title.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.javaspringcourse.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@NotNull
@Size(min = 5, max = 40)
@Constraint(validatedBy = {})
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Title {
String message() default "Длина названия должна находиться в диапазоне от 5 до 40.";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}