Skip to content

Commit

Permalink
S3 업로드 , 중복확인 post로 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
Miensoap committed May 29, 2024
1 parent 61bbb95 commit 3d2ddaa
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 5 deletions.
3 changes: 3 additions & 0 deletions be/issue_tracker/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ dependencies {
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

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

// Test
testRuntimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
package team1.issuetracker;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import team1.issuetracker.util.S3Util;

import java.io.IOException;

@RestController
@RequestMapping("/")
@RequiredArgsConstructor
public class HomeController {

private final S3Util s3Util;

@GetMapping("/github")
public void redirectToGithub(HttpServletResponse httpServletResponse) throws IOException {
httpServletResponse.sendRedirect("https://github.com/codesquad-masters2024-team01/issue-tracker");
}

@PostMapping("/upload")
public String uploadImage(@RequestPart(value = "image", required = false) MultipartFile image){
return s3Util.upload(image);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package team1.issuetracker.config;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
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("${aws.s3.accessKey}")
private String accessKey;
@Value("${aws.s3.secretKey}")
private String secretKey;

@Bean
public AmazonS3 amazonS3Client(){
return AmazonS3ClientBuilder.standard()
.withCredentials(
new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey))
)
.withRegion(Regions.AP_NORTHEAST_2)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void register(@RequestBody RegisterInfo registerInfo) throws IllegalArgum
userService.createUser(registerInfo);
}

@GetMapping("/duplicate")
@PostMapping("/duplicate")
public boolean isDuplicate(@RequestBody CheckDuplicateRequest request) {
if (request.id() != null) return userService.isDuplicateId(request.id());
if (request.nickname() != null) return userService.isDuplicateNickName(request.nickname());
Expand All @@ -42,7 +42,7 @@ public String login(@RequestBody LoginInfo loginInfo) {
@GetMapping("/login/github")
public String githubLogin(@RequestParam String code){
String id = githubLoginUtil.validateCode(code);
return id;
return jwtUtil.generateToken(id);
}

@GetMapping("/{id}")
Expand Down
95 changes: 95 additions & 0 deletions be/issue_tracker/src/main/java/team1/issuetracker/util/S3Util.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package team1.issuetracker.util;

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.amazonaws.util.IOUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

@Component
@RequiredArgsConstructor
public class S3Util {
private final AmazonS3 amazonS3;

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

public String upload(MultipartFile image) {
//입력받은 이미지 파일이 빈 파일인지 검증
if (image.isEmpty() || Objects.isNull(image.getOriginalFilename())) {
throw new IllegalArgumentException();
}
//uploadImage를 호출하여 S3에 저장된 이미지의 public url을 반환한다.
return this.uploadImage(image);
}

private String uploadImage(MultipartFile image) {
this.validateImageFileExtention(image.getOriginalFilename());
try {
return this.uploadImageToS3(image);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}

private void validateImageFileExtention(String filename) {
int lastDotIndex = filename.lastIndexOf(".");
if (lastDotIndex == -1) {
throw new IllegalArgumentException("-1");
}

String extention = filename.substring(lastDotIndex + 1).toLowerCase();
List<String> allowedExtentionList = Arrays.asList("jpg", "jpeg", "png", "gif");

if (!allowedExtentionList.contains(extention)) {
throw new IllegalArgumentException("extention");
}
}

private String uploadImageToS3(MultipartFile image) throws IOException {
String originalFilename = image.getOriginalFilename(); //원본 파일 명
String extention = originalFilename.substring(originalFilename.lastIndexOf(".")); //확장자 명

String s3FileName = UUID.randomUUID().toString().substring(0, 10) + originalFilename; //변경된 파일 명

InputStream is = image.getInputStream();
byte[] bytes = IOUtils.toByteArray(is); //image를 byte[]로 변환

ObjectMetadata metadata = new ObjectMetadata(); //metadata 생성
metadata.setContentType("image/" + extention);
metadata.setContentLength(bytes.length);

//S3에 요청할 때 사용할 byteInputStream 생성
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

try {
//S3로 putObject 할 때 사용할 요청 객체
//생성자 : bucket 이름, 파일 명, byteInputStream, metadata
PutObjectRequest putObjectRequest =
new PutObjectRequest(bucketName, s3FileName, byteArrayInputStream, metadata)
.withCannedAcl(CannedAccessControlList.PublicRead);

//실제로 S3에 이미지 데이터를 넣는 부분이다.
amazonS3.putObject(putObjectRequest); // put image to S3
} catch (Exception e) {
throw new IllegalArgumentException(e);
} finally {
byteArrayInputStream.close();
is.close();
}

return amazonS3.getUrl(bucketName, s3FileName).toString();
}
}
78 changes: 78 additions & 0 deletions be/issue_tracker/src/main/resources/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,46 @@
object-fit: cover;
border: 2px solid #000;
}

.upload-btn {
display: inline-block;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
transition: background-color 0.3s;
text-align: center;
text-decoration: none;
margin-top: 10px;
}

.upload-btn:hover {
background-color: #45a049;
}

.upload-image-container {
margin-top: 20px;
width: 300px;
height: 300px;
border: 2px solid #000;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}

.upload-image-container img {
width: 100%;
height: 100%;
object-fit: cover;
}

input[type="file"] {
display: none;
}
</style>
</head>
<body>
Expand All @@ -48,6 +88,7 @@
>
로그인
</a>

<a href="https://github.com/codesquad-masters2024-team01/issue-tracker/tree/Deploy" target="_blank">
<img src="https://soapbucket51.s3.ap-northeast-2.amazonaws.com/issue_tracker/github.png" alt="Image 1">
</a>
Expand All @@ -59,5 +100,42 @@
<img src="https://soapbucket51.s3.ap-northeast-2.amazonaws.com/issue_tracker/poo.jpg" alt="Image 3">
</a>
</div>

<div style="padding-left: 100px">
<form id="uploadForm" action="/upload" method="post" enctype="multipart/form-data">
<input type="file" id="fileInput" name="image" accept="image/*">
<div class="upload-image-container" id="imagePreview">
<p>No image chosen</p>
</div>
<button type="button" class="upload-btn" id="chooseImageBtn">Choose Image</button>
<button type="submit" class="upload-btn">Upload Image</button>
</form>
</div>

<script>
const fileInput = document.getElementById('fileInput');
const chooseImageBtn = document.getElementById('chooseImageBtn');
const imagePreview = document.getElementById('imagePreview');

chooseImageBtn.addEventListener('click', function() {
fileInput.click();
});

fileInput.addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
const img = document.createElement('img');
img.src = e.target.result;
imagePreview.innerHTML = '';
imagePreview.appendChild(img);
};
reader.readAsDataURL(file);
} else {
imagePreview.innerHTML = '<p>No image chosen</p>';
}
});
</script>
</body>
</html>

0 comments on commit 3d2ddaa

Please sign in to comment.