|
1 | 1 | package PodoeMarket.podoemarket.product.service; |
2 | 2 |
|
3 | 3 | import PodoeMarket.podoemarket.common.entity.*; |
4 | | -import PodoeMarket.podoemarket.common.entity.type.StageType; |
5 | | -import PodoeMarket.podoemarket.common.entity.type.StandardType; |
| 4 | +import PodoeMarket.podoemarket.common.entity.type.*; |
6 | 5 | import PodoeMarket.podoemarket.common.repository.*; |
7 | | -import PodoeMarket.podoemarket.common.entity.type.PlayType; |
8 | | -import PodoeMarket.podoemarket.common.entity.type.ProductStatus; |
9 | 6 | import PodoeMarket.podoemarket.product.dto.request.ReviewRequestDTO; |
10 | 7 | import PodoeMarket.podoemarket.product.dto.request.ReviewUpdateRequestDTO; |
11 | 8 | import PodoeMarket.podoemarket.product.dto.response.ReviewResponseDTO; |
|
15 | 12 | import PodoeMarket.podoemarket.product.type.ReviewSortType; |
16 | 13 | import PodoeMarket.podoemarket.service.S3Service; |
17 | 14 | import PodoeMarket.podoemarket.service.ViewCountService; |
| 15 | +import com.amazonaws.services.s3.AmazonS3; |
| 16 | +import com.amazonaws.services.s3.model.S3Object; |
18 | 17 | import com.itextpdf.io.source.ByteArrayOutputStream; |
19 | 18 | import org.apache.pdfbox.Loader; |
| 19 | +import org.springframework.beans.factory.annotation.Value; |
20 | 20 | import org.springframework.http.MediaType; |
21 | 21 | import org.springframework.http.ResponseEntity; |
22 | 22 | import org.springframework.transaction.annotation.Transactional; |
|
37 | 37 | import java.io.OutputStream; |
38 | 38 | import java.net.URL; |
39 | 39 | import java.time.LocalDateTime; |
| 40 | +import java.util.ArrayList; |
40 | 41 | import java.util.List; |
41 | 42 | import java.util.UUID; |
42 | 43 | import java.util.zip.ZipEntry; |
|
48 | 49 | public class ProductService { |
49 | 50 | private final ProductRepository productRepo; |
50 | 51 | private final OrderItemRepository orderItemRepo; |
51 | | - private final ApplicantRepository applicantRepo; |
52 | 52 | private final ProductLikeRepository productLikeRepo; |
53 | 53 | private final ReviewRepository reviewRepo; |
54 | 54 | private final ReviewLikeRepository reviewLikeRepo; |
55 | 55 |
|
56 | 56 | private final ViewCountService viewCountService; |
57 | 57 | private final S3Service s3Service; |
| 58 | + private final AmazonS3 amazonS3; |
| 59 | + |
| 60 | + @Value("${cloud.aws.s3.bucket}") |
| 61 | + private String bucket; |
58 | 62 |
|
59 | 63 | public List<ScriptListResponseDTO.ProductListDTO> getPlayList(int page, UserEntity userInfo, PlayType playType, int pageSize, ProductSortType sortType) { |
60 | 64 | try { |
@@ -128,7 +132,7 @@ public ScriptDetailResponseDTO getScriptDetailInfo(UserEntity userInfo, UUID pro |
128 | 132 | .scene(script.getScene()) |
129 | 133 | .act(script.getAct()) |
130 | 134 | .intention(script.getIntention()) |
131 | | - .buyStatus(buyStatus(userInfo, productId)) // 로그인한 유저의 해당 작품 구매 이력 확인 |
| 135 | + .buyOptions(buyOption(userInfo, productId)) // 로그인한 유저의 해당 작품 구매 이력 확인 |
132 | 136 | .like(getProductLikeStatus(userInfo, productId)) // 로그인한 유저의 좋아요 여부 확인 |
133 | 137 | .likeCount(script.getLikeCount()) // 총 좋아요 수 |
134 | 138 | .isReviewWritten(isReviewWritten) |
@@ -193,19 +197,28 @@ public String toggleLikeProduct(UserEntity userInfo, UUID productId) { |
193 | 197 | } |
194 | 198 | } |
195 | 199 |
|
196 | | - public ResponseEntity<StreamingResponseBody> generateFullPDF(String preSignedURL) { |
197 | | - StreamingResponseBody stream = outputStream -> { |
198 | | - try { |
199 | | - streamPdfFromZip(preSignedURL, outputStream); |
200 | | - } catch (Exception e) { |
201 | | - throw new RuntimeException("ZIP에서 PDF 추출 또는 스트리밍 중 오류 발생", e); |
| 200 | + public StreamingResponseBody viewScript(final UUID productId) { |
| 201 | + return outputStream -> { |
| 202 | + ProductEntity product = getProduct(productId); |
| 203 | + |
| 204 | + try(S3Object s3Object = amazonS3.getObject(bucket, product.getFilePath()); |
| 205 | + InputStream s3Stream = s3Object.getObjectContent()) { |
| 206 | + |
| 207 | + streamPdfFromZip(s3Stream, outputStream); |
202 | 208 | } |
203 | 209 | }; |
| 210 | + } |
204 | 211 |
|
205 | | - return ResponseEntity.ok() |
206 | | - .header("Content-Disposition", "inline; filename=\"script.pdf\"") |
207 | | - .contentType(MediaType.APPLICATION_PDF) |
208 | | - .body(stream); |
| 212 | + public StreamingResponseBody viewDescription(final UUID productId) { |
| 213 | + return outputStream -> { |
| 214 | + ProductEntity product = getProduct(productId); |
| 215 | + |
| 216 | + try(S3Object s3Object = amazonS3.getObject(bucket, product.getDescriptionPath()); |
| 217 | + InputStream s3Stream = s3Object.getObjectContent()) { |
| 218 | + |
| 219 | + streamPdfFromZip(s3Stream, outputStream); |
| 220 | + } |
| 221 | + }; |
209 | 222 | } |
210 | 223 |
|
211 | 224 | public boolean getProductLikeStatus(final UserEntity userInfo, final UUID productId) { |
@@ -405,17 +418,18 @@ private static byte[] extractPdfFromZip(String preSignedURL) throws IOException |
405 | 418 | } |
406 | 419 | } |
407 | 420 |
|
408 | | - // PDF 추출 메서드 (스트리밍 방식) |
409 | | - private static void streamPdfFromZip(String preSignedURL, OutputStream outputStream) throws IOException { |
410 | | - try (InputStream inputStream = new URL(preSignedURL).openStream(); |
411 | | - ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { |
| 421 | + // ZIP → PDF 추출 (스트리밍) |
| 422 | + private static void streamPdfFromZip(InputStream zipStream, OutputStream outputStream) throws IOException { |
| 423 | + try (ZipInputStream zipInputStream = new ZipInputStream(zipStream)) { |
412 | 424 | ZipEntry entry; |
413 | 425 | boolean found = false; |
414 | 426 |
|
| 427 | + byte[] buffer = new byte[8192]; |
| 428 | + |
415 | 429 | while ((entry = zipInputStream.getNextEntry()) != null) { |
| 430 | + |
416 | 431 | if (entry.getName().toLowerCase().endsWith(".pdf")) { |
417 | 432 | found = true; |
418 | | - byte[] buffer = new byte[8192]; // 8KB 씩 스트리밍 |
419 | 433 | int len; |
420 | 434 |
|
421 | 435 | while ((len = zipInputStream.read(buffer)) > 0) { |
@@ -474,31 +488,36 @@ protected void createProductLike(final ProductLikeEntity like, final UUID produc |
474 | 488 | } |
475 | 489 | } |
476 | 490 |
|
477 | | - private int buyStatus(final UserEntity userInfo, final UUID productId) { |
| 491 | + private List<BuyOption> buyOption(final UserEntity userInfo, final UUID productId) { |
478 | 492 | try { |
479 | | - if(userInfo == null) |
480 | | - return 0; |
| 493 | + // <대본> |
| 494 | + // 권리기간(열람기간) : 3개월 |
| 495 | + // 환불 : 불가 |
| 496 | + // 한 번에 1개만 소유 가능 |
481 | 497 |
|
482 | | - final List<OrderItemEntity> orderItems = orderItemRepo.findByProductIdAndUserId(productId, userInfo.getId()); |
| 498 | + List<BuyOption> options = new ArrayList<>(); |
483 | 499 |
|
484 | | - for(OrderItemEntity item : orderItems) { |
485 | | - final boolean isBuyScript = item.getScript(); // 대본 구매 여부 |
486 | | - final boolean isExpiryDate = LocalDateTime.now().isAfter(item.getCreatedAt().plusYears(1)); // 권리 기간 만료 여부 |
487 | | - final boolean isBuyPerformance = applicantRepo.existsByOrderItemId(item.getId()); // 공연권 구매 여부 |
| 500 | + if (userInfo == null) { |
| 501 | + options.add(BuyOption.SCRIPT); |
| 502 | + options.add(BuyOption.PERFORMANCE); |
488 | 503 |
|
489 | | - if(isBuyScript && !isExpiryDate) { // 대본 구매 (대본 권리 기간 유효) |
490 | | - return 1; |
491 | | - } else if(isBuyScript && !isExpiryDate && isBuyPerformance) { // 대본 + 공연권 구매 (대본 권리 기간 유효) |
492 | | - return 1; |
493 | | - } |
494 | | - else if(isBuyScript && isExpiryDate && isBuyPerformance) { // 공연권만 보유 |
495 | | - return 2; |
496 | | - } |
| 504 | + return options; |
497 | 505 | } |
498 | 506 |
|
499 | | - return 0; |
| 507 | + boolean hasValidScript = orderItemRepo.existsByProduct_IdAndUser_IdAndScriptTrueAndOrder_OrderStatusAndCreatedAtAfter( |
| 508 | + productId, userInfo.getId(), OrderStatus.PAID, LocalDateTime.now().minusMonths(3) |
| 509 | + ); |
| 510 | + |
| 511 | + // 유효한 대본이 없으면 대본 구매 가능 |
| 512 | + if(!hasValidScript) |
| 513 | + options.add(BuyOption.SCRIPT); |
| 514 | + |
| 515 | + // 공연권은 항상 가능 |
| 516 | + options.add(BuyOption.PERFORMANCE); |
| 517 | + |
| 518 | + return options; |
500 | 519 | } catch (Exception e) { |
501 | | - return 0; // 오류 발생 시 구매하지 않은 것으로 처리 |
| 520 | + throw e; |
502 | 521 | } |
503 | 522 | } |
504 | 523 |
|
|
0 commit comments