ๆฏไปไธญๅฟ๏ผ็ปไธๆถ้ถๅฐ็่ฎพ่ฎกๅ็ไธๆไฝณๅฎ่ทต
่ฟๆฏๆฏไปไธญๅฟ็ณปๅ็็ฌฌ8็ฏ๏ผไนๆฏๆๅไธ็ฏใๆไปฌๆฅ่่็ปไธๆถ้ถๅฐโโ่ฟไธช็ดๆฅ้ขๅ็จๆทใๅณๅฎๆฏไป่ฝฌๅ็็ๅ ณ้ฎ็ปไปถใๅฎๅฐฑๅๅๅบ็ๆถ้ถๅฐ๏ผๆฏ็จๆทๅฎๆไบคๆ็ๆๅไธ้้จใ
ไธใไธบไปไน้่ฆ็ปไธๆถ้ถๅฐ
ๅฆๆไฝ ็ปๅ่ฟ็ตๅๆฉๆ๏ผๅฏ่ฝ่ง่ฟ่ฟๆ ท็ๅบๆฏ๏ผๆฏไธชไธๅก็บฟ่ชๅทฑๅฏนๆฅๆฏไปๆธ ้๏ผๅพฎไฟกๆฏไปๆพๅพฎไฟกใๆฏไปๅฎๆพๆฏไปๅฎใ้ถ่ๆพ้ถ่โฆโฆ็ปๆๅฐฑๆฏ๏ผ
- ้ๅค้ ่ฝฎๅญ๏ผๆฏไธชไธๅก้ฝ่ฆๅฎ็ฐไธๅฅๆฏไปๆต็จ
- ไฝ้ชไธไธ่ด๏ผAไธๅก็ๆถ้ถๅฐ้ฟๅพๅ่กจๅ๏ผBไธๅก็ๅๆฝๅฅ้กต้ข
- ็ปดๆคๆๆฌ้ซ๏ผๆฏไปๆธ ้ๅ็บง๏ผๆๆไธๅก็บฟ้ฝ่ฆๆน
- ๆฐๆฎๅๆฃ๏ผๆณ็ๆดไฝๆฏไปๆฐๆฎ๏ผไธๅฅฝๆๆ๏ผ่ฆๅปไธไธช็ณป็ปๅๅซๅฏผๅบ
็ปไธๆถ้ถๅฐๅฐฑๆฏไธบไบ่งฃๅณ่ฟไบ้ฎ้ขใๅฎ็ๆ ธๅฟไปทๅผๅพ็ฎๅ๏ผๆๆฏไป่ฟไปถไบ๏ผๅๆไธไธชๆ ๅๆๅก๏ผ่ไธๆฏๆฏไธชไธๅก็็งไบใ
1.1 ไปๆถๆ่ง่ง็็ปไธๆถ้ถๅฐ
ๆณ่ฑกไธไธ๏ผๅฆๆๆฏไธชไธๅก้ฝ่ชๅทฑๅฏนๆฅๆฏไป๏ผ
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ ็ตๅไธๅก โ โ ่ฎขๅไธๅก โ โ ไผๅไธๅก โ
โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ
โ โ โ
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๅ่ชๅฏนๆฅๆฏไปๆธ ้ โ
โ ๅพฎไฟกๆฏไป โ ๆฏไปๅฎ โ ้ถ่ โ PayPal โ ... โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
่ฟๅฐฑๅๆฏไธช้จ้จ้ฝ่ชๅทฑๆไฟๆด๏ผ่ไธๆฏๆพ็ฉไธๅ ฌๅธใ่กจ้ข็ไผผไน็ตๆดป๏ผๅฎ้ ไธๆ็ไฝไธใๆๆฌ้ซๆใ
ๆไบ็ปไธๆถ้ถๅฐๅ๏ผ
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
โ ็ตๅไธๅก โ โ ่ฎขๅไธๅก โ โ ไผๅไธๅก โ
โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ
โ โ โ
โโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ็ปไธๆถ้ถๅฐ โ
โ (็ปไธๆฅๅฃ + ่ทฏ็ฑ็ญ็ฅ) โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ
โๅพฎไฟกๆฏไป โ โ ๆฏไปๅฎ โ โ ้ถ่ โ
โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ
่ฟ็งๆฝ่ฑกๅธฆๆฅ็ๅฅฝๅคๆฏ๏ผ
- ไธๅก่งฃ่ฆ๏ผไธๅก็ณป็ปๅชๅ ณๅฟไธๅก้ป่พ๏ผๆฏไป็ป่ๅ จ้จไธๆฒ
- ็ปไธไฝ้ช๏ผๆ ่ฎบๅชไธชไธๅก็บฟ๏ผ็จๆท็ๅฐ็ๆถ้ถๅฐ้ฃๆ ผไธ่ด
- ็ตๆดปๆฉๅฑ๏ผๆฐๅขๆฏไปๆธ ้๏ผไธๅก็ณป็ปๆ ๆ็ฅ
- ้ไธญ็ๆง๏ผๆๆๆฏไปๆต้ๆฑ่ไธๅค๏ผ้ฎ้ขไธ็ฎไบ็ถ
1.2 ไธไธช็ๅฎ็ๆ ไบ
ๆไนๅๆฅ่งฆ่ฟไธไธช็ตๅๅนณๅฐ๏ผๆฉๆๆฏไธชไธๅก็บฟ่ชๅทฑๅฏนๆฅๆฏไปใๆไธๆฌกๆฏไปๅฎๅ็บงๆฅๅฃ๏ผ่ฆๆฑๆๆๅๆทๅจไธไธชๆๅ ๅฎๆ่ฟ็งปใ็ปๆๅข๏ผไธไธชไธๅก็บฟ๏ผไธๅฅไปฃ็ ๏ผไธๆณขไบบ้ฉฌๅ่ชๆ่ พใๆๆจ็ๆฏไผๅ็ณป็ป๏ผๅ ไธบๅผๅ่ตๆบ็ดงๅผ ๏ผๆๅฐๆๅไธๅจๆๅจๅทฅ๏ผๅทฎ็นๅฝฑๅ็บฟไธๆฏไปใ
ๅๆฅไปไปฌ่ฑไบไธคไธชๆๆญๅปบ็ปไธๆถ้ถๅฐ๏ผๅ้ๅฐๆธ ้ๅ็บง๏ผๅช้่ฆๆถ้ถๅฐๅข้ๆนไธไธชๅฐๆน๏ผๆๆไธๅก็บฟ่ชๅจๅ็ใ่ฟๅฐฑๆฏๆถๆ็ๅ้โโๆ้ๅคๅณๅจๅๆๅบ็ก่ฎพๆฝใ
1.3 ็ปไธๆถ้ถๅฐ็ๅฎไฝ
ๅจๆดไฝๆฏไปๆถๆไธญ๏ผ็ปไธๆถ้ถๅฐๅคไบๆฟไธๅฏไธ็ๅ ณ้ฎไฝ็ฝฎ๏ผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ไธๅก็ณป็ปๅฑ โ
โ ็ตๅ็ณป็ป โ ่ฎขๅ็ณป็ป โ ไผๅ็ณป็ป โ ่ฅ้็ณป็ป โ ... โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ็ปไธๆฏไปAPI
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ็ปไธๆถ้ถๅฐ๏ผๆ ธๅฟ๏ผ โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ ่ฎขๅ็ฎก็ โ โ ่ทฏ็ฑ็ญ็ฅ โ โ ้ฃๆง้ๆ โ โ ็ถๆ็ฎก็ โ โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๆธ ้้้
ๅจ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๆฏไปๆธ ้ๅฑ โ
โ ๅพฎไฟกๆฏไป โ ๆฏไปๅฎ โ ้ถ่ โ PayPal โ ๆฐๅญไบบๆฐๅธ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- ็ปไธๆฅๅ ฅ๏ผไธบไธๅก็ณป็ปๆไพๆ ๅๅ็ๆฏไปๆฅๅฃ
- ๆธ ้่ๅ๏ผๅฑ่ฝๅบๅฑๆธ ้ๅทฎๅผ๏ผๅฎ็ฐๅคๆธ ้็ปไธ็ฎก็
- ๆบ่ฝ่ทฏ็ฑ๏ผๆ นๆฎๅบๆฏ่ชๅจ้ๆฉๆไผๆฏไปๆธ ้
- ็ถๆ็ฎก็๏ผ็ฎก็ๆฏไป่ฎขๅ็ๅฎๆด็ๅฝๅจๆ
- ้ฃๆง้ๆ๏ผๅจๆฏไปๆต็จไธญๅตๅ ฅ้ฃ้ฉ่ฏๅซ
ไบใๆถ้ถๅฐๆถๆ่ฎพ่ฎกๆทฑๅบฆๅๆ
็ปไธๆถ้ถๅฐ็่ตทๆฅๅฐฑๆฏไธไธชๆฏไป้กต้ข๏ผไฝ่ๅๆฟ่ฝฝ็ๅ่ฝ่ฟๆฏ่กจ้ขๅคๆใๅฎๅฐฑๅไธไธช็ฒพๅฏ็ๆๆฅไธญๅฟ๏ผๅ่ฐ็็จๆทใไธๅก็ณป็ปใๆฏไปๆธ ้ไธๆนใ
2.1 ๆดไฝๆถๆๅๅฑ
ๆถ้ถๅฐๅจๆถๆไธๅไธบๅๅฑ๏ผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๅฑ็คบๅฑ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ App SDK โ โ H5้กต้ข โ โ ๅฐ็จๅบ โ โ PC้กต้ข โ โ
โ โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ โ
โ โ โ โ โ โ
โ โโโโโโโโโโโโโโโดโโโโโโโฌโโโโโโโดโโโโโโโโโโโโโโ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโ โ
โ โ API Gateway โ โ
โ โ (้ดๆ/้ๆต/่ทฏ็ฑ)โ โ
โ โโโโโโโโโฌโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โผ โ
โ ไธๅก้ป่พๅฑ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ
โ โ ่ฎขๅๆๅก โ โ ่ทฏ็ฑๆๅก โ โ ้ฃๆงๆๅก โ โ
โ โ (็ถๆๆบ) โ โ (็ญ็ฅๅผๆ) โ โ (่งๅๅผๆ) โ โ
โ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โ
โ โ โ โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ
โ โ ็จๆทๆๅก โ โ ไผๆ ๆๅก โ โ ้็ฅๆๅก โ โ
โ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โ
โ โ โ โ โ
โ โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโ โ
โ โ ๆธ ้ไปฃ็ๅฑ โ โ
โ โโโโโโโโโฌโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โผ โ
โ ๆธ ้้้
ๅฑ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ ๅพฎไฟก้้
ๅจโ โ ๆฏไปๅฎ้้
โ โ ้ถ่้้
ๅจโ โ PayPal โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
2.2 ๆฏไปๆธ ้้้ ๅจๆจกๅผ
ไธบไบ็ปไธ็ฎก็ไธๅๆฏไปๆธ ้็ๅทฎๅผ๏ผๆไปฌ้็จ้้ ๅจๆจกๅผ๏ผ
// ๆฏไปๆธ ้็ปไธๆฅๅฃ
public interface PaymentChannel {
/**
* ่ทๅๆธ ้็ผ็
*/
String getChannelCode();
/**
* ่ทๅๆธ ้ๅ็งฐ
*/
String getChannelName();
/**
* ๅๅปบๆฏไปๅ
* @param order ๆฏไป่ฎขๅ
* @return ๆฏไปๅญ่ฏ๏ผ็จไบๅค่ตทๆฏไป๏ผ
*/
CreatePayResult createPay(PayOrder order);
/**
* ๆฅ่ฏขๆฏไป็ถๆ
* @param payId ๆฏไปๅID
* @return ๆฏไป็ถๆ
*/
QueryPayResult queryPay(String payId);
/**
* ๅค็ๅ่ฐ้็ฅ
* @param request HTTP่ฏทๆฑ
* @return ่งฃๆๅ็ๅ่ฐๆฐๆฎ
*/
CallbackResult handleCallback(HttpServletRequest request);
/**
* ๅ
ณ้ญๆฏไปๅ
* @param payId ๆฏไปๅID
* @return ๅ
ณ้ญ็ปๆ
*/
CloseResult closePay(String payId);
/**
* ็ณ่ฏท้ๆฌพ
* @param refundRequest ้ๆฌพ่ฏทๆฑ
* @return ้ๆฌพ็ปๆ
*/
RefundResult refund(RefundRequest refundRequest);
/**
* ๆฃๆฅๆธ ้ๆฏๅฆๅฏ็จ
*/
boolean isAvailable();
/**
* ่ทๅๆธ ้่ดน็
*/
BigDecimal getFeeRate();
}
// ๅพฎไฟกๆฏไป้้
ๅจๅฎ็ฐ
@Component
public class WechatPayChannel implements PaymentChannel {
@Autowired
private WechatPayConfig config;
@Autowired
private WechatPayClient client;
@Override
public String getChannelCode() {
return "WECHAT";
}
@Override
public CreatePayResult createPay(PayOrder order) {
try {
// 1. ๆๅปบๅพฎไฟกๆฏไป่ฏทๆฑ
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
.outTradeNo(order.getOrderId())
.body(order.getSubject())
.totalFee(order.getAmount().multiply(new BigDecimal("100")).intValue())
.spbillCreateIp(order.getClientIp())
.notifyUrl(config.getNotifyUrl())
.tradeType(TradeType.JSAPI)
.openid(order.getOpenId())
.build();
// 2. ่ฐ็จๅพฎไฟกๆฏไปAPI
WxPayUnifiedOrderResult result = client.createOrder(request);
// 3. ็ๆๅ็ซฏๆฏไปๅญ่ฏ
String prepayId = result.getPrepayId();
Map<String, String> payInfo = buildJsapiPayInfo(prepayId);
return CreatePayResult.success(prepayId, payInfo);
} catch (WxPayException e) {
log.error("ๅพฎไฟกๆฏไปๅๅปบๅคฑ่ดฅ: orderId={}, error={}", order.getOrderId(), e.getMessage());
return CreatePayResult.fail(e.getErrCodeDes());
}
}
@Override
public CallbackResult handleCallback(HttpServletRequest request) {
try {
// 1. ่ฏปๅๅ่ฐๆฐๆฎ
String xmlData = readRequestData(request);
// 2. ้ช่ฏ็ญพๅ
if (!client.checkSign(xmlData)) {
return CallbackResult.fail("็ญพๅ้ช่ฏๅคฑ่ดฅ");
}
// 3. ่งฃๆๅ่ฐๆฐๆฎ
WxPayOrderNotifyResult result = client.parseOrderNotifyResult(xmlData);
// 4. ่ฟๅ็ปไธๆ ผๅผ
return CallbackResult.success()
.setOrderId(result.getOutTradeNo())
.setTransactionId(result.getTransactionId())
.setPayTime(result.getTimeEnd())
.setAmount(new BigDecimal(result.getTotalFee()).divide(new BigDecimal("100")));
} catch (Exception e) {
log.error("ๅพฎไฟกๆฏไปๅ่ฐๅค็ๅคฑ่ดฅ", e);
return CallbackResult.fail(e.getMessage());
}
}
// ... ๅ
ถไปๆนๆณๅฎ็ฐ
}
// ๆฏไปๅฎ้้
ๅจๅฎ็ฐ
@Component
public class AlipayChannel implements PaymentChannel {
@Autowired
private AlipayConfig config;
@Autowired
private AlipayClient client;
@Override
public String getChannelCode() {
return "ALIPAY";
}
@Override
public CreatePayResult createPay(PayOrder order) {
try {
// 1. ๆๅปบๆฏไปๅฎ่ฏทๆฑ
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo(order.getOrderId());
model.setTotalAmount(order.getAmount().toString());
model.setSubject(order.getSubject());
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl(config.getNotifyUrl());
// 2. ่ฐ็จๆฏไปๅฎAPI
AlipayTradeAppPayResponse response = client.sdkExecute(request);
if (response.isSuccess()) {
return CreatePayResult.success(response.getTradeNo(),
Map.of("orderString", response.getBody()));
} else {
return CreatePayResult.fail(response.getSubMsg());
}
} catch (AlipayApiException e) {
log.error("ๆฏไปๅฎๅๅปบๅคฑ่ดฅ: orderId={}", order.getOrderId(), e);
return CreatePayResult.fail(e.getErrMsg());
}
}
// ... ๅ
ถไปๆนๆณๅฎ็ฐ
}
- ็ปไธๆฅๅฃ๏ผไธๅฑไธๅกไธ้่ฆๅ ณๅฟๅบๅฑๆธ ้ๅทฎๅผ
- ๆไบๆฉๅฑ๏ผๆฐๅขๆธ ้ๅช้ๅฎ็ฐๆฅๅฃ๏ผๆ ้ไฟฎๆน็ฐๆไปฃ็
- ้็ฆปๅๆด๏ผๆธ ้ๅ็บงๅชๅฝฑๅๅฏนๅบ็้้ ๅจ
2.3 ๆธ ้ไปฃ็ๅฑ่ฎพ่ฎก
ๆธ ้ไปฃ็ๅฑๆฏๆถ้ถๅฐ็"็ฎกๅฎถ"๏ผ่ด่ดฃ็ฎก็ๅ่ฐๅบฆๆๆๆฏไปๆธ ้๏ผ
@Service
public class ChannelProxyService {
// ๆธ ้ๆณจๅ่กจ
private final Map<String, PaymentChannel> channelRegistry = new ConcurrentHashMap<>();
@Autowired
public ChannelProxyService(List<PaymentChannel> channels) {
// ่ชๅจๆณจๅๆๆๆธ ้
channels.forEach(channel -> {
channelRegistry.put(channel.getChannelCode(), channel);
log.info("ๆณจๅๆฏไปๆธ ้: {} - {}", channel.getChannelCode(), channel.getChannelName());
});
}
/**
* ่ทๅๆๅฎๆธ ้
*/
public PaymentChannel getChannel(String channelCode) {
PaymentChannel channel = channelRegistry.get(channelCode);
if (channel == null) {
throw new ChannelNotFoundException("ๆธ ้ไธๅญๅจ: " + channelCode);
}
return channel;
}
/**
* ่ทๅๆๆๅฏ็จๆธ ้
*/
public List<PaymentChannel> getAvailableChannels() {
return channelRegistry.values().stream()
.filter(PaymentChannel::isAvailable)
.collect(Collectors.toList());
}
/**
* ๆง่กๆฏไป๏ผๅธฆ้็บง๏ผ
*/
public CreatePayResult executeWithFallback(PayOrder order, String preferredChannel) {
// 1. ๅฐ่ฏ้ฆ้ๆธ ้
PaymentChannel channel = channelRegistry.get(preferredChannel);
if (channel != null && channel.isAvailable()) {
CreatePayResult result = channel.createPay(order);
if (result.isSuccess()) {
return result;
}
}
// 2. ้็บงๅฐๅ
ถไปๆธ ้
for (PaymentChannel fallbackChannel : getAvailableChannels()) {
if (!fallbackChannel.getChannelCode().equals(preferredChannel)) {
CreatePayResult result = fallbackChannel.createPay(order);
if (result.isSuccess()) {
log.warn("ๆฏไปๆธ ้้็บง: {} -> {}", preferredChannel, fallbackChannel.getChannelCode());
return result;
}
}
}
return CreatePayResult.fail("ๆๆๆฏไปๆธ ้ๅไธๅฏ็จ");
}
}
ไธใๅคๆธ ้่ๅๆๆฏๅฎ็ฐ
ๅฝๆๅค็งๆฏไปๆธ ้ๅฏ้ๆถ๏ผๅฆไฝ้ๆฉๆไผๆธ ้๏ผ่ฟๅฐฑๆฏๅคๆธ ้่ๅๅ่ทฏ็ฑ็ญ็ฅ่ฆ่งฃๅณ็้ฎ้ขใ
3.1 ๅคๆธ ้่ๅ็ๆ ธๅฟๆๆ
3.2 ่ทฏ็ฑๅณ็ญ็ปดๅบฆ
่ทฏ็ฑๅณ็ญไธๆฏ้ๆบ็๏ผ้่ฆ็ปผๅ่่ๅคไธช็ปดๅบฆ๏ผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ่ทฏ็ฑๅณ็ญๆจกๅ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโดโโโโโ
โผ โผ
โโโโโโโโโ โโโโโโโโโ
โ ็กฌ็บฆๆ โ โ ่ฝฏ็บฆๆ โ
โโโโโฌโโโโ โโโโโฌโโโโ
โ โ
โผ โผ
โข ้้ข โข ๆๆฌ
โข ๅธ็ง โข ๆๅ็
โข ไธๅก่งๅ โข ๅๅบๆถ้ด
โข ๅฐๅบ โข ็จๆทๅๅฅฝ
โข ่ฅ้็ญ็ฅ
| ็บฆๆ็ฑปๅ | ่ฏดๆ | ็คบไพ |
|---|---|---|
| ้้ข | ๆธ ้ๅ็ฌ/ๅๆฅ้้ข | 5ไธ่ฎขๅไธ่ฝ่ตฐๅพฎไฟก |
| ๅธ็ง | ๆธ ้ๆฏๆ็่ดงๅธ | ็พๅ ๆฏไป่ตฐPayPal |
| ไธๅก่งๅ | ไธๅกๅบๆฏ้ๅถ | ่ทจๅขๅช่ฝ่ตฐ็นๅฎๆธ ้ |
| ๅฐๅบ | ๆธ ้่ฆ็่ๅด | ๆธฏๆพณๅฐ้่ฆ็นๆฎๆธ ้ |
| ๆธ ้็ถๆ | ๆธ ้ๆฏๅฆๅฏ็จ | ็ปดๆคไธญ็ๆธ ้ๆ้ค |
| ็บฆๆ็ฑปๅ | ๆ้ๅปบ่ฎฎ | ่ฏดๆ |
|---|---|---|
| ๆๆฌ | 30% | ๆ็ปญ่ดน่ถไฝ่ถๅฅฝ |
| ๆๅ็ | 40% | ๅๅฒๆๅ็้ซ็ไผๅ |
| ๅๅบๆถ้ด | 10% | ๅๅบๅฟซ็ไผๅ |
| ็จๆทๅๅฅฝ | 15% | ็จๆทๅธธ็จๆธ ้ไผๅ |
| ่ฅ้็ญ็ฅ | 5% | ๆจๅนฟๆๆธ ้ๅ ๅ |
3.3 ่ทฏ็ฑ็ญ็ฅๅฎ็ฐ
3.3.1 ้ๆไผๅ ็บง็ญ็ฅ
ๆ็ฎๅ็็ญ็ฅ๏ผๆ้ข่ฎพไผๅ ็บง้กบๅบๅฐ่ฏ๏ผ
@Component
public class StaticPriorityRouter {
// ๆธ ้ไผๅ
็บง้
็ฝฎ
@Value("${pay.router.priority:wechat,alipay,unionpay}")
private String priorityConfig;
private List<String> channelPriority;
@PostConstruct
public void init() {
channelPriority = Arrays.asList(priorityConfig.split(","));
}
public RouteResult route(PayOrder order) {
for (String channelCode : channelPriority) {
PaymentChannel channel = channelProxy.getChannel(channelCode);
// ๆฃๆฅ็กฌ็บฆๆ
if (!checkConstraints(channel, order)) {
continue;
}
// ๆฃๆฅๅฏ็จๆง
if (channel.isAvailable()) {
return RouteResult.success(channelCode, "้ๆไผๅ
็บง");
}
}
return RouteResult.fail("ๆ ๅฏ็จๆธ ้");
}
private boolean checkConstraints(PaymentChannel channel, PayOrder order) {
// ๆฃๆฅ้้ข
if (order.getAmount().compareTo(channel.getMaxAmount()) > 0) {
return false;
}
// ๆฃๆฅๅธ็ง
if (!channel.getSupportedCurrencies().contains(order.getCurrency())) {
return false;
}
return true;
}
}
3.3.2 ๅจๆๆ้็ญ็ฅ
ๆ นๆฎๆธ ้็ๅฎๆถ่กจ็ฐๅจๆ่ฐๆดๆ้๏ผ
@Component
public class DynamicWeightRouter {
// ๅๆธ ้ๅฝๅๆ้๏ผๅฎๆๆดๆฐ๏ผ
private final Map<String, Double> channelWeights = new ConcurrentHashMap<>();
// ๆธ ้็ป่ฎกๆฐๆฎ
@Autowired
private ChannelStatisticsService statisticsService;
/**
* ่ทฏ็ฑๅณ็ญ
*/
public RouteResult route(PayOrder order) {
// 1. ่ทๅๆปก่ถณ็กฌ็บฆๆ็ๅ้ๆธ ้
List<PaymentChannel> candidates = getCandidateChannels(order);
if (candidates.isEmpty()) {
return RouteResult.fail("ๆ ๆปก่ถณๆกไปถ็ๆธ ้");
}
// 2. ๅ ๆ้ๆบ้ๆฉ
String selectedChannel = weightedRandomSelect(candidates);
return RouteResult.success(selectedChannel, "ๅจๆๆ้่ทฏ็ฑ");
}
/**
* ๅ ๆ้ๆบ้ๆฉ
*/
private String weightedRandomSelect(List<PaymentChannel> candidates) {
// ่ฎก็ฎๆปๆ้
double totalWeight = candidates.stream()
.mapToDouble(ch -> channelWeights.getOrDefault(ch.getChannelCode(), 1.0))
.sum();
// ้ๆบ้ๆฉ
double random = Math.random() * totalWeight;
double cumulative = 0;
for (PaymentChannel channel : candidates) {
cumulative += channelWeights.getOrDefault(channel.getChannelCode(), 1.0);
if (random < cumulative) {
return channel.getChannelCode();
}
}
// ๅ
ๅบ่ฟๅ็ฌฌไธไธช
return candidates.get(0).getChannelCode();
}
/**
* ๅฎๆถๆดๆฐๆ้๏ผๆฏๅ้๏ผ
*/
@Scheduled(fixedRate = 60000)
public void updateWeights() {
for (String channelCode : channelProxy.getAllChannelCodes()) {
// ่ทๅๆ่ฟ1ๅฐๆถ็็ป่ฎกๆฐๆฎ
ChannelStats stats = statisticsService.getStats(channelCode, Duration.ofHours(1));
if (stats == null || stats.getTotalCount() < 10) {
// ๆฐๆฎไธ่ถณ๏ผไฝฟ็จ้ป่ฎคๆ้
channelWeights.put(channelCode, 1.0);
continue;
}
// ่ฎก็ฎ็ปผๅๆ้
// ๆ้ = ๆๅ็ * 1000 / (ๅนณๅๅปถ่ฟ + 100)
double successRate = stats.getSuccessRate();
double avgLatency = stats.getAvgLatency();
double feeRate = channelProxy.getChannel(channelCode).getFeeRate().doubleValue();
// ็ปผๅ่ฏๅ๏ผๆๅ็ๅ 60%๏ผๅปถ่ฟๅ 20%๏ผ่ดน็ๅ 20%
double weight = (successRate * 0.6)
+ (1000.0 / (avgLatency + 100) * 0.2)
+ ((1 - feeRate) * 0.2);
channelWeights.put(channelCode, weight);
log.info("ๆธ ้ๆ้ๆดๆฐ: {} -> weight={:.4f}, successRate={:.2%}, latency={:.0f}ms",
channelCode, weight, successRate, avgLatency);
}
}
}
3.3.3 ่งๅๅผๆ็ญ็ฅ
้่ฟ้ ็ฝฎ่งๅๆฅๅณๅฎ่ทฏ็ฑ๏ผ
# ่ทฏ็ฑ่งๅ้
็ฝฎ
routing_rules:
- name: "่ทจๅขๆฏไปไธ็จ"
id: rule_cross_border
priority: 100
condition:
currency: ["USD", "EUR", "JPY"]
channels: ["paypal", "stripe", "international_card"]
- name: "ๅคง้ขไฝ่ดน็"
id: rule_large_amount
priority: 90
condition:
amount_min: 10000
user_vip_level_min: 3
channels: ["unionpay", "bank_direct"]
- name: "ๅฐ้ขๅฟซๆท"
id: rule_small_amount
priority: 80
condition:
amount_max: 100
channels: ["wechat", "alipay"]
- name: "ๆฐๅญไบบๆฐๅธๆจๅนฟ"
id: rule_dcep_promotion
priority: 85
condition:
time_range: "2026-03-01~2026-03-31"
user_tags: ["dcep_beta"]
channels: ["dcep"]
bonus_weight: 50
- name: "้ป่ฎค่ทฏ็ฑ"
id: rule_default
priority: 0
condition: {}
channels: ["wechat", "alipay", "unionpay"]
่งๅๅผๆๅฎ็ฐ๏ผ
@Service
public class RuleEngineRouter {
@Autowired
private RuleConfigRepository ruleConfigRepository;
private List<RoutingRule> rules;
@PostConstruct
public void loadRules() {
rules = ruleConfigRepository.loadAllRules();
// ๆไผๅ
็บงๆๅบ
rules.sort(Comparator.comparingInt(RoutingRule::getPriority).reversed());
}
public RouteResult route(PayOrder order, UserContext user) {
// ้ๅๆๆ่งๅ๏ผๆพๅฐ็ฌฌไธไธชๅน้
็
for (RoutingRule rule : rules) {
if (rule.matches(order, user)) {
List<String> channels = rule.getChannels();
// ไปๅน้
็ๆธ ้ไธญ้ๆฉๆไผ็
String selectedChannel = selectBestChannel(channels, order);
return RouteResult.success(selectedChannel,
String.format("่งๅๅน้
: %s", rule.getName()));
}
}
// ๅ
ๅบ๏ผไฝฟ็จ้ป่ฎคๆธ ้
return RouteResult.success("wechat", "้ป่ฎค่ทฏ็ฑ");
}
private String selectBestChannel(List<String> channelCodes, PayOrder order) {
// ๅจๅ้ๆธ ้ไธญ๏ผ้ๆฉๅฏ็จๆงๆๅฅฝใๆๆฌๆไฝ็
return channelCodes.stream()
.filter(code -> channelProxy.getChannel(code).isAvailable())
.min(Comparator.comparing(code -> channelProxy.getChannel(code).getFeeRate()))
.orElse(channelCodes.get(0));
}
}
// ่งๅๅน้
้ป่พ
@Data
public class RoutingRule {
private String id;
private String name;
private int priority;
private RuleCondition condition;
private List<String> channels;
private double bonusWeight;
public boolean matches(PayOrder order, UserContext user) {
return condition.matches(order, user);
}
}
@Data
public class RuleCondition {
private List<String> currency;
private BigDecimal amountMin;
private BigDecimal amountMax;
private Integer userVipLevelMin;
private List<String> userTags;
private String timeRange;
public boolean matches(PayOrder order, UserContext user) {
// ๅธ็งๆฃๆฅ
if (currency != null && !currency.contains(order.getCurrency())) {
return false;
}
// ้้ขๆฃๆฅ
if (amountMin != null && order.getAmount().compareTo(amountMin) < 0) {
return false;
}
if (amountMax != null && order.getAmount().compareTo(amountMax) > 0) {
return false;
}
// VIP็ญ็บงๆฃๆฅ
if (userVipLevelMin != null && user.getVipLevel() < userVipLevelMin) {
return false;
}
// ็จๆทๆ ็ญพๆฃๆฅ
if (userTags != null && !userTags.isEmpty()) {
boolean hasTag = userTags.stream().anyMatch(tag -> user.getTags().contains(tag));
if (!hasTag) {
return false;
}
}
// ๆถ้ด่ๅดๆฃๆฅ
if (timeRange != null) {
// ่งฃๆๆถ้ด่ๅดๅนถๆฃๆฅ
// ...
}
return true;
}
}
3.4 ๅฎๆไธญ็่ทฏ็ฑ็ญ็ฅ็ปๅ
ๅฎ้ ๅบ็จไธญ๏ผๅพๅพๆฏๅค็ง็ญ็ฅ็็ปๅ๏ผ
@Service
public class CompositeRouter {
@Autowired
private RuleEngineRouter ruleEngineRouter;
@Autowired
private DynamicWeightRouter dynamicWeightRouter;
@Autowired
private UserPreferenceService userPreferenceService;
public RouteResult route(PayOrder order, UserContext user) {
// Step 1: ่งๅๅผๆ่ฟๆปค๏ผ่ทๅๅ้ๆธ ้๏ผ
List<String> candidates = ruleEngineRouter.getCandidateChannels(order, user);
if (candidates.isEmpty()) {
return RouteResult.fail("ๆ ๆปก่ถณไธๅก่งๅ็ๆธ ้");
}
// Step 2: ๆธ ้ๅฏ็จๆงๆฃๆฅ๏ผๅ้คไธๅฏ็จๆธ ้๏ผ
candidates = candidates.stream()
.filter(code -> channelProxy.getChannel(code).isAvailable())
.collect(Collectors.toList());
if (candidates.isEmpty()) {
return RouteResult.fail("ๆๆๆธ ้ๅไธๅฏ็จ");
}
// Step 3: ๅจๆๆ้ๆๅบ๏ผๆๅฎๆถ่กจ็ฐๆๅบ๏ผ
candidates.sort((a, b) -> {
double weightA = dynamicWeightRouter.getWeight(a);
double weightB = dynamicWeightRouter.getWeight(b);
return Double.compare(weightB, weightA); // ้ๅบ
});
// Step 4: ๆๆฌไผๅ๏ผๅ็ญๆกไปถ้ไฝ่ดน็๏ผ
String bestChannel = candidates.get(0);
double bestWeight = dynamicWeightRouter.getWeight(bestChannel);
for (String candidate : candidates) {
double weight = dynamicWeightRouter.getWeight(candidate);
// ๆ้ๅทฎๅผๅจ5%ไปฅๅ
๏ผไผๅ
้ไฝ่ดน็
if (Math.abs(weight - bestWeight) / bestWeight < 0.05) {
BigDecimal feeRate = channelProxy.getChannel(candidate).getFeeRate();
if (feeRate.compareTo(channelProxy.getChannel(bestChannel).getFeeRate()) < 0) {
bestChannel = candidate;
}
}
}
// Step 5: ็จๆทๅๅฅฝ๏ผๅฆๆ็จๆทๆๅผบ็ๅๅฅฝ๏ผไผๅ
ๆปก่ถณ๏ผ
String preferredChannel = userPreferenceService.getUserPreferredChannel(user.getUserId());
if (preferredChannel != null && candidates.contains(preferredChannel)) {
// ็จๆทๅๅฅฝๆธ ้ๅจๅ้ๅ่กจไธญ๏ผไธๆ้ๅทฎๅผไธๅคง๏ผ20%ไปฅๅ
๏ผ๏ผไฝฟ็จ็จๆทๅๅฅฝ
double preferredWeight = dynamicWeightRouter.getWeight(preferredChannel);
if (preferredWeight > bestWeight * 0.8) {
bestChannel = preferredChannel;
}
}
return RouteResult.success(bestChannel, "็ปๅ่ทฏ็ฑ็ญ็ฅ");
}
}
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ่ทฏ็ฑๅณ็ญๆต็จ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. ่งๅๅผๆ่ฟๆปค โ
โ (ไธๅก่งๅ + ้้ขๆ ก้ช) โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ
โ 2. ๆธ ้ๅฏ็จๆงๆฃๆฅ โ
โ (ๅ้ค็ปดๆค/ๆ
้ๆธ ้) โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ
โ 3. ๅจๆๆ้ๆๅบ โ
โ (ๆๅ็ * ๅๅบๆถ้ด) โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ
โ 4. ๆๆฌไผๅ โ
โ (ๅ็ญๆกไปถ้ไฝ่ดน็) โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโ
โ 5. ็จๆทๅๅฅฝ โ
โ (ไผๅ
็จๆทๅธธ็จๆธ ้) โ
โโโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโ
โ ้ๅฎๆธ ้ โ
โโโโโโโโโโโโโโโ
ๅใ่ฎขๅ็ถๆๆบ่ฎพ่ฎก
็จๆทๅจๆถ้ถๅฐ็ๆไฝไธๆฏๅๅญๆง็ใไปๅฏ่ฝ้ไบๅพฎไฟกๆฏไป๏ผๆซ็ ๅๅๅๆ๏ผๅๅๆฏไปๅฎใๆ่ ๆฏไป่ถ ๆถ๏ผๅ้ๆฐๅ่ตทใๆถ้ถๅฐ้่ฆ็ฎก็่ฟไบไธญ้ด็ถๆ๏ผ็กฎไฟ๏ผ
- ๅไธ็ฌ่ฎขๅไธไผ้ๅคๆฏไป
- ่ถ ๆถ่ฎขๅ่ชๅจๅ ณ้ญ
- ็จๆทๆพๅผๅๅฏไปฅ้ๆฐๅ่ตท
4.1 ่ฎขๅ็ถๆๅฎไน
public enum PayOrderStatus {
/**
* ๅๅง็ถๆ - ่ฎขๅๅๅปบ
*/
INIT("ๅๅง", 0),
/**
* ๅพ
ๆฏไป - ็ญๅพ
็จๆทๆไฝ
*/
WAIT_PAY("ๅพ
ๆฏไป", 10),
/**
* ๆฏไปไธญ - ็จๆทๆญฃๅจๆฏไป
*/
PAYING("ๆฏไปไธญ", 20),
/**
* ๆฏไปๆๅ - ไบคๆๅฎๆ
*/
PAY_SUCCESS("ๆฏไปๆๅ", 30),
/**
* ๆฏไปๅคฑ่ดฅ - ไบคๆๅคฑ่ดฅ
*/
PAY_FAILED("ๆฏไปๅคฑ่ดฅ", 40),
/**
* ๅทฒๅ
ณ้ญ - ่ถ
ๆถๆ็จๆทๅๆถ
*/
CLOSED("ๅทฒๅ
ณ้ญ", 50),
/**
* ๅทฒ้ๆฌพ - ไบคๆ้ๆฌพ
*/
REFUNDED("ๅทฒ้ๆฌพ", 60),
/**
* ้จๅ้ๆฌพ - ้จๅ้้ข้ๆฌพ
*/
PARTIAL_REFUNDED("้จๅ้ๆฌพ", 65);
private final String desc;
private final int code;
PayOrderStatus(String desc, int code) {
this.desc = desc;
this.code = code;
}
// ็ถๆๆต่ฝฌ่งๅ
private static final Map<PayOrderStatus, Set<PayOrderStatus>> TRANSITIONS = Map.of(
INIT, Set.of(WAIT_PAY, CLOSED),
WAIT_PAY, Set.of(PAYING, CLOSED),
PAYING, Set.of(PAY_SUCCESS, PAY_FAILED, WAIT_PAY, CLOSED),
PAY_SUCCESS, Set.of(REFUNDED, PARTIAL_REFUNDED),
PAY_FAILED, Set.of(WAIT_PAY, CLOSED),
PARTIAL_REFUNDED, Set.of(REFUNDED)
);
/**
* ๆฃๆฅๆฏๅฆๅฏไปฅๆต่ฝฌๅฐ็ฎๆ ็ถๆ
*/
public boolean canTransitionTo(PayOrderStatus target) {
return TRANSITIONS.getOrDefault(this, Set.of()).contains(target);
}
}
4.2 ็ถๆๆต่ฝฌๅพ
โโโโโโโโโโโโโโโโโโโโ
โ ้่ฏ โ
โ โ
โผ โ
โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ โ โโโโโโโโโโ
โ ๅๅง โโโโโถโ ๅพ
ๆฏไป โโโโโถโ ๆฏไปไธญ โโโโโโโถโ ๆฏไปๆๅ โ
โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ
โ โ โ
โ โ โ
โผ โผ โผ
โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ
โ ๅทฒๅ
ณ้ญ โ โ ๆฏไปๅคฑ่ดฅ โ โ ๅทฒ้ๆฌพ โ
โโโโโโโโโโ โโโโโโโโโโ โโโโโโโโโโ
4.3 ็ถๆๆบๅฎ็ฐ
@Service
public class PayOrderStateMachine {
@Autowired
private PayOrderRepository orderRepository;
@Autowired
private PayOrderLogRepository logRepository;
/**
* ็ถๆๆต่ฝฌ๏ผๅธฆไบๅกไฟ่ฏ๏ผ
*/
@Transactional
public boolean transition(String orderId, PayOrderStatus targetStatus, String operator) {
// 1. ๆฅ่ฏข่ฎขๅ
PayOrder order = orderRepository.selectById(orderId);
if (order == null) {
throw new OrderNotFoundException("่ฎขๅไธๅญๅจ: " + orderId);
}
PayOrderStatus currentStatus = order.getStatus();
// 2. ๆ ก้ช็ถๆๆต่ฝฌๅๆณๆง
if (!currentStatus.canTransitionTo(targetStatus)) {
log.warn("้ๆณ็ถๆๆต่ฝฌ: {} -> {}, orderId={}", currentStatus, targetStatus, orderId);
return false;
}
// 3. ไน่ง้ๆดๆฐ
int updated = orderRepository.updateStatus(
orderId,
currentStatus.getCode(),
targetStatus.getCode()
);
if (updated == 0) {
log.warn("็ถๆๆต่ฝฌๅฒ็ช: orderId={}, expected={}", orderId, currentStatus);
return false;
}
// 4. ่ฎฐๅฝ็ถๆๅๆดๆฅๅฟ
PayOrderLog orderLog = PayOrderLog.builder()
.orderId(orderId)
.fromStatus(currentStatus.getCode())
.toStatus(targetStatus.getCode())
.operator(operator)
.operateTime(LocalDateTime.now())
.build();
logRepository.insert(orderLog);
log.info("่ฎขๅ็ถๆๆต่ฝฌๆๅ: {} -> {}, orderId={}", currentStatus, targetStatus, orderId);
return true;
}
/**
* ๆฏไปๆๅๅค็
*/
@Transactional
public void handlePaySuccess(String orderId, PayCallbackData callbackData) {
// 1. ็ถๆๆต่ฝฌ
boolean success = transition(orderId, PayOrderStatus.PAY_SUCCESS, "SYSTEM");
if (!success) {
// ๅฏ่ฝๆฏ้ๅคๅ่ฐ๏ผๅน็ญๅค็
log.warn("ๆฏไปๆๅๅค็ๅคฑ่ดฅ๏ผๅฏ่ฝๆฏ้ๅคๅ่ฐ: orderId={}", orderId);
return;
}
// 2. ๆดๆฐๆฏไปๆธ ้ไฟกๆฏ
PayOrder order = orderRepository.selectById(orderId);
order.setTransactionId(callbackData.getTransactionId());
order.setPayTime(callbackData.getPayTime());
order.setChannelCode(callbackData.getChannelCode());
orderRepository.updateById(order);
// 3. ๅผๆญฅ้็ฅไธๅก็ณป็ป
notifyBusinessSystem(order);
}
}
4.4 ่ถ ๆถ่ฎขๅๅค็
@Component
public class OrderTimeoutHandler {
@Autowired
private PayOrderRepository orderRepository;
@Autowired
private PayOrderStateMachine stateMachine;
/**
* ๆฏๅ้ๆซๆ่ถ
ๆถ่ฎขๅ
*/
@Scheduled(cron = "0 * * * * ?")
public void handleTimeoutOrders() {
// ๆฅ่ฏข่ถ
ๆถๆชๆฏไป็่ฎขๅ๏ผ่ถ
่ฟ30ๅ้๏ผ
LocalDateTime timeoutThreshold = LocalDateTime.now().minusMinutes(30);
List<PayOrder> timeoutOrders = orderRepository.selectList(
new QueryWrapper<PayOrder>()
.eq("status", PayOrderStatus.WAIT_PAY.getCode())
.lt("create_time", timeoutThreshold)
);
for (PayOrder order : timeoutOrders) {
try {
// ๅ
ณ้ญ่ฎขๅ
stateMachine.transition(order.getOrderId(), PayOrderStatus.CLOSED, "TIMEOUT_JOB");
// ๅฆๆๅทฒ็ปๅๅปบไบๆธ ้ๆฏไปๅ๏ผ้่ฆๅ
ณ้ญ
if (StringUtils.isNotBlank(order.getChannelPayId())) {
closeChannelPay(order);
}
log.info("่ฎขๅ่ถ
ๆถๅ
ณ้ญๆๅ: orderId={}", order.getOrderId());
} catch (Exception e) {
log.error("่ฎขๅ่ถ
ๆถๅ
ณ้ญๅคฑ่ดฅ: orderId={}", order.getOrderId(), e);
}
}
}
}
ไบใๆฏไปๆต็จไผๅ
5.1 ๅฎๆดๆฏไปๆต็จ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ๆฏไปๅฎๆดๆต็จ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
็จๆท็ซฏ ๆถ้ถๅฐ ๆธ ้ๅฑ
โ โ โ
โ 1.ๅ่ตทๆฏไป โ โ
โโโโโโโโโโโโโโโโโโโโโถโ โ
โ โ โ
โ โ 2.ๅๅปบ่ฎขๅ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโถโ
โ โ โ
โ โ 3.่ฟๅ่ฎขๅID โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ โ
โ 4.่ฟๅๆฏไปๅญ่ฏ โ โ
โโโโโโโโโโโโโโโโโโโโโโค โ
โ โ โ
โ 5.็จๆทๆฏไป โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโถโ
โ โ โ
โ โ 6.ๆฏไปๅ่ฐ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ โ
โ โ 7.ๆดๆฐ่ฎขๅ็ถๆ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโถโ
โ โ โ
โ 8.ๆฏไป็ปๆ โ โ
โโโโโโโโโโโโโโโโโโโโโโค โ
โ โ โ
5.2 ๅผๆญฅๅ่ฐๅค็
@RestController
@RequestMapping("/callback")
public class PayCallbackController {
@Autowired
private PayCallbackService callbackService;
/**
* ๅพฎไฟกๆฏไปๅ่ฐ
*/
@PostMapping("/wechat")
public String wechatCallback(HttpServletRequest request) {
try {
// 1. ่ฏปๅๅ่ฐๆฐๆฎ
String xmlData = readRequestBody(request);
// 2. ๅค็ๅ่ฐ
CallbackResult result = callbackService.handleCallback("WECHAT", xmlData);
if (result.isSuccess()) {
// 3. ่ฟๅๆๅๅๅบ
return "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>";
} else {
log.error("ๅพฎไฟกๅ่ฐๅค็ๅคฑ่ดฅ: {}", result.getErrorMsg());
return "<xml><return_code><![CDATA[FAIL]]></return_code></xml>";
}
} catch (Exception e) {
log.error("ๅพฎไฟกๅ่ฐๅผๅธธ", e);
return "<xml><return_code><![CDATA[FAIL]]></return_code></xml>";
}
}
}
@Service
public class PayCallbackService {
@Autowired
private ChannelProxyService channelProxy;
@Autowired
private PayOrderStateMachine stateMachine;
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* ๅค็ๆฏไปๅ่ฐ๏ผๅน็ญ๏ผ
*/
public CallbackResult handleCallback(String channelCode, String callbackData) {
// 1. ่งฃๆๅ่ฐๆฐๆฎ
PaymentChannel channel = channelProxy.getChannel(channelCode);
CallbackResult result = channel.parseCallback(callbackData);
if (!result.isSuccess()) {
return result;
}
String orderId = result.getOrderId();
// 2. ๅน็ญๆงๆ ก้ช๏ผไฝฟ็จRedisๅๅธๅผ้๏ผ
String lockKey = "callback_lock:" + orderId;
Boolean locked = redisTemplate.opsForValue().setIfAbsent(
lockKey, "1", Duration.ofMinutes(5)
);
if (!locked) {
// ๆญฃๅจๅค็ไธญๆๅทฒๅค็๏ผ็ดๆฅ่ฟๅๆๅ
log.info("ๅ่ฐๆญฃๅจๅค็ไธญ๏ผ่ทณ่ฟ: orderId={}", orderId);
return CallbackResult.success();
}
try {
// 3. ๆฃๆฅ่ฎขๅ็ถๆ๏ผ้ฟๅ
้ๅคๅค็๏ผ
PayOrder order = orderRepository.selectById(orderId);
if (order.getStatus() == PayOrderStatus.PAY_SUCCESS) {
log.info("่ฎขๅๅทฒๆฏไปๆๅ๏ผ่ทณ่ฟ: orderId={}", orderId);
return CallbackResult.success();
}
// 4. ๆดๆฐ่ฎขๅ็ถๆ
stateMachine.handlePaySuccess(orderId, result);
// 5. ๅผๆญฅ้็ฅไธๅก็ณป็ป
CompletableFuture.runAsync(() -> {
notifyBusinessSystem(order);
});
return CallbackResult.success();
} finally {
// ้ๆพ้
redisTemplate.delete(lockKey);
}
}
}
5.3 ไธปๅจๆฅ่ฏข่กฅๅฟ
@Component
public class PayStatusCompensator {
@Autowired
private PayOrderRepository orderRepository;
@Autowired
private ChannelProxyService channelProxy;
@Autowired
private PayOrderStateMachine stateMachine;
/**
* ๆฏๅ้ๆฃๆฅๆฏไปไธญ็่ฎขๅ
*/
@Scheduled(cron = "0 * * * * ?")
public void checkPayingOrders() {
// ๆฅ่ฏขๆฏไปไธญ่ถ
่ฟ5ๅ้็่ฎขๅ
LocalDateTime threshold = LocalDateTime.now().minusMinutes(5);
List<PayOrder> payingOrders = orderRepository.selectList(
new QueryWrapper<PayOrder>()
.eq("status", PayOrderStatus.PAYING.getCode())
.lt("update_time", threshold)
.isNotNull("channel_pay_id")
);
for (PayOrder order : payingOrders) {
try {
// ไธปๅจๆฅ่ฏขๆธ ้็ถๆ
QueryPayResult result = channelProxy.getChannel(order.getChannelCode())
.queryPay(order.getChannelPayId());
if (result.isSuccess()) {
// ๆดๆฐ่ฎขๅ็ถๆ
if ("SUCCESS".equals(result.getPayStatus())) {
stateMachine.handlePaySuccess(order.getOrderId(), result);
} else if ("CLOSED".equals(result.getPayStatus())) {
stateMachine.transition(order.getOrderId(), PayOrderStatus.CLOSED, "COMPENSATOR");
}
}
} catch (Exception e) {
log.error("่ฎขๅ็ถๆๆฅ่ฏขๅคฑ่ดฅ: orderId={}", order.getOrderId(), e);
}
}
}
}
ๅ ญใๅฎๅ จไธ้ฃๆง้ๆ
ๆถ้ถๅฐ็ดๆฅๆถๅ่ต้ๆต่ฝฌ๏ผๅฎๅ จๆฏ้ไธญไน้ใ
6.1 ๅฎๅ จๆชๆฝๆธ ๅ
| ๅฎๅ จๆชๆฝ | ่ฏดๆ | ๅฎ็ฐๆนๅผ |
|---|---|---|
| ่ฏทๆฑ็ญพๅ | ้ฒๆญข่ฏทๆฑ็ฏกๆน | HMAC-SHA256 |
| HTTPS | ้ฒๆญขไธญ้ดไบบๆปๅป | ๅผบๅถHTTPS |
| Tokenๆถๆ | ้ฒๆญข้ๆพๆปๅป | 5ๅ้ๆๆๆ |
| ้้ขๆ ก้ช | ้ฒๆญข้้ข็ฏกๆน | ๅ็ซฏ้ๆฐ่ฎก็ฎ |
| ่ฎพๅคๆ็บน | ่ฏๅซๅผๅธธ่ฎพๅค | ๆถ้่ฎพๅคไฟกๆฏ |
| ้ช่ฏ็ | ้ฒๆญขๆบๅจๆปๅป | ๅพๅฝข/็ญไฟก้ช่ฏ็ |
6.2 ่ฏทๆฑ็ญพๅๅฎ็ฐ
@Component
public class RequestSigner {
private static final String SIGN_ALGORITHM = "HmacSHA256";
/**
* ็ๆ็ญพๅ
*/
public String sign(Map<String, String> params, String secretKey) {
// 1. ๅๆฐๆๅบ
List<String> sortedKeys = params.keySet().stream()
.sorted()
.collect(Collectors.toList());
// 2. ๆผๆฅๅพ
็ญพๅๅญ็ฌฆไธฒ
StringBuilder sb = new StringBuilder();
for (String key : sortedKeys) {
String value = params.get(key);
if (StringUtils.isNotBlank(value)) {
sb.append(key).append("=").append(value).append("&");
}
}
sb.append("key=").append(secretKey);
// 3. HMAC-SHA256็ญพๅ
try {
Mac mac = Mac.getInstance(SIGN_ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), SIGN_ALGORITHM);
mac.init(keySpec);
byte[] hash = mac.doFinal(sb.toString().getBytes(StandardCharsets.UTF_8));
return Hex.encodeHexString(hash).toUpperCase();
} catch (Exception e) {
throw new RuntimeException("็ญพๅๅคฑ่ดฅ", e);
}
}
/**
* ้ช่ฏ็ญพๅ
*/
public boolean verify(Map<String, String> params, String expectedSign, String secretKey) {
String actualSign = sign(params, secretKey);
return actualSign.equals(expectedSign);
}
}
6.3 ้ฃๆง็ณป็ป้ๆ
@Service
public class RiskControlService {
@Autowired
private List<RiskRule> riskRules;
/**
* ๆฏไปๅ้ฃๆงๆฃๆฅ
*/
public RiskResult checkRisk(PayOrder order, UserContext user, DeviceInfo device) {
int totalScore = 0;
List<String> riskReasons = new ArrayList<>();
for (RiskRule rule : riskRules) {
RiskCheckResult result = rule.check(order, user, device);
if (result.isBlocked()) {
// ็ดๆฅๆฆๆช
return RiskResult.block(result.getReason());
}
totalScore += result.getScore();
if (result.getScore() > 0) {
riskReasons.add(result.getReason());
}
}
// ็ปผๅ่ฏๅ
if (totalScore >= 100) {
return RiskResult.block("็ปผๅ้ฃ้ฉ่ฏๅ่ฟ้ซ");
} else if (totalScore >= 50) {
return RiskResult.review("้ไบบๅทฅๅฎกๆ ธ", riskReasons);
} else {
return RiskResult.pass();
}
}
}
// ้ฃๆง่งๅ็คบไพ๏ผ้้ข้ฃ้ฉ่งๅ
@Component
public class AmountRiskRule implements RiskRule {
@Override
public RiskCheckResult check(PayOrder order, UserContext user, DeviceInfo device) {
BigDecimal amount = order.getAmount();
// ๅคง้ขๆฏไป้ฃ้ฉๆฃๆฅ
if (amount.compareTo(new BigDecimal("10000")) > 0) {
// ๆฃๆฅ็จๆทๅๅฒๆถ่ดน
BigDecimal avgAmount = getUserAvgAmount(user.getUserId());
if (amount.compareTo(avgAmount.multiply(new BigDecimal("5"))) > 0) {
return RiskCheckResult.block("้้ขๅผๅธธ๏ผ่ถ
่ฟๅๅฒๅนณๅๆถ่ดน5ๅ");
}
}
// ่ถ
ๅคง้ข้่ฆไบบๅทฅๅฎกๆ ธ
if (amount.compareTo(new BigDecimal("50000")) > 0) {
return RiskCheckResult.review(30, "่ถ
ๅคง้ขๆฏไป้ๅฎกๆ ธ");
}
return RiskCheckResult.pass();
}
}
// ้ข็้ฃ้ฉ่งๅ
@Component
public class FrequencyRiskRule implements RiskRule {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public RiskCheckResult check(PayOrder order, UserContext user, DeviceInfo device) {
String userId = user.getUserId();
// ๆฃๆฅ1ๅฐๆถๅ
ๆฏไปๆฌกๆฐ
String key = "pay_frequency:" + userId;
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, 1, TimeUnit.HOURS);
}
if (count > 50) {
return RiskCheckResult.block("ๆฏไป้ข็่ฟ้ซ๏ผ็ไผผๅผๅธธ");
} else if (count > 20) {
return RiskCheckResult.review(20, "ๆฏไป้ข็่พ้ซ");
}
return RiskCheckResult.pass();
}
}
ไธใ็จๆทไฝ้ชไผๅ
ๆถ้ถๅฐๆฏๆฏไป่ฝฌๅ็ๆๅไธๅ ฌ้๏ผไฝ้ชๅฅฝๅ็ดๆฅๅฝฑๅๆไบค็ใๆ นๆฎๆฐๆฎ็ป่ฎก๏ผไผๅๆถ้ถๅฐไฝ้ช่ฝๆๅ3-8%็ๆฏไป่ฝฌๅ็ใ
7.1 ๆบ่ฝ้ป่ฎคๆฏไปๆนๅผ
ๆ นๆฎ็จๆทๅๅฒๅๅฅฝ่ชๅจ้ไธญ๏ผ
@Service
public class UserPreferenceService {
@Autowired
private UserPayHistoryRepository historyRepository;
/**
* ่ทๅ็จๆท้ฆ้ๆฏไปๆนๅผ
*/
public String getUserPreferredChannel(String userId) {
// ๆฅ่ฏข็จๆทๆ่ฟ30ๅคฉ็ๆฏไป่ฎฐๅฝ
List<UserPayHistory> histories = historyRepository.findRecentByUserId(userId, 30);
if (histories.isEmpty()) {
return null; // ๆฐ็จๆท๏ผๆ ๅๅฅฝ
}
// ็ป่ฎกๅๆธ ้ไฝฟ็จ้ข็
Map<String, Long> channelCount = histories.stream()
.collect(Collectors.groupingBy(
UserPayHistory::getChannelCode,
Collectors.counting()
));
// ่ฟๅไฝฟ็จๆๅค็ๆธ ้
return channelCount.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse(null);
}
}
7.2 ๆธ ๆฐ็้้ขๆ็ปๅฑ็คบ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ่ฎขๅ้้ขๆ็ป โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๅๅ้้ข ยฅ 299.00 โ
โ ไผๆ ๅธ -ยฅ 50.00 โ
โ ่ฟ่ดน ยฅ 10.00 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ ๅฎไป้้ข ยฅ 259.00 โ
โ โ
โ ๆฏไปๅ้ข่ฎก2ๅฐๆถๅ
ๅ่ดง โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
7.3 ๅๅฅฝ็ๅคฑ่ดฅๅค็
ๆฏไปๅคฑ่ดฅๆถ๏ผ็ปๅบๆ็กฎ็ๅคฑ่ดฅๅๅ ๅ่งฃๅณๅปบ่ฎฎ๏ผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ ๆฏไปๅคฑ่ดฅ โ
โ โ
โ ๅคฑ่ดฅๅๅ ๏ผ้ถ่กๅกไฝ้ขไธ่ถณ โ
โ โ
โ ๅปบ่ฎฎๆจๅฐ่ฏ๏ผ โ
โ โข ๆดๆขๅ
ถไป้ถ่กๅกๆฏไป โ
โ โข ไฝฟ็จไฝ้ขๆฏไป โ
โ โข ้ๆฉไฟก็จๆฏไป๏ผ่ฑๅ/็ฝๆก๏ผ โ
โ โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ ๆขๅกๆฏไป โ โ ไฝ้ขๆฏไป โ โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
7.4 ่ทจ็ซฏ็ถๆๅๆญฅ
็จๆทๅจPC็ซฏๆซ็ ๆฏไป๏ผ้่ฆๅฎๆถๆดๆฐPC็ซฏ็ถๆ๏ผ
// ๅ็ซฏ่ฝฎ่ฏขๆฏไป็ถๆ
class PayStatusPoller {
constructor(orderId) {
this.orderId = orderId;
this.stopped = false;
}
start() {
this.poll();
}
stop() {
this.stopped = true;
}
async poll() {
while (!this.stopped) {
try {
const response = await fetch(`/api/pay/status?orderId=${this.orderId}`);
const data = await response.json();
if (data.status === 'SUCCESS') {
this.onSuccess(data);
this.stop();
break;
} else if (data.status === 'FAILED' || data.status === 'CLOSED') {
this.onFailed(data);
this.stop();
break;
}
// ็ญๅพ
2็งๅ็ปง็ปญ่ฝฎ่ฏข
await this.sleep(2000);
} catch (error) {
console.error('่ฝฎ่ฏขๆฏไป็ถๆๅคฑ่ดฅ', error);
await this.sleep(3000);
}
}
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
onSuccess(data) {
// ๆพ็คบๆฏไปๆๅ้กต้ข
window.location.href = `/pay/success?orderId=${this.orderId}`;
}
onFailed(data) {
// ๆพ็คบๆฏไปๅคฑ่ดฅ้กต้ข
showError(data.reason || 'ๆฏไปๅคฑ่ดฅ๏ผ่ฏท้่ฏ');
}
}
ๅ ซใ้ซๅฏ็จไธๆง่ฝไผๅ
ๆฏไป็ณป็ปๅฏนๅฏ็จๆง่ฆๆฑๆ้ซ๏ผ้ๅธธ่ฆๆฑ่พพๅฐ99.99%๏ผๅ จๅนดไธๅฏ็จๆถ้ดไธ่ถ ่ฟ53ๅ้๏ผใ
8.1 ้ซๅฏ็จๆถๆ
โโโโโโโโโโโโโโโโโโโ
โ DNS/CDN โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโดโโโโโโโโโ
โ ่ด่ฝฝๅ่กก โ
โ (Nginx/F5) โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โผ โผ โผ
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
โ ๆบๆฟ A โ โ ๆบๆฟ B โ โ ๆบๆฟ C โ
โ (ๅไบฌ) โ โ (ไธๆตท) โ โ (ๅนฟๅท) โ
โโโโโโฌโโโโโ โโโโโโฌโโโโโ โโโโโโฌโโโโโ
โ โ โ
โโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโดโโโโโโโโ
โ ๆฐๆฎๅบ้็พค โ
โ (ไธปไป + ๅ็) โ
โโโโโโโโโโโโโโโโโ
8.2 ็ๆญ้็บง๏ผ็่ฎบ็ฏ๏ผ
ๅฝๆฏไปๆธ ้ๅบ็ฐๆ ้๏ผๅๅบ่ถ ๆถใ้่ฏฏ็้ฃๅ๏ผๆถ๏ผ็ปง็ปญ่ฐ็จๅชไผๆตช่ดนๆถ้ดใ็ๆญๅจไผ"่ทณ้ธ"๏ผๅ็ปญ่ฏทๆฑ่ชๅจๅๆขๅฐๅค็จๆธ ้ๆ่ฟๅๅคฑ่ดฅใ
// ็่ฎบไปฃ็ ็คบไพ๏ผไฝฟ็จResilience4jๅฎ็ฐ็ๆญ
@Configuration
public class CircuitBreakerConfig {
@Bean
public CircuitBreaker wechatPayCircuitBreaker() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // ๅคฑ่ดฅ็50%่งฆๅ็ๆญ
.waitDurationInOpenState(Duration.ofSeconds(30)) // ็ๆญ30็ง
.permittedNumberOfCallsInHalfOpenState(5) // ๅๅผ็ถๆ่ฏ5ๆฌก
.slidingWindowSize(20) // ๆปๅจ็ชๅฃ20ๆฌก
.slidingWindowType(SlidingWindowType.COUNT_BASED)
.build();
return CircuitBreaker.of("wechat-pay", config);
}
}
- ็ฝๅ ณๅฑ็้ๆตไฟๆค
- ๅคๆฏไปๆธ ้็ๅไฝ้ ็ฝฎ
- ๆๅจๅๆขๆธ ้็่ฝๅ
้็ไธๅกๅข้ฟ๏ผๆชๆฅไผๅผๅ ฅๆด็ฒพ็ป็็ๆญ็ญ็ฅใ } catch (CallNotPermittedException e) { // ็ๆญๅจๆๅผ๏ผ่ฟๅ้็บง็ปๆ log.warn("ๅพฎไฟกๆฏไป็ๆญไธญ๏ผ่ฟๅ้็บง็ปๆ"); return CreatePayResult.fallback("ๅพฎไฟกๆฏไปๆๆถไธๅฏ็จ๏ผ่ฏทๅฐ่ฏๅ ถไปๆฏไปๆนๅผ"); } } }
### 8.3 ้ๆตไฟๆค
java
@Component public class RateLimiterInterceptor implements HandlerInterceptor {
private final RateLimiter globalLimiter = RateLimiter.create(10000); // ๅ
จๅฑ1ไธQPS private final Map
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // ๅ จๅฑ้ๆต if (!globalLimiter.tryAcquire()) { response.setStatus(429); response.getWriter().write("{\"code\":\"RATE_LIMIT\",\"msg\":\"็ณป็ป็นๅฟ๏ผ่ฏท็จๅ้่ฏ\"}"); return false; }
// ็จๆท็บง้ๆต๏ผๆฏ็จๆทๆฏ็ง5ๆฌก๏ผ String userId = getCurrentUserId(); RateLimiter userLimiter = userLimiters.computeIfAbsent(userId, k -> RateLimiter.create(5));
if (!userLimiter.tryAcquire()) { response.setStatus(429); response.getWriter().write("{\"code\":\"RATE_LIMIT\",\"msg\":\"ๆไฝ่ฟไบ้ข็น\"}"); return false; }
return true; } }
### 8.4 ๅฏ่งๆตๆง
**ๅ
จ้พ่ทฏ่ฟฝ่ธช็คบไพ**๏ผ
TraceID: trace_20260301_abc123 โโโ [10:30:00.000] API Gateway - ๆฅๆถ่ฏทๆฑ โโโ [10:30:00.005] Auth Service - ้ดๆ้่ฟ โโโ [10:30:00.015] Order Service - ๅๅปบ่ฎขๅ โโโ [10:30:00.020] Risk Service - ้ฃๆง้่ฟ โโโ [10:30:00.022] Router Service - ้ๆฉๆธ ้: wechat โโโ [10:30:00.180] Channel Service - ่ฐ็จๅพฎไฟกๆฏไป โโโ [10:30:00.190] Order Service - ๆดๆฐ่ฎขๅ็ถๆ โโโ [10:30:00.195] API Gateway - ่ฟๅ็ปๆ ```
| ๆๆ | ่ฏดๆ | ๅ่ญฆ้ๅผ |
|---|---|---|
| ๆฏไปๆๅ็ | ๆๅ่ฎขๅ/ๆป่ฎขๅ | < 90% |
| ๅนณๅๅๅบๆถ้ด | P95ๅปถ่ฟ | > 500ms |
| ๆธ ้ๅฏ็จๆง | ๆธ ้ๆญฃๅธธๆฏไพ | < 95% |
| ่ฎขๅๅ ็งฏๆฐ | ๅพ ๅค็่ฎขๅๆฐ | > 1000 |
ไนใๅฎๆๆกไพๅๆ
9.1 ๆ็ตๅๅนณๅฐๆถ้ถๅฐๆน้
- ๆฅ่ฎขๅ้50ไธ๏ผๆฏไปๆๅ็ไป 85%
- ๅไธๅก็บฟๆฏไปไฝ้ชๅทฎๅผๅคง
- ๅนดๆ็ปญ่ดนๆฏๅบ็บฆ300ไธ
- ็ปไธๆถ้ถๅฐๆๅก๏ผๆๆไธๅก็บฟๆฅๅ ฅ็ปไธAPI
- ๆบ่ฝ่ทฏ็ฑ็ณป็ป๏ผๆ นๆฎๅบๆฏ้ๆฉๆไผๆธ ้
- ็จๆทไฝ้ชไผๅ๏ผๆบ่ฝ้ป่ฎคใๅคฑ่ดฅๅผๅฏผ
- ๆฏไปๆๅ็๏ผ85% โ 93%๏ผ+8%๏ผ
- ๅนด่็ๆ็ปญ่ดน๏ผ็บฆ280ไธ
- ็จๆทๆ่ฏไธ้๏ผ60%
9.2 ๅๅไธ้ซๅนถๅๅบๅฏน
- ้ข็ญๆฉๅฎน๏ผๆๅ2ๅฐๆถๅฎๆๆๅกๅจๆฉๅฎน
- ๅๅณฐๅกซ่ฐท๏ผๅผๅ ฅๆถๆฏ้ๅ๏ผๅนณๆปๅค็่ฏทๆฑ
- ๅค็บง้็บง๏ผๅถๅฎๅค็บง้็บง็ญ็ฅ
ๅใๆป็ปไธๅฑๆ
10.1 ๆ ธๅฟ่ฎพ่ฎกๅๅๅ้กพ
็ปไธๆถ้ถๅฐ็่ฎพ่ฎก๏ผๆฌ่ดจไธๆฏๅจ่งฃๅณไธไธช้ฎ้ข๏ผ
- ไธๅก่งฃ่ฆ๏ผๆๆฏไปๅๆๅบ็ก่ฎพๆฝ๏ผ่ฎฉไธๅก็ณป็ปไธๆณจไธๅก
- ๆธ ้่ๅ๏ผๅฑ่ฝๆธ ้ๅทฎๅผ๏ผๆไพ็ปไธ็ๆๅก่ฝๅ
- ไฝ้ชไผๅ๏ผๅๅฐ็จๆทๆไฝ๏ผๆๅๆฏไป่ฝฌๅ็
10.2 ๆๆฏ้ๅๅปบ่ฎฎ
| ็ปไปถ | ๆจ่ๆนๆก | ่ฏดๆ |
|---|---|---|
| ็ฝๅ ณๅฑ | Spring Cloud Gateway | ้ซๆง่ฝใๆๆฉๅฑ |
| ๆๅกๆกๆถ | Spring Boot | ็ๆๆ็ |
| ๆถๆฏ้ๅ | RocketMQ | ้ซๅฏ้ ใไบๅกๆถๆฏ |
| ็ผๅญ | Redis Cluster | ้ซๅฏ็จใๅๅธๅผ้ |
| ๆฐๆฎๅบ | MySQL + ๅๅบๅ่กจ | ๆ็็จณๅฎ |
| ็ๆง | Prometheus + Grafana | ๅผๆบใๅ่ฝๅผบๅคง |
| ้พ่ทฏ่ฟฝ่ธช | SkyWalking | ๅ จ้พ่ทฏๅฏ่งๅ |
10.3 ๆชๆฅ่ถๅฟ
ๆฏไปไธญๅฟ็ณปๅๅๅฐ่ฟ้ๅฐฑ็ปๆไบใไป่ดฆๆทไฝ็ณปๅฐไบคๆๆตๆฐด๏ผไปๆธ ้ๅฏนๆฅๅฐๅฏน่ดฆ็ณป็ป๏ผไป้ฃๆง่ฎพ่ฎกๅฐ็ปไธๆถ้ถๅฐ๏ผๆไปฌ่ฏๅพๆๆฏไปไธญๅฟ็ๅฎๆด้ข่ฒๅ็ฐๅบๆฅใ
ๅฝ็ถ๏ผๆฏไธช็ฏ่ๅฑๅผ้ฝๆฏไธ้จๅญฆ้ฎ๏ผ่ฟ้ๅช่ฝ่ฎฒๅ็ใ่ฐๆ่ทฏใๅ ทไฝ็ๅฎ็ฐ็ป่๏ผ้่ฆๅจๅฎ่ทตไธญๆธ็ดขใๅฐฑๅๅญฆๆธธๆณณ๏ผ็ๅๅคๆ็จไนไธๅฆไธๆฐด่ฏไธ่ฏใ
ๅธๆ่ฟไธช็ณปๅๅฏนๆญฃๅจๆญๅปบๆไผๅๆฏไป็ณป็ป็ๆๅๆๆๅธฎๅฉใ
๐ฌ ่ฏ่ฎบ (0)