Skip to content

Commit 910a8f4

Browse files
authored
[Refactor]: 입찰 리팩토링
1. 메소드 기능별로 구분 (create, find/get, validation, update, helper) 2. 불필요한 entityManager/ 검증 메소드 제거 3. 결제관련 서비스 분리 4. rsData 정적 메소드 활용 5. status필드 String-> enum 6. 응답코드 문서화 개선 7. 테스트코드 수정
2 parents 80a28b5 + bbc1260 commit 910a8f4

File tree

15 files changed

+474
-290
lines changed

15 files changed

+474
-290
lines changed

build.gradle.kts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,6 @@ dependencies {
7373

7474
tasks.withType<Test> {
7575
useJUnitPlatform()
76-
filter {
77-
includeTestsMatching("com.backend.domain.member.controller.ApiV1MemberControllerTest")
78-
}
7976
}
8077

8178
// QueryDSL 설정 - 생성된 Q클래스들이 저장될 디렉토리 설정
Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.backend.domain.bid.controller;
22

33
import com.backend.domain.bid.dto.*;
4+
import com.backend.domain.bid.service.BidPaymentService;
45
import com.backend.domain.bid.service.BidService;
56
import com.backend.domain.member.repository.MemberRepository;
67
import com.backend.global.response.RsData;
@@ -26,85 +27,94 @@
2627
public class ApiV1BidController {
2728

2829
private final BidService bidService;
30+
private final BidPaymentService bidPaymentService;
2931
private final MemberRepository memberRepository;
3032

3133
@Operation(summary = "입찰 생성", description = "특정 상품에 대해 입찰 생성.")
3234
@ApiResponses(value = {
33-
@ApiResponse(responseCode = "200", description = "입찰 생성 성공",
34-
content = @Content(schema = @Schema(implementation = RsData.class))),
35-
@ApiResponse(responseCode = "400", description = "잘못된 요청",
36-
content = @Content(schema = @Schema(implementation = RsData.class))),
37-
@ApiResponse(responseCode = "401", description = "인증 실패",
38-
content = @Content(schema = @Schema(implementation = RsData.class)))
35+
@ApiResponse(responseCode = "201", description = "입찰 생성 성공",
36+
content = @Content(schema = @Schema(implementation = RsData.class))),
37+
@ApiResponse(responseCode = "400", description = "잘못된 요청",
38+
content = @Content(schema = @Schema(implementation = RsData.class))),
39+
@ApiResponse(responseCode = "401", description = "인증 실패",
40+
content = @Content(schema = @Schema(implementation = RsData.class)))
3941
})
4042
@PostMapping("/products/{productId}")
4143
public RsData<BidResponseDto> createBid(
4244
@Parameter(description = "상품 ID", required = true) @PathVariable Long productId,
4345
@Parameter(description = "입찰 요청 정보", required = true) @Valid @RequestBody BidRequestDto request,
4446
@Parameter(hidden = true) @AuthenticationPrincipal User user) {
45-
// TODO: JWT 토큰에서 사용자 추출로직으로 대체
46-
Long bidderId;
47-
if (user != null) {
48-
bidderId = Long.parseLong(user.getUsername());
49-
} else {
50-
// 테스트용: 인증이 없으면 첫 번째 사용자 사용
51-
bidderId = 1L;
52-
}
47+
Long bidderId = extractMemberId(user);
5348
return bidService.createBid(productId, bidderId, request);
5449
}
5550

5651
@Operation(summary = "입찰 현황 조회", description = "특정 상품의 현재 입찰 현황 조회.")
5752
@ApiResponses(value = {
58-
@ApiResponse(responseCode = "200", description = "입찰 현황 조회 성공",
59-
content = @Content(schema = @Schema(implementation = RsData.class))),
60-
@ApiResponse(responseCode = "404", description = "상품을 찾을 수 없음",
61-
content = @Content(schema = @Schema(implementation = RsData.class)))
53+
@ApiResponse(responseCode = "200", description = "입찰 현황 조회 성공",
54+
content = @Content(schema = @Schema(implementation = RsData.class))),
55+
@ApiResponse(responseCode = "404", description = "상품을 찾을 수 없음",
56+
content = @Content(schema = @Schema(implementation = RsData.class)))
6257
})
6358
@GetMapping("/products/{productId}")
6459
public RsData<BidCurrentResponseDto> getBidStatus(
65-
@Parameter(description = "상품 ID", required = true) @PathVariable Long productId
66-
){
60+
@Parameter(description = "상품 ID", required = true) @PathVariable Long productId) {
6761
return bidService.getBidStatus(productId);
6862
}
6963

7064
@Operation(summary = "내 입찰 내역 조회", description = "현재 사용자의 입찰 내역을 페이지네이션으로 조회.")
7165
@ApiResponses(value = {
72-
@ApiResponse(responseCode = "200", description = "내 입찰 내역 조회 성공",
73-
content = @Content(schema = @Schema(implementation = RsData.class))),
74-
@ApiResponse(responseCode = "401", description = "인증 실패",
75-
content = @Content(schema = @Schema(implementation = RsData.class)))
66+
@ApiResponse(responseCode = "200", description = "내 입찰 내역 조회 성공",
67+
content = @Content(schema = @Schema(implementation = RsData.class))),
68+
@ApiResponse(responseCode = "401", description = "인증 실패",
69+
content = @Content(schema = @Schema(implementation = RsData.class)))
7670
})
7771
@GetMapping("/me")
7872
public RsData<MyBidResponseDto> getMyBids(
7973
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0") @RequestParam(defaultValue = "0") int page,
8074
@Parameter(description = "페이지 크기", example = "10") @RequestParam(defaultValue = "10") int size,
81-
@Parameter(hidden = true) @AuthenticationPrincipal User user
82-
){
83-
// TODO: JWT 토큰에서 사용자 추출로직으로 대체
84-
Long memberId = Long.parseLong(user.getUsername());
85-
return bidService.getMyBids(memberId,page,size);
75+
@Parameter(hidden = true) @AuthenticationPrincipal User user) {
76+
Long memberId = extractMemberId(user);
77+
return bidService.getMyBids(memberId, page, size);
8678
}
8779

8880
@Operation(summary = "낙찰 결제", description = "내가 낙찰한 입찰 건에 대해 지갑에서 출금하고 결제 완료로 표시합니다.")
81+
@ApiResponses(value = {
82+
@ApiResponse(responseCode = "200", description = "결제 성공",
83+
content = @Content(schema = @Schema(implementation = RsData.class))),
84+
@ApiResponse(responseCode = "401", description = "인증 실패",
85+
content = @Content(schema = @Schema(implementation = RsData.class))),
86+
@ApiResponse(responseCode = "403", description = "권한 없음",
87+
content = @Content(schema = @Schema(implementation = RsData.class)))
88+
})
8989
@PostMapping("/{bidId}/pay")
9090
public RsData<BidPayResponseDto> payBid(
91-
@PathVariable Long bidId,
92-
@AuthenticationPrincipal User user
93-
) {
91+
@Parameter(description = "입찰 ID", required = true) @PathVariable Long bidId,
92+
@Parameter(hidden = true) @AuthenticationPrincipal User user) {
9493
if (user == null) {
9594
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.");
9695
}
9796

98-
Long memberId;
97+
Long memberId = extractMemberId(user);
98+
return bidPaymentService.payForBid(memberId, bidId);
99+
}
100+
101+
// ======================================= helper methods ======================================= //
102+
private Long extractMemberId(User user) {
103+
if (user == null) {
104+
// 테스트용: 인증이 없으면 첫 번째 사용자 사용
105+
return 1L;
106+
}
107+
99108
String username = user.getUsername();
100109
try {
101-
memberId = Long.parseLong(username);
110+
return Long.parseLong(username);
102111
} catch (NumberFormatException e) {
103-
var me = memberRepository.findByEmail(username)
104-
.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "유효하지 않은 인증 정보입니다."));
105-
memberId = me.getId();
112+
var member = memberRepository.findByEmail(username)
113+
.orElseThrow(() -> new ResponseStatusException(
114+
HttpStatus.UNAUTHORIZED,
115+
"유효하지 않은 인증 정보입니다."
116+
));
117+
return member.getId();
106118
}
107-
108-
return bidService.payForBid(memberId, bidId);
109119
}
110120
}
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package com.backend.domain.bid.dto;
22

