ๆ”ฏไป˜ไธญๅฟƒ๏ผš็ปŸไธ€ๆ”ถ้“ถๅฐ็š„่ฎพ่ฎกๅŽŸ็†ไธŽๆœ€ไฝณๅฎž่ทต

่ฟ™ๆ˜ฏๆ”ฏไป˜ไธญๅฟƒ็ณปๅˆ—็š„็ฌฌ8็ฏ‡๏ผŒไนŸๆ˜ฏๆœ€ๅŽไธ€็ฏ‡ใ€‚ๆˆ‘ไปฌๆฅ่Š่Š็ปŸไธ€ๆ”ถ้“ถๅฐโ€”โ€”่ฟ™ไธช็›ดๆŽฅ้ขๅ‘็”จๆˆทใ€ๅ†ณๅฎšๆ”ฏไป˜่ฝฌๅŒ–็އ็š„ๅ…ณ้”ฎ็ป„ไปถใ€‚ๅฎƒๅฐฑๅƒๅ•†ๅœบ็š„ๆ”ถ้“ถๅฐ๏ผŒๆ˜ฏ็”จๆˆทๅฎŒๆˆไบคๆ˜“็š„ๆœ€ๅŽไธ€้“้—จใ€‚


ไธ€ใ€ไธบไป€ไนˆ้œ€่ฆ็ปŸไธ€ๆ”ถ้“ถๅฐ

ๅฆ‚ๆžœไฝ ็ปๅކ่ฟ‡็”ตๅ•†ๆ—ฉๆœŸ๏ผŒๅฏ่ƒฝ่ง่ฟ‡่ฟ™ๆ ท็š„ๅœบๆ™ฏ๏ผšๆฏไธชไธšๅŠก็บฟ่‡ชๅทฑๅฏนๆŽฅๆ”ฏไป˜ๆธ ้“๏ผŒๅพฎไฟกๆ”ฏไป˜ๆ‰พๅพฎไฟกใ€ๆ”ฏไป˜ๅฎๆ‰พๆ”ฏไป˜ๅฎใ€้“ถ่”ๆ‰พ้“ถ่”โ€ฆโ€ฆ็ป“ๆžœๅฐฑๆ˜ฏ๏ผš

็ปŸไธ€ๆ”ถ้“ถๅฐๅฐฑๆ˜ฏไธบไบ†่งฃๅ†ณ่ฟ™ไบ›้—ฎ้ข˜ใ€‚ๅฎƒ็š„ๆ ธๅฟƒไปทๅ€ผๅพˆ็ฎ€ๅ•๏ผšๆŠŠๆ”ฏไป˜่ฟ™ไปถไบ‹๏ผŒๅ˜ๆˆไธ€ไธชๆ ‡ๅ‡†ๆœๅŠก๏ผŒ่€Œไธๆ˜ฏๆฏไธชไธšๅŠก็š„็งไบ‹ใ€‚

1.1 ไปŽๆžถๆž„่ง†่ง’็œ‹็ปŸไธ€ๆ”ถ้“ถๅฐ

ๆƒณ่ฑกไธ€ไธ‹๏ผŒๅฆ‚ๆžœๆฏไธชไธšๅŠก้ƒฝ่‡ชๅทฑๅฏนๆŽฅๆ”ฏไป˜๏ผš

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   ็”ตๅ•†ไธšๅŠก   โ”‚     โ”‚   ่ฎขๅ•ไธšๅŠก   โ”‚     โ”‚   ไผšๅ‘˜ไธšๅŠก   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚                   โ”‚                   โ”‚
       โ–ผ                   โ–ผ                   โ–ผ
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚              ๅ„่‡ชๅฏนๆŽฅๆ”ฏไป˜ๆธ ้“                    โ”‚
   โ”‚  ๅพฎไฟกๆ”ฏไป˜ โ”‚ ๆ”ฏไป˜ๅฎ โ”‚ ้“ถ่” โ”‚ PayPal โ”‚ ...       โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

่ฟ™ๅฐฑๅƒๆฏไธช้ƒจ้—จ้ƒฝ่‡ชๅทฑๆ‹›ไฟๆด๏ผŒ่€Œไธๆ˜ฏๆ‰พ็‰ฉไธšๅ…ฌๅธใ€‚่กจ้ข็œ‹ไผผไนŽ็ตๆดป๏ผŒๅฎž้™…ไธŠๆ•ˆ็އไฝŽไธ‹ใ€ๆˆๆœฌ้ซ˜ๆ˜‚ใ€‚

