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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.mysql:mysql-connector-j'

// s3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'


// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Expand Down
34 changes: 34 additions & 0 deletions src/main/java/com/blog/common/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.blog.common.config;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class S3Config {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;

@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;

@Value("${cloud.aws.region.static}")
private String region;

@Bean
public AmazonS3 amazonS3() {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);

return AmazonS3ClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region)
.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

@RestController
@RequestMapping("/auth")
public class AuthController {

private static final Logger log = LoggerFactory.getLogger(AuthController.class);
// 자체 회원가입
private final RegisterUserUseCase registerService;

Expand All @@ -36,7 +41,7 @@ public AuthController(RegisterUserUseCase registerService, AuthUserUseCase authS

/// 자체 회원가입 기능
@PostMapping
public ApiResponse<String> register(@RequestBody @Valid UserRegisterRequest request) {
public ApiResponse<String> register(@ModelAttribute UserRegisterRequest request) throws IOException {

registerService.registerUser(request);
return ApiResponse.ok("회원 가입 성공했습니다.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

@RestController
@RequestMapping("/posts")
public class PostController {
Expand All @@ -24,7 +26,7 @@ public PostController(PostUseCase postService) {

// 게시글 작성
@PostMapping
ApiResponse<String> createPost(@RequestUserId Long userId, @RequestBody @Valid PostRequest postRequest) {
ApiResponse<String> createPost(@RequestUserId Long userId, @ModelAttribute @Valid PostRequest postRequest) throws IOException {

postService.savePost(postRequest, userId);

Expand Down Expand Up @@ -52,7 +54,7 @@ ApiResponse<Page<PostListResponse>> getPostList(@RequestParam(required = false,

// 게시글 수정
@PutMapping("/{postId}")
ApiResponse<String> updatePost(@RequestUserId Long userId, @PathVariable Long postId, @RequestBody PostUpdateRequest request) {
ApiResponse<String> updatePost(@RequestUserId Long userId, @PathVariable Long postId, @ModelAttribute PostUpdateRequest request) throws IOException {

postService.updatePost(postId, userId, request);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.blog.workspace.domain.user.User;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;

@RestController
@RequestMapping("/users")
public class UserController {
Expand All @@ -31,7 +33,7 @@ public ApiResponse<UserResponse> getMyInfo(@RequestUserId Long userId) {
}

@PutMapping()
public ApiResponse<String> updateMyInfo(@RequestUserId Long userId, @RequestBody UserUpdateRequest request) {
public ApiResponse<String> updateMyInfo(@RequestUserId Long userId, @ModelAttribute UserUpdateRequest request) throws IOException {

updateService.updateUser(userId, request);
return ApiResponse.ok("수정되었습니다.");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package com.blog.workspace.adapter.in.web.dto.request;

import com.blog.workspace.domain.post.ContentType;
import org.springframework.web.multipart.MultipartFile;

public class ContentRequest {

private ContentType type;
private String content;
private MultipartFile image;

public ContentRequest(ContentType type, String content, MultipartFile image) {
this.type = type;
this.content = content;
this.image = image;
}

/// @Getter
public ContentType getType() {
Expand All @@ -15,4 +23,8 @@ public ContentType getType() {
public String getContent() {
return content;
}

public MultipartFile getImage() {
return image;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ public class PostRequest {
@NotNull(message = "글 작성은 필수입니다.")
private List<ContentRequest> content;

public PostRequest(String title, List<ContentRequest> content) {
this.title = title;
this.content = content;
}

public String getTitle() {
return title;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ public class PostUpdateRequest {

private List<ContentRequest> content;


public PostUpdateRequest(String title, List<ContentRequest> content) {
this.title = title;
this.content = content;
}

/**@Getter
*/
public String getTitle() {
return title;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.blog.workspace.adapter.in.web.dto.request;

import jakarta.validation.constraints.*;
import org.springframework.web.multipart.MultipartFile;

public class UserRegisterRequest {

Expand All @@ -22,7 +23,7 @@ public class UserRegisterRequest {
@NotNull(message = "passwordCheck는 반드시 입력해야하는 필수 사항입니다!")
private String passwordCheck;

private String imageUrl;
private MultipartFile imageUrl;

@NotNull(message = "description는 반드시 입력해야하는 필수 사항입니다!")
@Size(max = 30, message = "한 줄 소개는 최대 30글자 입니다.")
Expand All @@ -32,6 +33,17 @@ public class UserRegisterRequest {
// 3월 16일 이전의 경우는 입력이 안되도록 추가 설정
private String birthday;

public UserRegisterRequest(String email, String username, String nickname, String password, String passwordCheck, MultipartFile imageUrl, String description, String birthday) {
this.email = email;
this.username = username;
this.nickname = nickname;
this.password = password;
this.passwordCheck = passwordCheck;
this.imageUrl = imageUrl;
this.description = description;
this.birthday = birthday;
}


// Getter
public String getEmail() {
Expand All @@ -54,7 +66,7 @@ public String getPasswordCheck() {
return passwordCheck;
}

public String getImageUrl() {
public MultipartFile getImageUrl() {
return imageUrl;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import org.springframework.web.multipart.MultipartFile;

public class UserUpdateRequest {

Expand All @@ -16,14 +17,23 @@ public class UserUpdateRequest {
@NotBlank(message = "passwordCheck는 반드시 입력해야하는 필수 사항입니다!")
private String passwordCheck;

private String imageUrl;
private MultipartFile imageUrl;

@NotBlank(message = "description는 반드시 입력해야하는 필수 사항입니다!")
private String description;

@NotBlank(message = "descripbirthdaytion는 반드시 입력해야하는 필수 사항입니다!")
private String birthday;

public UserUpdateRequest(String nickname, String password, String passwordCheck, MultipartFile imageUrl, String description, String birthday) {
this.nickname = nickname;
this.password = password;
this.passwordCheck = passwordCheck;
this.imageUrl = imageUrl;
this.description = description;
this.birthday = birthday;
}


public String getNickname() {
return nickname;
Expand All @@ -33,7 +43,7 @@ public String getPassword() {
return password;
}

public String getImageUrl() {
public MultipartFile getImageUrl() {
return imageUrl;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ public class UserResponse {

private final Long id;
private final String nickname;
private final String imageUrl;
private final String description;

public UserResponse(User user) {
this.id = user.getId();
this.nickname = user.getNickname();
this.description = user.getDescription();
this.imageUrl = user.getImageUrl();
}

/// @Getter
Expand All @@ -29,4 +31,8 @@ public String getNickname() {
public String getDescription() {
return description;
}

public String getImageUrl() {
return imageUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import com.blog.workspace.adapter.out.jdbc.post.ContentBlockJdbc;
import com.blog.workspace.adapter.out.jdbc.post.ContentBlockJdbcRepository;
import com.blog.workspace.application.out.image.ImagePort;
import com.blog.workspace.application.out.post.ContentBlockPort;
import com.blog.workspace.domain.post.ContentBlock;
import com.blog.workspace.domain.post.ContentType;
import org.springframework.stereotype.Component;

import java.util.List;
Expand All @@ -12,9 +14,11 @@
public class ContentBlockPersistenceAdapter implements ContentBlockPort {

private final ContentBlockJdbcRepository repository;
private final ImagePort imagePort;

public ContentBlockPersistenceAdapter(ContentBlockJdbcRepository repository) {
public ContentBlockPersistenceAdapter(ContentBlockJdbcRepository repository, ImagePort imagePort) {
this.repository = repository;
this.imagePort = imagePort;
}

@Override
Expand All @@ -40,6 +44,15 @@ public void deleteBlockById(Long id) {

@Override
public void deleteBlockByPost(Long postId) {

/// S3에서 이미지 삭제
List<ContentBlock> blockList = loadBlocks(postId);
for (ContentBlock block : blockList) {
if (block.getType() == ContentType.IMAGE){
imagePort.deleteFile(block.getContent());
}
}

repository.deleteByPostId(postId);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.blog.workspace.adapter.out.external.s3;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.blog.workspace.application.out.image.ImagePort;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;

import java.io.IOException;
import java.util.UUID;

@Component
public class S3ImagePersistenceAdapter implements ImagePort {

private final AmazonS3 amazonS3;

@Value("${cloud.aws.s3.bucket}")
private String bucket;

public S3ImagePersistenceAdapter(AmazonS3 amazonS3) {
this.amazonS3 = amazonS3;
}

// 이미지 하나 저장
@Override
public String uploadFiles(MultipartFile file) throws IOException {

/// S3 관련 내용 저장
String filename = createFileName(file.getOriginalFilename());

ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
metadata.setContentType(file.getContentType());

try {
amazonS3.putObject(new PutObjectRequest(bucket, filename, file.getInputStream(), metadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "파일 업로드에 실패했습니다.");
}

return amazonS3.getUrl(bucket, filename).toString();
}

// 이미지 하나 삭제
@Override
public void deleteFile(String fileName) {
amazonS3.deleteObject(bucket, fileName);
}

/// 내부 함수
// 파일명을 난수화하기 위해 UUID 를 활용하여 난수를 돌린다.
private String createFileName(String fileName){
return UUID.randomUUID().toString().concat(fileName);
}

}
Loading