3+
import com.backend.domain.bid.enums.BidStatus;
4+
35
import java.time.LocalDateTime;
46

5-
public record BidResponseDto (
7+
public record BidResponseDto(
68
Long id,
79
Long productId,
810
Long bidderId,
911
long price,
10-
String status,
12+
BidStatus status,
1113
LocalDateTime createDate
12-
) {}
14+
) {
15+
}
Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,37 @@
11
package com.backend.domain.bid.dto;
22

3+
import com.backend.domain.bid.enums.BidStatus;
4+
35
import java.time.LocalDateTime;
46
import java.util.List;
57

6-
public record MyBidResponseDto (
8+
public record MyBidResponseDto(
79
List<MyBidItem> content,
810
Integer totalElements,
911
Integer totalPages,
1012
Integer currentPage,
1113
Integer pageSize,
1214
Boolean hasNext
13-
){
14-
public record MyBidItem (
15+
) {
16+
public record MyBidItem(
1517
Long bidId,
1618
Long productId,
1719
String productName,
1820
String thumbnailUrl,
1921
Long myBidPrice,
2022
Long currentPrice,
21-
String status,
23+
BidStatus status,
2224
Boolean isWinning,
2325
LocalDateTime bidTime,
2426
LocalDateTime endTime,
2527
String productStatus,
2628
SellerInfo Seller
27-
) {}
28-
public record SellerInfo (
29+
) {
30+
}
31+
32+
public record SellerInfo(
2933
Long id,
3034
String nickname
31-
) {}
35+
) {
36+
}
3237
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.backend.domain.bid.dto;
2+
3+
public interface ProductCurrentPriceDto {
4+
Long getProductId();
5+
Long getCurrentPrice();
6+
}

src/main/java/com/backend/domain/bid/entity/Bid.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.backend.domain.bid.entity;
22

3+
import com.backend.domain.bid.enums.BidStatus;
34
import com.backend.domain.member.entity.Member;
45
import com.backend.domain.product.entity.Product;
56
import com.backend.global.jpa.entity.BaseEntity;
@@ -10,7 +11,8 @@
1011

1112
@Entity
1213
@Table(name = "bids")
13-
@Getter @Setter
14+
@Getter
15+
@Setter
1416
@Builder
1517
@NoArgsConstructor
1618
@AllArgsConstructor
@@ -19,8 +21,9 @@ public class Bid extends BaseEntity {
1921
@Column(name = "bid_price", nullable = false)
2022
private Long bidPrice;
2123

24+
@Enumerated(EnumType.STRING)
2225
@Column(nullable = false, length = 50)
23-
private String status;
26+
private BidStatus status;
2427

2528
@ManyToOne(fetch = FetchType.LAZY)
2629
@JoinColumn(name = "product_id")
@@ -35,4 +38,4 @@ public class Bid extends BaseEntity {
3538

3639
@Column(name = "paid_amount")
3740
private Long paidAmount;
38-
}
41+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.backend.domain.bid.enums;
2+
3+
import lombok.Getter;
4+
5+
@Getter
6+
public enum BidStatus {
7+
BIDDING("bidding", "입찰 중"),
8+
WINNING("winning", "낙찰"),
9+
LOSING("losing", "낙찰 실패"),
10+
PAID("paid", "결제 완료"),
11+
CANCELLED("cancelled", "취소됨");
12+
13+
private final String value;
14+
private final String displayName;
15+
16+
BidStatus(String value, String displayName) {
17+
this.value = value;
18+
this.displayName = displayName;
19+
}
20+
}
Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.backend.domain.bid.repository;
22

3+
import com.backend.domain.bid.dto.ProductCurrentPriceDto;
34
import com.backend.domain.bid.entity.Bid;
5+
import com.backend.domain.bid.enums.BidStatus;
46
import org.springframework.data.domain.Page;
57
import org.springframework.data.domain.Pageable;
68
import org.springframework.data.jpa.repository.JpaRepository;
@@ -11,46 +13,52 @@
1113
import java.util.Optional;
1214
import java.util.Set;
1315

14-
public interface BidRepository extends JpaRepository<Bid,Long> {
16+
public interface BidRepository extends JpaRepository<Bid, Long> {
1517
// 현재 최고 입찰가 조회
16-
@Query("SELECT MAX(b.bidPrice) FROM Bid b WHERE b.product.id = :productId AND b.status = 'bidding'")
18+
@Query("SELECT MAX(b.bidPrice) FROM Bid b WHERE b.product.id = :productId AND b.status = 'BIDDING'")
1719
Optional<Long> findHighestBidPrice(@Param("productId") Long productId);
20+
1821
// 이미 입찰했는지 여부
19-
@Query("SELECT COUNT(b)>0 FROM Bid b WHERE b.product.id = :productId AND b.member.id = :memberId AND b.status = 'bidding'")
20-
boolean existsProductBid(@Param("productId") Long productId,@Param("memberId") Long memberId,@Param("status") String status);
22+
boolean existsByProductIdAndMemberIdAndStatus(Long productId, Long memberId, BidStatus status);
23+
2124
// 입찰 개수 조회
22-
@Query("SELECT COUNT(b) FROM Bid b WHERE b.product.id = :productId AND b.status = 'bidding'")
25+
@Query("SELECT COUNT(b) FROM Bid b WHERE b.product.id = :productId AND b.status = 'BIDDING'")
2326
Integer countProductBid(@Param("productId") Long productId);
27+
2428
// 상품 입찰내역(페이징)
25-
@Query("SELECT b FROM Bid b WHERE b.product.id = :productId AND b.status = 'bidding' ORDER BY b.createDate DESC")
29+
@Query("SELECT b FROM Bid b WHERE b.product.id = :productId AND b.status = 'BIDDING' ORDER BY b.createDate DESC")
2630
Page<Bid> findAllBids(@Param("productId") Long productId, Pageable pageable);
31+
2732
// 상품 입찰내역(상위 n개)
28-
@Query("SELECT b FROM Bid b WHERE b.product.id = :productId AND b.status = 'bidding' ORDER BY b.createDate DESC LIMIT:limit")
33+
@Query("SELECT b FROM Bid b WHERE b.product.id = :productId AND b.status = 'BIDDING' ORDER BY b.createDate DESC LIMIT :limit")
2934
List<Bid> findNBids(@Param("productId") Long productId, @Param("limit") Integer limit);
35+
3036
// 내 입찰내역 조회
31-
@Query("SELECT b FROM Bid b JOIN FETCH b.product p WHERE b.member.id = :memberId AND b.status = 'bidding' ORDER BY b.createDate DESC")
37+
@Query("SELECT b FROM Bid b JOIN FETCH b.product p WHERE b.member.id = :memberId AND b.status = 'BIDDING' ORDER BY b.createDate DESC")
3238
Page<Bid> findMyBids(@Param("memberId") Long memberId, Pageable pageable);
39+
3340
// 상품들 현재 최고 입찰가 조회
3441
@Query("""
35-
SELECT b.product.id, MAX(b.bidPrice)
36-
FROM Bid b
37-
WHERE b.product.id IN :productIds
38-
AND b.status = 'bidding'
39-
GROUP BY b.product.id
40-
""")
41-
List<Object[]> findCurrentPricesForProducts(@Param("productIds") Set<Long> productIds);
42+
SELECT b.product.id AS productId, MAX(b.bidPrice) AS currentPrice
43+
FROM Bid b
44+
WHERE b.product.id IN :productIds
45+
AND b.status = 'BIDDING'
46+
GROUP BY b.product.id
47+
""")
48+
List<ProductCurrentPriceDto> findCurrentPricesForProducts(@Param("productIds") Set<Long> productIds);
49+
4250
// 내가 최고 입찰자인 상품들
4351
@Query("""
44-
SELECT b FROM Bid b
45-
WHERE b.member.id = :memberId
46-
AND b.status = 'bidding'
47-
AND b.bidPrice = (
48-
SELECT MAX(b2.bidPrice)
49-
FROM Bid b2
50-
WHERE b2.product.id = b.product.id
51-
AND b2.status = 'bidding'
52-
)
53-
ORDER BY b.createDate DESC
54-
""")
52+
SELECT b FROM Bid b
53+
WHERE b.member.id = :memberId
54+
AND b.status = 'BIDDING'
55+
AND b.bidPrice = (
56+
SELECT MAX(b2.bidPrice)
57+
FROM Bid b2
58+
WHERE b2.product.id = b.product.id
59+
AND b2.status = 'BIDDING'
60+
)
61+
ORDER BY b.createDate DESC
62+
""")
5563
List<Bid> findWinningBids(@Param("memberId") Long memberId);
5664
}

0 commit comments

Comments
 (0)