๐Ÿ’ป PROJECT/[Spring Boot, React] ๋…์„œ ์Šต๊ด€ ๊ด€๋ฆฌ ์„œ๋น„์Šค

[ํ”„๋กœ์ ํŠธ] ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ •์ฑ… ๊ฒฐ์ • ๊ณผ์ •

devCloud 2026. 4. 30. 12:09
728x90
PROJECT

์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ •์ฑ… ๊ฒฐ์ • ๊ณผ์ •

01. ๋ฐฐ๊ฒฝ

๋…์„œ ์Šต๊ด€ ๊ด€๋ฆฌ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์— ๊ณ ๋ฏผ์ด ๋งŽ์•˜๋‹ค. ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€๊ฐ€ ์žˆ์—ˆ๋‹ค. ์–ด๋–ค ๋ฐฉ์‹์ด ๋” ์ข‹์€ ์„ ํƒ์ธ์ง€ ํŒ๋‹จํ•˜๊ธฐ๊ฐ€ ์‰ฝ์ง€ ์•Š์•˜๊ณ , ํŠนํžˆ AWS S3๋ฅผ ์ถ”์ฒœ๋ฐ›์•˜์ง€๋งŒ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๋ ค๋ฉด AWS ๊ณ„์ •์—์„œ ํ‚ค๋ฅผ ๋ณ„๋„๋กœ ๋ฐœ๊ธ‰๋ฐ›์•„์•ผ ํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์ด ์žˆ์–ด์„œ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ• ์ง€ ๊ณ ๋ฏผ์ด ๋งŽ์•˜๋‹ค.

02. ์ด๋ฏธ์ง€ ์ €์žฅ ๋ฐฉ์‹ ๋น„๊ต

๋ฐฉ์‹ 1. ์™ธ๋ถ€ URL ๊ทธ๋Œ€๋กœ ์ €์žฅ

์™ธ๋ถ€ ์„œ๋น„์Šค(์•Œ๋ผ๋”˜ ๋“ฑ)๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ด๋ฏธ์ง€ URL์„ DB์— ๊ทธ๋Œ€๋กœ ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

.coverImageUrl(picked.cover()) // ์•Œ๋ผ๋”˜ CDN URL ๊ทธ๋Œ€๋กœ ์ €์žฅ
์žฅ์  ๋‹จ์ 
• ๊ตฌํ˜„์ด ๋‹จ์ˆœํ•˜๊ณ  ๋น ๋ฆ„
• ๋ณ„๋„ ์ธํ”„๋ผ ๋ถˆํ•„์š”
• S3 ๋น„์šฉ ์—†์Œ
• ์™ธ๋ถ€ URL์ด ๋งŒ๋ฃŒ/๋ณ€๊ฒฝ๋˜๋ฉด ์ด๋ฏธ์ง€ ๊นจ์ง
• ์™ธ๋ถ€ ๋„๋ฉ”์ธ์— ์˜์กด
• ์ด๋ฏธ์ง€ ๊ฐ€์šฉ์„ฑ์„ ์™ธ๋ถ€ ์„œ๋น„์Šค์— ์˜์กด

๋ฐฉ์‹ 2. ์„œ๋ฒ„์—์„œ ๋‹ค์šด๋กœ๋“œ ํ›„ S3 ์žฌ์—…๋กœ๋“œ

์™ธ๋ถ€ URL์˜ ์ด๋ฏธ์ง€๋ฅผ ์„œ๋ฒ„๊ฐ€ ๋‹ค์šด๋กœ๋“œํ•œ ๋’ค S3์— ์ €์žฅํ•˜๊ณ , S3 URL์„ DB์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

public String uploadImageFromUrl(String imageUrl) {
    URL url = new URL(imageUrl);
    URLConnection connection = url.openConnection();

    try (InputStream inputStream = connection.getInputStream()) {
        byte[] imageBytes = inputStream.readAllBytes();
        String fileName = UUID.randomUUID() + "_cover.jpg";

        s3Client.putObject(
                PutObjectRequest.builder()
                        .bucket(bucket)
                        .key(fileName)
                        .contentType("image/jpeg")
                        .contentLength((long) imageBytes.length)
                        .build(),
                RequestBody.fromBytes(imageBytes)
        );
        return "https://" + bucket + ".s3." + region + ".amazonaws.com/" + fileName;
    }
}
์žฅ์  ๋‹จ์ 
• ์ด๋ฏธ์ง€ ์˜์†์„ฑ ๋ณด์žฅ
• ์™ธ๋ถ€ ์˜์กด์„ฑ ์ œ๊ฑฐ
• ์ด๋ฏธ์ง€ ๊ด€๋ฆฌ ์ผ์›ํ™”
• AWS ํ‚ค ๋ฐœ๊ธ‰ ํ•„์š”
• S3 ๋น„์šฉ ๋ฐœ์ƒ
• ๊ตฌํ˜„ ๋ณต์žก๋„ ์ฆ๊ฐ€

๋ฐฉ์‹ 3. ํด๋ผ์ด์–ธํŠธ์—์„œ ์ง์ ‘ S3 ์—…๋กœ๋“œ (Presigned URL)

์„œ๋ฒ„๊ฐ€ S3 Presigned URL์„ ๋ฐœ๊ธ‰ํ•ด์ฃผ๋ฉด, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ•ด๋‹น URL๋กœ ์ง์ ‘ S3์— ์—…๋กœ๋“œํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

