-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
๐ณ ๊ฒฐ์ ์๋น์ค #10
base: develop
Are you sure you want to change the base?
Changes from all commits
773d105
a7ad360
dd5722d
3e03967
9daedfc
6a3a9ba
5f95e59
bd37cc1
8bb8286
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.dhmall.auction.dto; | ||
|
||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@Getter | ||
@Setter | ||
public class AuctionWinnerDto { | ||
|
||
private String userId; | ||
|
||
private String productCode; | ||
|
||
private String amount; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.dhmall.auction.dto; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
import java.math.BigInteger; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public class ProductDto { | ||
|
||
private final BigInteger id; | ||
|
||
private final String code; | ||
|
||
private final String name; | ||
|
||
private final String category; | ||
|
||
private final String owner; | ||
|
||
private final int auctionStatus; | ||
|
||
private final String description; | ||
|
||
private final String createdAt; | ||
|
||
private final String updatedAt; | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,19 @@ | ||
package com.dhmall.auction.mapper; | ||
|
||
import com.dhmall.auction.dto.ChatMessageDto; | ||
import com.dhmall.auction.dto.ProductDto; | ||
import com.dhmall.user.dto.UserDto; | ||
import org.apache.ibatis.annotations.Mapper; | ||
import org.springframework.stereotype.Repository; | ||
|
||
import java.math.BigInteger; | ||
|
||
@Mapper | ||
@Repository | ||
public interface AuctionMapper { | ||
void insertChatMessage(ChatMessageDto chatMessage); | ||
|
||
UserDto findUserById(BigInteger id); | ||
|
||
ProductDto findProductByCode(String productCode); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
package com.dhmall.config; | ||
|
||
import org.springframework.boot.web.client.RestTemplateBuilder; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.web.client.RestTemplate; | ||
import org.springframework.web.servlet.config.annotation.EnableWebMvc; | ||
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; | ||
|
||
|
@@ -14,4 +16,9 @@ public FreeMarkerConfigurer freemarkerConfig() { | |
freeMarkerConfigurer.setTemplateLoaderPath("classpath:templates/"); | ||
return freeMarkerConfigurer; | ||
} | ||
|
||
@Bean | ||
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { | ||
return restTemplateBuilder.build(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ง๊ธ์ restTemplate ์์ฑ์ default๊ฐ์ผ๋ก ์ค์ ์ ์ด๊ธฐํ ํ์ง๋ง ์ค์ ์ด์์ ์ฌ์ฉ๋ ๋๋ ์ค์ ์ค์ ๊ฐ์ ์ ์ดํด๋ณด๊ณ ์ค์ ํด์ฃผ์ด์ผํฉ๋๋ค. ์๋ฅผ๋ค์ด connectionPoolSize, connectionTimeout, readTimeout ๊ฐ์ ๊ฐ๋ค์ด ๊ทธ ์ค ํ๋ ์ผ ๊ฒ ๊ฐ์์ :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ ๋ต๋ต ์๊ฒ ์ต๋๋ค ์ผ๋จ ์ด ๋ถ๋ถ์ ์ ๊ฐ ์ด์๋ก ๋ฑ๋กํด๋๊ณ ์ด์์์ ๋๋ฆด ์ ์ ์์ ํ๋ ๋ฐฉํฅ์ผ๋ก ๊ฐ์ ธ๊ฐ๋ณผ๊ฒ์ |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.dhmall.payment.controller; | ||
|
||
import com.dhmall.auction.dto.AuctionDto; | ||
import com.dhmall.payment.dto.PaymentDto; | ||
import com.dhmall.payment.dto.TossPayDto; | ||
import com.dhmall.payment.service.PaymentService; | ||
import com.dhmall.util.SagoApiResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.math.BigDecimal; | ||
import java.net.URI; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/payments/") | ||
public class PaymentController { | ||
|
||
private final PaymentService paymentService; | ||
@Value("${redirect.url}") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lombok config ํ์ผ์ ์ด์ฉํ๋ฉด final ๋ณ์๋ก ์ ์ธํ์๋ ์ฌ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค. lombok์ด ํด๋น ์ ๋ํ ์ด์ ์ ์์ฑ์์๋ ์ ์ฉํด์ฃผ๊ธฐ ๋๋ฌธ์ธ๋ฐ์. lombok.config ํ์ผ์ ํ๋ฒ ์ดํด๋ณด์๋ฉด ์ข์ ๊ฒ ๊ฐ์์. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ ๋ต๋ต ํด๋น ๋งํฌ ์ฐธ๊ณ ํด์ ๋ค๋ฅธ ๋ถ๋ถ๋ค๋ ์์ ํ๋ ๋ฐฉํฅ์ผ๋ก ๊ฐ๊ฒ ์ต๋๋ค! |
||
private final String serverUrl; | ||
|
||
@GetMapping("request") | ||
public SagoApiResponse<AuctionDto> requestPayment(@RequestParam String chatRoomId) { | ||
// TODO: Auction ์ฑํ ๋ฐฉ์์ Redis์๋ค๊ฐ userId, productCode, amount ์ ์ฅํ๊ธฐ | ||
AuctionDto auctionResult = paymentService.confirmAuction(chatRoomId); | ||
return new SagoApiResponse<>(HttpStatus.ACCEPTED, auctionResult); | ||
} | ||
|
||
@PostMapping("/redirect") | ||
@ResponseBody | ||
public String paySuccess(TossPayDto payResult) { | ||
// TODO: API ๊ฒฐ๊ณผ๊ฐ redirect ์ฒ๋ฆฌ | ||
return "Success!"; | ||
} | ||
|
||
@GetMapping("success") | ||
public ResponseEntity<TossPayDto> confirmPayment(@RequestParam String paymentKey, @RequestParam String orderId, @RequestParam BigDecimal amount) throws Exception { | ||
TossPayDto payResult = paymentService.approvePayment(paymentKey, amount, orderId); | ||
URI redirectUri = new URI( serverUrl + "payments/redirect"); | ||
HttpHeaders httpHeaders = new HttpHeaders(); | ||
httpHeaders.setLocation(redirectUri); | ||
return new ResponseEntity<>(payResult, httpHeaders, HttpStatus.OK); | ||
} | ||
|
||
@GetMapping("fail") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fail์์๋ ๋ณ๋์ ๋ก์ง ์ฒ๋ฆฌ๊ฐ ํ์์๋๊ฑธ๊น์?? ๊ฒฐ์ ์ํ๋ฅผ ์คํจ๋ก ๋ณ๊ฒฝํ๋ค๋์ง ๋ฑ์ ๋ก์ง์ด์ ์๋ฌด์ฒ๋ฆฌ๋ ์ํ๊ณ ์๋ต๋ง ๋ด๋ ค์ฃผ๋ api๊ฐ ํ์ํ๊ฑด๊ฐ ๊ถ๊ธํด์์ :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ ์ ๊ฐ ๋ก์ง ์์ ํ๋ฉด์ ์ fail ์ฒ๋ฆฌ๋ฌ์ ์ ์ ํด์ค์ผ ํ๋ ์์ ์ด ์๋ก ์๊ธฐ๊ธด ํ์ต๋๋ค ํด๋น ๋ถ๋ถ ์์ ํด์ ๊ฐ์ ธ๊ฐ๋ณด๋๋ก ํ ๊ฒ์! |
||
public SagoApiResponse<String> failRequestPayment() { | ||
return new SagoApiResponse<>(HttpStatus.BAD_REQUEST, "๊ฒฐ์ ์์ฒญ์ด ์คํจํ์์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์."); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.dhmall.payment.dto; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
import java.math.BigDecimal; | ||
import java.math.BigInteger; | ||
import java.time.ZonedDateTime; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public class PaymentDto { | ||
private final BigInteger id; | ||
|
||
private final BigInteger userId; | ||
|
||
private final String nickname; | ||
|
||
private final String productCode; | ||
|
||
private final String productName; | ||
|
||
private final BigDecimal amount; | ||
|
||
private final BigDecimal taxFreeAmount; | ||
|
||
private final BigDecimal vatAmount; | ||
|
||
private final String paymentCode; | ||
|
||
private final ZonedDateTime createdAt; | ||
|
||
private final ZonedDateTime updatedAt; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.dhmall.payment.dto; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
import java.math.BigDecimal; | ||
|
||
@Getter | ||
@Setter | ||
@Builder | ||
public class TossPayDto { | ||
|
||
private String paymentKey; | ||
|
||
private BigDecimal amount; | ||
|
||
private BigDecimal taxFreeAmount; | ||
|
||
private BigDecimal vatAmount; | ||
|
||
private String message; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.dhmall.payment.mapper; | ||
|
||
import com.dhmall.payment.dto.PaymentDto; | ||
import org.apache.ibatis.annotations.Mapper; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Mapper | ||
@Repository | ||
public interface PaymentMapper { | ||
void insertPayment(PaymentDto paymentDto); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package com.dhmall.payment.service; | ||
|
||
import com.dhmall.auction.dto.AuctionDto; | ||
import com.dhmall.auction.dto.AuctionWinnerDto; | ||
import com.dhmall.auction.dto.ProductDto; | ||
import com.dhmall.auction.service.AuctionService; | ||
import com.dhmall.payment.dto.TossPayDto; | ||
import com.dhmall.payment.mapper.PaymentMapper; | ||
import com.dhmall.user.dto.UserDto; | ||
import com.fasterxml.jackson.databind.JsonNode; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.http.*; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.web.client.RestTemplate; | ||
|
||
import java.math.BigDecimal; | ||
import java.math.BigInteger; | ||
import java.time.ZoneId; | ||
import java.time.ZonedDateTime; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class PaymentService { | ||
|
||
private final PaymentMapper paymentMapper; | ||
private final AuctionService auctionService; | ||
private final RedisTemplate redisTemplate; | ||
private final RestTemplate restTemplate; | ||
private final ObjectMapper objectMapper; | ||
|
||
@Value("${toss.secret}") | ||
private final String TOSS_SECRET_KEY; | ||
|
||
public TossPayDto approvePayment(String paymentKey, BigDecimal amount, String orderId) throws Exception { | ||
HttpHeaders headers = new HttpHeaders(); | ||
headers.setBasicAuth(TOSS_SECRET_KEY, ""); // spring framework 5.2 ์ด์ ๋ฒ์ ์์ ์ง์ | ||
headers.setContentType(MediaType.APPLICATION_JSON); | ||
|
||
Map<String, String> payloadMap = new HashMap<>(); | ||
payloadMap.put("orderId", orderId); | ||
payloadMap.put("amount", String.valueOf(amount)); | ||
|
||
HttpEntity<String> request = new HttpEntity<>(objectMapper.writeValueAsString(payloadMap), headers); | ||
|
||
ResponseEntity<JsonNode> responseEntity = restTemplate.postForEntity( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ ์ ๋ง์๋๋ ธ๋ฏ์ด ์ธ๋ถ ์ฐ๋์ ๋ณ๋์ adapter๋ ํด๋ผ์ด์ธํธ๋ฅผ ๋ง๋ค์ด์ ์ฒ๋ฆฌํ๋๊ฒ ์ข์ ๊ฒ ๊ฐ์์. ์ง์ ์ฒ๋ฆฌํ๋ค๋ณด๋ PaymentService๋ Toss ๊ฒฐ์ ๋ง์ ์ํ ๊ฐ์ฒด๊ฐ ๋์ด๋ฒ๋ ธ๋๋ฐ์. PaymentService ์๊ตฌ์กฐ๊ฑด์ ์นด์นด์คํ์ด ๊ฒฐ์ , ์นด๋ ๊ฒฐ์ ๋ฑ์ ๋ค์ํ ์๋จ์ด ์ถ๊ฐ๋๋ ์๊ตฌ ์ฌํญ์ด ์ถ๊ฐ๋๋ฉด ์ฝ๋๋ฅผ ๋ค ๋ฏ์ด๊ณ ์ณ์ผํ ๊ฒ ๊ฐ์์์. TossPaymentClient, KakaoPaymentClient ๊ฐ์ ํด๋์ค๋ฅผ ๋ง๋ค์ด์ ์ธ๋ถ์ฐ๋์ ํ๋ฉด ์ข์ ๊ฒ ๊ฐ์์. ๊ฒฐ์ ๋ผ๋ ๊ฐ๋ ์ ์ถ์ํํ์ฌ ์ฝ๋์ ๋ น์ฌ๋ด๋ฉด ๋ ์ข์ ์ฝ๋๊ฐ ๋ ๊ฒ ๊ฐ์์ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ ๋ต๋ต ํด๋น ๋ถ๋ถ ์๊ทธ๋๋ ์์ ํ๋๋ฐ ์ฌ๊ธฐ ๋ถ๋ถ์๋ค ๊ฐ์ด ๋จธ์งํด์ ์ฌ๋ฆฌ๊ฒ ์ต๋๋ค~ |
||
"https://api.tosspayments.com/v1/payments/" + paymentKey, request, JsonNode.class); | ||
|
||
if(responseEntity.getStatusCode() == HttpStatus.OK) { | ||
|
||
JsonNode body = responseEntity.getBody(); | ||
|
||
TossPayDto payResult = TossPayDto.builder() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ํ๋์ ๋ฉ์๋๊ฐ ๋๋ฌด ๋ง์ ์ฑ ์์ ๊ฐ์ง๊ณ ์๋ ๊ฒ ๊ฐ์์.
ํด๋น ๋ฉ์๋๋ฅผ ํ ์คํธํ๋ ค๋ฉด ์์ ๋ด์ฉ๋ค์ด ๋ค ํ ์คํธํด์ผํ๋๋ฐ ํ ์คํธํ๊ธฐ๊ฐ ์ฌ์๋ณด์ด์ง ์์์์. 2๋ฒ์ JsonUtils ๊ฐ์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ json handling์ ์ฑ ์์ ์ฎ๊ฒจ์ค์ ์์ ๊ฒ ๊ฐ์์. json handling์ jackson์ ObjectMapper๊ฐ ์๋๋ผ Gson์ผ๋ก๋ ์ฒ๋ฆฌํ ์ ์๋๋ฐ json handling์ด ํ๋์ ๊ตฌํ์ฒด์ ๋๋ฌด ๊ฐ๊ฒฐํฉ ๋์ด์๋ ๊ฒ ๊ฐ์์. json๋ ๊ตฌํ์ฒด๊ฐ ์๋๋ผ ๋ฐ์ดํฐ ํฌ๋งท์ ๊ฐ๋ ์ ๋๋ค. ๊ตฌํ์ฒด๋ ์ฌ๋ฌ๊ฐ์ง๋ฅผ ์ฌ์ฉํ ์ ์์ด์ jackson์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฉด ObjectMapper๋ฅผ ์ฌ์ฉํ๋ ๋ชจ๋ ์ฝ๋๋ฅผ ๋ค ๋ฐ๊ฟ์ค์ผํ๋๋ฐ ๊ฐ๋ฅํ ๊น์?? JsonUtils ์์ json์ handlingํด์ค๋ค๋ฉด JsonUtils๋ง ์ฝ๋๋ฅผ ์์ ํด ์ฃผ๋ฉด ๋์ด์ ์ฝ๋๊ฐ ์ข ๋ ์ ์ฐํด์ง ์ ์์ด์. ์ด์๊ฐ์ด Toss ์ฐ๋๋ PaymentClient๋ฅผ interface๋ก ๋ง๋ค์ด๋๊ณ ๊ฒฐ์ ๊ฐ๋ ์ ์ถ์ํํ๊ณ ๊ตฌํ์ฒด๋ ์ฌ๋ฌ๊ฐ๋ฅผ ์ ์ฐํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ฉด ๋ ์ข์ ๊ฒ ๊ฐ์์ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ ๋ต๋ต!์๊ทธ๋๋ ํ ์คํธ ์ฝ๋ ์์ฑํ๋๋ฐ ๋ฆฌํฉํ ๋ง ํด์ผํ ๊ฒ์ผ๋ก ํ๋จ์ด ๋๋๋ผ๊ตฌ์ ์ฐธ๊ณ ํด์ ์์ ํ๊ฒ ์ต๋๋ค~! |
||
.paymentKey(body.get("paymentKey").asText()) | ||
.amount(new BigDecimal(body.get("totalAmount").asText())) | ||
// TEST api key๋ก๋ ํด๋น index json response ๊ฐ์ด ์กด์ฌํ์ง ์์(null). | ||
// .taxFreeAmount(new BigDecimal(body.get("suppliedAmount").asText())) | ||
// .vatAmount(new BigDecimal(body.get("vat").asText())) | ||
.message("๊ฒฐ์ ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์ด๋ฃจ์ด์ก์ต๋๋ค.") | ||
.build(); | ||
|
||
return payResult; | ||
} | ||
|
||
TossPayDto payResult = TossPayDto.builder() | ||
.paymentKey("") | ||
.amount(new BigDecimal(0)) | ||
.taxFreeAmount(new BigDecimal(0)) | ||
.vatAmount(new BigDecimal(0)) | ||
.message("๊ฒฐ์ ์คํจํ์์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.") | ||
.build(); | ||
|
||
return payResult; | ||
} | ||
|
||
public AuctionDto confirmAuction(String chatRoomId) { | ||
// TODO: Auction ์ฑํ ๋ฐฉ์์ Redis์๋ค๊ฐ userId, productCode, amount ์ ์ฅํ๊ธฐ | ||
AuctionWinnerDto winner = (AuctionWinnerDto) redisTemplate.opsForValue().get(chatRoomId); | ||
|
||
UserDto user = auctionService.getAuctionWinner(new BigInteger(winner.getUserId())); | ||
ProductDto product = auctionService.getAuctionProduct(winner.getProductCode()); | ||
|
||
AuctionDto auctionInfo = new AuctionDto(); | ||
auctionInfo.setUserId(user.getId()); | ||
auctionInfo.setNickname(user.getNickname()); | ||
auctionInfo.setProductCode(product.getCode()); | ||
auctionInfo.setProductName(product.getName()); | ||
auctionInfo.setAmount(new BigDecimal(winner.getAmount())); | ||
auctionInfo.setDescription(product.getDescription()); | ||
auctionInfo.setCreatedAt(ZonedDateTime.now(ZoneId.of("UTC"))); | ||
auctionInfo.setUpdatedAt(ZonedDateTime.now(ZoneId.of("UTC"))); | ||
|
||
return auctionInfo; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final์ธ ํ๋๊ฐ ์์ด์ ์ํ์๋๋๋ก ์ฝ๋๊ฐ generate๋์ง ์๊ณ ๊ธฐ๋ณธ์์ฑ์๋ก ์์ฑ๋ ๊ฒ ๊ฐ์์. dto๋ ๋ณดํต ๋ถ๋ณ๊ฐ์ฒด๋ก ์ฌ์ฉํ๊ธฐ์ํด final ๋ณ์๋ฅผ ์ ์ธํ์ฌ ์ฌ์ฉํฉ๋๋ค. ํ์ง๋ง framework์์ setter๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋๋ฌธ์ ์ด์ฉ์ ์์ด setter๋ฅผ ์ ์ ํ๋ ๊ฒฝ์ฐ๊ฐ ์๋๋ฐ์. ํด๋น ๊ฒฝ์ฐ๋ง ์ ์ธํ๊ณ final ๋ณ์๋ก ์ ์ธํ์ฌ ๋ถ๋ณ๊ฐ์ฒด๋ก ์ด์ฉํ์๋๊ฒ ์ข์ต๋๋ค.
immutable object ref:
spring framework setter ref:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
์ ๋ต๋ต ํด๋น ๋ถ๋ถ ์ฐธ๊ณ ํด์ ๋ฆฌํฉํ ๋ง ์งํํ๊ฒ ์ต๋๋ค!