From 2173d8a6c993394004be216979b3e76b91e818f9 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sat, 7 Dec 2024 00:19:44 +0900 Subject: [PATCH 01/11] =?UTF-8?q?#127=20[feat]=20:=20S3=20Config=EB=A5=BC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../side/onetime/global/config/S3Config.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/java/side/onetime/global/config/S3Config.java diff --git a/src/main/java/side/onetime/global/config/S3Config.java b/src/main/java/side/onetime/global/config/S3Config.java new file mode 100644 index 0000000..08b5d07 --- /dev/null +++ b/src/main/java/side/onetime/global/config/S3Config.java @@ -0,0 +1,31 @@ +package side.onetime.global.config; + +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("${spring.cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${spring.cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${spring.cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3 amazonS3() { + BasicAWSCredentials awsCreds = new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) + .build(); + } +} From c28a2a1c2293a5161f7701a4478da31bef5db324 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sat, 7 Dec 2024 00:19:59 +0900 Subject: [PATCH 02/11] =?UTF-8?q?#127=20[feat]=20:=20S3=EB=A5=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/side/onetime/util/S3Util.java | 45 +++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/main/java/side/onetime/util/S3Util.java diff --git a/src/main/java/side/onetime/util/S3Util.java b/src/main/java/side/onetime/util/S3Util.java new file mode 100644 index 0000000..6440c59 --- /dev/null +++ b/src/main/java/side/onetime/util/S3Util.java @@ -0,0 +1,45 @@ +package side.onetime.util; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class S3Util { + private final AmazonS3 amazonS3; + + @Value("${spring.cloud.aws.s3.bucket}") + private String bucket; + + /** + * S3에 이미지 업로드 하기 + */ + public String uploadImage(MultipartFile image) throws IOException { + String fileName = UUID.randomUUID() + "_" + image.getOriginalFilename(); // 고유한 파일 이름 생성 + + // 메타데이터 설정 + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(image.getContentType()); + metadata.setContentLength(image.getSize()); + + // S3에 파일 업로드 요청 생성 + PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, fileName, image.getInputStream(), metadata); + + // S3에 파일 업로드 + amazonS3.putObject(putObjectRequest); + + return fileName; + } + + private String getPublicUrl(String fileName) { + return String.format("https://%s.s3.%s.amazonaws.com/%s", bucket, amazonS3.getRegionName(), fileName); + } +} From 1324f3d43a26ef39f5259c614fb9497f792c1268 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sat, 7 Dec 2024 00:20:11 +0900 Subject: [PATCH 03/11] =?UTF-8?q?#127=20[feat]=20:=20QR=20Code=EB=A5=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/side/onetime/util/QrUtil.java | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/java/side/onetime/util/QrUtil.java diff --git a/src/main/java/side/onetime/util/QrUtil.java b/src/main/java/side/onetime/util/QrUtil.java new file mode 100644 index 0000000..2c905f6 --- /dev/null +++ b/src/main/java/side/onetime/util/QrUtil.java @@ -0,0 +1,57 @@ +package side.onetime.util; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@RequiredArgsConstructor +@Service +public class QrUtil { + + @Value("${qr.event-base-url}") + private String qrEventBaseUrl; + + public MultipartFile getQrCodeFile(UUID eventId) throws Exception { + // QR 코드 생성 + byte[] qrCodeBytes = generateQRCode(qrEventBaseUrl + eventId); + + // 파일 이름 설정 + String fileName = String.format("qr"); + + // ByteArray를 MultipartFile로 변환 + return FileUtil.convertToMultipartFile(qrCodeBytes, fileName); + } + + public byte[] generateQRCode(String eventUrl) throws Exception { + + // JSON 형식의 데이터 생성 + String qrContent = String.format(eventUrl); + + // QR 코드 생성 객체 + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + + // 인코딩 힌트 설정 + Map hints = new HashMap<>(); + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + + // QR 코드 생성 + BitMatrix bitMatrix = qrCodeWriter.encode(qrContent, BarcodeFormat.QR_CODE, 100, 100, hints); + + // ByteArrayOutputStream을 사용해 이미지를 바이트 배열로 변환 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + MatrixToImageWriter.writeToStream(bitMatrix, "PNG", outputStream); + + return outputStream.toByteArray(); + } +} From f4b2e7cfb2d1c8bccd6417ef430b3c581f9aeb58 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sat, 7 Dec 2024 00:20:43 +0900 Subject: [PATCH 04/11] =?UTF-8?q?#127=20[feat]=20:=20File=20=EC=9C=A0?= =?UTF-8?q?=ED=8B=B8=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/side/onetime/util/FileUtil.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/side/onetime/util/FileUtil.java diff --git a/src/main/java/side/onetime/util/FileUtil.java b/src/main/java/side/onetime/util/FileUtil.java new file mode 100644 index 0000000..1e4968d --- /dev/null +++ b/src/main/java/side/onetime/util/FileUtil.java @@ -0,0 +1,19 @@ +package side.onetime.util; + +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class FileUtil { + + public static MultipartFile convertToMultipartFile(byte[] fileContent, String fileName) throws IOException { + return new MockMultipartFile( + fileName, + fileName, + "image/png", + new ByteArrayInputStream(fileContent) + ); + } +} From 0b670a987a27bb3b50fd0f0af9f84d0c87f94ff5 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sat, 7 Dec 2024 00:21:05 +0900 Subject: [PATCH 05/11] =?UTF-8?q?#127=20[chore]=20:=20S3=20&=20QR=20Code?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A5=BC=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 379998c..75a161e 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,7 @@ dependencies { // Web implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-test' // DB implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.mysql:mysql-connector-j' @@ -60,6 +60,14 @@ dependencies { testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc:3.0.0' testImplementation 'com.squareup.okhttp3:mockwebserver' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' + // AWS S3 + implementation 'io.awspring.cloud:spring-cloud-aws-starter:3.1.1' + implementation 'com.amazonaws:aws-java-sdk-s3:1.12.767' + // org.json + implementation 'org.json:json:20231013' + // Zxing + implementation 'com.google.zxing:core:3.5.1' + implementation 'com.google.zxing:javase:3.5.1' } // QueryDSL 디렉토리 @@ -126,4 +134,4 @@ tasks.register('copyDocument', Copy) { tasks.named("build") { dependsOn copyDocument -} \ No newline at end of file +} From fad84f512277e9d3bc288f12a8c4485a262db0c7 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sat, 7 Dec 2024 00:21:22 +0900 Subject: [PATCH 06/11] =?UTF-8?q?#127=20[feat]=20:=20S3=20&=20QR=20Code=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=A5=BC=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yaml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 1f64809..ba78feb 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -76,6 +76,16 @@ spring: host: ${REDIS_HOST} port: ${REDIS_PORT} + cloud: + aws: + credentials: + access-key: ${S3_ACCESS_KEY} + secret-key: ${S3_SECRET_KEY} + region: + static: ap-northeast-2 + s3: + bucket: ${S3_BUCKET_NAME} + jwt: secret: ${JWT_SECRET} redirect: @@ -98,4 +108,7 @@ springdoc: operations-sorter: alpha api-docs: path: /v3/api-docs - show-actuator: true \ No newline at end of file + show-actuator: true + +qr: + event-base-url: ${QR_EVENT_BASE_URL} From 2c38362aedc839efae83e5c00c64070694423f22 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sat, 7 Dec 2024 00:22:03 +0900 Subject: [PATCH 07/11] =?UTF-8?q?#127=20[feat]=20:=20QR=20Code=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=AA=85=20=EC=BB=AC=EB=9F=BC=EC=9D=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/side/onetime/domain/Event.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/side/onetime/domain/Event.java b/src/main/java/side/onetime/domain/Event.java index 24793c7..e4cfd02 100644 --- a/src/main/java/side/onetime/domain/Event.java +++ b/src/main/java/side/onetime/domain/Event.java @@ -5,7 +5,6 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hibernate.annotations.BatchSize; import side.onetime.domain.enums.Category; import side.onetime.global.common.dao.BaseEntity; @@ -38,6 +37,9 @@ public class Event extends BaseEntity { @Enumerated(EnumType.STRING) private Category category; + @Column(name = "qr_file_name") + private String qrFileName; + @OneToMany(mappedBy = "event",cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List members; @@ -59,4 +61,8 @@ public Event(UUID eventId, String title, String startTime, String endTime, Categ public void updateTitle(String title) { this.title = title; } -} \ No newline at end of file + + public void addQrFileName(String qrFileName) { + this.qrFileName = qrFileName; + } +} From 3ef902246c33c3e0b8f6a1920ed0eae8d9155df1 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sat, 7 Dec 2024 00:22:25 +0900 Subject: [PATCH 08/11] =?UTF-8?q?#127=20[feat]=20:=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20QR=20Code=EB=8F=84?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=ED=95=98=EC=97=AC=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../side/onetime/service/EventService.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/java/side/onetime/service/EventService.java b/src/main/java/side/onetime/service/EventService.java index 639c4c0..001686e 100644 --- a/src/main/java/side/onetime/service/EventService.java +++ b/src/main/java/side/onetime/service/EventService.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import side.onetime.domain.*; import side.onetime.domain.enums.Category; import side.onetime.domain.enums.EventStatus; @@ -19,6 +20,8 @@ import side.onetime.repository.SelectionRepository; import side.onetime.util.DateUtil; import side.onetime.util.JwtUtil; +import side.onetime.util.QrUtil; +import side.onetime.util.S3Util; import java.time.LocalTime; import java.util.*; @@ -34,11 +37,17 @@ public class EventService { private final ScheduleRepository scheduleRepository; private final SelectionRepository selectionRepository; private final JwtUtil jwtUtil; + private final S3Util s3Util; + private final QrUtil qrUtil; // 이벤트 생성 메서드 (비로그인) @Transactional public CreateEventResponse createEventForAnonymousUser(CreateEventRequest createEventRequest) { Event event = createEventRequest.toEntity(); + + // QR 코드 생성 및 업로드 + String qrFileName = generateAndUploadQrCode(event.getEventId()); + event.addQrFileName(qrFileName); eventRepository.save(event); if (createEventRequest.category().equals(Category.DATE)) { @@ -61,12 +70,17 @@ public CreateEventResponse createEventForAnonymousUser(CreateEventRequest create public CreateEventResponse createEventForAuthenticatedUser(CreateEventRequest createEventRequest, String authorizationHeader) { User user = jwtUtil.getUserFromHeader(authorizationHeader); Event event = createEventRequest.toEntity(); + + // QR 코드 생성 및 업로드 + String qrFileName = generateAndUploadQrCode(event.getEventId()); + event.addQrFileName(qrFileName); + eventRepository.save(event); + // 이벤트 참여 여부 저장 EventParticipation eventParticipation = EventParticipation.builder() .user(user) .event(event) .eventStatus(EventStatus.CREATOR) .build(); - eventRepository.save(event); eventParticipationRepository.save(eventParticipation); if (createEventRequest.category().equals(Category.DATE)) { @@ -84,6 +98,16 @@ public CreateEventResponse createEventForAuthenticatedUser(CreateEventRequest cr return CreateEventResponse.of(event); } + // QR 코드 생성 및 S3 업로드 + private String generateAndUploadQrCode(UUID eventId) { + try { + MultipartFile qrCodeFile = qrUtil.getQrCodeFile(eventId); + return s3Util.uploadImage(qrCodeFile); + } catch (Exception e) { + throw new RuntimeException("QR 코드 생성 또는 업로드 실패", e); + } + } + // 날짜 스케줄을 생성하고 저장하는 메서드 @Transactional protected void createAndSaveDateSchedules(Event event, List ranges, String startTime, String endTime) { From 3f8cd917f6a91b6e54995c23ad2fa30781a2c40d Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sat, 7 Dec 2024 00:38:47 +0900 Subject: [PATCH 09/11] =?UTF-8?q?#127=20[feat]=20:=20=ED=8A=B9=EC=A0=95=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20QR=20=EC=BD=94=EB=93=9C=EB=A5=BC?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/controller/EventController.java | 18 ++++++- .../response/GetEventQrCodeResponse.java | 17 +++++++ .../global/common/status/SuccessStatus.java | 3 +- .../side/onetime/service/EventService.java | 11 +++++ src/main/java/side/onetime/util/S3Util.java | 2 +- .../onetime/event/EventControllerTest.java | 48 ++++++++++++++++++- 6 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/main/java/side/onetime/dto/event/response/GetEventQrCodeResponse.java diff --git a/src/main/java/side/onetime/controller/EventController.java b/src/main/java/side/onetime/controller/EventController.java index f88ee8a..776aff4 100644 --- a/src/main/java/side/onetime/controller/EventController.java +++ b/src/main/java/side/onetime/controller/EventController.java @@ -149,4 +149,20 @@ public ResponseEntity> modifyUserCreatedEventTitle( eventService.modifyUserCreatedEventTitle(authorizationHeader, eventId, modifyUserCreatedEventTitleRequest); return ApiResponse.onSuccess(SuccessStatus._MODIFY_USER_CREATED_EVENT_TITLE); } -} \ No newline at end of file + + /** + * 이벤트 QR Code 조회 API + * + * 이 API는 이벤트로 이동할 수 있는 QR Code 이미지를 반환합니다. + * + * @param eventId QR Code를 조회할 이벤트의 ID + * @return QR Code 이미지 URL + */ + @GetMapping("/qr/{event_id}") + public ResponseEntity> getEventQrCode( + @PathVariable("event_id") String eventId) { + + GetEventQrCodeResponse response = eventService.getEventQrCode(eventId); + return ApiResponse.onSuccess(SuccessStatus._GET_EVENT_QR_CODE, response); + } +} diff --git a/src/main/java/side/onetime/dto/event/response/GetEventQrCodeResponse.java b/src/main/java/side/onetime/dto/event/response/GetEventQrCodeResponse.java new file mode 100644 index 0000000..25d23aa --- /dev/null +++ b/src/main/java/side/onetime/dto/event/response/GetEventQrCodeResponse.java @@ -0,0 +1,17 @@ +package side.onetime.dto.event.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +public record GetEventQrCodeResponse( + String qrCodeImgUrl +) { + public static GetEventQrCodeResponse from(String qrCodeImgUrl) { + return new GetEventQrCodeResponse( + qrCodeImgUrl + ); + } +} diff --git a/src/main/java/side/onetime/global/common/status/SuccessStatus.java b/src/main/java/side/onetime/global/common/status/SuccessStatus.java index a649526..f1c8914 100644 --- a/src/main/java/side/onetime/global/common/status/SuccessStatus.java +++ b/src/main/java/side/onetime/global/common/status/SuccessStatus.java @@ -20,6 +20,7 @@ public enum SuccessStatus implements BaseCode { _GET_USER_PARTICIPATED_EVENTS(HttpStatus.OK, "200", "유저 참여 이벤트 목록 조회에 성공했습니다."), _REMOVE_USER_CREATED_EVENT(HttpStatus.OK, "200", "유저가 생성한 이벤트 삭제에 성공했습니다."), _MODIFY_USER_CREATED_EVENT_TITLE(HttpStatus.OK, "200", "유저가 생성한 이벤트 제목 수정에 성공했습니다."), + _GET_EVENT_QR_CODE(HttpStatus.OK, "200", "이벤트 QR 코드 조회에 성공했습니다."), // Member _REGISTER_MEMBER(HttpStatus.CREATED, "201", "멤버 등록에 성공했습니다."), _LOGIN_MEMBER(HttpStatus.OK, "200", "멤버 로그인에 성공했습니다."), @@ -76,4 +77,4 @@ public ReasonDto getReasonHttpStatus() { .message(message) .build(); } -} \ No newline at end of file +} diff --git a/src/main/java/side/onetime/service/EventService.java b/src/main/java/side/onetime/service/EventService.java index 001686e..246c1cf 100644 --- a/src/main/java/side/onetime/service/EventService.java +++ b/src/main/java/side/onetime/service/EventService.java @@ -361,4 +361,15 @@ private EventParticipation verifyUserIsEventCreator(String authorizationHeader, return eventParticipation; } + + // 이벤트 QR Code 조회 메서드 + @Transactional(readOnly = true) + public GetEventQrCodeResponse getEventQrCode(String eventId) { + Event event = eventRepository.findByEventId(UUID.fromString(eventId)) + .orElseThrow(() -> new CustomException(EventErrorStatus._NOT_FOUND_EVENT)); + + String qrCodeImgUrl = s3Util.getPublicUrl(event.getQrFileName()); + + return GetEventQrCodeResponse.from(qrCodeImgUrl); + } } diff --git a/src/main/java/side/onetime/util/S3Util.java b/src/main/java/side/onetime/util/S3Util.java index 6440c59..ff78d98 100644 --- a/src/main/java/side/onetime/util/S3Util.java +++ b/src/main/java/side/onetime/util/S3Util.java @@ -39,7 +39,7 @@ public String uploadImage(MultipartFile image) throws IOException { return fileName; } - private String getPublicUrl(String fileName) { + public String getPublicUrl(String fileName) { return String.format("https://%s.s3.%s.amazonaws.com/%s", bucket, amazonS3.getRegionName(), fileName); } } diff --git a/src/test/java/side/onetime/event/EventControllerTest.java b/src/test/java/side/onetime/event/EventControllerTest.java index 0dd9f82..5ce4100 100644 --- a/src/test/java/side/onetime/event/EventControllerTest.java +++ b/src/test/java/side/onetime/event/EventControllerTest.java @@ -436,4 +436,50 @@ public void modifyUserCreatedEventTitle() throws Exception { ) )); } -} \ No newline at end of file + + @Test + @DisplayName("이벤트 QR 코드를 조회한다.") + public void getEventQrCode() throws Exception { + // given + String eventId = UUID.randomUUID().toString(); + String qrCodeImgUrl = "https://example.com/qr-code-image.png"; + GetEventQrCodeResponse response = GetEventQrCodeResponse.from(qrCodeImgUrl); + + Mockito.when(eventService.getEventQrCode(anyString())).thenReturn(response); + + // when + ResultActions resultActions = this.mockMvc.perform(RestDocumentationRequestBuilders.get("/api/v1/events/qr/{event_id}", eventId) + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.is_success").value(true)) + .andExpect(jsonPath("$.code").value("200")) + .andExpect(jsonPath("$.message").value("이벤트 QR 코드 조회에 성공했습니다.")) + .andExpect(jsonPath("$.payload.qr_code_img_url").value(qrCodeImgUrl)) + + // docs + .andDo(MockMvcRestDocumentationWrapper.document("event/get-event-qr-code", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + resource( + ResourceSnippetParameters.builder() + .tag("Event API") + .description("이벤트 QR 코드를 조회한다.") + .pathParameters( + parameterWithName("event_id").description("조회할 이벤트의 ID [예시 : dd099816-2b09-4625-bf95-319672c25659]") + ) + .responseFields( + fieldWithPath("is_success").type(JsonFieldType.BOOLEAN).description("성공 여부"), + fieldWithPath("code").type(JsonFieldType.STRING).description("응답 코드"), + fieldWithPath("message").type(JsonFieldType.STRING).description("응답 메시지"), + fieldWithPath("payload").type(JsonFieldType.OBJECT).description("응답 데이터"), + fieldWithPath("payload.qr_code_img_url").type(JsonFieldType.STRING).description("QR 코드 이미지 URL") + ) + .responseSchema(Schema.schema("GetEventQrCodeResponseSchema")) + .build() + ) + )); + } +} From ffe1ed997883ab516c6801848341dc6fdcf85be7 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sat, 7 Dec 2024 01:10:35 +0900 Subject: [PATCH 10/11] =?UTF-8?q?#127=20[feat]=20:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20=EC=A3=BC=EC=84=9D=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/status/EventErrorStatus.java | 6 +- .../side/onetime/service/EventService.java | 176 ++++++++++++++++-- 2 files changed, 160 insertions(+), 22 deletions(-) diff --git a/src/main/java/side/onetime/exception/status/EventErrorStatus.java b/src/main/java/side/onetime/exception/status/EventErrorStatus.java index 11aaf50..8a395fa 100644 --- a/src/main/java/side/onetime/exception/status/EventErrorStatus.java +++ b/src/main/java/side/onetime/exception/status/EventErrorStatus.java @@ -11,7 +11,9 @@ public enum EventErrorStatus implements BaseErrorCode { _NOT_FOUND_EVENT(HttpStatus.NOT_FOUND, "EVENT-001", "이벤트를 찾을 수 없습니다."), _IS_NOT_DATE_FORMAT(HttpStatus.BAD_REQUEST, "EVENT-002", "날짜 이벤트에 요일을 입력할 수 없습니다."), - _IS_NOT_DAY_FORMAT(HttpStatus.BAD_REQUEST, "EVENT-003", "요일 이벤트에 날짜를 입력할 수 없습니다.") + _IS_NOT_DAY_FORMAT(HttpStatus.BAD_REQUEST, "EVENT-003", "요일 이벤트에 날짜를 입력할 수 없습니다."), + _NOT_FOUND_EVENT_QR_CODE(HttpStatus.NOT_FOUND, "EVENT-004", "이벤트 QR 코드를 찾을 수 없습니다."), + _FAILED_GENERATE_QR_CODE(HttpStatus.INTERNAL_SERVER_ERROR, "EVENT-005", "QR 코드를 생성하고 업로드 하는 과정에서 문제가 발생했습니다."), ; private final HttpStatus httpStatus; @@ -36,4 +38,4 @@ public ErrorReasonDto getReasonHttpStatus() { .message(message) .build(); } -} \ No newline at end of file +} diff --git a/src/main/java/side/onetime/service/EventService.java b/src/main/java/side/onetime/service/EventService.java index 246c1cf..c1726a4 100644 --- a/src/main/java/side/onetime/service/EventService.java +++ b/src/main/java/side/onetime/service/EventService.java @@ -40,7 +40,14 @@ public class EventService { private final S3Util s3Util; private final QrUtil qrUtil; - // 이벤트 생성 메서드 (비로그인) + /** + * 비로그인 사용자를 위한 이벤트 생성 메서드. + * 이 메서드는 비로그인 사용자가 제공한 정보를 기반으로 새로운 이벤트를 생성하고, QR 코드를 생성하여 S3에 업로드합니다. + * + * @param createEventRequest 이벤트 생성 요청 데이터 (제목, 시간, 카테고리, 설문 범위 등) + * @return 생성된 이벤트의 응답 데이터 (이벤트 ID 포함) + * @throws CustomException QR 코드 생성 실패 또는 잘못된 날짜/요일 포맷일 경우 + */ @Transactional public CreateEventResponse createEventForAnonymousUser(CreateEventRequest createEventRequest) { Event event = createEventRequest.toEntity(); @@ -65,7 +72,16 @@ public CreateEventResponse createEventForAnonymousUser(CreateEventRequest create return CreateEventResponse.of(event); } - // 이벤트 생성 메서드 (로그인) + /** + * 인증된 사용자를 위한 이벤트 생성 메서드. + * 이 메서드는 인증된 사용자가 제공한 정보를 기반으로 새로운 이벤트를 생성하고, QR 코드를 생성하여 S3에 업로드합니다. + * 생성된 이벤트에 대해 참여 정보(EventParticipation)도 저장됩니다. + * + * @param createEventRequest 이벤트 생성 요청 데이터 (제목, 시간, 카테고리, 설문 범위 등) + * @param authorizationHeader 인증된 사용자의 토큰 + * @return 생성된 이벤트의 응답 데이터 (이벤트 ID 포함) + * @throws CustomException QR 코드 생성 실패 또는 잘못된 날짜/요일 포맷일 경우 + */ @Transactional public CreateEventResponse createEventForAuthenticatedUser(CreateEventRequest createEventRequest, String authorizationHeader) { User user = jwtUtil.getUserFromHeader(authorizationHeader); @@ -98,17 +114,32 @@ public CreateEventResponse createEventForAuthenticatedUser(CreateEventRequest cr return CreateEventResponse.of(event); } - // QR 코드 생성 및 S3 업로드 + /** + * QR 코드를 생성하고 S3에 업로드하는 메서드. + * 주어진 이벤트 ID를 기반으로 QR 코드를 생성한 후, S3에 업로드합니다. + * + * @param eventId QR 코드를 생성할 이벤트의 ID + * @return S3에 업로드된 QR 코드 파일 이름 + * @throws CustomException QR 코드 생성 또는 S3 업로드 실패 시 발생 + */ private String generateAndUploadQrCode(UUID eventId) { try { MultipartFile qrCodeFile = qrUtil.getQrCodeFile(eventId); return s3Util.uploadImage(qrCodeFile); } catch (Exception e) { - throw new RuntimeException("QR 코드 생성 또는 업로드 실패", e); + throw new CustomException(EventErrorStatus._FAILED_GENERATE_QR_CODE); } } - // 날짜 스케줄을 생성하고 저장하는 메서드 + /** + * 날짜 기반 스케줄을 생성하고 저장하는 메서드. + * 이벤트의 날짜 범위와 시작/종료 시간을 기반으로 모든 가능한 스케줄을 생성합니다. + * + * @param event 이벤트 객체 + * @param ranges 날짜 범위 리스트 (예: ["2024.12.10", "2024.12.11"]) + * @param startTime 시작 시간 (HH:mm 포맷) + * @param endTime 종료 시간 (HH:mm 포맷) + */ @Transactional protected void createAndSaveDateSchedules(Event event, List ranges, String startTime, String endTime) { List timeSets = DateUtil.createTimeSets(startTime, endTime); @@ -123,7 +154,15 @@ protected void createAndSaveDateSchedules(Event event, List ranges, Stri scheduleRepository.saveAll(schedules); } - // 요일 스케줄을 생성하고 저장하는 메서드 + /** + * 요일 기반 스케줄을 생성하고 저장하는 메서드. + * 이벤트의 요일 범위와 시작/종료 시간을 기반으로 모든 가능한 스케줄을 생성합니다. + * + * @param event 이벤트 객체 + * @param ranges 요일 범위 리스트 (예: ["MONDAY", "TUESDAY"]) + * @param startTime 시작 시간 (HH:mm 포맷) + * @param endTime 종료 시간 (HH:mm 포맷) + */ @Transactional protected void createAndSaveDaySchedules(Event event, List ranges, String startTime, String endTime) { List timeSets = DateUtil.createTimeSets(startTime, endTime); @@ -138,7 +177,15 @@ protected void createAndSaveDaySchedules(Event event, List ranges, Strin scheduleRepository.saveAll(schedules); } - // 이벤트 조회 메서드 + /** + * 이벤트 조회 메서드. + * 특정 이벤트의 세부 정보를 조회하며, 인증된 유저의 경우 추가 정보를 반환합니다. + * + * @param eventId 조회할 이벤트의 ID + * @param authorizationHeader 인증된 유저의 토큰 (선택 사항) + * @return 조회된 이벤트의 세부 정보 + * @throws CustomException 이벤트 또는 관련 스케줄을 찾을 수 없는 경우 + */ @Transactional(readOnly = true) public GetEventResponse getEvent(String eventId, String authorizationHeader) { Event event = eventRepository.findByEventId(UUID.fromString(eventId)) @@ -163,7 +210,14 @@ public GetEventResponse getEvent(String eventId, String authorizationHeader) { return GetEventResponse.of(event, ranges, eventStatus); } - // 참여자 조회 메서드 + /** + * 이벤트 참여자 조회 메서드. + * 특정 이벤트에 참여한 모든 참여자의 이름 목록(멤버 및 유저)을 반환합니다. + * + * @param eventId 참여자를 조회할 이벤트의 ID + * @return 참여자의 이름 목록 + * @throws CustomException 이벤트를 찾을 수 없는 경우 + */ @Transactional(readOnly = true) public GetParticipantsResponse getParticipants(String eventId) { Event event = eventRepository.findByEventId(UUID.fromString(eventId)) @@ -187,7 +241,14 @@ public GetParticipantsResponse getParticipants(String eventId) { return GetParticipantsResponse.of(members, users); } - // 가장 많이 되는 시간 조회 메서드 + /** + * 가장 많이 되는 시간 조회 메서드. + * 특정 이벤트에서 참여자 수가 가장 많은 시간대를 계산하여 반환합니다. + * + * @param eventId 조회할 이벤트의 ID + * @return 가능 인원이 많은 시간대 목록 + * @throws CustomException 이벤트를 찾을 수 없는 경우 + */ @Transactional(readOnly = true) public List getMostPossibleTime(String eventId) { Event event = eventRepository.findByEventId(UUID.fromString(eventId)) @@ -231,7 +292,13 @@ public List getMostPossibleTime(String eventId) { return DateUtil.sortMostPossibleTimes(mostPossibleTimes, event.getCategory()); } - // 스케줄과 선택된 참여자 이름 매핑 (멤버 이름 / 유저 닉네임) + /** + * 스케줄과 선택된 참여자 이름 매핑 메서드. + * 각 스케줄에 대해 해당 시간에 참여할 수 있는 멤버와 유저의 이름을 매핑합니다. + * + * @param selections 선택 정보 리스트 + * @return 스케줄과 참여자 이름의 매핑 데이터 + */ private Map> buildScheduleToNamesMap(List selections) { return selections.stream() .collect(Collectors.groupingBy( @@ -248,7 +315,16 @@ private Map> buildScheduleToNamesMap(List sele )); } - // 최적 시간대 리스트 생성 + /** + * 가장 많이 되는 시간대 리스트 생성 메서드. + * 참여 가능한 인원이 많은 시간대를 기반으로 시간대 리스트를 생성합니다. + * + * @param scheduleToNamesMap 스케줄과 참여자 이름 매핑 데이터 + * @param mostPossibleCnt 가장 많은 참여자 수 + * @param allMembersName 이벤트 참여자의 전체 이름 목록 + * @param category 이벤트의 카테고리 (날짜 또는 요일) + * @return 가장 많이 되는 시간대 리스트 + */ private List buildMostPossibleTimes(Map> scheduleToNamesMap, int mostPossibleCnt, List allMembersName, Category category) { List mostPossibleTimes = new ArrayList<>(); GetMostPossibleTime previousTime = null; @@ -286,7 +362,16 @@ private List buildMostPossibleTimes(Map curNames, Category category) { if (previousTime == null) return false; @@ -299,19 +384,40 @@ private boolean canMergeWithPrevious(GetMostPossibleTime previousTime, Schedule && new HashSet<>(previousTime.possibleNames()).containsAll(curNames); } - // 새로운 시간대 객체 생성 + /** + * 새로운 시간대 객체 생성 메서드. + * 주어진 스케줄 정보와 참여자 데이터를 기반으로 새로운 시간대 객체를 생성합니다. + * + * @param schedule 스케줄 정보 + * @param curNames 참여 가능한 이름 목록 + * @param impossibleNames 참여 불가능한 이름 목록 + * @param category 이벤트의 카테고리 (날짜 또는 요일) + * @return 생성된 시간대 객체 + */ private GetMostPossibleTime createMostPossibleTime(Schedule schedule, List curNames, List impossibleNames, Category category) { return category.equals(Category.DAY) ? GetMostPossibleTime.dayOf(schedule, curNames, impossibleNames) : GetMostPossibleTime.dateOf(schedule, curNames, impossibleNames); } - // 날짜 포맷인지 검증 + /** + * 날짜 포맷 여부 검증 메서드. + * 주어진 문자열이 날짜 형식인지 확인합니다. + * + * @param range 검증할 문자열 + * @return 날짜 형식 여부 + */ private boolean isDateFormat(String range) { return Character.isDigit(range.charAt(0)); } - // 유저 참여 이벤트 반환 메서드 + /** + * 유저 참여 이벤트 반환 메서드. + * 인증된 유저가 참여한 모든 이벤트 목록을 조회하며, 각 이벤트에 대한 세부 정보를 반환합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @return 유저가 참여한 이벤트 목록 + */ @Transactional(readOnly = true) public List getUserParticipatedEvents(String authorizationHeader) { User user = jwtUtil.getUserFromHeader(authorizationHeader); @@ -331,21 +437,42 @@ public List getUserParticipatedEvents(String .collect(Collectors.toList()); } - // 유저가 생성한 이벤트 삭제 메서드 + /** + * 유저가 생성한 이벤트 삭제 메서드. + * 인증된 유저가 생성한 특정 이벤트를 삭제합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @param eventId 삭제할 이벤트의 ID + */ @Transactional public void removeUserCreatedEvent(String authorizationHeader, String eventId) { EventParticipation eventParticipation = verifyUserIsEventCreator(authorizationHeader, eventId); eventRepository.deleteEvent(eventParticipation.getEvent()); } - // 유저가 생성한 이벤트 제목 수정 메서드 + /** + * 유저가 생성한 이벤트 제목 수정 메서드. + * 인증된 유저가 생성한 특정 이벤트의 제목을 수정합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @param eventId 수정할 이벤트의 ID + * @param modifyUserCreatedEventTitleRequest 새로운 제목 데이터 + */ @Transactional public void modifyUserCreatedEventTitle(String authorizationHeader, String eventId, ModifyUserCreatedEventTitleRequest modifyUserCreatedEventTitleRequest) { EventParticipation eventParticipation = verifyUserIsEventCreator(authorizationHeader, eventId); eventParticipation.getEvent().updateTitle(modifyUserCreatedEventTitleRequest.title()); } - // 유저가 이벤트의 생성자인지 검증하는 메서드 + /** + * 유저가 이벤트의 생성자인지 검증하는 메서드. + * 인증된 유저가 특정 이벤트의 생성자인지 확인하고, 생성자인 경우 관련 정보를 반환합니다. + * + * @param authorizationHeader 인증된 유저의 토큰 + * @param eventId 확인할 이벤트의 ID + * @return 이벤트 생성자의 참여 정보 + * @throws CustomException 유저가 생성자가 아니거나 참여 정보를 찾을 수 없는 경우 + */ private EventParticipation verifyUserIsEventCreator(String authorizationHeader, String eventId) { User user = jwtUtil.getUserFromHeader(authorizationHeader); Event event = eventRepository.findByEventId(UUID.fromString(eventId)) @@ -362,14 +489,23 @@ private EventParticipation verifyUserIsEventCreator(String authorizationHeader, return eventParticipation; } - // 이벤트 QR Code 조회 메서드 + /** + * 이벤트 QR Code 조회 메서드. + * 특정 이벤트의 QR 코드 이미지를 S3에서 가져와 URL을 반환합니다. + * + * @param eventId QR 코드를 조회할 이벤트의 ID + * @return QR 코드 이미지 URL + * @throws CustomException 이벤트 또는 QR 코드를 찾을 수 없는 경우 + */ @Transactional(readOnly = true) public GetEventQrCodeResponse getEventQrCode(String eventId) { Event event = eventRepository.findByEventId(UUID.fromString(eventId)) .orElseThrow(() -> new CustomException(EventErrorStatus._NOT_FOUND_EVENT)); + if (event.getQrFileName() == null) { + throw new CustomException(EventErrorStatus._NOT_FOUND_EVENT_QR_CODE); + } String qrCodeImgUrl = s3Util.getPublicUrl(event.getQrFileName()); - return GetEventQrCodeResponse.from(qrCodeImgUrl); } } From 4cc80add4194e47f7913b2bd4dc7fbed3ef995a6 Mon Sep 17 00:00:00 2001 From: bbbang105 <2018111366@dgu.ac.kr> Date: Sat, 7 Dec 2024 01:10:47 +0900 Subject: [PATCH 11/11] =?UTF-8?q?#127=20[style]=20:=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../onetime/controller/EventController.java | 16 ++++++++-------- src/main/java/side/onetime/util/QrUtil.java | 18 +++++++++++++++++- src/main/java/side/onetime/util/S3Util.java | 14 +++++++++++++- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/main/java/side/onetime/controller/EventController.java b/src/main/java/side/onetime/controller/EventController.java index 776aff4..e0de833 100644 --- a/src/main/java/side/onetime/controller/EventController.java +++ b/src/main/java/side/onetime/controller/EventController.java @@ -20,7 +20,7 @@ public class EventController { private final EventService eventService; /** - * 이벤트 생성 API + * 이벤트 생성 API. * * 이 API는 새로운 이벤트를 생성합니다. 인증된 유저와 익명 유저 모두 이벤트를 생성할 수 있으며, * 인증된 유저의 경우 추가적인 정보가 저장됩니다. @@ -45,7 +45,7 @@ public ResponseEntity> createEvent( } /** - * 이벤트 조회 API + * 이벤트 조회 API. * * 이 API는 특정 이벤트의 세부 정보를 조회합니다. 이벤트의 제목, 시간, 카테고리 등의 정보를 제공하며 * 인증된 유저일 경우 추가적인 정보가 포함될 수 있습니다. @@ -65,7 +65,7 @@ public ResponseEntity> getEvent( } /** - * 참여자 조회 API + * 참여자 조회 API. * * 이 API는 특정 이벤트에 참여한 모든 참여자의 이름 목록을 조회합니다. * @@ -81,7 +81,7 @@ public ResponseEntity> getParticipants( } /** - * 가장 많이 되는 시간 조회 API + * 가장 많이 되는 시간 조회 API. * * 이 API는 특정 이벤트에서 가장 많이 가능한 시간대를 조회하여, 가능 인원과 해당 시간대 정보를 제공합니다. * @@ -97,7 +97,7 @@ public ResponseEntity>> getMostPossibleTim } /** - * 유저 참여 이벤트 목록 조회 API + * 유저 참여 이벤트 목록 조회 API. * * 이 API는 인증된 유저가 참여한 모든 이벤트 목록을 조회합니다. 유저의 참여 상태, 이벤트 정보 등이 포함됩니다. * @@ -113,7 +113,7 @@ public ResponseEntity>> getU } /** - * 유저가 생성한 이벤트 삭제 API + * 유저가 생성한 이벤트 삭제 API. * * 이 API는 인증된 유저가 생성한 특정 이벤트를 삭제합니다. * @@ -131,7 +131,7 @@ public ResponseEntity> removeUserCreatedEvent( } /** - * 유저가 생성한 이벤트 제목 수정 API + * 유저가 생성한 이벤트 제목 수정 API. * * 이 API는 인증된 유저가 생성한 특정 이벤트의 제목을 수정합니다. * @@ -151,7 +151,7 @@ public ResponseEntity> modifyUserCreatedEventTitle( } /** - * 이벤트 QR Code 조회 API + * 이벤트 QR Code 조회 API. * * 이 API는 이벤트로 이동할 수 있는 QR Code 이미지를 반환합니다. * diff --git a/src/main/java/side/onetime/util/QrUtil.java b/src/main/java/side/onetime/util/QrUtil.java index 2c905f6..e26204d 100644 --- a/src/main/java/side/onetime/util/QrUtil.java +++ b/src/main/java/side/onetime/util/QrUtil.java @@ -22,6 +22,14 @@ public class QrUtil { @Value("${qr.event-base-url}") private String qrEventBaseUrl; + /** + * QR 코드 파일 생성 메서드. + * 주어진 이벤트 ID를 기반으로 QR 코드를 생성하고, 이를 MultipartFile로 변환하여 반환합니다. + * + * @param eventId QR 코드를 생성할 이벤트의 ID + * @return 생성된 QR 코드 파일 + * @throws Exception QR 코드 생성 또는 파일 변환 중 오류 발생 시 + */ public MultipartFile getQrCodeFile(UUID eventId) throws Exception { // QR 코드 생성 byte[] qrCodeBytes = generateQRCode(qrEventBaseUrl + eventId); @@ -33,6 +41,14 @@ public MultipartFile getQrCodeFile(UUID eventId) throws Exception { return FileUtil.convertToMultipartFile(qrCodeBytes, fileName); } + /** + * QR 코드 생성 메서드. + * 주어진 URL을 QR 코드 이미지 데이터로 변환합니다. + * + * @param eventUrl QR 코드에 포함될 이벤트 URL + * @return QR 코드 이미지 데이터 + * @throws Exception QR 코드 생성 중 오류 발생 시 + */ public byte[] generateQRCode(String eventUrl) throws Exception { // JSON 형식의 데이터 생성 @@ -46,7 +62,7 @@ public byte[] generateQRCode(String eventUrl) throws Exception { hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // QR 코드 생성 - BitMatrix bitMatrix = qrCodeWriter.encode(qrContent, BarcodeFormat.QR_CODE, 100, 100, hints); + BitMatrix bitMatrix = qrCodeWriter.encode(qrContent, BarcodeFormat.QR_CODE, 512, 512, hints); // ByteArrayOutputStream을 사용해 이미지를 바이트 배열로 변환 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); diff --git a/src/main/java/side/onetime/util/S3Util.java b/src/main/java/side/onetime/util/S3Util.java index ff78d98..9506c99 100644 --- a/src/main/java/side/onetime/util/S3Util.java +++ b/src/main/java/side/onetime/util/S3Util.java @@ -20,7 +20,12 @@ public class S3Util { private String bucket; /** - * S3에 이미지 업로드 하기 + * S3에 이미지 업로드 메서드. + * 주어진 MultipartFile 이미지를 S3에 업로드하고 고유한 파일 이름을 반환합니다. + * + * @param image 업로드할 이미지 파일 + * @return S3에 저장된 파일 이름 + * @throws IOException 파일 업로드 중 오류 발생 시 */ public String uploadImage(MultipartFile image) throws IOException { String fileName = UUID.randomUUID() + "_" + image.getOriginalFilename(); // 고유한 파일 이름 생성 @@ -39,6 +44,13 @@ public String uploadImage(MultipartFile image) throws IOException { return fileName; } + /** + * S3에 저장된 파일의 퍼블릭 URL 반환 메서드. + * 주어진 파일 이름에 해당하는 S3 파일의 퍼블릭 URL을 반환합니다. + * + * @param fileName S3에 저장된 파일 이름 + * @return 파일의 퍼블릭 URL + */ public String getPublicUrl(String fileName) { return String.format("https://%s.s3.%s.amazonaws.com/%s", bucket, amazonS3.getRegionName(), fileName); }