// ์„œ๋ฒ„: Presigned URL ๋ฐœ๊ธ‰
public String generatePresignedUrl(String fileName) {
    PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
            .signatureDuration(Duration.ofMinutes(10))
            .putObjectRequest(r -> r.bucket(bucket).key(fileName))
            .build();
    return presigner.presignPutObject(presignRequest).url().toString();
}
// ํด๋ผ์ด์–ธํŠธ: Presigned URL๋กœ ์ง์ ‘ ์—…๋กœ๋“œ
await axios.put(presignedUrl, file, {
    headers: { 'Content-Type': file.type }
});
์žฅ์  ๋‹จ์ 
• ์„œ๋ฒ„ ๋ถ€ํ•˜ ์—†์Œ
• ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ์— ์œ ๋ฆฌ
• ๋„คํŠธ์›Œํฌ ํšจ์œจ์ 
• AWS ํ‚ค ๋ฐœ๊ธ‰ ํ•„์š”
• ๊ตฌํ˜„ ๋ณต์žก๋„ ๊ฐ€์žฅ ๋†’์Œ
• ํด๋ผ์ด์–ธํŠธ-์„œ๋ฒ„ ๊ฐ„ ์ถ”๊ฐ€ ํ†ต์‹  ํ•„์š”

03. ๊ฒฐ์ • ๋‚ด์šฉ

ํ‘œ์ง€ ์ด๋ฏธ์ง€ (coverImageUrl)

์ดˆ๊ธฐ์—๋Š” ๋ฐฉ์‹ 2(์„œ๋ฒ„์—์„œ S3 ์žฌ์—…๋กœ๋“œ)๋ฅผ ๊ณ„ํšํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ S3 ํ‚ค ๋ฐœ๊ธ‰ ์ „๊นŒ์ง€ ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•œ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๊ณ , ์•Œ๋ผ๋”˜ CDN URL์ด ๋‹น์žฅ ๋งŒ๋ฃŒ๋˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝ๋  ๊ฐ€๋Šฅ์„ฑ์ด ๋‚ฎ๋‹ค๊ณ  ํŒ๋‹จํ•ด ๋ฐฉ์‹ 1๋กœ ๋ณ€๊ฒฝํ–ˆ๋‹ค. Post ์—”ํ‹ฐํ‹ฐ์˜ coverImageUrl ํ•„๋“œ๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•œ๋‹ค.

// ๋ณ€๊ฒฝ ์ „ (๊ณ„ํš)
.coverImageUrl(s3Service.uploadImageFromUrl(picked.cover()))

// ๋ณ€๊ฒฝ ํ›„ (ํ˜„์žฌ)
.coverImageUrl(picked.cover())

ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ (profileImageUrl)

์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์—…๋กœ๋“œํ•˜๋Š” ์ด๋ฏธ์ง€์ด๋ฏ€๋กœ ์™ธ๋ถ€ URL ์ €์žฅ ๋ฐฉ์‹์„ ์ ์šฉํ•  ์ˆ˜ ์—†์–ด ๋ฐฉ์‹ 2(S3 ์—…๋กœ๋“œ)๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. AWS ํ‚ค ๋ฐœ๊ธ‰ ๋ฐ ์‹ค์ œ ์—ฐ๋™์€ D13 ๋ฐฐํฌ ํ™˜๊ฒฝ ๊ตฌ์„ฑ ๋‹จ๊ณ„์—์„œ ์ง„ํ–‰ํ•˜๊ณ , ๊ทธ ์ „๊นŒ์ง€๋Š” ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ๊ด€๋ จ API ํ…Œ์ŠคํŠธ๋ฅผ ์Šคํ‚ตํ•œ๋‹ค. S3Config, S3Service, UserService์˜ ์ด๋ฏธ์ง€ ๊ด€๋ จ ์ฝ”๋“œ๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•œ๋‹ค.

// S3Service — ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ (D13์—์„œ ์‹ค์ œ ์—ฐ๋™)
public String uploadImage(MultipartFile image) {
    String fileName = UUID.randomUUID() + "_" + image.getOriginalFilename();
    s3Client.putObject(
            PutObjectRequest.builder()
                    .bucket(bucket)
                    .key(fileName)
                    .contentType(image.getContentType())
                    .contentLength(image.getSize())
                    .build(),
            RequestBody.fromInputStream(image.getInputStream(), image.getSize())
    );
    return "https://" + bucket + ".s3." + region + ".amazonaws.com/" + fileName;
}

04. ์ •๋ฆฌ

์ด๋ฏธ์ง€ ์ข…๋ฅ˜ ์ €์žฅ ๋ฐฉ์‹ ๋น„๊ณ 
ํ‘œ์ง€ ์ด๋ฏธ์ง€ ์™ธ๋ถ€ URL ๊ทธ๋Œ€๋กœ ์ €์žฅ ์•Œ๋ผ๋”˜ CDN URL
ํ”„๋กœํ•„ ์ด๋ฏธ์ง€ S3 ์—…๋กœ๋“œ D13(๋ฐฐํฌ ์ค€๋น„ ๋‹จ๊ณ„)์—์„œ ์‹ค์ œ ์—ฐ๋™
Last Sync: 2026-04-30 | Booktine Project Log
728x90