![[프로젝트]중고 경매 사이트 - 제시요(구매자 : 경매 참여중인 물품 - 입찰 취소, 재입찰)](https://image.inblog.dev?url=https%3A%2F%2Finblog.ai%2Fapi%2Fog%3Ftitle%3D%255B%25ED%2594%2584%25EB%25A1%259C%25EC%25A0%259D%25ED%258A%25B8%255D%25EC%25A4%2591%25EA%25B3%25A0%2520%25EA%25B2%25BD%25EB%25A7%25A4%2520%25EC%2582%25AC%25EC%259D%25B4%25ED%258A%25B8%2520-%2520%25EC%25A0%259C%25EC%258B%259C%25EC%259A%2594%28%25EA%25B5%25AC%25EB%25A7%25A4%25EC%259E%2590%2520%253A%2520%25EA%25B2%25BD%25EB%25A7%25A4%2520%25EC%25B0%25B8%25EC%2597%25AC%25EC%25A4%2591%25EC%259D%25B8%2520%25EB%25AC%25BC%25ED%2592%2588%2520-%2520%25EC%259E%2585%25EC%25B0%25B0%2520%25EC%25B7%25A8%25EC%2586%258C%252C%2520%25EC%259E%25AC%25EC%259E%2585%25EC%25B0%25B0%29%26logoUrl%3Dhttps%253A%252F%252Finblog.ai%252Finblog_logo.png%26blogTitle%3D%25EC%2586%25A1%25EC%258A%25B9%25ED%2598%2584%25EC%259D%2598%2520%25EB%25B8%2594%25EB%25A1%259C%25EA%25B7%25B8&w=2048&q=75)
화면





Mustache
<div class="btn-group mt-1">
<button type="button" class="btn btn-custom btn-sm" onclick="cancel({{buyerId}},{{goodsId}},{{buyerTryPrice}})">입찰 취소</button>
<button type="button" class="btn btn-custom btn-sm" data-bs-toggle="modal"
data-bs-target="#t1-re-bid-modal" onclick ="addReBid({{id}},{{goodsId}},{{buyerTryPrice}})">입찰 금액 수정</button>
</div>
JavaScript
async function cancel(userId,goodsId, tryPrice){
let userDetails = await getAuthentication()
let url =`/api/v1/cancelBid`
let response = await fetch(url, {
method : "POST",
body : JSON.stringify({
"userId" : userDetails.id,
"goodsId":goodsId,
"tryPrice" : tryPrice}),
headers : {
"Content-Type" : "Application/JSON; cherset = utf-8"
}
})
let responseData = await response.json();
if(!responseData.success){
throw new Error("네트워크 응답에 문제가 있습니다.")
}else {
alert("입찰을 취소하였습니다.")
}
window.location.href = "/s/mypage-participating-auction";
}
let bid_Id ="";
let goods_Id ="";
let try_Price ="";
function addReBid(id,goodsId,buyerTryPrice){
bid_Id = id;
goods_Id = goodsId;
try_Price = buyerTryPrice;
}
async function reRid(){
let re_Bid = document.getElementById("t1-re-bid-price").value;
let url = "/api/v1/re-bid"
let response =await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body : JSON.stringify({
bidId: bid_Id,
goodsId: goods_Id,
tryPrice : try_Price,
reTryPrice: re_Bid
})
});
let responseData = await response.json();
console.log(responseData)
if(!responseData.success){
alert(responseData.message);
}else{
alert("입찰가를 수정하였습니다.")
}
window.location.href = `/s/mypage-participating-auction`;
}
Controller
// 입찰 취소
@PostMapping("/api/v1/cancelBid")
@ResponseBody
public ResponseEntity<?> cancelBid(@RequestBody BidRequest.CancelBidDTO cancelBidDTO) {
bidService.cancelBid(cancelBidDTO);
CommonResp resp = new CommonResp(true,"성공", null);
return new ResponseEntity<>(resp, HttpStatus.OK);
}
// 재입찰
@PostMapping("/api/v1/re-bid")
@ResponseBody
public ResponseEntity<?> reBid(@AuthenticationPrincipal User user,@RequestBody BidRequest.ReBidRequestDTO dto) {
bidService.reBid(user.getId(),dto);
CommonResp resp = new CommonResp(true,"성공", null);
return new ResponseEntity<>(resp, HttpStatus.OK);
}
Service
// 입찰 취소
@Transactional
public void cancelBid(BidRequest.CancelBidDTO dto) {
bidRepository.deleteBid(dto.getGoodsId(), dto.getUserId());
UserAccount ua = userAccountRepository.findById(dto.getUserId());
ua.updateUserInfo(dto.getTryPrice());
}
// 재입찰
@Transactional
public void reBid(Integer userId, BidRequest.ReBidRequestDTO dto) {
// 동시성 체크
// 물품의 최고 입찰가를 조회
Optional<Bid> highestBid = bidRepository.findByGoodsIdDescWithLock(dto.getGoodsId());
if (highestBid.isPresent()) {
Integer currentHighestPrice = highestBid.get().getTryPrice();
if (dto.getReTryPrice() <= currentHighestPrice) {
throw new Exception400NotHTML("입찰금액이 현재 최고 입찰 금액보다 높아야합니다.");
}
}
// 유저의 잔액을 조회 - 업데이트
UserAccount ua = userAccountRepository.findById(userId);
if ((dto.getReTryPrice() - dto.getTryPrice()) > ua.getHasPrice()) {
throw new Exception400("자신의 보유 금액보다 높게 입찰할 수 없습니다.");
}
ua.minusPrice((dto.getReTryPrice() - dto.getTryPrice()));
// 입찰 정보를 조회 - 업데이트
Bid bid = bidRepository.findById(dto.getBidId());
bid.updatePrice(dto.getReTryPrice());
}
Repository
// bid 테이블에서 물품을 입찰한 사람 한명만 delete
public void deleteBid(Integer goodsId, Integer userId){
String sql = """
delete from bid_tb where goods_id = ? and buyer_id = ?
""";
Query q = em.createNativeQuery(sql);
q.setParameter(1, goodsId);
q.setParameter(2, userId);
q.executeUpdate();
}
// 유저 id를 이용하여 계좌 정보 조회
public UserAccount findById(Integer userId){
String sql = """
select u from UserAccount u join fetch u.user where u.user.id=:userId
""";
Query query = em.createQuery(sql);
query.setParameter("userId", userId);
query.setMaxResults(1);
return (UserAccount) query.getSingleResult();
}
// 사용자 입찰 시도 시 현재 최고금액 조회 (SELECT ~ FOR UPDATE)
public Optional<Bid> findByGoodsIdDescWithLock(Integer id) {
try{
Query q = em.createNativeQuery("select * from bid_tb where goods_id = ? order by id desc limit 1 for update", Bid.class);
q.setParameter(1, id);
return Optional.ofNullable((Bid) q.getSingleResult());
}catch (NoResultException e) {
return Optional.empty();
}
}
// 유저 계좌 정보 조회
public UserAccount findById(Integer userId){
String sql = """
select u from UserAccount u join fetch u.user where u.user.id=:userId
""";
Query query = em.createQuery(sql);
query.setParameter("userId", userId);
query.setMaxResults(1);
return (UserAccount) query.getSingleResult();
}
// 유저의 입찰 정보를 조회
public Bid findById(Integer id) {
String sql = "select * from bid_tb where id = ?";
Query q = em.createNativeQuery(sql, Bid.class);
q.setParameter(1, id);
return (Bid) q.getSingleResult();
}
해설
입찰 취소
유저의 정보, 내가 입찰한 금액, 물품의 정보를 받음
입찰 정보를 삭제
입찰금액을 입찰자에게 반환
재입찰
내가 입찰한 goods에 대한 최고 입찰가를 조회(비관적 락을 통한 동시성 제어)
내가 재입찰할 금액이 최고 입찰금 보다 작으면 throw를 던지고
최고 입찰금 보다 높으면
유저의 계좌 정보를 조회
잔액이 재입찰금 - 이전입찰금 보다 적으면 throw
높으면 입찰 정보롤 조회하여 업데이트
Share article