{"id":355600,"date":"2024-05-20T23:12:08","date_gmt":"2024-05-20T23:12:08","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=355600"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=355600","title":{"rendered":"<span>\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0437\u044b\u0432\u043e\u0432 \u0441 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044f\u043c\u0438 \u0432 S3 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e AWS SDK Java<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430 &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0437\u044b\u0432\u043e\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u0438\u0442\u044c \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438, \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0438\u0445 \u0432 \u043e\u0431\u043b\u0430\u043a\u043e \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443 \u0444\u0430\u0439\u043b \u0438\u0437 S3, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e Yandex Object Storage, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f AWS SDK Java. <\/p>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u041d\u0438\u043a\u0438\u0442\u0430, \u044f \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0439 Java \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a. \u0412 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u0441\u0432\u043e\u0438\u0445 \u0443\u0447\u0435\u0431\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0441\u043e\u0437\u0434\u0430\u043b \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0438\u0306 \u043e\u0431 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0445 \u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u0434\u043b\u044f \u0443\u0447\u0430\u0441\u0442\u0438\u044f \u0432 \u043d\u0438\u0445.<\/p>\n<p>\u041e\u0434\u043d\u0430 \u0438\u0437 \u0444\u0438\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0437\u044b\u0432\u043e\u0432 \u043e \u043f\u043e\u0441\u0435\u0449\u0435\u043d\u043d\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0445. \u041a\u0430\u043a \u0438 \u0443 \u043c\u043d\u043e\u0433\u0438\u0445 \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u043a \u0441\u0432\u043e\u0438\u043c \u043e\u0442\u0437\u044b\u0432\u0430\u043c &#8212; \u043c\u0430\u0441\u0442\u0445\u0435\u0432. \u041f\u044b\u0442\u0430\u044f\u0441\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u0443 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u043d\u0435 \u0441\u043c\u043e\u0433 \u043d\u0430\u0439\u0442\u0438 \u043d\u0438 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u043e\u0436\u043d\u043e \u0443\u0433\u043b\u0443\u0431\u0438\u0442\u044c\u0441\u044f \u0432 \u0442\u0435\u043c\u0443 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u043e. \u041f\u043e\u044d\u0442\u043e\u043c\u0443, \u0441\u043e\u0431\u0440\u0430\u0432 \u0432\u0441\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u044c\u0441\u044f, \u0441\u043f\u0435\u0448\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441\u0432\u043e\u0438\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c!<\/p>\n<h2>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0434\u0435\u043b\u0443<\/h2>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h3>\n<p>Java 11, Maven, Spring Boot, Hibernate, Lombok, Docker, RestTemplate, <a href=\"https:\/\/aws.amazon.com\/ru\/sdk-for-java\/\" rel=\"noopener noreferrer nofollow\">AWS SDK<\/a><\/p>\n<p>\u041f\u0440\u043e\u0435\u043a\u0442 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d \u043d\u0430 \u0434\u0432\u0430 \u0441\u0435\u0440\u0432\u0438\u0441\u0430: <\/p>\n<ul>\n<li>\n<p><strong>main<\/strong> &#8212; \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e, \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044e \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044e \u043e\u0442\u0437\u044b\u0432\u043e\u0432 \u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. \u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u0438\u043c\u0435\u0435\u043c \u0434\u0432\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438: <strong>Comment<\/strong> \u0438 <strong>User<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>upload<\/strong> &#8212; \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 <strong>main <\/strong>\u0438 \u0441\u0430\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043d\u0430 S3<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 AWS SDK \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0438\u043c\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 <em>upload-\u0441\u0435\u0440\u0432\u0438\u0441\u0435:<\/em><\/p>\n<pre><code class=\"xml\">&lt;dependency>     &lt;groupId>com.amazonaws&lt;\/groupId>     &lt;artifactId>aws-java-sdk&lt;\/artifactId>     &lt;version>1.12.429&lt;\/version> &lt;\/dependency>  &lt;dependency>     &lt;groupId>javax.xml.bind&lt;\/groupId>     &lt;artifactId>jaxb-api&lt;\/artifactId>     &lt;version>2.3.1&lt;\/version> &lt;\/dependency><\/code><\/pre>\n<p>docker-compose.yml:<\/p>\n<pre><code class=\"yaml\">version: '3.1' services:   upload-service:     build: upload\/upload-server     image: upload-server     container_name: upload-server     ports:       - \"9090:9090\"     environment:       - BUCKET_NAME=your_bucket       - ACCESS_KEY_ID=your_key       - SECRET_KEY=your_secret    s3-service:     build: main     image: main-service     container_name: main-service     ports:       - \"8080:8080\"     depends_on:       - main-db     environment:       - UPLOAD_SERVER_URL=http:\/\/upload-server:9090       - SPRING_DATASOURCE_URL=jdbc:postgresql:\/\/main-db:5432\/s3-db       - POSTGRES_USER=admin       - POSTGRES_PASSWORD=admin    main-db:     image: postgres:14-alpine     container_name: main-db     ports:       - \"6551:5432\"     environment:       - POSTGRES_DB=s3-db       - POSTGRES_USER=admin       - POSTGRES_PASSWORD=admin       - TZ=GMT<\/code><\/pre>\n<p>\u0422\u0430\u043a \u043a\u0430\u043a \u0446\u0435\u043b\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043d\u0430 \u043e\u0431\u043b\u0430\u043a\u043e &#8212; \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0438\u0445 \u043f\u0440\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0438 \u043e\u0442\u0437\u044b\u0432\u0430 \u0432 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435, \u043f\u0440\u0438\u043d\u044f\u0442\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0432 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 PostgreSQL \u0441 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u043e\u0439 \u043a \u043e\u0442\u0437\u044b\u0432\u0443 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0438 \u0431\u0435\u0437 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043a S3.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/d4e\/c72\/297\/d4ec722976c4e3bd54a7739cd791962d.jpg\" alt=\"ER \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430\" title=\"ER \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430\" width=\"4424\" height=\"3100\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d4e\/c72\/297\/d4ec722976c4e3bd54a7739cd791962d.jpg\" data-blurred=\"true\"\/><\/p>\n<div><figcaption>ER \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430<\/figcaption><\/div>\n<\/figure>\n<h3>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441<\/h3>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u041d\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u0431\u0443\u0434\u0443, \u0432\u0430\u0436\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0443\u0447\u0435\u0441\u0442\u044c, \u0447\u0442\u043e \u043f\u0435\u0440\u0435\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439 \u043e\u0442\u0437\u044b\u0432\u0430 \u0445\u043e\u0442\u044c \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u0447\u0435\u043b\u043e\u0432\u0435\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d (\u043c\u043e\u0436\u043d\u043e \u0432 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435 \u0438 \u043a\u043e\u0442\u0430 \u0437\u0430\u0440\u0435\u0433\u0430\u0442\u044c).<\/p>\n<pre><code class=\"java\">@RestController @RequiredArgsConstructor @RequestMapping(path = \"\/comments\") public class CommentController {      private final CommentService commentService;      \/**      * \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u043e\u0442\u0437\u044b\u0432\u0430      * @param userId ID \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f      * @param newCommentDto \u0414\u0430\u043d\u043d\u044b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c\u043e\u0433\u043e \u043e\u0442\u0437\u044b\u0432\u0430      * @return \u0421\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u043e\u0442\u0437\u044b\u0432      *\/     @PostMapping(\"\/users\/{userId}\")     @ResponseStatus(HttpStatus.CREATED)     public CommentDto addComment(@PathVariable Long userId, @Validated @ModelAttribute NewCommentDto newCommentDto) {         return commentService.addComment(userId, newCommentDto);     }   }<\/code><\/pre>\n<p>\u0412\u0437\u0433\u043b\u044f\u043d\u0435\u043c \u043d\u0430 DTO \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 <s>\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f<\/s> \u043e\u0442\u0437\u044b\u0432\u0430, \u043e\u043d \u043d\u0430\u043c \u0435\u0449\u0435 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f.<\/p>\n<pre><code class=\"java\">@Data @NoArgsConstructor @AllArgsConstructor public class NewCommentDto {      \/**      * \u0422\u0435\u043a\u0441\u0442 \u043e\u0442\u0437\u044b\u0432\u0430      *\/     @NotNull     @Size(min = 50, max = 2000)     private String text;      \/**      * \u0420\u0435\u0439\u0442\u0438\u043d\u0433, \u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0432 \u043e\u0442\u0437\u044b\u0432\u0435 (\u043e\u0442 1 \u0434\u043e 5)      *\/     @NotNull     @Min(1)     @Max(5)     private int rating;      \/**      * \u0424\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438, \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u043d\u044b\u0435 \u043a \u043e\u0442\u0437\u044b\u0432\u0443      *\/     private List&lt;MultipartFile> photos; }<\/code><\/pre>\n<p>\u0422\u0430\u043a \u043a\u0430\u043a \u043d\u0435 \u0432\u0441\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043e\u0445\u043e\u0442\u043d\u043e \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u044f\u044e\u0442 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u043a \u0441\u0432\u043e\u0438\u043c \u043e\u0442\u0437\u044b\u0432\u0430\u043c, \u0441\u0442\u043e\u0438\u0442 \u0443\u0447\u0435\u0441\u0442\u044c, \u0447\u0442\u043e \u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438 \u0431\u0435\u0437 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439. \u0412 \u0442\u0430\u043a\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u043d \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u0441 \u043f\u0443\u0441\u0442\u044b\u043c \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u0441\u0441\u044b\u043b\u043e\u043a \u043d\u0430 \u0444\u043e\u0442\u043e \u0432 \u043e\u0442\u0432\u0435\u0442\u0435. <strong>\u0410 \u0432\u043e\u0442, \u043a\u0441\u0442\u0430\u0442\u0438, \u0438 \u043e\u0442\u0432\u0435\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430:<\/strong> <\/p>\n<pre><code class=\"java\">@Data @NoArgsConstructor @AllArgsConstructor public class CommentDto {      private Long id;      private String authorName;      private String text;      private String created;      private int rating;      private List&lt;String> photos; }<\/code><\/pre>\n<p>\u041f\u043e\u0440\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0437\u044b\u0432\u0430 \u0432 \u0441\u0435\u0440\u0432\u0438\u0441\u0435. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <em>CommentService<\/em>, \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0435\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442, \u0437\u0430\u0442\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0435\u0433\u043e \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c:<\/p>\n<pre><code class=\"java\">@Service @Slf4j @RequiredArgsConstructor public class CommentServiceImpl implements CommentService {      private final CommentRepository commentRepository;     private final UserService userService;     private final UploadClient uploadClient;      @Override     public CommentDto addComment(Long userId, NewCommentDto newCommentDto) {         User user = userService.getExistingUser(userId);         List&lt;String> photoUrls = new ArrayList&lt;>();          if (newCommentDto.getPhotos() != null) {             ResponseEntity&lt;List&lt;String>> response = uploadClient.upload(newCommentDto.getPhotos());              if (response.getStatusCode().is2xxSuccessful()) {                 List&lt;String> responseBody = response.getBody();                 if (responseBody != null) {                     photoUrls.addAll(responseBody);                 }             } else {                 log.error(\"Cannot upload photos. Upload service returned status {}\", response.getStatusCode());             }         }          Comment comment = CommentMapper.toComment(newCommentDto);         comment.setAuthor(user);         comment.setCreated(LocalDateTime.now());         comment.setPhotos(photoUrls);          Comment addedComment = commentRepository.save(comment);         log.info(\"Added new comment: comment = {}\", addedComment);          return CommentMapper.toCommentDto(addedComment);     }   }<\/code><\/pre>\n<p>\u041e \u0447\u0435\u043c \u044f \u0433\u043e\u0432\u043e\u0440\u0438\u043b \u0440\u0430\u043d\u0435\u0435, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043c\u043e\u0433\u0443\u0442 \u043f\u0438\u0441\u0430\u0442\u044c \u043e\u0442\u0437\u044b\u0432\u044b \u0438 \u043d\u0435 \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u044f\u0442\u044c \u0444\u043e\u0442\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0438 \u0435\u0441\u043b\u0438 \u0438\u0445 \u043d\u0435\u0442 &#8212; \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u043f\u0443\u0441\u0442\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a. <\/p>\n<p>\u0417\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0437\u044b\u0432\u0430 (\u0432\u0441\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u043c \u043f\u0440\u043e DTO \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438) \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u043c <code>Content-Type: multipart\/form-data.<\/code> \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>photos<\/code> \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f58\/e00\/1ed\/f58e001ed03192de6ca9c02449071905.png\" width=\"2398\" height=\"720\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f58\/e00\/1ed\/f58e001ed03192de6ca9c02449071905.png\"\/><\/figure>\n<p>\u041f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043c\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0438\u0445 \u0447\u0435\u0440\u0435\u0437 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 <code>RestTemplate<\/code> <strong>upload-client<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 <em>\u0440\u0430\u0441\u0449\u0435\u043f\u043b\u044f\u0435\u0442<\/em> \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0432 \u0431\u0430\u0439\u0442\u044b, \u0438 \u0437\u0430\u0442\u0435\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0445 \u043d\u0430 <strong>upload-server<\/strong>. \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0432 Yandex Object Storage, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043c\u043d\u043e\u0433\u043e\u043f\u043e\u0442\u043e\u0447\u043d\u043e\u0441\u0442\u044c Java. \u0414\u0432\u0438\u0433\u0430\u0435\u043c\u0441\u044f \u0434\u0430\u043b\u044c\u0448\u0435.<\/p>\n<h3>\u0421\u0435\u0440\u0432\u0438\u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439<\/h3>\n<p>\u041f\u043e\u043b\u0443\u0447\u0438\u0432 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0441 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u0435\u043b\u0430\u0435\u0442 \u0441\u0432\u043e\u0438 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f, \u043e \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u044f \u0433\u043e\u0432\u043e\u0440\u0438\u043b \u0432\u044b\u0448\u0435, \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443.<\/p>\n<pre><code class=\"java\">@RestController @RequiredArgsConstructor public class UploadController {      private final UploadService uploadService;      @PostMapping(\"\/upload\")     @ResponseStatus(HttpStatus.CREATED)     public List&lt;String> upload(@RequestBody List&lt;byte[]> photos) {         return uploadService.uploadPhoto(photos);     } }<\/code><\/pre>\n<h4>\u041a\u0430\u043a \u0442\u0430\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 AWS SDK<\/h4>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0441\u0430\u043c \u043c\u0435\u0442\u043e\u0434 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u0435\u0435. \u041a\u043e\u0434, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u043d\u0438\u0436\u0435, \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a S3, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043a Yandex Object Storage, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f <code>accessKeyId<\/code> \u0438 <code>secretAccessKey<\/code><em> <\/em>&#8212; \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043a\u043b\u044e\u0447\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430. <a href=\"https:\/\/cloud.yandex.ru\/docs\/iam\/operations\/sa\/create-access-key\" rel=\"noopener noreferrer nofollow\">\u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e.<\/a> \u041f\u043e\u043c\u0438\u043c\u043e \u044d\u0442\u043e\u0433\u043e \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0438\u043c\u044f \u0431\u0430\u043a\u0435\u0442\u0430 <code>bucketName<\/code>. \u0412 \u0441\u0432\u043e\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e \u0438\u0445 \u0432 <em>docker-compose.yml<\/em> \u0438 \u0438\u043d\u0436\u0435\u043a\u0447\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u0438 <code>@Value<\/code><\/p>\n<pre><code class=\"java\">s3Client = AmazonS3ClientBuilder.standard()                     .withEndpointConfiguration(                             new com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration(                                     \"https:\/\/storage.yandexcloud.net\",                                     \"ru-central1\"                             )                     )                     .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKeyId, secretAccessKey)))                     .build();<\/code><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044e \u0432 \u0431\u0430\u043a\u0435\u0442, \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 <code>putObject()<\/code><\/p>\n<pre><code class=\"java\">s3Client.putObject(bucketName, fileName, inputStream, metadata);<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u0415\u0449\u0435 \u043c\u043e\u0436\u043d\u043e \u0442\u0430\u043a, \u043d\u043e \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 Yandex Object Storage<\/summary>\n<div class=\"spoiler__content\">\n<p>\u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c, \u043d\u043e \u0447\u0435\u0440\u0435\u0437 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 <code>PutObjectRequest<\/code><\/p>\n<pre><code class=\"java\">s3client.putObject(new PutObjectRequest(bucketName, fileName, inputStream, metadata));<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0447\u0442\u0435\u043d\u0438\u044e \u0444\u0430\u0439\u043b\u0430, \u0435\u0441\u043b\u0438 \u0443 \u0431\u0430\u043a\u0435\u0442\u0430 \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f<\/p>\n<pre><code class=\"java\">s3client.putObject(new PutObjectRequest(bucketName, fileName, inputStream, metadata)                     .withCannedAcl(CannedAccessControlList.PublicRead))<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041e\u0431\u044a\u0435\u043a\u0442 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c. \u041c\u043e\u0436\u043d\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a, \u0434\u043b\u0438\u043d\u0443 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0438 \u0442.\u0434., \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0432\u0430\u0448\u0438\u0445 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u0435\u0439.<\/p>\n<pre><code class=\"java\">ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(photoBytes.length);<\/code><\/pre>\n<p>\u0412 \u043c\u043e\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u043d\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u0441\u044b\u043b\u043e\u043a \u043d\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0438\u0445 \u043a \u043e\u0442\u0437\u044b\u0432\u0443, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0441\u0442\u0430\u043d\u0435\u043c \u0441\u0441\u044b\u043b\u043a\u0443 \u0438\u0437 S3. \u042d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434\u043e\u043c <code>getUrl()<\/code><\/p>\n<pre><code class=\"java\">s3Client.getUrl(bucketName, fileName).toExternalForm();<\/code><\/pre>\n<h4>\u0425\u043e\u0447\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438?<\/h4>\n<p>\u041f\u0440\u0438 \u043f\u043e\u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0439 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0446\u0438\u043a\u043b\u043e\u043c 10 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0432 \u043e\u0442\u0437\u044b\u0432, \u0443 \u043c\u0435\u043d\u044f \u044d\u0442\u043e \u0437\u0430\u043d\u044f\u043b\u043e 6 \u0441\u0435\u043a\u0443\u043d\u0434.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/584\/5c6\/d22\/5845c6d22ca73530751f012159730dc8.png\" alt=\"\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 POST \/comments\/users\/{userId} \u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 10 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043f\u043e\u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\" title=\"\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 POST \/comments\/users\/{userId} \u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 10 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043f\u043e\u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\" width=\"2302\" height=\"710\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/584\/5c6\/d22\/5845c6d22ca73530751f012159730dc8.png\"\/><\/p>\n<div><figcaption>\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 POST \/comments\/users\/{userId} \u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 10 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043f\u043e\u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e<\/figcaption><\/div>\n<\/figure>\n<p>\u0417\u0434\u0435\u0441\u044c \u044f\u0432\u043d\u043e \u043d\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u0430\u0436\u0434\u043e\u0439 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043c\u044b \u043f\u043e\u043d\u0438\u043c\u0430\u0435\u043c, \u0447\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043d\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043a \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u0438\u044e. \u0417\u043d\u0430\u0447\u0438\u0442 \u043f\u043e\u0442\u043e\u043a\u0430\u043c\u0438 \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c. <\/p>\n<p>\u041e\u0431\u0440\u0430\u0449\u0430\u0435\u043c\u0441\u044f \u043a Java Concurrency. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0443\u043b \u043f\u043e\u0442\u043e\u043a\u043e\u0432 <code>ExecutorService<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441\u043f\u0438\u0441\u043e\u043a <code>Future<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043e\u0431\u0435\u0449\u0430\u043d\u0438\u0435 \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430. \u041a\u0430\u0436\u0434\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c URL \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0432 \u0432\u0438\u0434\u0435 \u0441\u0442\u0440\u043e\u043a\u0438, \u0438 \u044d\u0442\u0438 \u043e\u0431\u0435\u0449\u0430\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u0447.<\/p>\n<pre><code class=\"java\">ExecutorService executorService = Executors.newFixedThreadPool(photos.size());  List&lt;Future&lt;String>> futures = new ArrayList&lt;>();<\/code><\/pre>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <code>executorService.submit()<\/code> \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0437\u0430\u0434\u0430\u0447 \u043d\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0432 \u043f\u0443\u043b \u043f\u043e\u0442\u043e\u043a\u043e\u0432. \u042d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 <code>Callable&lt;String><\/code> (\u0438\u043b\u0438 <code>Runnable<\/code>) \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 <code>Future<\/code>, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0449\u0438\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u0447\u0438. \u0412\u043d\u0443\u0442\u0440\u0438 \u043b\u044f\u043c\u0431\u0434\u0430-\u0432\u044b\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043a\u043e\u0434 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f URL. \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 (URL) \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442\u0441\u044f \u0438\u0437 \u0437\u0430\u0434\u0430\u0447\u0438.<\/p>\n<pre><code class=\"java\">Future&lt;String> future = executorService.submit(() -> {     \/\/ \u043a\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f URL     return url; });  futures.add(future);<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0432\u0441\u0435\u0445 \u0437\u0430\u0434\u0430\u0447 \u043d\u0430 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435, \u043c\u044b \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u043c \u043f\u043e \u0441\u043f\u0438\u0441\u043a\u0443 <code>futures<\/code> \u0438 \u043e\u0436\u0438\u0434\u0430\u0435\u043c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430 <code>future.get()<\/code>. \u042d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u043e \u0442\u0435\u0445 \u043f\u043e\u0440, \u043f\u043e\u043a\u0430 \u0437\u0430\u0434\u0430\u0447\u0430 \u043d\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u0441\u044f, \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442. \u0418, \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u0432 \u043a\u043e\u043d\u0446\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c <code>executorService.shutdown()<\/code> \u0434\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0433\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u043f\u0443\u043b\u0430 \u043f\u043e\u0442\u043e\u043a\u043e\u0432. <\/p>\n<p>\u0412 \u0438\u0442\u043e\u0433\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u043b\u0430\u0441\u044c <strong>\u0432 5 \u0440\u0430\u0437<\/strong>. \u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0442\u0437\u044b\u0432\u0430 \u0441 \u0442\u0430\u043a\u0438\u043c\u0438 \u0436\u0435 \u0434\u0435\u0441\u044f\u0442\u044c\u044e \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044f\u043c\u0438 \u0437\u0430\u043d\u044f\u043b\u043e 1 \u0441 \u043b\u0438\u0448\u043d\u0438\u043c \u0441\u0435\u043a\u0443\u043d\u0434\u0443.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4d4\/aa6\/361\/4d4aa636176caf5cbb26bb6626592719.png\" alt=\"\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 POST \/comments\/users\/{userId} \u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 10 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u0442\u043e\u043a\u0430\u0445\" title=\"\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 POST \/comments\/users\/{userId} \u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 10 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u0442\u043e\u043a\u0430\u0445\" width=\"2260\" height=\"706\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/4d4\/aa6\/361\/4d4aa636176caf5cbb26bb6626592719.png\"\/><\/p>\n<div><figcaption>\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 POST \/comments\/users\/{userId} \u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 10 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u0442\u043e\u043a\u0430\u0445<\/figcaption><\/div>\n<\/figure>\n<h4>\u0418\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u043a\u043b\u0430\u0441\u0441\u0430 UploadService<\/h4>\n<p>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043c\u0435\u0442\u043e\u0434\u0430 <code>upload()<\/code> \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u0430\u0441\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"java\">@Service @Slf4j @RequiredArgsConstructor public class UploadServiceImpl implements UploadService {      private final ObjectStorageConfig objectStorageConfig;      @Override     public List&lt;String> uploadPhoto(List&lt;byte[]> photos) {         List&lt;String> urls = new ArrayList&lt;>();          final String bucketName = objectStorageConfig.getBucketName();         final String accessKeyId = objectStorageConfig.getAccessKeyId();         final String secretAccessKey = objectStorageConfig.getSecretAccessKey();         final AmazonS3 s3Client;          try {             s3Client = AmazonS3ClientBuilder.standard()                     .withEndpointConfiguration(                             new com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration(                                     \"https:\/\/storage.yandexcloud.net\",                                     \"ru-central1\"                             )                     )                     .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKeyId, secretAccessKey)))                     .build();         } catch (SdkClientException e) {             log.error(\"Error creating client for Object Storage via AWS SDK. Reason: {}\", e.getMessage());             throw new SdkClientException(e.getMessage());         }          try {             ExecutorService executorService = Executors.newFixedThreadPool(photos.size());             List&lt;Future&lt;String>> futures = new ArrayList&lt;>();              for (byte[] photoBytes : photos) {                 Future&lt;String> future = executorService.submit(() -> {                     String fileName = generateUniqueName();                     ObjectMetadata metadata = new ObjectMetadata();                     metadata.setContentLength(photoBytes.length);                      ByteArrayInputStream inputStream = new ByteArrayInputStream(photoBytes);                     s3Client.putObject(bucketName, fileName, inputStream, metadata);                     log.info(\"Upload Service. Added file: \" + fileName + \" to bucket: \" + bucketName);                      \/\/ \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b                     String url = s3Client.getUrl(bucketName, fileName).toExternalForm();                      return url;                 });                  futures.add(future);             }                        for (Future&lt;String> future : futures) {                 try {                     String url = future.get();                     urls.add(url);                 } catch (InterruptedException | ExecutionException e) {                     log.error(\"One of the thread ended with exception. Reason: {}\", e.getMessage());                     throw new RuntimeException(e);                 }             }              executorService.shutdown();         } catch (AmazonS3Exception e) {             log.error(\"Error uploading photos to Object Storage. Reason: {}\", e.getMessage());             throw new AmazonS3Exception(e.getMessage());         }         return urls;     }      private String generateUniqueName() {         UUID uuid = UUID.randomUUID();         return uuid.toString();     } }<\/code><\/pre>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 AWS SDK \u0434\u043b\u044f Java \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043b\u0435\u0433\u043a\u043e \u0438\u043d\u0442\u0435\u0433\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 Yandex Object Storage \u0438\u043b\u0438 Amazon S3 \u0432 \u0432\u0430\u0448\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u044f \u043d\u0430\u0434\u0435\u0436\u043d\u043e\u0435 \u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u0443\u0435\u043c\u043e\u0435 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0444\u0430\u0439\u043b\u0430\u043c. \u041f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u044d\u0442\u043e\u0433\u043e SDK \u043c\u043e\u0436\u0435\u0442 \u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442\u044c \u0437\u0430\u0434\u0430\u0447\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043e\u0431\u044a\u0435\u043a\u0442\u043d\u044b\u043c\u0438 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430\u043c\u0438, \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044f \u0441\u043e\u0441\u0440\u0435\u0434\u043e\u0442\u043e\u0447\u0438\u0442\u044c\u0441\u044f \u043d\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0432\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>\u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0443 \u043c\u0435\u043d\u044f \u043f\u043e\u043b\u0443\u0447\u0438\u043b\u043e\u0441\u044c \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0439 \u0433\u0430\u0439\u0434 \u0438 \u0440\u0430\u0441\u043a\u0440\u044b\u0442\u044c \u0434\u0430\u043d\u043d\u0443\u044e \u0442\u0435\u043c\u0443. \u0412\u0435\u0441\u044c \u043a\u043e\u0434 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043b <a href=\"https:\/\/github.com\/NikitaBuffy\/java-s3\" rel=\"noopener noreferrer nofollow\">\u0443 \u0441\u0435\u0431\u044f \u0432 GitHub<\/a>. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0435\u0433\u043e \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u044d\u0442\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e. <\/p>\n<p>\u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0432\u0441\u0435\u043c, \u043a\u0442\u043e \u0434\u043e\u0447\u0438\u0442\u0430\u043b \u0434\u043e \u043a\u043e\u043d\u0446\u0430! \u0411\u0443\u0434\u0443 \u0440\u0430\u0434 \u0432\u0430\u0448\u0438\u043c \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u043c, \u0441\u043e\u0432\u0435\u0442\u0430\u043c \u0438 <em>\u043a\u0440\u0438\u0442\u0438\u043a\u0435, <\/em>\u0430 \u0442\u0430\u043a\u0436\u0435 best-practice \u0432 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430 \u0432 \u043a\u043e\u043c\u043c\u0435\u0440\u0447\u0435\u0441\u043a\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0435! <\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/762800\/\"> https:\/\/habr.com\/ru\/articles\/762800\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u0430 &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0437\u044b\u0432\u043e\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c\u0438 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u0438\u0442\u044c \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438, \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0438\u0445 \u0432 \u043e\u0431\u043b\u0430\u043a\u043e \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0443 \u0444\u0430\u0439\u043b \u0438\u0437 S3, \u0430 \u0438\u043c\u0435\u043d\u043d\u043e Yandex Object Storage, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f AWS SDK Java. <\/p>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u041d\u0438\u043a\u0438\u0442\u0430, \u044f \u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0438\u0439 Java \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a. \u0412 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u0441\u0432\u043e\u0438\u0445 \u0443\u0447\u0435\u0431\u043d\u044b\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0441\u043e\u0437\u0434\u0430\u043b \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u043c \u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0438\u0306 \u043e\u0431 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0445 \u0438 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u0434\u043b\u044f \u0443\u0447\u0430\u0441\u0442\u0438\u044f \u0432 \u043d\u0438\u0445.<\/p>\n<p>\u041e\u0434\u043d\u0430 \u0438\u0437 \u0444\u0438\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f &#8212; \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0437\u044b\u0432\u043e\u0432 \u043e \u043f\u043e\u0441\u0435\u0449\u0435\u043d\u043d\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u0445. \u041a\u0430\u043a \u0438 \u0443 \u043c\u043d\u043e\u0433\u0438\u0445 \u043f\u043e\u0434\u043e\u0431\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u043a \u0441\u0432\u043e\u0438\u043c \u043e\u0442\u0437\u044b\u0432\u0430\u043c &#8212; \u043c\u0430\u0441\u0442\u0445\u0435\u0432. \u041f\u044b\u0442\u0430\u044f\u0441\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u0443 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c, \u043d\u0435 \u0441\u043c\u043e\u0433 \u043d\u0430\u0439\u0442\u0438 \u043d\u0438 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043c\u043e\u0436\u043d\u043e \u0443\u0433\u043b\u0443\u0431\u0438\u0442\u044c\u0441\u044f \u0432 \u0442\u0435\u043c\u0443 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u043e. \u041f\u043e\u044d\u0442\u043e\u043c\u0443, \u0441\u043e\u0431\u0440\u0430\u0432 \u0432\u0441\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u044c\u0441\u044f, \u0441\u043f\u0435\u0448\u0443 \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441\u0432\u043e\u0438\u043c \u0440\u0435\u0448\u0435\u043d\u0438\u0435\u043c!<\/p>\n<h2>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0434\u0435\u043b\u0443<\/h2>\n<h3>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h3>\n<p>Java 11, Maven, Spring Boot, Hibernate, Lombok, Docker, RestTemplate, <a href=\"https:\/\/aws.amazon.com\/ru\/sdk-for-java\/\" rel=\"noopener noreferrer nofollow\">AWS SDK<\/a><\/p>\n<p>\u041f\u0440\u043e\u0435\u043a\u0442 \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d \u043d\u0430 \u0434\u0432\u0430 \u0441\u0435\u0440\u0432\u0438\u0441\u0430: <\/p>\n<ul>\n<li>\n<p><strong>main<\/strong> &#8212; \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e, \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044e \u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044e \u043e\u0442\u0437\u044b\u0432\u043e\u0432 \u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439. \u0412 \u0440\u0430\u043c\u043a\u0430\u0445 \u0434\u0430\u043d\u043d\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u0438\u043c\u0435\u0435\u043c \u0434\u0432\u0435 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438: <strong>Comment<\/strong> \u0438 <strong>User<\/strong><\/p>\n<\/li>\n<li>\n<p><strong>upload<\/strong> &#8212; \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 <strong>main <\/strong>\u0438 \u0441\u0430\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043d\u0430 S3<\/p>\n<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 AWS SDK \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0438\u043c\u0435\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0432 <em>upload-\u0441\u0435\u0440\u0432\u0438\u0441\u0435:<\/em><\/p>\n<pre><code class=\"xml\">&lt;dependency>     &lt;groupId>com.amazonaws&lt;\/groupId>     &lt;artifactId>aws-java-sdk&lt;\/artifactId>     &lt;version>1.12.429&lt;\/version> &lt;\/dependency>  &lt;dependency>     &lt;groupId>javax.xml.bind&lt;\/groupId>     &lt;artifactId>jaxb-api&lt;\/artifactId>     &lt;version>2.3.1&lt;\/version> &lt;\/dependency><\/code><\/pre>\n<p>docker-compose.yml:<\/p>\n<pre><code class=\"yaml\">version: '3.1' services:   upload-service:     build: upload\/upload-server     image: upload-server     container_name: upload-server     ports:       - \"9090:9090\"     environment:       - BUCKET_NAME=your_bucket       - ACCESS_KEY_ID=your_key       - SECRET_KEY=your_secret    s3-service:     build: main     image: main-service     container_name: main-service     ports:       - \"8080:8080\"     depends_on:       - main-db     environment:       - UPLOAD_SERVER_URL=http:\/\/upload-server:9090       - SPRING_DATASOURCE_URL=jdbc:postgresql:\/\/main-db:5432\/s3-db       - POSTGRES_USER=admin       - POSTGRES_PASSWORD=admin    main-db:     image: postgres:14-alpine     container_name: main-db     ports:       - \"6551:5432\"     environment:       - POSTGRES_DB=s3-db       - POSTGRES_USER=admin       - POSTGRES_PASSWORD=admin       - TZ=GMT<\/code><\/pre>\n<p>\u0422\u0430\u043a \u043a\u0430\u043a \u0446\u0435\u043b\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043d\u0430 \u043e\u0431\u043b\u0430\u043a\u043e &#8212; \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0438\u0445 \u043f\u0440\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0438 \u043e\u0442\u0437\u044b\u0432\u0430 \u0432 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435, \u043f\u0440\u0438\u043d\u044f\u0442\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0432 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 PostgreSQL \u0441 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u043e\u0439 \u043a \u043e\u0442\u0437\u044b\u0432\u0443 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0438 \u0431\u0435\u0437 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e\u0433\u043e \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u044f \u043a S3.<\/p>\n<figure class=\"full-width\">\n<div><figcaption>ER \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0430<\/figcaption><\/div>\n<\/figure>\n<h3>\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0441\u0435\u0440\u0432\u0438\u0441<\/h3>\n<p>\u0420\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u044b \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430. \u041d\u0430 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u0431\u0443\u0434\u0443, \u0432\u0430\u0436\u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0443\u0447\u0435\u0441\u0442\u044c, \u0447\u0442\u043e \u043f\u0435\u0440\u0435\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439 \u043e\u0442\u0437\u044b\u0432\u0430 \u0445\u043e\u0442\u044c \u043a\u0430\u043a\u043e\u0439-\u0442\u043e \u0447\u0435\u043b\u043e\u0432\u0435\u043a \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u043d (\u043c\u043e\u0436\u043d\u043e \u0432 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0435 \u0438 \u043a\u043e\u0442\u0430 \u0437\u0430\u0440\u0435\u0433\u0430\u0442\u044c).<\/p>\n<pre><code class=\"java\">@RestController @RequiredArgsConstructor @RequestMapping(path = \"\/comments\") public class CommentController {      private final CommentService commentService;      \/**      * \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u043e\u0442\u0437\u044b\u0432\u0430      * @param userId ID \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f      * @param newCommentDto \u0414\u0430\u043d\u043d\u044b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c\u043e\u0433\u043e \u043e\u0442\u0437\u044b\u0432\u0430      * @return \u0421\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 \u043e\u0442\u0437\u044b\u0432      *\/     @PostMapping(\"\/users\/{userId}\")     @ResponseStatus(HttpStatus.CREATED)     public CommentDto addComment(@PathVariable Long userId, @Validated @ModelAttribute NewCommentDto newCommentDto) {         return commentService.addComment(userId, newCommentDto);     }   }<\/code><\/pre>\n<p>\u0412\u0437\u0433\u043b\u044f\u043d\u0435\u043c \u043d\u0430 DTO \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 <s>\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f<\/s> \u043e\u0442\u0437\u044b\u0432\u0430, \u043e\u043d \u043d\u0430\u043c \u0435\u0449\u0435 \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f.<\/p>\n<pre><code class=\"java\">@Data @NoArgsConstructor @AllArgsConstructor public class NewCommentDto {      \/**      * \u0422\u0435\u043a\u0441\u0442 \u043e\u0442\u0437\u044b\u0432\u0430      *\/     @NotNull     @Size(min = 50, max = 2000)     private String text;      \/**      * \u0420\u0435\u0439\u0442\u0438\u043d\u0433, \u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u0432 \u043e\u0442\u0437\u044b\u0432\u0435 (\u043e\u0442 1 \u0434\u043e 5)      *\/     @NotNull     @Min(1)     @Max(5)     private int rating;      \/**      * \u0424\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438, \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u043d\u044b\u0435 \u043a \u043e\u0442\u0437\u044b\u0432\u0443      *\/     private List&lt;MultipartFile> photos; }<\/code><\/pre>\n<p>\u0422\u0430\u043a \u043a\u0430\u043a \u043d\u0435 \u0432\u0441\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043e\u0445\u043e\u0442\u043d\u043e \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u044f\u044e\u0442 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u043a \u0441\u0432\u043e\u0438\u043c \u043e\u0442\u0437\u044b\u0432\u0430\u043c, \u0441\u0442\u043e\u0438\u0442 \u0443\u0447\u0435\u0441\u0442\u044c, \u0447\u0442\u043e \u0435\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0438 \u0431\u0435\u0437 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439. \u0412 \u0442\u0430\u043a\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u043d \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e \u0441 \u043f\u0443\u0441\u0442\u044b\u043c \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u0441\u0441\u044b\u043b\u043e\u043a \u043d\u0430 \u0444\u043e\u0442\u043e \u0432 \u043e\u0442\u0432\u0435\u0442\u0435. <strong>\u0410 \u0432\u043e\u0442, \u043a\u0441\u0442\u0430\u0442\u0438, \u0438 \u043e\u0442\u0432\u0435\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430:<\/strong> <\/p>\n<pre><code class=\"java\">@Data @NoArgsConstructor @AllArgsConstructor public class CommentDto {      private Long id;      private String authorName;      private String text;      private String created;      private int rating;      private List&lt;String> photos; }<\/code><\/pre>\n<p>\u041f\u043e\u0440\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0437\u044b\u0432\u0430 \u0432 \u0441\u0435\u0440\u0432\u0438\u0441\u0435. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 <em>CommentService<\/em>, \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0435\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442, \u0437\u0430\u0442\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0435\u0433\u043e \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044e \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c:<\/p>\n<pre><code class=\"java\">@Service @Slf4j @RequiredArgsConstructor public class CommentServiceImpl implements CommentService {      private final CommentRepository commentRepository;     private final UserService userService;     private final UploadClient uploadClient;      @Override     public CommentDto addComment(Long userId, NewCommentDto newCommentDto) {         User user = userService.getExistingUser(userId);         List&lt;String> photoUrls = new ArrayList&lt;>();          if (newCommentDto.getPhotos() != null) {             ResponseEntity&lt;List&lt;String>> response = uploadClient.upload(newCommentDto.getPhotos());              if (response.getStatusCode().is2xxSuccessful()) {                 List&lt;String> responseBody = response.getBody();                 if (responseBody != null) {                     photoUrls.addAll(responseBody);                 }             } else {                 log.error(\"Cannot upload photos. Upload service returned status {}\", response.getStatusCode());             }         }          Comment comment = CommentMapper.toComment(newCommentDto);         comment.setAuthor(user);         comment.setCreated(LocalDateTime.now());         comment.setPhotos(photoUrls);          Comment addedComment = commentRepository.save(comment);         log.info(\"Added new comment: comment = {}\", addedComment);          return CommentMapper.toCommentDto(addedComment);     }   }<\/code><\/pre>\n<p>\u041e \u0447\u0435\u043c \u044f \u0433\u043e\u0432\u043e\u0440\u0438\u043b \u0440\u0430\u043d\u0435\u0435, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043c\u043e\u0433\u0443\u0442 \u043f\u0438\u0441\u0430\u0442\u044c \u043e\u0442\u0437\u044b\u0432\u044b \u0438 \u043d\u0435 \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u044f\u0442\u044c \u0444\u043e\u0442\u043e, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0438 \u0435\u0441\u043b\u0438 \u0438\u0445 \u043d\u0435\u0442 &#8212; \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u043f\u0443\u0441\u0442\u043e\u0439 \u0441\u043f\u0438\u0441\u043e\u043a. <\/p>\n<p>\u0417\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043e\u0442\u0437\u044b\u0432\u0430 (\u0432\u0441\u043f\u043e\u043c\u0438\u043d\u0430\u0435\u043c \u043f\u0440\u043e DTO \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438) \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u043e\u043c <code>Content-Type: multipart\/form-data.<\/code> \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 <code>photos<\/code> \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u043e\u0436\u0435\u0442 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c.<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u041f\u0440\u0438 \u043d\u0430\u043b\u0438\u0447\u0438\u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043c\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0438\u0445 \u0447\u0435\u0440\u0435\u0437 \u0441\u043e\u0437\u0434\u0430\u043d\u043d\u044b\u0439 <code>RestTemplate<\/code> <strong>upload-client<\/strong>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 <em>\u0440\u0430\u0441\u0449\u0435\u043f\u043b\u044f\u0435\u0442<\/em> \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0432 \u0431\u0430\u0439\u0442\u044b, \u0438 \u0437\u0430\u0442\u0435\u043c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0445 \u043d\u0430 <strong>upload-server<\/strong>. \u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0432 Yandex Object Storage, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043c\u043d\u043e\u0433\u043e\u043f\u043e\u0442\u043e\u0447\u043d\u043e\u0441\u0442\u044c Java. \u0414\u0432\u0438\u0433\u0430\u0435\u043c\u0441\u044f \u0434\u0430\u043b\u044c\u0448\u0435.<\/p>\n<h3>\u0421\u0435\u0440\u0432\u0438\u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439<\/h3>\n<p>\u041f\u043e\u043b\u0443\u0447\u0438\u0432 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0441 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u043a\u043b\u0438\u0435\u043d\u0442 \u0434\u0435\u043b\u0430\u0435\u0442 \u0441\u0432\u043e\u0438 \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f, \u043e \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u044f \u0433\u043e\u0432\u043e\u0440\u0438\u043b \u0432\u044b\u0448\u0435, \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0437\u0430\u043f\u0440\u043e\u0441 \u043d\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443.<\/p>\n<pre><code class=\"java\">@RestController @RequiredArgsConstructor public class UploadController {      private final UploadService uploadService;      @PostMapping(\"\/upload\")     @ResponseStatus(HttpStatus.CREATED)     public List&lt;String> upload(@RequestBody List&lt;byte[]> photos) {         return uploadService.uploadPhoto(photos);     } }<\/code><\/pre>\n<h4>\u041a\u0430\u043a \u0442\u0430\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 AWS SDK<\/h4>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0441\u0430\u043c \u043c\u0435\u0442\u043e\u0434 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0434\u0435\u0442\u0430\u043b\u044c\u043d\u0435\u0435. \u041a\u043e\u0434, \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0439 \u043d\u0438\u0436\u0435, \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a S3, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043a Yandex Object Storage, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f <code>accessKeyId<\/code> \u0438 <code>secretAccessKey<\/code><em> <\/em>&#8212; \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043a\u043b\u044e\u0447\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430. <a href=\"https:\/\/cloud.yandex.ru\/docs\/iam\/operations\/sa\/create-access-key\" rel=\"noopener noreferrer nofollow\">\u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e.<\/a> \u041f\u043e\u043c\u0438\u043c\u043e \u044d\u0442\u043e\u0433\u043e \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f \u0438\u043c\u044f \u0431\u0430\u043a\u0435\u0442\u0430 <code>bucketName<\/code>. \u0412 \u0441\u0432\u043e\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u044f \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e \u0438\u0445 \u0432 <em>docker-compose.yml<\/em> \u0438 \u0438\u043d\u0436\u0435\u043a\u0447\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u0438 <code>@Value<\/code><\/p>\n<pre><code class=\"java\">s3Client = AmazonS3ClientBuilder.standard()                     .withEndpointConfiguration(                             new com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration(                                     \"https:\/\/storage.yandexcloud.net\",                                     \"ru-central1\"                             )                     )                     .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKeyId, secretAccessKey)))                     .build();<\/code><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u044e \u0432 \u0431\u0430\u043a\u0435\u0442, \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 <code>putObject()<\/code><\/p>\n<pre><code class=\"java\">s3Client.putObject(bucketName, fileName, inputStream, metadata);<\/code><\/pre>\n<details class=\"spoiler\">\n<summary>\u0415\u0449\u0435 \u043c\u043e\u0436\u043d\u043e \u0442\u0430\u043a, \u043d\u043e \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 Yandex Object Storage<\/summary>\n<div class=\"spoiler__content\">\n<p>\u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u043c, \u043d\u043e \u0447\u0435\u0440\u0435\u0437 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 <code>PutObjectRequest<\/code><\/p>\n<pre><code class=\"java\">s3client.putObject(new PutObjectRequest(bucketName, fileName, inputStream, metadata));<\/code><\/pre>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0447\u0442\u0435\u043d\u0438\u044e \u0444\u0430\u0439\u043b\u0430, \u0435\u0441\u043b\u0438 \u0443 \u0431\u0430\u043a\u0435\u0442\u0430 \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u043f\u0443\u0431\u043b\u0438\u0447\u043d\u044b\u0439 \u0434\u043e\u0441\u0442\u0443\u043f<\/p>\n<pre><code class=\"java\">s3client.putObject(new PutObjectRequest(bucketName, fileName, inputStream, metadata)                     .withCannedAcl(CannedAccessControlList.PublicRead))<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041e\u0431\u044a\u0435\u043a\u0442 \u043c\u0435\u0442\u0430\u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c. \u041c\u043e\u0436\u043d\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a, \u0434\u043b\u0438\u043d\u0443 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0438 \u0442.\u0434., \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0432\u0430\u0448\u0438\u0445 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u0435\u0439.<\/p>\n<pre><code class=\"java\">ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentLength(photoBytes.length);<\/code><\/pre>\n<p>\u0412 \u043c\u043e\u0435\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043c\u043d\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0441\u0441\u044b\u043b\u043e\u043a \u043d\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0438\u0445 \u043a \u043e\u0442\u0437\u044b\u0432\u0443, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043e\u0441\u0442\u0430\u043d\u0435\u043c \u0441\u0441\u044b\u043b\u043a\u0443 \u0438\u0437 S3. \u042d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434\u043e\u043c <code>getUrl()<\/code><\/p>\n<pre><code class=\"java\">s3Client.getUrl(bucketName, fileName).toExternalForm();<\/code><\/pre>\n<h4>\u0425\u043e\u0447\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438?<\/h4>\n<p>\u041f\u0440\u0438 \u043f\u043e\u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e\u0439 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0446\u0438\u043a\u043b\u043e\u043c 10 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u0432 \u043e\u0442\u0437\u044b\u0432, \u0443 \u043c\u0435\u043d\u044f \u044d\u0442\u043e \u0437\u0430\u043d\u044f\u043b\u043e 6 \u0441\u0435\u043a\u0443\u043d\u0434.<\/p>\n<figure class=\"full-width\">\n<div><figcaption>\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 POST \/comments\/users\/{userId} \u0441 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 10 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043f\u043e\u043e\u0447\u0435\u0440\u0435\u0434\u043d\u043e<\/figcaption><\/div>\n<\/figure>\n<p>\u0417\u0434\u0435\u0441\u044c \u044f\u0432\u043d\u043e \u043d\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u0430\u0436\u0434\u043e\u0439 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435, \u043f\u0440\u0438 \u044d\u0442\u043e\u043c \u043c\u044b \u043f\u043e\u043d\u0438\u043c\u0430\u0435\u043c, \u0447\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u0435 \u043d\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439 \u043a \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u0438\u044e. \u0417\u043d\u0430\u0447\u0438\u0442 \u043f\u043e\u0442\u043e\u043a\u0430\u043c\u0438 \u043c\u043e\u0436\u043d\u043e \u043b\u0435\u0433\u043a\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c. <\/p>\n<p>\u041e\u0431\u0440\u0430\u0449\u0430\u0435\u043c\u0441\u044f \u043a Java Concurrency. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0443\u043b \u043f\u043e\u0442\u043e\u043a\u043e\u0432 <code>ExecutorService<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0439, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441\u043f\u0438\u0441\u043e\u043a <code>Future<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043e\u0431\u0435\u0449\u0430\u043d\u0438\u0435 \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430. \u041a\u0430\u0436\u0434\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u043e\u0442\u043e\u0433\u0440\u0430\u0444\u0438\u0438 \u0431\u0443\u0434\u0435\u0442 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c URL \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0432 \u0432\u0438\u0434\u0435 \u0441\u0442\u0440\u043e\u043a\u0438, \u0438 \u044d\u0442\u0438 \u043e\u0431\u0435\u0449\u0430\u043d\u0438\u044f \u0431\u0443\u0434\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0437\u0430\u0434\u0430\u0447.<\/p>\n<pre><code class=\"java\">ExecutorService executorService =<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-355600","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/355600","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=355600"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/355600\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=355600"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=355600"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=355600"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}