ๆœ‰ไบ†็ปŸไธ€ๆ”ถ้“ถๅฐๅŽ๏ผš

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   ็”ตๅ•†ไธšๅŠก   โ”‚     โ”‚   ่ฎขๅ•ไธšๅŠก   โ”‚     โ”‚   ไผšๅ‘˜ไธšๅŠก   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚                   โ”‚                   โ”‚
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                           โ–ผ
              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
              โ”‚      ็ปŸไธ€ๆ”ถ้“ถๅฐ         โ”‚
              โ”‚  (็ปŸไธ€ๆŽฅๅฃ + ่ทฏ็”ฑ็ญ–็•ฅ)   โ”‚
              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          โ”‚
       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
       โ–ผ                  โ–ผ                  โ–ผ
   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
   โ”‚ๅพฎไฟกๆ”ฏไป˜ โ”‚        โ”‚ ๆ”ฏไป˜ๅฎ  โ”‚        โ”‚  ้“ถ่”  โ”‚
   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

่ฟ™็งๆŠฝ่ฑกๅธฆๆฅ็š„ๅฅฝๅค„ๆ˜ฏ๏ผš

  1. ไธšๅŠก่งฃ่€ฆ๏ผšไธšๅŠก็ณป็ปŸๅชๅ…ณๅฟƒไธšๅŠก้€ป่พ‘๏ผŒๆ”ฏไป˜็ป†่Š‚ๅ…จ้ƒจไธ‹ๆฒ‰
  2. ็ปŸไธ€ไฝ“้ชŒ๏ผšๆ— ่ฎบๅ“ชไธชไธšๅŠก็บฟ๏ผŒ็”จๆˆท็œ‹ๅˆฐ็š„ๆ”ถ้“ถๅฐ้ฃŽๆ ผไธ€่‡ด
  3. ็ตๆดปๆ‰ฉๅฑ•๏ผšๆ–ฐๅขžๆ”ฏไป˜ๆธ ้“๏ผŒไธšๅŠก็ณป็ปŸๆ— ๆ„Ÿ็Ÿฅ
  4. ้›†ไธญ็›‘ๆŽง๏ผšๆ‰€ๆœ‰ๆ”ฏไป˜ๆต้‡ๆฑ‡่šไธ€ๅค„๏ผŒ้—ฎ้ข˜ไธ€็›ฎไบ†็„ถ

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 userLimiters = new ConcurrentHashMap<>();

@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 ๆŸ็”ตๅ•†ๅนณๅฐๆ”ถ้“ถๅฐๆ”น้€ 

  1. ็ปŸไธ€ๆ”ถ้“ถๅฐๆœๅŠก๏ผšๆ‰€ๆœ‰ไธšๅŠก็บฟๆŽฅๅ…ฅ็ปŸไธ€API
  2. ๆ™บ่ƒฝ่ทฏ็”ฑ็ณป็ปŸ๏ผšๆ นๆฎๅœบๆ™ฏ้€‰ๆ‹ฉๆœ€ไผ˜ๆธ ้“
  3. ็”จๆˆทไฝ“้ชŒไผ˜ๅŒ–๏ผšๆ™บ่ƒฝ้ป˜่ฎคใ€ๅคฑ่ดฅๅผ•ๅฏผ

9.2 ๅŒๅไธ€้ซ˜ๅนถๅ‘ๅบ”ๅฏน

  1. ้ข„็ƒญๆ‰ฉๅฎน๏ผšๆๅ‰2ๅฐๆ—ถๅฎŒๆˆๆœๅŠกๅ™จๆ‰ฉๅฎน
  2. ๅ‰Šๅณฐๅกซ่ฐท๏ผšๅผ•ๅ…ฅๆถˆๆฏ้˜Ÿๅˆ—๏ผŒๅนณๆป‘ๅค„็†่ฏทๆฑ‚
  3. ๅคš็บง้™็บง๏ผšๅˆถๅฎšๅคš็บง้™็บง็ญ–็•ฅ

ๅใ€ๆ€ป็ป“ไธŽๅฑ•ๆœ›

10.1 ๆ ธๅฟƒ่ฎพ่ฎกๅŽŸๅˆ™ๅ›ž้กพ

