๋์ ์ทจ์ง
ํ์ผ ์คํ ๋ฆฌ์ง๋ก ์ฌ์ฉํ ์๋น์ค๋ฅผ ์ฐพ๋ ๋์ค, ์ด๋ฏธ์ง ์คํ ๋ฆฌ์ง๋ก ๋ง์ด ํ์ฉํ๋ค๋ AWS S3 ์๋น์ค์ ๋ํด ์๊ฒ ๋์๊ณ , ๋ค์๊ณผ ๊ฐ์ ์ฅ์ ์ ๊ทผ๊ฑฐ๋ก ๋์ ํ๊ธฐ๋ก ํ๋ค
์ฅ์
- ํ์ฅ์ฑ: S3๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฑฐ์ ๋ฌด์ ํ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ๊ฒ์ํ ์ ์์ผ๋ฏ๋ก ๋น ๋ฅด๊ฒ ํ์ฅํ ์ ์๋ ๊ธฐ๋ฅ์ด ํ์ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ํฉํ๋ค.
- ๋ณด์: S3๋ ์๋ฒ ์ธก ์ํธํ, ACL(์ก์ธ์ค ์ ์ด ๋ชฉ๋ก) ๋ฐ ๋ฒํท ์ ์ฑ ์ ๋น๋กฏํ ๋ค์ํ ๋ณด์ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ณดํธํ๋ค.
- ์ฌ์ฉ ์ฉ์ด์ฑ: S3๋ ์ฌ์ฉํ๊ธฐ ์ฝ๊ณ ๋ค๋ฅธ AWS ์๋น์ค์ ์ํํ๊ฒ ํตํฉ๋๋ฏ๋ก ํ์ฅ ๊ฐ๋ฅํ๊ณ ์์ ์ ์ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ๋ ค๋ ๊ฐ๋ฐ์์๊ฒ ์ ํฉํ๋ค.
๊ตฌํ ๊ธฐ๊ฐ
02. 28 ~ 03. 03
์คํ ํ๊ฒฝ
- Spring boot 3.02
- Java 17
- Gradle
- Spring Cloud Starter aws 2.2.6
๊ตฌํํ ์ฝ๋
1. Backend
0) AmazonS3Config
@Configuration
public class AmazonS3Config {
@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 AmazonS3Client amazonS3Client() {
BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey);
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.build();
}
}
0) application.properties
# multipart
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
spring.file-dir=/home/ubuntu/app/src/main/resources
# aws
cloud.aws.credentials.access-key=ENC(SLFPdcj89oNQLKpRBGoFh/uaCmu7/41Z59rnsX6lMdg=)
cloud.aws.credentials.secret-key=ENC(hBKshx6HaHSeeXsGvqYxXC/MNBAQWZNjk5Jrau0+OnTchrj7HL2Pmm3vGkOq35W9twvLVmFqrKE=)
# S3 bucket region
cloud.aws.region.static=ap-northeast-2
cloud.aws.s3.bucket=knock-knock-image-bucket-1
1) S3 Controller
@RestController
@RequiredArgsConstructor
public class S3Controller {
private final S3ServiceImpl s3Service;
// ์ด๋ฏธ์ง ํ๋กํ์ฉ
@PostMapping("/api/images/upload/profiles")
public String imageUploadProfile(@RequestParam("profile") MultipartFile multipartFile) throws IOException {
String s = s3Service.upload(multipartFile, "knock-knock-image-bucket-1", "profile");
System.out.println(s);
return s;
}
// ์ด๋ฏธ์ง ์ํ์ฉ
@PostMapping("/api/images/upload/products")
public String imageUploadProduct(@RequestParam("product") MultipartFile multipartFile) throws IOException {
String s = s3Service.upload(multipartFile, "knock-knock-image-bucket-1", "product");
System.out.println(s);
return s;
}
}
2) S3 Service
@Service
@RequiredArgsConstructor
public class S3ServiceImpl implements S3Service{
private final AmazonS3Client amazonS3Client;
@Value("${spring.file-dir}")
private String fileDirectory;
// ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง
@Override
public String upload(MultipartFile multipartFile, String bucket, String dirName) throws IOException {
File uploadFile = convert(multipartFile).orElseThrow(
() -> new IllegalArgumentException("ํ์ผ ๋ณํ์ ์คํจํ์ต๋๋ค")
);
String fileName = dirName + "/" + UUID.randomUUID() + uploadFile.getName();
String putS3 = putS3(uploadFile, bucket, fileName);
removeFile(uploadFile);
return putS3;
}
// S3๋ก ์
๋ก๋ ~ PutObjectFile ํ์
: ๋ฒํท ์
๋ก๋ ๋ชฉ์ ์ผ๋ก ๋ง๋๋ ๊ฐ์ฒด์ URL์ ๋ฐํ
// cannedAcl : ๋ฒํท ๊ฐ์ฒด๋ฅผ ๋ฒํท ์ ์ฑ
์ ๋ง๊ฒ ์ค์
@Override
public String putS3(File uploadFile, String bucket, String fileName) {
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile)
.withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}
// ๋ก์ปฌ์ ์ ์ฅ๋ ์ด๋ฏธ์ง ์ง์ฐ๊ธฐ
@Override
public void removeFile(File savedLocalFile) {
if (savedLocalFile.delete()) {
System.out.println(savedLocalFile.getName() + "์ด ์ญ์ ๋์์ต๋๋ค");
} else {
System.out.println(savedLocalFile.getName() + "์ด ์ญ์ ๋์ง ์์์ต๋๋ค");
}
}
// ์ด๋ฏธ์ง๋ฅผ File ํ์
์ผ๋ก ๋ณํํ์ฌ Optional๋ก ๋ฐํ
@Override
public Optional<File> convert(MultipartFile multipartFile) throws IOException {
if(multipartFile.isEmpty()) return Optional.empty();
// ํ์ผ ์ด๋ฆ ์กฐํ
String originalFilename = multipartFile.getOriginalFilename();
// ์ด๋ฆ๋ณ๊ฒฝ, ํ์์ file๋ก ๋ณ๊ฒฝ ํ ํ์ผ ํ์
๋ณํ
File file = new File(fileDirectory + createStoreFileName(originalFilename));
multipartFile.transferTo(file);
return Optional.of(file);
}
// ํ์ผ ์ด๋ฆ์ ๊ธฐ์กด ํ์ผ๊ณผ ๊ฒน์น์ง ์๋๋ก UUID๋ฅผ ์ฌ์ฉํ์ฌ ๋ง๋ค๊ณ ํ์ฅ์๋ฅผ ๋ค์ ๋ถ์ฌ ์ด๋ฏธ์ง ํ์ฅ์๋ฅผ ์ ์งํ๋ค
@Override
public String createStoreFileName(String originalFilename) {
return UUID.randomUUID() + "." + extractExt(originalFilename);
}
// ์ฌ์ฉ์๊ฐ ์
๋ก๋ํ ํ์ผ์์ ํ์ฅ์๋ฅผ ์ถ์ถ
@Override
public String extractExt(String originalFilename) {
int pos = originalFilename.lastIndexOf(".");
return originalFilename.substring(pos + 1);
}
}
2. Frontend
1) product.js
// ์ฌ์ง ๋ฑ๋กํ๊ธฐ
$('#uploadPhoto').click(function(event) {
event.preventDefault();
var fileInput = document.getElementById("fileInput");
var file = fileInput.files[0];
var formData = new FormData();
formData.append("product", file);
$.ajax({
url: URL_VARIABLE + "api/images/upload/products",
type: 'POST',
data: formData,
headers: {
Authorization: userToken
},
processData: false,
contentType: false,
success: function(response) {
console.log(response)
document.getElementById("image-url").value = response
let image_preview = response
let temp_html = `<img src=${image_preview} style="width: 300px; height: 300px;">`
$('#image-preview').append(temp_html);
},
error: function() {
alert("An error occurred while uploading the file.");
}
});
});
2) addProduct.html
<tr>
<th><label for="productPhoto">์ํ์ฌ์ง</label></th>
<td><input type="file" id="fileInput" name="fileInput" /></td>
<td><button class="form-control" id="uploadPhoto">์ฌ์ง ์
๋ก๋</button></td>
</tr>
Sequence Diagram์ ํ์ฉํ ๊ธฐ๋ฅ ํ๋ฆ
ํธ๋ฌ๋ธ ์ํ
- ํ์ผ๋ช ์ ์ค๋ณต๋์ง ์๊ฒ ๋ณํํ๊ณ ์ด๋ฅผ S3์ ์ ๋ก๋ํ๊ธฐ ์ ์ ์ ์ฅํ๋ ๋ก์ง์ธ๋ฐ, ์ด๋ฅผ ๋ฐฐํฌํ๊ธฐ ์ํด์ ์ฐ๋ถํฌ๋ฅผ ํ์ฉํด์ผ ํ๋ค. ์ต์ข ํ๋ก์ ํธ์์๋ ์ฐ๋ถํฌ๋ก ์ฐ๊ฒฐํด์ ๋ฐฐํฌํ๋ ๋ถ๋ถ์ ๋ด๊ฐ ์ง์ ๊ตฌํํ ๊ฒ์ ์๋๊ธฐ ๋๋ฌธ์ ์ดํ ๊ฐ์ธ ํ๋ก์ ํธ์ ์ถ๊ฐ๋ก ์ฐ์ตํด๋ด์ผ๊ฒ ๋ค.
์๊ฐ
๊ฐ๋จํ๋ค๋ฉด ๊ฐ๋จํ๊ณ , ๊น๋ค๋กญ๋ค๋ฉด ๊น๋ค๋ก์ธ ์๋ ์๋ ๊ธฐ๋ฅ ๊ตฌํ์ด์๋ค. ๋ค๋ง ์ด๋ฅผ ํตํด AWS์ ๋์ฑ ์น์ํด์ง ์ ์์๋ค. ํด๋ผ์ฐ๋๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ก ํ์ฉํ๋ ๊ฒ์ด ์ ์ ๋ ์์ฐ์ค๋ฌ์์ง๊ณ ๋์ธ๊ฐ ๋๊ณ ์๋ ํ๊ฒฝ์ ์์ ๊ธฐ์ ์ค ํ๋๋ฅผ ๊ตฌํํ ์ ์๋ ๊ธฐํ๋ฅผ ์ป์ ์ ์์ด ๊ฐ์ง ์๊ฐ์ด์๋ค.
'๊ฐ๋ฐ๊ณต๋ถ > AWS ๐ฐ๏ธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๊ฐ์ ์ ๋ฆฌ : AWS ์ค๋ฌด ๊ธฐ์ด (1) (0) | 2023.02.15 |
---|---|
CI/ CD + Github Actions & AWS EC2 (0) | 2023.01.31 |