็ปŸไธ€ๆ”ถ้“ถๅฐ็š„่ฎพ่ฎก๏ผŒๆœฌ่ดจไธŠๆ˜ฏๅœจ่งฃๅ†ณไธ‰ไธช้—ฎ้ข˜๏ผš

  1. ไธšๅŠก่งฃ่€ฆ๏ผšๆŠŠๆ”ฏไป˜ๅ˜ๆˆๅŸบ็ก€่ฎพๆ–ฝ๏ผŒ่ฎฉไธšๅŠก็ณป็ปŸไธ“ๆณจไธšๅŠก
  2. ๆธ ้“่šๅˆ๏ผšๅฑ่”ฝๆธ ้“ๅทฎๅผ‚๏ผŒๆไพ›็ปŸไธ€็š„ๆœๅŠก่ƒฝๅŠ›
  3. ไฝ“้ชŒไผ˜ๅŒ–๏ผšๅ‡ๅฐ‘็”จๆˆทๆ“ไฝœ๏ผŒๆๅ‡ๆ”ฏไป˜่ฝฌๅŒ–็އ

10.2 ๆŠ€ๆœฏ้€‰ๅž‹ๅปบ่ฎฎ

็ป„ไปถ ๆŽจ่ๆ–นๆกˆ ่ฏดๆ˜Ž
็ฝ‘ๅ…ณๅฑ‚ Spring Cloud Gateway ้ซ˜ๆ€ง่ƒฝใ€ๆ˜“ๆ‰ฉๅฑ•
ๆœๅŠกๆก†ๆžถ Spring Boot ็”Ÿๆ€ๆˆ็†Ÿ
ๆถˆๆฏ้˜Ÿๅˆ— RocketMQ ้ซ˜ๅฏ้ ใ€ไบ‹ๅŠกๆถˆๆฏ
็ผ“ๅญ˜ Redis Cluster ้ซ˜ๅฏ็”จใ€ๅˆ†ๅธƒๅผ้”
ๆ•ฐๆฎๅบ“ MySQL + ๅˆ†ๅบ“ๅˆ†่กจ ๆˆ็†Ÿ็จณๅฎš
็›‘ๆŽง Prometheus + Grafana ๅผ€ๆบใ€ๅŠŸ่ƒฝๅผบๅคง
้“พ่ทฏ่ฟฝ่ธช SkyWalking ๅ…จ้“พ่ทฏๅฏ่ง†ๅŒ–

10.3 ๆœชๆฅ่ถ‹ๅŠฟ


ๆ”ฏไป˜ไธญๅฟƒ็ณปๅˆ—ๅ†™ๅˆฐ่ฟ™้‡Œๅฐฑ็ป“ๆŸไบ†ใ€‚ไปŽ่ดฆๆˆทไฝ“็ณปๅˆฐไบคๆ˜“ๆตๆฐด๏ผŒไปŽๆธ ้“ๅฏนๆŽฅๅˆฐๅฏน่ดฆ็ณป็ปŸ๏ผŒไปŽ้ฃŽๆŽง่ฎพ่ฎกๅˆฐ็ปŸไธ€ๆ”ถ้“ถๅฐ๏ผŒๆˆ‘ไปฌ่ฏ•ๅ›พๆŠŠๆ”ฏไป˜ไธญๅฟƒ็š„ๅฎŒๆ•ด้ข่ฒŒๅ‘ˆ็Žฐๅ‡บๆฅใ€‚

ๅฝ“็„ถ๏ผŒๆฏไธช็Žฏ่Š‚ๅฑ•ๅผ€้ƒฝๆ˜ฏไธ€้—จๅญฆ้—ฎ๏ผŒ่ฟ™้‡Œๅช่ƒฝ่ฎฒๅŽŸ็†ใ€่ฐˆๆ€่ทฏใ€‚ๅ…ทไฝ“็š„ๅฎž็Žฐ็ป†่Š‚๏ผŒ้œ€่ฆๅœจๅฎž่ทตไธญๆ‘ธ็ดขใ€‚ๅฐฑๅƒๅญฆๆธธๆณณ๏ผŒ็œ‹ๅ†ๅคšๆ•™็จ‹ไนŸไธๅฆ‚ไธ‹ๆฐด่ฏ•ไธ€่ฏ•ใ€‚

ๅธŒๆœ›่ฟ™ไธช็ณปๅˆ—ๅฏนๆญฃๅœจๆญๅปบๆˆ–ไผ˜ๅŒ–ๆ”ฏไป˜็ณป็ปŸ็š„ๆœ‹ๅ‹ๆœ‰ๆ‰€ๅธฎๅŠฉใ€‚


๐Ÿ’ฌ ่ฏ„่ฎบ (0)

0/500
ๆŽ’ๅบ๏ผš