+
+
+
+
+
diff --git a/src/.vuepress/navbar/en.ts b/src/.vuepress/navbar/en.ts
index 4e54e5f877..d3ae47a5bc 100644
--- a/src/.vuepress/navbar/en.ts
+++ b/src/.vuepress/navbar/en.ts
@@ -7,5 +7,5 @@ export const enNavbar = navbar([
"/blog/",
"/activity/",
"/donation/",
- "/about/"
+ "/about/",
]);
diff --git a/src/.vuepress/navbar/zh.ts b/src/.vuepress/navbar/zh.ts
index 84393322b5..7dd8cb3fce 100644
--- a/src/.vuepress/navbar/zh.ts
+++ b/src/.vuepress/navbar/zh.ts
@@ -7,5 +7,5 @@ export const zhNavbar = navbar([
"/zh/blog/",
"/zh/activity/",
"/zh/donation/",
- "/zh/about/"
+ "/zh/about/",
]);
diff --git a/src/.vuepress/public/Dlogo.svg b/src/.vuepress/public/Dlogo.svg
new file mode 100644
index 0000000000..bd89da40c8
--- /dev/null
+++ b/src/.vuepress/public/Dlogo.svg
@@ -0,0 +1,35 @@
+
+
\ No newline at end of file
diff --git a/src/.vuepress/public/assets/img/Banner.gif b/src/.vuepress/public/assets/img/Banner.gif
new file mode 100644
index 0000000000..be08b24bde
Binary files /dev/null and b/src/.vuepress/public/assets/img/Banner.gif differ
diff --git a/src/.vuepress/public/assets/img/activity.png b/src/.vuepress/public/assets/img/activity.png
index 38c2cd5cc1..bda72f8d12 100644
Binary files a/src/.vuepress/public/assets/img/activity.png and b/src/.vuepress/public/assets/img/activity.png differ
diff --git a/src/.vuepress/public/assets/img/activity/Dromara-OSPP-2025-0.png b/src/.vuepress/public/assets/img/activity/Dromara-OSPP-2025-0.png
new file mode 100644
index 0000000000..476a402819
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/Dromara-OSPP-2025-0.png differ
diff --git a/src/.vuepress/public/assets/img/activity/Dromara-OSPP-2025-1.png b/src/.vuepress/public/assets/img/activity/Dromara-OSPP-2025-1.png
new file mode 100644
index 0000000000..4d52fb8e50
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/Dromara-OSPP-2025-1.png differ
diff --git a/src/.vuepress/public/assets/img/activity/Dromara-OSPP-2025-2.png b/src/.vuepress/public/assets/img/activity/Dromara-OSPP-2025-2.png
new file mode 100644
index 0000000000..ed914bbc5d
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/Dromara-OSPP-2025-2.png differ
diff --git a/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-10.jpg b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-10.jpg
new file mode 100644
index 0000000000..1deaa9f060
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-10.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-12.png b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-12.png
new file mode 100644
index 0000000000..7799e9255f
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-12.png differ
diff --git a/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-14.png b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-14.png
new file mode 100644
index 0000000000..205851e2ab
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-14.png differ
diff --git a/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-2.gif b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-2.gif
new file mode 100644
index 0000000000..ffb10880af
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-2.gif differ
diff --git a/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-3.jpg b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-3.jpg
new file mode 100644
index 0000000000..e7da802d94
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-3.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-9.jpg b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-9.jpg
new file mode 100644
index 0000000000..a3d7da61b8
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/PowerData-AI-shanghai-4.19-9.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/apache-iceberg-meetup2025-0.png b/src/.vuepress/public/assets/img/activity/apache-iceberg-meetup2025-0.png
new file mode 100644
index 0000000000..0abd24af17
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/apache-iceberg-meetup2025-0.png differ
diff --git a/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-0.webp b/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-0.webp
new file mode 100644
index 0000000000..c234b4826c
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-0.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-1.webp b/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-1.webp
new file mode 100644
index 0000000000..40d4745158
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-1.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-2.png b/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-2.png
new file mode 100644
index 0000000000..ccbf797989
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-2.png differ
diff --git a/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-9.webp b/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-9.webp
new file mode 100644
index 0000000000..8dc609a2ce
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/coscon24-forum-intro-9.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dev-ai-frontier-meet-0.gif b/src/.vuepress/public/assets/img/activity/dev-ai-frontier-meet-0.gif
new file mode 100644
index 0000000000..acdc7ccde3
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dev-ai-frontier-meet-0.gif differ
diff --git a/src/.vuepress/public/assets/img/activity/dev-ai-frontier-meet-1.webp b/src/.vuepress/public/assets/img/activity/dev-ai-frontier-meet-1.webp
new file mode 100644
index 0000000000..413a7849ba
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dev-ai-frontier-meet-1.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dev-ai-frontier-meet-2.webp b/src/.vuepress/public/assets/img/activity/dev-ai-frontier-meet-2.webp
new file mode 100644
index 0000000000..11b3a7f0bb
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dev-ai-frontier-meet-2.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-0.png b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-0.png
new file mode 100644
index 0000000000..474fcc2913
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-0.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-1.png b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-1.png
new file mode 100644
index 0000000000..f146aa6074
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-1.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-10.png b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-10.png
new file mode 100644
index 0000000000..dfe94707bc
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-10.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-3.png b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-3.png
new file mode 100644
index 0000000000..d7f17012e9
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-3.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-4.png b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-4.png
new file mode 100644
index 0000000000..bf3efe565c
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-4.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-6.png b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-6.png
new file mode 100644
index 0000000000..f146aa6074
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-6.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-8.png b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-8.png
new file mode 100644
index 0000000000..d7f17012e9
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-8.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-9.png b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-9.png
new file mode 100644
index 0000000000..98da180eb4
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-COSCon-letter-9.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-0.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-0.webp
new file mode 100644
index 0000000000..fb3c76d83b
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-0.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-10.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-10.webp
new file mode 100644
index 0000000000..74eb0ed011
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-10.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-11.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-11.png
new file mode 100644
index 0000000000..bb0b790666
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-11.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-12.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-12.png
new file mode 100644
index 0000000000..a06ba36f4f
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-12.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-13.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-13.png
new file mode 100644
index 0000000000..d6c9e22e79
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-13.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-14.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-14.png
new file mode 100644
index 0000000000..2f5c95bf67
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-14.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-15.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-15.png
new file mode 100644
index 0000000000..3740b434e4
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-15.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-16.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-16.webp
new file mode 100644
index 0000000000..a79aed681d
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-16.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-17.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-17.png
new file mode 100644
index 0000000000..ef3780eb06
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-17.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-18.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-18.png
new file mode 100644
index 0000000000..567e1805fd
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-18.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-19.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-19.png
new file mode 100644
index 0000000000..121b4bd3f0
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-19.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-20.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-20.png
new file mode 100644
index 0000000000..ccb06ee319
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-20.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-21.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-21.png
new file mode 100644
index 0000000000..225c04d6d8
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-21.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-22.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-22.png
new file mode 100644
index 0000000000..a92064945b
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-22.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-23.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-23.png
new file mode 100644
index 0000000000..0661f95520
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-23.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-24.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-24.webp
new file mode 100644
index 0000000000..149c0581a6
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-24.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-25.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-25.png
new file mode 100644
index 0000000000..3cfcccf8eb
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-25.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-26.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-26.png
new file mode 100644
index 0000000000..d1b35f111c
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-26.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-27.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-27.webp
new file mode 100644
index 0000000000..9c3ed5d3d3
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-27.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-28.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-28.webp
new file mode 100644
index 0000000000..fd66a74d39
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-28.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-29.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-29.png
new file mode 100644
index 0000000000..52e42e1853
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-29.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-3.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-3.webp
new file mode 100644
index 0000000000..37aa210584
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-3.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-30.png b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-30.png
new file mode 100644
index 0000000000..170ea11be4
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-30.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-31.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-31.webp
new file mode 100644
index 0000000000..879a7d889e
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-31.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-4.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-4.webp
new file mode 100644
index 0000000000..ec6d6bbc0f
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-4.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-5.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-5.webp
new file mode 100644
index 0000000000..0e8de5e333
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-5.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-6.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-6.webp
new file mode 100644
index 0000000000..3eab18e0cd
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-6.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-7.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-7.webp
new file mode 100644
index 0000000000..30ab2bf4ac
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-7.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-8.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-8.webp
new file mode 100644
index 0000000000..9e5aa2c73e
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-8.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-market-9.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-9.webp
new file mode 100644
index 0000000000..4960336242
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-market-9.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-0.jpg b/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-0.jpg
new file mode 100644
index 0000000000..129e3e4e33
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-0.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-1.jpg b/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-1.jpg
new file mode 100644
index 0000000000..ae47d44a4a
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-1.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-2.jpg b/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-2.jpg
new file mode 100644
index 0000000000..f54e492f3f
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-2.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-3.jpg b/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-3.jpg
new file mode 100644
index 0000000000..27a68673ee
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon-showcase-3.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-countdown-0.jpg b/src/.vuepress/public/assets/img/activity/dromara-coscon24-countdown-0.jpg
new file mode 100644
index 0000000000..ac02379bab
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-countdown-0.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-0.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-0.webp
new file mode 100644
index 0000000000..c234b4826c
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-0.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-1.png b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-1.png
new file mode 100644
index 0000000000..d7c8df06d0
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-1.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-10.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-10.webp
new file mode 100644
index 0000000000..c152b1e3a1
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-10.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-11.png b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-11.png
new file mode 100644
index 0000000000..d7c8df06d0
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-11.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-12.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-12.webp
new file mode 100644
index 0000000000..37b2ff4b91
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-12.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-13.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-13.webp
new file mode 100644
index 0000000000..d5af7ce93e
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-13.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-14.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-14.webp
new file mode 100644
index 0000000000..beb7b7f3c8
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-14.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-15.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-15.webp
new file mode 100644
index 0000000000..beb7b7f3c8
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-15.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-16.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-16.webp
new file mode 100644
index 0000000000..8dc609a2ce
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-16.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-2.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-2.webp
new file mode 100644
index 0000000000..37b2ff4b91
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-2.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-3.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-3.webp
new file mode 100644
index 0000000000..eb089ec1c0
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-3.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-4.png b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-4.png
new file mode 100644
index 0000000000..d7c8df06d0
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-4.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-5.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-5.webp
new file mode 100644
index 0000000000..37b2ff4b91
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-5.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-6.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-6.webp
new file mode 100644
index 0000000000..767bea426a
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-6.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-7.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-7.webp
new file mode 100644
index 0000000000..3be5e3bbf5
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-7.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-8.png b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-8.png
new file mode 100644
index 0000000000..d7c8df06d0
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-8.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-9.webp b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-9.webp
new file mode 100644
index 0000000000..37b2ff4b91
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-coscon24-forum-9.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-0.png b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-0.png
new file mode 100644
index 0000000000..6b67b3d3c7
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-0.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-1.png b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-1.png
new file mode 100644
index 0000000000..a4bb123481
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-1.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-2.png b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-2.png
new file mode 100644
index 0000000000..fbfb714c90
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-2.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-3.png b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-3.png
new file mode 100644
index 0000000000..39b6e1ffdc
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-3.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-4.png b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-4.png
new file mode 100644
index 0000000000..8875e25e13
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-4.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-5.png b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-5.png
new file mode 100644
index 0000000000..babfc718f7
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-5.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-6.png b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-6.png
new file mode 100644
index 0000000000..8637ffe2c3
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-milvusplus-live-preview-6.png differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-0.jpg b/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-0.jpg
new file mode 100644
index 0000000000..2f64109fa3
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-0.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-1.jpg b/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-1.jpg
new file mode 100644
index 0000000000..023cf92c8e
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-1.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-2.jpg b/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-2.jpg
new file mode 100644
index 0000000000..c8b5260bde
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-2.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-3.jpg b/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-3.jpg
new file mode 100644
index 0000000000..d4f84aec9b
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/dromara-wuzhen-summit-2024-3.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-reward-submit-0.gif b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-0.gif
new file mode 100644
index 0000000000..acdc7ccde3
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-0.gif differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-reward-submit-1.webp b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-1.webp
new file mode 100644
index 0000000000..774c1d2ec3
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-1.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-reward-submit-2.webp b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-2.webp
new file mode 100644
index 0000000000..da2f3f524b
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-2.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-reward-submit-3.webp b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-3.webp
new file mode 100644
index 0000000000..d80c330621
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-3.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-reward-submit-4.webp b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-4.webp
new file mode 100644
index 0000000000..5e9096accc
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-4.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-reward-submit-5.webp b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-5.webp
new file mode 100644
index 0000000000..2028ca1d23
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-5.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-reward-submit-6.webp b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-6.webp
new file mode 100644
index 0000000000..9993b5c0c2
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-6.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-reward-submit-7.webp b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-7.webp
new file mode 100644
index 0000000000..5dad928a46
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-reward-submit-7.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-0.gif b/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-0.gif
new file mode 100644
index 0000000000..acdc7ccde3
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-0.gif differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-1.png b/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-1.png
new file mode 100644
index 0000000000..3a16bdaa26
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-1.png differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-2.png b/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-2.png
new file mode 100644
index 0000000000..9ab81d382f
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-2.png differ
diff --git a/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-3.jpg b/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-3.jpg
new file mode 100644
index 0000000000..72f856e4f5
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/gstar-shenzhen-developers-3.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-0.gif b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-0.gif
new file mode 100644
index 0000000000..acdc7ccde3
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-0.gif differ
diff --git a/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-1.png b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-1.png
new file mode 100644
index 0000000000..a74afc1350
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-1.png differ
diff --git a/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-2.svg b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-2.svg
new file mode 100644
index 0000000000..2754e13500
--- /dev/null
+++ b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-2.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-3.svg b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-3.svg
new file mode 100644
index 0000000000..7412d77d69
--- /dev/null
+++ b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-3.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-4.png b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-4.png
new file mode 100644
index 0000000000..64adcea33c
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-4.png differ
diff --git a/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-5.png b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-5.png
new file mode 100644
index 0000000000..b2b6f0d6bc
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-5.png differ
diff --git a/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-6.jpg b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-6.jpg
new file mode 100644
index 0000000000..05fc222821
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/harmonyos-tool-gift-6.jpg differ
diff --git a/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-0.webp b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-0.webp
new file mode 100644
index 0000000000..b23d95d685
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-0.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-1.webp b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-1.webp
new file mode 100644
index 0000000000..60fe26fbf5
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-1.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-2.webp b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-2.webp
new file mode 100644
index 0000000000..ba5c92b2bc
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-2.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-3.webp b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-3.webp
new file mode 100644
index 0000000000..60fe26fbf5
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-3.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-4.png b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-4.png
new file mode 100644
index 0000000000..638f990deb
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-4.png differ
diff --git a/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-5.webp b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-5.webp
new file mode 100644
index 0000000000..f638b503ce
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-5.webp differ
diff --git a/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-6.webp b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-6.webp
new file mode 100644
index 0000000000..bbd53f1134
Binary files /dev/null and b/src/.vuepress/public/assets/img/activity/secretflow-3rd-upgrade-showcase-6.webp differ
diff --git a/src/.vuepress/public/assets/img/blog.png b/src/.vuepress/public/assets/img/blog.png
index d588cf450a..dc9c8ecd60 100644
Binary files a/src/.vuepress/public/assets/img/blog.png and b/src/.vuepress/public/assets/img/blog.png differ
diff --git a/src/.vuepress/public/assets/img/blog/HertzBea-collection-works-0.png b/src/.vuepress/public/assets/img/blog/HertzBea-collection-works-0.png
new file mode 100644
index 0000000000..343d051684
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/HertzBea-collection-works-0.png differ
diff --git a/src/.vuepress/public/assets/img/blog/architect-software-craft-0.webp b/src/.vuepress/public/assets/img/blog/architect-software-craft-0.webp
new file mode 100644
index 0000000000..c628de64b2
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/architect-software-craft-0.webp differ
diff --git a/src/.vuepress/public/assets/img/blog/dromara-warmflow-assignee-guide-0.webp b/src/.vuepress/public/assets/img/blog/dromara-warmflow-assignee-guide-0.webp
new file mode 100644
index 0000000000..6a8e78e71c
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/dromara-warmflow-assignee-guide-0.webp differ
diff --git a/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-0.webp b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-0.webp
new file mode 100644
index 0000000000..4d7e9c19e5
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-0.webp differ
diff --git a/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-1.png b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-1.png
new file mode 100644
index 0000000000..d5d2b74a03
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-1.png differ
diff --git a/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-2.png b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-2.png
new file mode 100644
index 0000000000..3fceba1bbd
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-2.png differ
diff --git a/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-3.png b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-3.png
new file mode 100644
index 0000000000..2344b78b8b
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-3.png differ
diff --git a/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-4.webp b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-4.webp
new file mode 100644
index 0000000000..f55cb35981
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-4.webp differ
diff --git a/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-5.webp b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-5.webp
new file mode 100644
index 0000000000..490ad7925e
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/milvus-easyai-face-java-5.webp differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-0.gif b/src/.vuepress/public/assets/img/blog/os-data-community-team-0.gif
new file mode 100644
index 0000000000..41d0a20ab4
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-0.gif differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-10.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-10.jpg
new file mode 100644
index 0000000000..da5356aa79
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-10.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-11.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-11.jpg
new file mode 100644
index 0000000000..e92d7dbe14
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-11.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-12.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-12.jpg
new file mode 100644
index 0000000000..5f986f1d37
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-12.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-13.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-13.jpg
new file mode 100644
index 0000000000..410ccd686b
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-13.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-14.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-14.jpg
new file mode 100644
index 0000000000..6e302b0d0d
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-14.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-15.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-15.jpg
new file mode 100644
index 0000000000..d8a2d51bd0
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-15.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-16.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-16.jpg
new file mode 100644
index 0000000000..879ae703b8
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-16.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-17.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-17.jpg
new file mode 100644
index 0000000000..b875dc53d6
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-17.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-18.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-18.jpg
new file mode 100644
index 0000000000..73b9775ed6
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-18.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-19.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-19.jpg
new file mode 100644
index 0000000000..955c4a3d47
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-19.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-20.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-20.jpg
new file mode 100644
index 0000000000..16d75ac2e3
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-20.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-21.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-21.jpg
new file mode 100644
index 0000000000..0e0d04420c
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-21.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-22.gif b/src/.vuepress/public/assets/img/blog/os-data-community-team-22.gif
new file mode 100644
index 0000000000..41d0a20ab4
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-22.gif differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-6.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-6.jpg
new file mode 100644
index 0000000000..99c5f88b14
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-6.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/os-data-community-team-9.jpg b/src/.vuepress/public/assets/img/blog/os-data-community-team-9.jpg
new file mode 100644
index 0000000000..0196d0e239
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/os-data-community-team-9.jpg differ
diff --git a/src/.vuepress/public/assets/img/blog/springboot-forest-deepseek-integration-0.png b/src/.vuepress/public/assets/img/blog/springboot-forest-deepseek-integration-0.png
new file mode 100644
index 0000000000..afbcfccd5a
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/springboot-forest-deepseek-integration-0.png differ
diff --git a/src/.vuepress/public/assets/img/blog/springboot-forest-deepseek-integration-1.png b/src/.vuepress/public/assets/img/blog/springboot-forest-deepseek-integration-1.png
new file mode 100644
index 0000000000..b1cb4f920b
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/springboot-forest-deepseek-integration-1.png differ
diff --git a/src/.vuepress/public/assets/img/blog/springboot-forest-deepseek-integration-2.png b/src/.vuepress/public/assets/img/blog/springboot-forest-deepseek-integration-2.png
new file mode 100644
index 0000000000..9908cbfb6b
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/springboot-forest-deepseek-integration-2.png differ
diff --git a/src/.vuepress/public/assets/img/blog/warm-flow-1.3.0-0.png b/src/.vuepress/public/assets/img/blog/warm-flow-1.3.0-0.png
new file mode 100644
index 0000000000..7b1d0ea71b
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/warm-flow-1.3.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/blog/warm-flow-1.3.0-1.webp b/src/.vuepress/public/assets/img/blog/warm-flow-1.3.0-1.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/blog/warm-flow-1.3.0-1.webp differ
diff --git a/src/.vuepress/public/assets/img/logo-animation/logo.webp b/src/.vuepress/public/assets/img/logo-animation/logo.webp
new file mode 100644
index 0000000000..ec91a0c576
Binary files /dev/null and b/src/.vuepress/public/assets/img/logo-animation/logo.webp differ
diff --git a/src/.vuepress/public/assets/img/logo.webp b/src/.vuepress/public/assets/img/logo.webp
new file mode 100644
index 0000000000..7cd2f90b7a
Binary files /dev/null and b/src/.vuepress/public/assets/img/logo.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/Akali.webp b/src/.vuepress/public/assets/img/logo/Akali.webp
index b652027e54..0c5449dca7 100644
Binary files a/src/.vuepress/public/assets/img/logo/Akali.webp and b/src/.vuepress/public/assets/img/logo/Akali.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/Disjob.webp b/src/.vuepress/public/assets/img/logo/Disjob.webp
index de77272c88..c9cb34c407 100644
Binary files a/src/.vuepress/public/assets/img/logo/Disjob.webp and b/src/.vuepress/public/assets/img/logo/Disjob.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/J2EEFAST.webp b/src/.vuepress/public/assets/img/logo/J2EEFAST.webp
index 9e5af471a0..43ab87f5f0 100644
Binary files a/src/.vuepress/public/assets/img/logo/J2EEFAST.webp and b/src/.vuepress/public/assets/img/logo/J2EEFAST.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/MaxKey.webp b/src/.vuepress/public/assets/img/logo/MaxKey.webp
index 96433a45e1..274e898183 100644
Binary files a/src/.vuepress/public/assets/img/logo/MaxKey.webp and b/src/.vuepress/public/assets/img/logo/MaxKey.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/Neutrino-Proxy.webp b/src/.vuepress/public/assets/img/logo/Neutrino-Proxy.webp
index 7397c8e1a2..4b20a5e76a 100644
Binary files a/src/.vuepress/public/assets/img/logo/Neutrino-Proxy.webp and b/src/.vuepress/public/assets/img/logo/Neutrino-Proxy.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/TLog-home.webp b/src/.vuepress/public/assets/img/logo/TLog-home.webp
new file mode 100644
index 0000000000..4417458e3f
Binary files /dev/null and b/src/.vuepress/public/assets/img/logo/TLog-home.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/TLog.webp b/src/.vuepress/public/assets/img/logo/TLog.webp
index 8cd2a24836..626a41ede7 100644
Binary files a/src/.vuepress/public/assets/img/logo/TLog.webp and b/src/.vuepress/public/assets/img/logo/TLog.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/TestHub.webp b/src/.vuepress/public/assets/img/logo/TestHub.webp
index 20ea42bff7..18627c0e06 100644
Binary files a/src/.vuepress/public/assets/img/logo/TestHub.webp and b/src/.vuepress/public/assets/img/logo/TestHub.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/WeMQ.webp b/src/.vuepress/public/assets/img/logo/WeMQ.webp
index 5644a3a51c..7cfcb9938e 100644
Binary files a/src/.vuepress/public/assets/img/logo/WeMQ.webp and b/src/.vuepress/public/assets/img/logo/WeMQ.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/cubic-home.webp b/src/.vuepress/public/assets/img/logo/cubic-home.webp
new file mode 100644
index 0000000000..4f55c67dbc
Binary files /dev/null and b/src/.vuepress/public/assets/img/logo/cubic-home.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/cubic.webp b/src/.vuepress/public/assets/img/logo/cubic.webp
index e2c9e4369b..797fa82800 100644
Binary files a/src/.vuepress/public/assets/img/logo/cubic.webp and b/src/.vuepress/public/assets/img/logo/cubic.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/domain-admin.webp b/src/.vuepress/public/assets/img/logo/domain-admin.webp
index b630442dca..52c73b9d83 100644
Binary files a/src/.vuepress/public/assets/img/logo/domain-admin.webp and b/src/.vuepress/public/assets/img/logo/domain-admin.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/dynamic-tp.webp b/src/.vuepress/public/assets/img/logo/dynamic-tp.webp
index 8f3bde3f82..c9c4b4cbeb 100644
Binary files a/src/.vuepress/public/assets/img/logo/dynamic-tp.webp and b/src/.vuepress/public/assets/img/logo/dynamic-tp.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/easy-es.webp b/src/.vuepress/public/assets/img/logo/easy-es.webp
index 820000be68..e25b2b0b54 100644
Binary files a/src/.vuepress/public/assets/img/logo/easy-es.webp and b/src/.vuepress/public/assets/img/logo/easy-es.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/electron-egg.webp b/src/.vuepress/public/assets/img/logo/electron-egg.webp
index 7d5b19a995..1b9ddbdccc 100644
Binary files a/src/.vuepress/public/assets/img/logo/electron-egg.webp and b/src/.vuepress/public/assets/img/logo/electron-egg.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/gobrs-async.webp b/src/.vuepress/public/assets/img/logo/gobrs-async.webp
index b93210b601..0b0079fed5 100644
Binary files a/src/.vuepress/public/assets/img/logo/gobrs-async.webp and b/src/.vuepress/public/assets/img/logo/gobrs-async.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/koalas-rpc.webp b/src/.vuepress/public/assets/img/logo/koalas-rpc.webp
index 310283eb3b..2323945acb 100644
Binary files a/src/.vuepress/public/assets/img/logo/koalas-rpc.webp and b/src/.vuepress/public/assets/img/logo/koalas-rpc.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/liteFlow.webp b/src/.vuepress/public/assets/img/logo/liteFlow.webp
index fbdc11f03e..05e5359dc3 100644
Binary files a/src/.vuepress/public/assets/img/logo/liteFlow.webp and b/src/.vuepress/public/assets/img/logo/liteFlow.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/newcar.webp b/src/.vuepress/public/assets/img/logo/newcar.webp
index 9e5bdae15e..4aa5594380 100644
Binary files a/src/.vuepress/public/assets/img/logo/newcar.webp and b/src/.vuepress/public/assets/img/logo/newcar.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/open-giteye-api.webp b/src/.vuepress/public/assets/img/logo/open-giteye-api.webp
index f6ac396893..d9c153a217 100644
Binary files a/src/.vuepress/public/assets/img/logo/open-giteye-api.webp and b/src/.vuepress/public/assets/img/logo/open-giteye-api.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/redisfront.png b/src/.vuepress/public/assets/img/logo/redisfront.png
new file mode 100644
index 0000000000..afe8ee9e1b
Binary files /dev/null and b/src/.vuepress/public/assets/img/logo/redisfront.png differ
diff --git a/src/.vuepress/public/assets/img/logo/redisfront.webp b/src/.vuepress/public/assets/img/logo/redisfront.webp
index 4c87657c8f..aa8628dd14 100644
Binary files a/src/.vuepress/public/assets/img/logo/redisfront.webp and b/src/.vuepress/public/assets/img/logo/redisfront.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/sa-token-home.webp b/src/.vuepress/public/assets/img/logo/sa-token-home.webp
new file mode 100644
index 0000000000..985b8eb456
Binary files /dev/null and b/src/.vuepress/public/assets/img/logo/sa-token-home.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/sa-token.webp b/src/.vuepress/public/assets/img/logo/sa-token.webp
index c75d22cecb..28b9ddd680 100644
Binary files a/src/.vuepress/public/assets/img/logo/sa-token.webp and b/src/.vuepress/public/assets/img/logo/sa-token.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/sayOrder.webp b/src/.vuepress/public/assets/img/logo/sayOrder.webp
index 293a413c06..eff47d4a04 100644
Binary files a/src/.vuepress/public/assets/img/logo/sayOrder.webp and b/src/.vuepress/public/assets/img/logo/sayOrder.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/sureness.webp b/src/.vuepress/public/assets/img/logo/sureness.webp
index 88446a9292..9d8027bfcd 100644
Binary files a/src/.vuepress/public/assets/img/logo/sureness.webp and b/src/.vuepress/public/assets/img/logo/sureness.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/tianai-captcha.webp b/src/.vuepress/public/assets/img/logo/tianai-captcha.webp
index 641bf49659..8fa5e936ea 100644
Binary files a/src/.vuepress/public/assets/img/logo/tianai-captcha.webp and b/src/.vuepress/public/assets/img/logo/tianai-captcha.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/x-easypdf.webp b/src/.vuepress/public/assets/img/logo/x-easypdf.webp
index 6134d65e9c..3647f7f74d 100644
Binary files a/src/.vuepress/public/assets/img/logo/x-easypdf.webp and b/src/.vuepress/public/assets/img/logo/x-easypdf.webp differ
diff --git a/src/.vuepress/public/assets/img/logo/zyplayer-doc.webp b/src/.vuepress/public/assets/img/logo/zyplayer-doc.webp
index ab436422f9..ebf73f8ce4 100644
Binary files a/src/.vuepress/public/assets/img/logo/zyplayer-doc.webp and b/src/.vuepress/public/assets/img/logo/zyplayer-doc.webp differ
diff --git a/src/.vuepress/public/assets/img/members/hefengen.webp b/src/.vuepress/public/assets/img/members/hefengen.webp
index 30b9f621b8..9e82b090c4 100644
Binary files a/src/.vuepress/public/assets/img/members/hefengen.webp and b/src/.vuepress/public/assets/img/members/hefengen.webp differ
diff --git a/src/.vuepress/public/assets/img/news.png b/src/.vuepress/public/assets/img/news.png
index 08c49ef4b3..4512c96132 100644
Binary files a/src/.vuepress/public/assets/img/news.png and b/src/.vuepress/public/assets/img/news.png differ
diff --git a/src/.vuepress/public/assets/img/news/Apache ShenYu-2.7.0-0.png b/src/.vuepress/public/assets/img/news/Apache ShenYu-2.7.0-0.png
new file mode 100644
index 0000000000..dbbbc52404
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Apache ShenYu-2.7.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Apache-Hertzbeat-1.6.1-0.png b/src/.vuepress/public/assets/img/news/Apache-Hertzbeat-1.6.1-0.png
new file mode 100644
index 0000000000..dae304dc81
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Apache-Hertzbeat-1.6.1-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Apache-Hertzbeat-1.6.1-1.png b/src/.vuepress/public/assets/img/news/Apache-Hertzbeat-1.6.1-1.png
new file mode 100644
index 0000000000..93cb1fa7cd
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Apache-Hertzbeat-1.6.1-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Carbon-0-0.png b/src/.vuepress/public/assets/img/news/Carbon-0-0.png
new file mode 100644
index 0000000000..82023b8de6
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Carbon-0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Carbon-0-1.png b/src/.vuepress/public/assets/img/news/Carbon-0-1.png
new file mode 100644
index 0000000000..b9801d4c0d
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Carbon-0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Carbon-2.6.0-0.png b/src/.vuepress/public/assets/img/news/Carbon-2.6.0-0.png
new file mode 100644
index 0000000000..f49ff5f74a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Carbon-2.6.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Carbon-v2.5.0-0.png b/src/.vuepress/public/assets/img/news/Carbon-v2.5.0-0.png
new file mode 100644
index 0000000000..9b024b7639
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Carbon-v2.5.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Carbon-v2.5.0-1.webp b/src/.vuepress/public/assets/img/news/Carbon-v2.5.0-1.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Carbon-v2.5.0-1.webp differ
diff --git a/src/.vuepress/public/assets/img/news/Dante-Cloud-3.3.5.0-0.png b/src/.vuepress/public/assets/img/news/Dante-Cloud-3.3.5.0-0.png
new file mode 100644
index 0000000000..13807af106
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Dante-Cloud-3.3.5.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Dante-Cloud-3.3.5.0-1.webp b/src/.vuepress/public/assets/img/news/Dante-Cloud-3.3.5.0-1.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Dante-Cloud-3.3.5.0-1.webp differ
diff --git a/src/.vuepress/public/assets/img/news/Dante-Cloud-3.4.3.3-0.png b/src/.vuepress/public/assets/img/news/Dante-Cloud-3.4.3.3-0.png
new file mode 100644
index 0000000000..13807af106
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Dante-Cloud-3.4.3.3-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Dante-Cloud-3.5.0.0-0.png b/src/.vuepress/public/assets/img/news/Dante-Cloud-3.5.0.0-0.png
new file mode 100644
index 0000000000..13807af106
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Dante-Cloud-3.5.0.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Dante-Cloud-3.5.5.0-0.png b/src/.vuepress/public/assets/img/news/Dante-Cloud-3.5.5.0-0.png
new file mode 100644
index 0000000000..13807af106
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Dante-Cloud-3.5.5.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/DyJava-0.png b/src/.vuepress/public/assets/img/news/DyJava-0.png
new file mode 100644
index 0000000000..7f23b799d0
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/DyJava-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/DynamicTp-v1.2.0-0.png b/src/.vuepress/public/assets/img/news/DynamicTp-v1.2.0-0.png
new file mode 100644
index 0000000000..a7e494a7ed
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/DynamicTp-v1.2.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/DynamicTp-v1.2.0-1.png b/src/.vuepress/public/assets/img/news/DynamicTp-v1.2.0-1.png
new file mode 100644
index 0000000000..01682f2490
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/DynamicTp-v1.2.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/DynamicTp-v1.2.0-2.png b/src/.vuepress/public/assets/img/news/DynamicTp-v1.2.0-2.png
new file mode 100644
index 0000000000..17e39b35cf
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/DynamicTp-v1.2.0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-0.png b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-0.png
new file mode 100644
index 0000000000..d226864c4b
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-1.png b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-1.png
new file mode 100644
index 0000000000..6af56792ad
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-2.jpg b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-2.jpg
new file mode 100644
index 0000000000..f09886d6b5
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-2.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-3.jpg b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-3.jpg
new file mode 100644
index 0000000000..f57b302581
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-3.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-4.png b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-4.png
new file mode 100644
index 0000000000..65b1294872
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-5.jpg b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-5.jpg
new file mode 100644
index 0000000000..d4f9968956
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-5.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-6.jpg b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-6.jpg
new file mode 100644
index 0000000000..cbddf30933
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Easy-Es-2.1.0-6.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-0.png b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-0.png
new file mode 100644
index 0000000000..eeeff3f239
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-1.png b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-1.png
new file mode 100644
index 0000000000..bf5f4f9b1c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-10.webp b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-10.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-10.webp differ
diff --git a/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-2.png b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-2.png
new file mode 100644
index 0000000000..25d29f8fd7
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-3.png b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-3.png
new file mode 100644
index 0000000000..369fde7e0d
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-4.jpg b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-4.jpg
new file mode 100644
index 0000000000..f626ffa173
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-4.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-5.png b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-5.png
new file mode 100644
index 0000000000..bedd0b2441
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-6.png b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-6.png
new file mode 100644
index 0000000000..3d3be28f6a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-6.png differ
diff --git a/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-7.png b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-7.png
new file mode 100644
index 0000000000..2ea9918752
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-7.png differ
diff --git a/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-8.png b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-8.png
new file mode 100644
index 0000000000..47f9a057f2
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-8.png differ
diff --git a/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-9.jpg b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-9.jpg
new file mode 100644
index 0000000000..3ddcc3ff8f
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/EasyAI-v1.3.8-9.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/ElectronEgg-v4-0.jpg b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-0.jpg
new file mode 100644
index 0000000000..dd43cfa1bc
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-0.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/ElectronEgg-v4-1.jpg b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-1.jpg
new file mode 100644
index 0000000000..1335644531
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-1.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/ElectronEgg-v4-2.png b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-2.png
new file mode 100644
index 0000000000..4087d3d0df
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/ElectronEgg-v4-3.png b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-3.png
new file mode 100644
index 0000000000..ddc603d4a6
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/ElectronEgg-v4-4.png b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-4.png
new file mode 100644
index 0000000000..843d926abb
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/ElectronEgg-v4-5.jpg b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-5.jpg
new file mode 100644
index 0000000000..ed9947cc47
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-5.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/ElectronEgg-v4-6.png b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-6.png
new file mode 100644
index 0000000000..f193c15a4f
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-6.png differ
diff --git a/src/.vuepress/public/assets/img/news/ElectronEgg-v4-7.webp b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-7.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/ElectronEgg-v4-7.webp differ
diff --git a/src/.vuepress/public/assets/img/news/Forest-v1.7-0.png b/src/.vuepress/public/assets/img/news/Forest-v1.7-0.png
new file mode 100644
index 0000000000..4f42d90537
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Forest-v1.7-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Gopher-0-0.png b/src/.vuepress/public/assets/img/news/Gopher-0-0.png
new file mode 100644
index 0000000000..30988d73e5
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Gopher-0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Gopher-0-1.png b/src/.vuepress/public/assets/img/news/Gopher-0-1.png
new file mode 100644
index 0000000000..a227305380
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Gopher-0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/LiteFlow-v2.13.0-0.png b/src/.vuepress/public/assets/img/news/LiteFlow-v2.13.0-0.png
new file mode 100644
index 0000000000..2fd0a96efb
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/LiteFlow-v2.13.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.2-0.png b/src/.vuepress/public/assets/img/news/MaxKey-4.1.2-0.png
new file mode 100644
index 0000000000..42b6dda8e1
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.2-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.2-1.png b/src/.vuepress/public/assets/img/news/MaxKey-4.1.2-1.png
new file mode 100644
index 0000000000..540b330919
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.2-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.2-2.webp b/src/.vuepress/public/assets/img/news/MaxKey-4.1.2-2.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.2-2.webp differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.3-0.png b/src/.vuepress/public/assets/img/news/MaxKey-4.1.3-0.png
new file mode 100644
index 0000000000..42b6dda8e1
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.3-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.3-1.png b/src/.vuepress/public/assets/img/news/MaxKey-4.1.3-1.png
new file mode 100644
index 0000000000..540b330919
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.3-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.3-2.webp b/src/.vuepress/public/assets/img/news/MaxKey-4.1.3-2.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.3-2.webp differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.4-0.webp b/src/.vuepress/public/assets/img/news/MaxKey-4.1.4-0.webp
new file mode 100644
index 0000000000..0586db24e2
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.4-0.webp differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.4-1.png b/src/.vuepress/public/assets/img/news/MaxKey-4.1.4-1.png
new file mode 100644
index 0000000000..540b330919
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.4-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.4-2.webp b/src/.vuepress/public/assets/img/news/MaxKey-4.1.4-2.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.4-2.webp differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.5-0.webp b/src/.vuepress/public/assets/img/news/MaxKey-4.1.5-0.webp
new file mode 100644
index 0000000000..0586db24e2
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.5-0.webp differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.5-1.png b/src/.vuepress/public/assets/img/news/MaxKey-4.1.5-1.png
new file mode 100644
index 0000000000..540b330919
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.5-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-0.png b/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-0.png
new file mode 100644
index 0000000000..42b6dda8e1
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-1.webp b/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-1.webp
new file mode 100644
index 0000000000..0586db24e2
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-1.webp differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-2.png b/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-2.png
new file mode 100644
index 0000000000..540b330919
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-3.webp b/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-3.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.6-3.webp differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.7-0.webp b/src/.vuepress/public/assets/img/news/MaxKey-4.1.7-0.webp
new file mode 100644
index 0000000000..0586db24e2
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.7-0.webp differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.7-1.png b/src/.vuepress/public/assets/img/news/MaxKey-4.1.7-1.png
new file mode 100644
index 0000000000..540b330919
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.7-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.8-0.webp b/src/.vuepress/public/assets/img/news/MaxKey-4.1.8-0.webp
new file mode 100644
index 0000000000..0586db24e2
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.8-0.webp differ
diff --git a/src/.vuepress/public/assets/img/news/MaxKey-4.1.8-1.png b/src/.vuepress/public/assets/img/news/MaxKey-4.1.8-1.png
new file mode 100644
index 0000000000..540b330919
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MaxKey-4.1.8-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-0.jpg b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-0.jpg
new file mode 100644
index 0000000000..eafc714711
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-0.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-1.png b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-1.png
new file mode 100644
index 0000000000..800feb55f6
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-2.png b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-2.png
new file mode 100644
index 0000000000..68e64507a6
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-3.png b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-3.png
new file mode 100644
index 0000000000..26defa0a53
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-4.png b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-4.png
new file mode 100644
index 0000000000..d625cad30c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.10.0-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-0.png b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-0.png
new file mode 100644
index 0000000000..fccc9cf9b8
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-1.png b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-1.png
new file mode 100644
index 0000000000..bd70460cc5
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-2.png b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-2.png
new file mode 100644
index 0000000000..15835c5505
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-3.png b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-3.png
new file mode 100644
index 0000000000..5a5ce67be0
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-4.png b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-4.png
new file mode 100644
index 0000000000..a58236e957
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-5.png b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-5.png
new file mode 100644
index 0000000000..82b32487ed
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.0-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.4-0.png b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.4-0.png
new file mode 100644
index 0000000000..364de934e9
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mayfly-Go-1.9.4-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-0.png b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-0.png
new file mode 100644
index 0000000000..6b4491f529
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-1.png b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-1.png
new file mode 100644
index 0000000000..64a4a3e6e8
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-2.png b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-2.png
new file mode 100644
index 0000000000..6a087f27c4
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-3.png b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-3.png
new file mode 100644
index 0000000000..7d125a99fa
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-4.png b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-4.png
new file mode 100644
index 0000000000..837efd0c9e
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-5.png b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-5.png
new file mode 100644
index 0000000000..6b4491f529
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MayflyGo-1.10.2-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/Mica-MQTT-0-0.png b/src/.vuepress/public/assets/img/news/Mica-MQTT-0-0.png
new file mode 100644
index 0000000000..9d4837d887
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Mica-MQTT-0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-0.png b/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-0.png
new file mode 100644
index 0000000000..e8984c5ec3
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-1.gif b/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-1.gif
new file mode 100644
index 0000000000..7b20119499
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-1.gif differ
diff --git a/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-2.png b/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-2.png
new file mode 100644
index 0000000000..43145f73c1
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-3.png b/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-3.png
new file mode 100644
index 0000000000..3c88a7a29e
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-4.png b/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-4.png
new file mode 100644
index 0000000000..405ea33947
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/MilvusPlus-v2.2.1-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-0.png b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-0.png
new file mode 100644
index 0000000000..3ca40ef6ff
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-1.png b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-1.png
new file mode 100644
index 0000000000..265802f236
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-2.png b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-2.png
new file mode 100644
index 0000000000..9d2b36a875
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-3.png b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-3.png
new file mode 100644
index 0000000000..7d7c5eabb9
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-4.png b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-4.png
new file mode 100644
index 0000000000..706447ddbb
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-5.png b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-5.png
new file mode 100644
index 0000000000..fead4a4468
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-6.jpg b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-6.jpg
new file mode 100644
index 0000000000..4224bd89a6
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-6.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-7.png b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-7.png
new file mode 100644
index 0000000000..405ea33947
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/NeutrinoProxy-2.0.2-7.png differ
diff --git a/src/.vuepress/public/assets/img/news/RedisFront-0-0.png b/src/.vuepress/public/assets/img/news/RedisFront-0-0.png
new file mode 100644
index 0000000000..8b97b21a13
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/RedisFront-0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/RedisFront-0-1.png b/src/.vuepress/public/assets/img/news/RedisFront-0-1.png
new file mode 100644
index 0000000000..c534bbb5b3
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/RedisFront-0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/RedisFront-0-2.png b/src/.vuepress/public/assets/img/news/RedisFront-0-2.png
new file mode 100644
index 0000000000..ced46290d6
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/RedisFront-0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/RuoYi-Vue-Plus-5.3.0-0.png b/src/.vuepress/public/assets/img/news/RuoYi-Vue-Plus-5.3.0-0.png
new file mode 100644
index 0000000000..30c07a5d7a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/RuoYi-Vue-Plus-5.3.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/SMS4j-3.3.4-0.png b/src/.vuepress/public/assets/img/news/SMS4j-3.3.4-0.png
new file mode 100644
index 0000000000..be65feffc1
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/SMS4j-3.3.4-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/SQLREST-0-0.png b/src/.vuepress/public/assets/img/news/SQLREST-0-0.png
new file mode 100644
index 0000000000..66c87f8e5a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/SQLREST-0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/SQLREST-0-1.png b/src/.vuepress/public/assets/img/news/SQLREST-0-1.png
new file mode 100644
index 0000000000..89e9d91425
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/SQLREST-0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-0.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-0.png
new file mode 100644
index 0000000000..bf43673033
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-1.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-1.png
new file mode 100644
index 0000000000..6b3002a272
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-2.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-2.png
new file mode 100644
index 0000000000..b1fb93b3a1
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-3.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-3.png
new file mode 100644
index 0000000000..4aa6dc0f5d
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-4.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-4.png
new file mode 100644
index 0000000000..b5bb15640d
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.41.0-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-0.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-0.png
new file mode 100644
index 0000000000..cf9bcb9e36
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-1.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-1.png
new file mode 100644
index 0000000000..76f63c0def
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-2.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-2.png
new file mode 100644
index 0000000000..4df6f106b1
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-3.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-3.png
new file mode 100644
index 0000000000..1f26396f4a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-4.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-4.png
new file mode 100644
index 0000000000..f3f55245cb
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-5.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-5.png
new file mode 100644
index 0000000000..b5bb15640d
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.42.0-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-0.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-0.png
new file mode 100644
index 0000000000..0bf6377f70
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-1.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-1.png
new file mode 100644
index 0000000000..1a3fe09792
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-2.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-2.png
new file mode 100644
index 0000000000..d23f5e87d5
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-3.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-3.png
new file mode 100644
index 0000000000..9d32cfa3ea
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-4.png b/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-4.png
new file mode 100644
index 0000000000..f01db026f6
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Sa-Token-v1.43.0-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-April-update-0.png b/src/.vuepress/public/assets/img/news/Skyeye-April-update-0.png
new file mode 100644
index 0000000000..3c697ade2a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-April-update-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-April-update-1.png b/src/.vuepress/public/assets/img/news/Skyeye-April-update-1.png
new file mode 100644
index 0000000000..6cc9d9fd7c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-April-update-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-April-update-2.png b/src/.vuepress/public/assets/img/news/Skyeye-April-update-2.png
new file mode 100644
index 0000000000..18c5a70f4b
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-April-update-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v2.5.8-0.png b/src/.vuepress/public/assets/img/news/Skyeye-v2.5.8-0.png
new file mode 100644
index 0000000000..f605e31f72
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v2.5.8-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v2.5.8-1.png b/src/.vuepress/public/assets/img/news/Skyeye-v2.5.8-1.png
new file mode 100644
index 0000000000..dfe787e1d4
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v2.5.8-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v2.5.8-2.png b/src/.vuepress/public/assets/img/news/Skyeye-v2.5.8-2.png
new file mode 100644
index 0000000000..7e8ca84c64
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v2.5.8-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-0.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-0.png
new file mode 100644
index 0000000000..4a6f21ae03
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-1.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-1.png
new file mode 100644
index 0000000000..4d9841b360
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-10.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-10.png
new file mode 100644
index 0000000000..f033bde064
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-10.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-11.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-11.png
new file mode 100644
index 0000000000..b6e7e771ed
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-11.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-12.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-12.png
new file mode 100644
index 0000000000..a9abd62d0c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-12.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-13.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-13.png
new file mode 100644
index 0000000000..e4a7c63a26
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-13.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-14.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-14.png
new file mode 100644
index 0000000000..e279ff8e62
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-14.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-15.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-15.png
new file mode 100644
index 0000000000..1498dc0907
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-15.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-16.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-16.png
new file mode 100644
index 0000000000..4bd2608186
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-16.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-17.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-17.png
new file mode 100644
index 0000000000..405ea33947
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-17.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-2.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-2.png
new file mode 100644
index 0000000000..897cd1f54a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-3.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-3.png
new file mode 100644
index 0000000000..b57ac69f43
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-4.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-4.png
new file mode 100644
index 0000000000..8740a4c1e8
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-5.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-5.png
new file mode 100644
index 0000000000..5ffcf86c86
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-6.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-6.png
new file mode 100644
index 0000000000..39bf85ec6f
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-6.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-7.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-7.png
new file mode 100644
index 0000000000..5d870fabd5
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-7.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-8.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-8.png
new file mode 100644
index 0000000000..e981f7d552
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-8.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-9.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-9.png
new file mode 100644
index 0000000000..5066d27075
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.14.17-9.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-0.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-0.png
new file mode 100644
index 0000000000..4a6f21ae03
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-1.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-1.png
new file mode 100644
index 0000000000..4d9841b360
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-10.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-10.png
new file mode 100644
index 0000000000..33d42cc468
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-10.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-11.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-11.png
new file mode 100644
index 0000000000..4461a95ffd
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-11.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-12.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-12.png
new file mode 100644
index 0000000000..45cbbaef33
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-12.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-13.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-13.png
new file mode 100644
index 0000000000..145181c19b
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-13.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-14.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-14.png
new file mode 100644
index 0000000000..df59f4fa7e
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-14.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-15.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-15.png
new file mode 100644
index 0000000000..804e4b1e02
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-15.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-16.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-16.png
new file mode 100644
index 0000000000..2aa4b5baee
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-16.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-17.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-17.png
new file mode 100644
index 0000000000..6ba33a47b8
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-17.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-2.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-2.png
new file mode 100644
index 0000000000..b322443eb8
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-3.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-3.png
new file mode 100644
index 0000000000..1b10097424
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-4.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-4.png
new file mode 100644
index 0000000000..4e6cd2ccc6
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-5.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-5.png
new file mode 100644
index 0000000000..adb6295f72
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-6.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-6.png
new file mode 100644
index 0000000000..5ea313d2ad
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-6.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-7.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-7.png
new file mode 100644
index 0000000000..39bf85ec6f
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-7.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-8.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-8.png
new file mode 100644
index 0000000000..5d870fabd5
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-8.png differ
diff --git a/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-9.png b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-9.png
new file mode 100644
index 0000000000..e981f7d552
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Skyeye-v3.15.14-9.png differ
diff --git a/src/.vuepress/public/assets/img/news/Stream-Query-0-0.gif b/src/.vuepress/public/assets/img/news/Stream-Query-0-0.gif
new file mode 100644
index 0000000000..acdc7ccde3
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Stream-Query-0-0.gif differ
diff --git a/src/.vuepress/public/assets/img/news/Stream-Query-0-1.jpg b/src/.vuepress/public/assets/img/news/Stream-Query-0-1.jpg
new file mode 100644
index 0000000000..073f6118ac
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Stream-Query-0-1.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/Stream-Query-0-2.png b/src/.vuepress/public/assets/img/news/Stream-Query-0-2.png
new file mode 100644
index 0000000000..c6cfedd25a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Stream-Query-0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Stream-Query-0-3.png b/src/.vuepress/public/assets/img/news/Stream-Query-0-3.png
new file mode 100644
index 0000000000..3ef8ccbf97
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Stream-Query-0-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/Stream-Query-0-4.png b/src/.vuepress/public/assets/img/news/Stream-Query-0-4.png
new file mode 100644
index 0000000000..11fa51e7ae
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Stream-Query-0-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/Stream-Query-0-5.png b/src/.vuepress/public/assets/img/news/Stream-Query-0-5.png
new file mode 100644
index 0000000000..f5067b9dd8
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Stream-Query-0-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/Stream-Query-0-6.png b/src/.vuepress/public/assets/img/news/Stream-Query-0-6.png
new file mode 100644
index 0000000000..57076afe26
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Stream-Query-0-6.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-0-0.jpg b/src/.vuepress/public/assets/img/news/WGAI-0-0.jpg
new file mode 100644
index 0000000000..78bf80f1e8
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-0-0.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-0-1.png b/src/.vuepress/public/assets/img/news/WGAI-0-1.png
new file mode 100644
index 0000000000..a4ffecb223
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-0-2.jpg b/src/.vuepress/public/assets/img/news/WGAI-0-2.jpg
new file mode 100644
index 0000000000..c3c42c60ed
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-0-2.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-0-3.jpg b/src/.vuepress/public/assets/img/news/WGAI-0-3.jpg
new file mode 100644
index 0000000000..91151bd45f
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-0-3.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-0-4.jpg b/src/.vuepress/public/assets/img/news/WGAI-0-4.jpg
new file mode 100644
index 0000000000..282679a0e1
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-0-4.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V2.0-0.png b/src/.vuepress/public/assets/img/news/WGAI-V2.0-0.png
new file mode 100644
index 0000000000..cd9dc061db
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V2.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V2.0-1.png b/src/.vuepress/public/assets/img/news/WGAI-V2.0-1.png
new file mode 100644
index 0000000000..a4b9910973
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V2.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V2.0-2.png b/src/.vuepress/public/assets/img/news/WGAI-V2.0-2.png
new file mode 100644
index 0000000000..4f5cd844de
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V2.0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V2.0-3.png b/src/.vuepress/public/assets/img/news/WGAI-V2.0-3.png
new file mode 100644
index 0000000000..6d9142aaec
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V2.0-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V2.0-4.jpg b/src/.vuepress/public/assets/img/news/WGAI-V2.0-4.jpg
new file mode 100644
index 0000000000..c8c29ee93a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V2.0-4.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V3.0-0.png b/src/.vuepress/public/assets/img/news/WGAI-V3.0-0.png
new file mode 100644
index 0000000000..bb67c8dcdf
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V3.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V3.0-1.png b/src/.vuepress/public/assets/img/news/WGAI-V3.0-1.png
new file mode 100644
index 0000000000..e8c1917499
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V3.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V3.0-2.png b/src/.vuepress/public/assets/img/news/WGAI-V3.0-2.png
new file mode 100644
index 0000000000..16ffa0f4ba
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V3.0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V3.0-3.png b/src/.vuepress/public/assets/img/news/WGAI-V3.0-3.png
new file mode 100644
index 0000000000..e1f0f2fe02
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V3.0-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V3.0-4.png b/src/.vuepress/public/assets/img/news/WGAI-V3.0-4.png
new file mode 100644
index 0000000000..b8345cbe19
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V3.0-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V3.0-5.jpg b/src/.vuepress/public/assets/img/news/WGAI-V3.0-5.jpg
new file mode 100644
index 0000000000..737fc487d8
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V3.0-5.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V4.0-0.jpg b/src/.vuepress/public/assets/img/news/WGAI-V4.0-0.jpg
new file mode 100644
index 0000000000..2be9f16075
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V4.0-0.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V4.0-1.png b/src/.vuepress/public/assets/img/news/WGAI-V4.0-1.png
new file mode 100644
index 0000000000..28f391a83d
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V4.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V4.0-2.jpg b/src/.vuepress/public/assets/img/news/WGAI-V4.0-2.jpg
new file mode 100644
index 0000000000..77c6bdfbc4
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V4.0-2.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V4.0-3.jpg b/src/.vuepress/public/assets/img/news/WGAI-V4.0-3.jpg
new file mode 100644
index 0000000000..8b9cef5ed5
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V4.0-3.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V4.0-4.jpg b/src/.vuepress/public/assets/img/news/WGAI-V4.0-4.jpg
new file mode 100644
index 0000000000..236232d5a5
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V4.0-4.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V4.0-5.jpg b/src/.vuepress/public/assets/img/news/WGAI-V4.0-5.jpg
new file mode 100644
index 0000000000..77c6bdfbc4
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V4.0-5.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V4.0-6.jpg b/src/.vuepress/public/assets/img/news/WGAI-V4.0-6.jpg
new file mode 100644
index 0000000000..abab52b7f3
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V4.0-6.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/WGAI-V4.0-7.jpg b/src/.vuepress/public/assets/img/news/WGAI-V4.0-7.jpg
new file mode 100644
index 0000000000..f491e0ea92
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/WGAI-V4.0-7.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.3.4-0.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.3.4-0.png
new file mode 100644
index 0000000000..61f6d95537
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.3.4-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.3.4-1.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.3.4-1.png
new file mode 100644
index 0000000000..55ee0e9b35
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.3.4-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.3.4-2.webp b/src/.vuepress/public/assets/img/news/Warm-Flow-1.3.4-2.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.3.4-2.webp differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-0.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-0.png
new file mode 100644
index 0000000000..8aec99fd61
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-1.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-1.png
new file mode 100644
index 0000000000..aab6da862d
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-2.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-2.png
new file mode 100644
index 0000000000..93d2575921
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-3.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-3.png
new file mode 100644
index 0000000000..9c02d9ebef
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-4.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-4.png
new file mode 100644
index 0000000000..259a75b4ad
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-5.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-5.png
new file mode 100644
index 0000000000..2322104931
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-6.webp b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-6.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.6-6.webp differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.8-0.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.8-0.png
new file mode 100644
index 0000000000..ec3ba70b31
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.8-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.8-1.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.8-1.png
new file mode 100644
index 0000000000..709edb2a95
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.8-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.8-2.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.8-2.png
new file mode 100644
index 0000000000..2322104931
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.8-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.9-0.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.9-0.png
new file mode 100644
index 0000000000..e6cc1d1bc1
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.9-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.9-1.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.9-1.png
new file mode 100644
index 0000000000..709edb2a95
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.9-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.9-2.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.9-2.png
new file mode 100644
index 0000000000..2322104931
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.6.9-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-0.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-0.png
new file mode 100644
index 0000000000..5678636618
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-1.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-1.png
new file mode 100644
index 0000000000..5678636618
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-2.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-2.png
new file mode 100644
index 0000000000..8030029a1f
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-3.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-3.png
new file mode 100644
index 0000000000..cb2757eab7
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-4.webp b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-4.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.0-4.webp differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.3-0.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.3-0.png
new file mode 100644
index 0000000000..04bc3eb2ac
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.3-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.3-1.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.3-1.png
new file mode 100644
index 0000000000..45179b4fc0
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.3-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.3-2.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.3-2.png
new file mode 100644
index 0000000000..746d5b9514
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.7.3-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.8.0-0.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.8.0-0.png
new file mode 100644
index 0000000000..2b4aa0d3a3
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.8.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.8.0-1.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.8.0-1.png
new file mode 100644
index 0000000000..42f21d2f1f
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.8.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-1.8.0-2.png b/src/.vuepress/public/assets/img/news/Warm-Flow-1.8.0-2.png
new file mode 100644
index 0000000000..617c4650f2
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-1.8.0-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-0.png b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-0.png
new file mode 100644
index 0000000000..d0fe2879a2
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-1.png b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-1.png
new file mode 100644
index 0000000000..8aec99fd61
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-2.png b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-2.png
new file mode 100644
index 0000000000..d016f71d44
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-2.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-3.png b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-3.png
new file mode 100644
index 0000000000..3ee6171d0b
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-4.png b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-4.png
new file mode 100644
index 0000000000..fb2fa6b797
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-5.png b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-5.png
new file mode 100644
index 0000000000..aee10f7484
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-6.webp b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-6.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/Warm-Flow-v1.6.7-6.webp differ
diff --git a/src/.vuepress/public/assets/img/news/carbon-2.6.2-0.png b/src/.vuepress/public/assets/img/news/carbon-2.6.2-0.png
new file mode 100644
index 0000000000..c5bf87529b
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/carbon-2.6.2-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-0.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-0.png
new file mode 100644
index 0000000000..eeeff3f239
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-1.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-1.png
new file mode 100644
index 0000000000..369fde7e0d
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-2.jpg b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-2.jpg
new file mode 100644
index 0000000000..ec96a2f254
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-2.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-3.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-3.png
new file mode 100644
index 0000000000..bedd0b2441
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-4.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-4.png
new file mode 100644
index 0000000000..3d3be28f6a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-5.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-5.png
new file mode 100644
index 0000000000..2ea9918752
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-6.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-6.png
new file mode 100644
index 0000000000..47f9a057f2
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-6.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-7.jpg b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-7.jpg
new file mode 100644
index 0000000000..3ddcc3ff8f
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-7.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-8.webp b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-8.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.6-8.webp differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-0.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-0.png
new file mode 100644
index 0000000000..eeeff3f239
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-1.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-1.png
new file mode 100644
index 0000000000..369fde7e0d
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-2.jpg b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-2.jpg
new file mode 100644
index 0000000000..f626ffa173
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-2.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-3.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-3.png
new file mode 100644
index 0000000000..bedd0b2441
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-4.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-4.png
new file mode 100644
index 0000000000..3d3be28f6a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-5.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-5.png
new file mode 100644
index 0000000000..2ea9918752
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-6.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-6.png
new file mode 100644
index 0000000000..47f9a057f2
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-6.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-7.jpg b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-7.jpg
new file mode 100644
index 0000000000..70e125dae0
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-7.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-8.webp b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-8.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.7-8.webp differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-0.webp b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-0.webp
new file mode 100644
index 0000000000..635708c6a1
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-0.webp differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-1.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-1.png
new file mode 100644
index 0000000000..369fde7e0d
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-2.jpg b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-2.jpg
new file mode 100644
index 0000000000..f626ffa173
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-2.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-3.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-3.png
new file mode 100644
index 0000000000..bedd0b2441
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-3.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-4.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-4.png
new file mode 100644
index 0000000000..3d3be28f6a
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-4.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-5.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-5.png
new file mode 100644
index 0000000000..2ea9918752
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-5.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-6.png b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-6.png
new file mode 100644
index 0000000000..47f9a057f2
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-6.png differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-7.jpg b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-7.jpg
new file mode 100644
index 0000000000..70e125dae0
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-7.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-8.webp b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-8.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/easyAI-v1.2.9-8.webp differ
diff --git a/src/.vuepress/public/assets/img/news/fastrequest-2024.1.9-0.gif b/src/.vuepress/public/assets/img/news/fastrequest-2024.1.9-0.gif
new file mode 100644
index 0000000000..2823dea576
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/fastrequest-2024.1.9-0.gif differ
diff --git a/src/.vuepress/public/assets/img/news/fastrequest-2024.1.9-1.webp b/src/.vuepress/public/assets/img/news/fastrequest-2024.1.9-1.webp
new file mode 100644
index 0000000000..4518bcd669
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/fastrequest-2024.1.9-1.webp differ
diff --git a/src/.vuepress/public/assets/img/news/mica-mqtt-2.4.0-0.png b/src/.vuepress/public/assets/img/news/mica-mqtt-2.4.0-0.png
new file mode 100644
index 0000000000..7aa127ca77
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/mica-mqtt-2.4.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/mica-mqtt-2.4.0-1.png b/src/.vuepress/public/assets/img/news/mica-mqtt-2.4.0-1.png
new file mode 100644
index 0000000000..405ea33947
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/mica-mqtt-2.4.0-1.png differ
diff --git a/src/.vuepress/public/assets/img/news/x-easypdf-v3.3.0-0.png b/src/.vuepress/public/assets/img/news/x-easypdf-v3.3.0-0.png
new file mode 100644
index 0000000000..56cad7ed2f
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/x-easypdf-v3.3.0-0.png differ
diff --git a/src/.vuepress/public/assets/img/news/x-easypdf-v3.3.0-1.jpg b/src/.vuepress/public/assets/img/news/x-easypdf-v3.3.0-1.jpg
new file mode 100644
index 0000000000..44765a4709
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/x-easypdf-v3.3.0-1.jpg differ
diff --git a/src/.vuepress/public/assets/img/news/x-easypdf-v3.3.0-2.webp b/src/.vuepress/public/assets/img/news/x-easypdf-v3.3.0-2.webp
new file mode 100644
index 0000000000..57dc0e2b3c
Binary files /dev/null and b/src/.vuepress/public/assets/img/news/x-easypdf-v3.3.0-2.webp differ
diff --git a/src/.vuepress/public/assets/img/open.png b/src/.vuepress/public/assets/img/open.png
index 5a52ccc277..a65d0aeb64 100644
Binary files a/src/.vuepress/public/assets/img/open.png and b/src/.vuepress/public/assets/img/open.png differ
diff --git a/src/.vuepress/public/assets/img/slogan.png b/src/.vuepress/public/assets/img/slogan.png
index eccabe2a79..a91424ca79 100644
Binary files a/src/.vuepress/public/assets/img/slogan.png and b/src/.vuepress/public/assets/img/slogan.png differ
diff --git a/src/.vuepress/public/assets/img/vision.png b/src/.vuepress/public/assets/img/vision.png
index 29e54f0324..4b9db4cd61 100644
Binary files a/src/.vuepress/public/assets/img/vision.png and b/src/.vuepress/public/assets/img/vision.png differ
diff --git a/src/.vuepress/styles/config.scss b/src/.vuepress/styles/config.scss
index 13244a6fb5..27c697eec6 100644
--- a/src/.vuepress/styles/config.scss
+++ b/src/.vuepress/styles/config.scss
@@ -1,2 +1,2 @@
$colors: #c0392b, #d35400, #f39c12, #27ae60, #16a085, #2980b9, #8e44ad, #2c3e50,
- #7f8c8d !default;
+ #7f8c8d !default;
\ No newline at end of file
diff --git a/src/.vuepress/styles/index.scss b/src/.vuepress/styles/index.scss
index 4b9d0fed93..f4caec0019 100644
--- a/src/.vuepress/styles/index.scss
+++ b/src/.vuepress/styles/index.scss
@@ -1,14 +1,12 @@
-body {
- background-color: #f9fbff;
-}
-
-.bg-default {
- background-color: #e9eef8;
-}
+// body {
+// background: linear-gradient(to bottom, #030513, #051455);
+// }
#navbar {
- background-color: #e4eeff;
box-shadow: none;
+ background: #fff0;
+ backdrop-filter: blur(5px);
+ border-bottom: 1px solid #2c2d3b;
}
.nav-item {
@@ -37,3 +35,7 @@ body {
display: block;
}
}
+
+.page-cover img {
+ display: none;
+}
\ No newline at end of file
diff --git a/src/.vuepress/theme.ts b/src/.vuepress/theme.ts
new file mode 100644
index 0000000000..52227fb5b9
--- /dev/null
+++ b/src/.vuepress/theme.ts
@@ -0,0 +1,6 @@
+import { hopeTheme } from "vuepress-theme-hope";
+export default hopeTheme({
+ plugins: {
+ slimsearch: true,
+ },
+});
diff --git a/src/about/README.md b/src/about/README.md
index 6ebdb73976..db26a19210 100644
--- a/src/about/README.md
+++ b/src/about/README.md
@@ -1,33 +1,34 @@
----
-title: About
-pageInfo: false
-contributors: false
-editLink: false
-lastUpdated: false
----
-
-## Idea
-
-Let every open source enthusiast, experience the joy of open source.
-
-## Community
-
-Technology stack comprehensive open source co-build, maintain community neutrality, harmony and happiness to do open source.
-
-## Website
-
-**[https://dromara.org](https://dromara.org)** Is **Dromara** Open Source Community Official Website.
-
-## Honors
-
-
-
-
-
-
+---
+title: About
+pageInfo: false
+contributors: false
+editLink: false
+lastUpdated: false
+sidebar: false
+---
+
+## Idea
+
+Let every open source enthusiast, experience the joy of open source.
+
+## Community
+
+Technology stack comprehensive open source co-build, maintain community neutrality, harmony and happiness to do open source.
+
+## Website
+
+**[https://dromara.org](https://dromara.org)** Is **Dromara** Open Source Community Official Website.
+
+## Honors
+
+
+
+
+
+
diff --git a/src/activity/Dromara-OSPP-2025.md b/src/activity/Dromara-OSPP-2025.md
new file mode 100644
index 0000000000..338e4df8ca
--- /dev/null
+++ b/src/activity/Dromara-OSPP-2025.md
@@ -0,0 +1,106 @@
+---
+title: Open Source Promotion Plan 2025 Student Recruitment | Claim Dromara Projects to Win Generous Rewards.
+author: Dromara
+date: 2025-05-12
+cover: /assets/img/activity/Dromara-OSPP-2025-0.png
+head:
+ - - meta
+ - name: Activity
+---
+
+# Open Source Promotion Plan 2025
+
+Open Source Promotion Plan (OSPP) is a summer open source initiative launched and long-term supported by the Open Source Supply Chain Lighting Plan of the Institute of Software, Chinese Academy of Sciences. It aims to encourage students to actively participate in the development and maintenance of open source software, cultivate and discover more excellent developers, promote the vigorous development of outstanding open source software communities, and support the construction of the open source software supply chain.
+
+
+
+## Student Registration Now Open
+
+Student registration for Open Source Promotion Plan 2025 is officially open! Students can now visit the OSPP official website at https://summer-ospp.ac.cn/ to browse projects, communicate with mentors, prepare project application materials, and submit their applications.
+
+> With so many project tasks available, are you already eager to give it a try? Whether you're a beginner or an experienced developer, whether you want to contribute code, learn open source technologies and gain development experience, or enhance your resume, OSPP offers opportunities for everyone.
+>
+> With a spirit of exploration and a thirst for knowledge in open source, we welcome all students to join Open Source Promotion Plan 2025 and explore infinite possibilities this summer!
+>
+> At OSPP, participating students have the opportunity to interact with numerous open source communities and experienced developer mentors, improving their technical skills and practical abilities while gaining more open source knowledge and expertise. Successful participants will also receive generous project rewards—8,000 RMB for basic difficulty projects and 12,000 RMB for advanced difficulty projects (pre-tax)—and may become members of the Dromara community, enjoying numerous benefits!
+
+## Introduction to Dromara Community
+
+Dromara is an open source community formed by top open source project authors in China.
+
+It provides a series of open source products, solutions, consulting, technical support, and training certification services, including distributed transactions, popular tools, enterprise-level authentication, microservices RPC, operation and maintenance monitoring, Agent monitoring, distributed logging, scheduling orchestration, and more. The community is committed to full open source collaboration, maintaining community neutrality, and striving to provide microservices cloud-native solutions for global users.
+
+Dromara aims to make every open source enthusiast experience the joy of open source. The Dromara open source community currently boasts 10+ GVP projects, with a total star count exceeding 100,000. It has built a community of over 10,000 members, with thousands of individuals and teams using Dromara's open source projects.
+
+## Dromara Community Project Topics
+
+We are excited to present two projects: **Dromara Open Source Community Integrated Management System** and **Dromara Incubator Website Optimization and Main Website Enhancement**.
+
+> The project tasks are designed to be accessible, and participants will receive dedicated guidance from Dromara community mentors throughout the process.
+
+### 1. Dromara Open Source Community Integrated Management System
+
+With the rapid development of open source, the Dromara open source community continues to expand, with an increasing number of projects and increasingly diverse participants. The current management approach is relatively fragmented, lacking a centralized and efficient system for managing personnel and projects. This makes it difficult to track project incubation processes, unclear the relationships between personnel and projects, and leads to chaotic permission management, significantly affecting community operational efficiency and collaboration.
+
+**Project Requirements**
+
+Build a fully functional and user-friendly open source community personnel and project management system to improve community management efficiency, promote better collaboration and communication among community members, and facilitate the smooth development of open source projects.
+
+**Key Deliverables**:
+
+1. Complete the front-end and back-end architecture design of the system, including detailed architecture diagrams, module division, interface design, etc.
+2. Develop an open source project management module to achieve full lifecycle management of project incubation processes, including project creation, review, progress tracking, document management, and other functions.
+3. Develop a community personnel management module to achieve personnel information entry and editing, manage relationships between personnel and projects, and display contributors' information in projects.
+4. Complete system testing, including functional testing, performance testing, security testing, etc., and submit test reports.
+5. Write system usage documentation, including administrator manuals and user manuals, to facilitate community members' use.
+
+* Address: https://dromara.org
+* Project Source Code: https://github.com/dromara/droer
+
+**Technical Requirements**:
+
+1. Familiarity with Java programming language and mastery of common Java development frameworks such as Spring Boot and MyBatis.
+2. Understanding of front-end development technologies, including HTML, CSS, JavaScript, and experience with front-end frameworks like Vue.js.
+3. Proficiency in database design and development, with experience using relational databases such as MySQL.
+4. Ability to design software systems, including reasonable module division and interface design based on requirements.
+5. Good coding habits, adherence to code standards, and ability to write code comments.
+
+* Mentor: Tang Zhenchao (tangzc2@gmail.com)
+* Project Repository: https://github.com/dromara/droer
+* Application Link: https://summer-ospp.ac.cn/org/prodetail/25ee40202?list=org&navpage=org
+
+### 2. Dromara Incubator Website Optimization and Main Website Enhancement
+
+**Project Description**
+
+Dromara is an open source community spontaneously organized by top open source project maintainers.
+
+The Dromara open source community currently has 10+ top-level projects and 50+ incubator projects, each with its official website. This project involves unifying the UI design and architecture of both official websites (incubator website and main website), optimizing page content, adding new website features, highlighting the display of top-level and incubator projects, improving the blog submission process, and writing website usage documentation.
+
+**Deliverables**:
+
+1. Optimize and refine the UI pages of the incubator website and the main website.
+2. Design and improve website content display:
+ - 2.1: Optimize the UI for displaying top-level project groups and incubator project groups.
+3. Unify the technical architecture of the incubator website and the main website, upgrade to the latest corresponding technology versions, and provide build documentation and local deployment guides.
+4. Develop other website pages as needed.
+
+**Technical Requirements**:
+
+1. Some open source experience is preferred; passion for open source is a plus.
+2. Proficiency in Vue, HTML, and CSS.
+3. Familiarity with common Git commands.
+
+* Mentor: Mao Da ren (549477611@qq.com)
+* Project Repositories:
+ - https://github.com/dromara/incubator
+ - https://github.com/dromara/dromara.github.io
+* Application Link: https://summer-ospp.ac.cn/org/prodetail/25ee40277?list=org&navpage=org
+
+## How to Participate in Open Source Promotion Plan 2025
+
+Starting May 9, 2025, the Dromara community will begin accepting student applications for the above project topics. We encourage interested students to contact the respective mentors via the provided联系方式 (contact information) to discuss the projects and prepare application materials.
+
+
+
+
\ No newline at end of file
diff --git a/src/activity/PowerData-AI-shanghai-4.19.md b/src/activity/PowerData-AI-shanghai-4.19.md
new file mode 100644
index 0000000000..82df406c98
--- /dev/null
+++ b/src/activity/PowerData-AI-shanghai-4.19.md
@@ -0,0 +1,93 @@
+---
+title: PowerData Digital AI - Shanghai Open Source Tour on April 19th! Registration Open~!
+author: April 7, 2025 14:02
+date: 2025-04-07
+cover: /assets/img/activity/PowerData-AI-shanghai-4.19-3.jpg
+head:
+ - - meta
+ - name: Activity
+---
+
+
+**Digital AI, Hand in Hand with the Future**
+
+**2025 PowerData Shanghai Open Source Tour**
+
+
+
+On April 19, 2025, at 13:30, in the Demo Hall of MOSpace, Xuhui District, Shanghai, a feast focusing on the integration of AI and data technology is about to begin! ✨
+
+This event brings together top domestic digital AI technology teams, featuring heavyweight guests such as **Yijia Su from SelectDB**, **Pengfei Ji from Chat2DB**, and **Bibo Wang from DataFocus**. They will provide in-depth analysis on cutting-edge topics like the AI-driven real-time data warehouse, natural language interaction with databases, and building a ChatBI in 15 minutes. ✈️
+
+Get up close and personal with experts to gain insights into AI-driven innovations in data infrastructure, industrial decision-making optimization, and the future of intelligent search! Registration is free. Scan the code to secure your spot and explore the boundaries of digital AI technology with hundreds of developers! 🚀
+
+The event will also feature **three rounds of lucky draws**, with tons of peripherals available. All participants have a chance to win. Come and join us~ 🎁
+
+
+
+**01**
+**Event Information**
+
+
+**Organizer**
+
+PowerData Community
+
+
+**Event Time**
+
+April 19, 2025, 13:30 - 17:30
+
+
+**Venue**
+
+Demo Hall, 1st Floor, Building F2, No. 180 Longtai Road, Xuhui District, Shanghai · MOSpace
+
+**02**
+**Four Event Highlights**
+
+
+**【Top-Tier Technical Practices】**
+Learn from database vendors like Doris, Chat2DB, OceanBase, KWDB, IoTDB, and DataFocus about their integration of concepts in the AI era. Explore the implementation of technologies like nl2sql and chatbi, and uncover the AI practices of leading companies.
+
+**【Zero-Distance Exchange】**
+Engage in face-to-face conversations with various technical experts, freely exchanging ideas.
+
+**【Abundant Gifts】**
+Lucky draws will be held at registration, during intermission, and at closing. Sponsored by open-source vendors, there will be plenty of peripheral gifts. Moreover, gifts will be distributed simply for participating in interactions.
+Our partners have sponsored **60+ peripheral items** for this event, including mugs, stickers, notebooks, stress relievers, badges, clothing, backpacks, hats, and more. Waiting for you to claim!
+
+**【Beautiful Venue with Expansive Views】**
+This event is venue-supported by MOSpace, Shanghai's **first** large-model incubation and acceleration hub.
+
+
+
+
+
+**03**
+**How to Participate**
+
+
+**Event Registration**
+
+
+
+🔥 Scan the code to register. Seats are limited, secure yours now! After registration, a volunteer will invite you to join the event group for the latest updates.
+
+**Live Stream**
+
+🔥 This event will be live-streamed in full. Click the video account below to预约 (set a reminder).
+
+**04**
+**Best Attendee**
+
+
+
+
+To create a truly vibrant, open, and communicative open-source event, PowerData has specially prepared "Best Attendee" certificates. These will be awarded on the spot during the event to participants who demonstrate outstanding engagement, such as those who ask questions actively, initiate discussions, share insights, or give lightning talks.
+
+<<< END >>>
+
+PowerData is an open-source data community formed by a group of data professionals united by their passion, built on the spirit of open source.
+
+The community group regularly organizes mock interviews, online sharing sessions, industry seminars, offline Meetups, city gatherings, job referrals, and other activities. Within the community group, you can engage in technical discussions, seek advice, and meet more like-minded data enthusiasts.
diff --git a/src/activity/README.md b/src/activity/README.md
index 084017e351..2b2c7f2e0b 100644
--- a/src/activity/README.md
+++ b/src/activity/README.md
@@ -1,33 +1,9 @@
----
-title: Activity
-pageInfo: false
-contributors: false
-editLink: false
-lastUpdated: false
----
-
-
-
-
-
-
+---
+title: Activity
+pageInfo: false
+contributors: false
+editLink: false
+lastUpdated: false
+sidebar: false
+layout: siteLayout
+---
diff --git a/src/activity/apache-iceberg-meetup2025.md b/src/activity/apache-iceberg-meetup2025.md
new file mode 100644
index 0000000000..72b90cd172
--- /dev/null
+++ b/src/activity/apache-iceberg-meetup2025.md
@@ -0,0 +1,26 @@
+---
+title: Domestic Premiere | Apache Iceberg Meetup 2025 Shenzhen Stop Event Preview
+author: January 10, 2025 08:33
+date: 2025-01-10
+cover: /assets/img/activity/apache-iceberg-meetup2025-0.png
+head:
+ - - meta
+ - name: Activity
+---
+
+# **Event Introduction**
+
+2024 was a year of vigorous development for the Lakehouse data architecture. More and more enterprises are choosing data lakes as the unified storage layer and building rich data applications, including BI and AI, on top of them. Apache Iceberg, as a highly popular open-source project in the data lake field, also made significant progress in 2024. The community-driven Rest Catalog is becoming increasingly mature, and the Iceberg V3 format is also趋向成熟 (maturing). Companies including Snowflake, AWS, Apple, and Databricks have adopted Iceberg as their preferred data lake table format, which undoubtedly推动 (propels) Iceberg to gradually become the standard for data lake table formats.
+
+At the beginning of 2025, we will host the first domestic offline Apache Iceberg Meetup in beautiful Shenzhen. This meetup is jointly organized by the Apache Iceberg community, Tencent Cloud Big Data, and AutoMQ. Focusing on the theme of best user practices on Iceberg, it invites some of China's most experienced Iceberg users & contributors—Tencent Cloud Big Data, AutoMQ, Bilibili, WeChat, and Huawei Terminal Cloud—to share their experiences in using Iceberg to build Lakehouse data architectures. The Apache Iceberg community and partners sincerely invite friends from the Iceberg and other major communities, data enthusiasts, architects, and enterprise representatives to participate.
+
+● Organizers: Tencent Cloud Big Data, AutoMQ
+● Event Time: January 18, 2025, 13:30-17:00
+● Event Address: Tencent Binhai Building 3611, Shenzhen
+● Event Format: Primarily offline, with simultaneous online live broadcast and replay
+
+**Event Agenda**
+
+
+
+# **- END -**
\ No newline at end of file
diff --git a/src/activity/coscon24-forum-intro.md b/src/activity/coscon24-forum-intro.md
new file mode 100644
index 0000000000..b73c894bac
--- /dev/null
+++ b/src/activity/coscon24-forum-intro.md
@@ -0,0 +1,118 @@
+---
+title: First Look at the Main Forum! Open Source, Open Life - Experience the New Open Source Lifestyle at COSCon'24
+author: October 29, 2024 12:36
+date: 2024-10-29
+cover: /assets/img/activity/coscon24-forum-intro-1.webp
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+
+
+We sincerely invite you to attend the **9th China Open Source Conference (COSCon'24)**, to join this grand event and witness how open source is changing lives and shaping the future! This year's main forum, themed **"Open Source, Open Life"**, will showcase how open source technology integrates into every aspect of our daily lives through diverse sharing and discussions.
+
+**How to Register:** Scan the QR code below or copy the link into your browser to register.
+
+
+
+**Registration Link:**
+**https://www.bagevent.com/event/coscon24**
+
+**1**
+
+
+**Day One:**
+**Open Source, Open Life - A Stage for Sharing Stories**
+
+The theme for the first day is "Open Source, Open Life." Our focus will be on personal open source stories. Through sharing these stories, we hope to inspire more friends—"I can become an open source contributor too!"
+
+**Opening Ceremony & Welcome Address (9:00~9:20)**
+
+Let's kick off COSCon 2024 together! Jiang Bo, Chairperson of KaiYuanshe, and Ted Liu, Co-founder and Board Director of KaiYuanshe, will provide forward-looking insights into the new open source lifestyle. Open source is not just about code; it changes how we think, collaborate, and profoundly impacts all aspects of life.
+
+**Keynote Speech (9:20~9:40)**
+
+As a platinum sponsor of this conference, Wei Kewei, CTO of Inspur KaiwuDB, will present "KWDB: Embarking on a New Open Source Journey."
+
+**Keynote Speeches + Dialogue:**
+**Stories of How Open Source Changes Lives (9:40~11:40)**
+
+Four guests from different industries will share their deep connections with open source: from becoming a famous Bilibili UP主 after retirement, to open-sourcing their sports data, to transforming healthcare through open source, and even shaping a new tech-driven life with open source—each story shines with the power of open source. Live dialogues between guests during the speeches, sparking ideas, is one of the innovations of this conference.
+
+**Lightning Talks:**
+**Moments When Open Source Changed Personal Lives (11:40~12:20)**
+
+Eight open source enthusiasts from diverse backgrounds will use 5-minute lightning talks to share how open source changed their career paths or lifestyles. Whether you are a student, professional, or hobbyist, these inspiring open source stories will ignite your imagination for the future! 【Limited spots available, register quickly! See details in today's second push notification】
+
+**2**
+
+
+**Day Two:**
+**An Open New Future - Collision of Exploration and Vision**
+
+The theme for the second day is "An Open New Future." Open source has already and will continue to change the world. Our discussions will focus on the development of open source technology and the myriad changes it brings. Furthermore, on the occasion of KaiYuanshe's 10th anniversary, we will review and celebrate this remarkable decade with all old and new friends, and envision the future together.
+
+**KaiYuanshe's Vision for the Next Decade (9:00~9:20)**
+
+KaiYuanshe Chairperson Jiang Bo will outline the vision for KaiYuanshe's next decade, exploring the latest trends in open source in China and globally. Let's step into the next golden decade of open source together!
+
+**Major Release:**
+**China Open Source Annual Report (9:20~9:40)**
+
+The highly anticipated annual "Open Source Annual Report" will be released live! Professor Wang Wei from East China Normal University will interpret the latest data and development trends in China's open source ecosystem.
+
+**Panel Discussion:**
+**The Future of Open Source and Artificial Intelligence (9:40~10:20)**
+
+Amid the wave of artificial intelligence, how does the power of open source drive technological change? Leaders of top AI open source projects will gather to discuss a win-win future for open source and AI technology.
+
+**Panel Discussion:**
+**The Power of Open Source Education (10:20~10:50)**
+
+Open source not only leads trends in technology but also brings变革 (transformation) to education. Let's listen to experts share innovative models of open source education and discuss how to cultivate the next generation of open source talent.
+
+**Panel Discussion:**
+**The Social Responsibility of Open Source (10:50~11:20)**
+
+Open source is not just a technology but a force for social progress. The "OpenGood Open Source Public Welfare Case Collection," initiated and collected by KaiYuanshe, will be released at this conference. Subsequently, representatives from enterprises, communities, and public welfare organizations will share practical cases of open source solving social problems and discuss how to better fulfill the social responsibility of the open source community.
+
+**Panel Discussion:**
+**My Ten-Year Journey with KaiYuanshe (11:20~12:00)**
+
+Looking back over the past decade, how did KaiYuanshe grow from a startup community to today's open source benchmark? Founders, core members, and senior fans will tell their stories of growing up with KaiYuanshe.
+
+**Special Segment:**
+**KaiYuanshe Ten-Year Story Wall (12:00~12:30)**
+
+In this special segment, you will have the opportunity to take the stage and share your story with KaiYuanshe, witnessing the birth of the "KaiYuanshe Ten-Year Story Wall." Let's witness the profound impact of open source on individuals and communities together! Of course, there will also be cake and a group photo to capture this historic moment!
+
+**COSCon'24 is not just a feast for technical exchange but a carnival for open source enthusiasts!** Whether you are a seasoned open source veteran or a newcomer just getting started, you can find resonance and inspiration here. Come join this annual open source extravaganza, envision the future of open source with like-minded friends, and experience the endless charm of the new open source lifestyle!
+
+**You can click "Read More" at the end of the article to purchase tickets and register. November 2-3, see you in Beijing! Let's not miss each other!**
+
+Produced by | COSCon'24 Organizing Committee
+Edited by | Mooncake
+Designed by | Wang Jun
+
+
+**Related Reading | Related Reading**
+
+[Forum Introduction | COSCon'24 Open Source AI Forum on AI for Science](http://mp.weixin.qq.com/s?__biz=MzA4NTM4NDc4NQ==&mid=2247539499&idx=1&sn=9f703834ad629cf93cab40f87aafc6fc&chksm=9fdad015a8ad59034d5c27731fb83ff1d9d4c11b89d085a8b6a93e084f6d97e591a5043529e2&scene=21#wechat_redirect)
+
+[COSCon'24 Volunteer Recruitment Call: Co-create a New Open Source Life!](http://mp.weixin.qq.com/s?__biz=MzA4NTM4NDc4NQ==&mid=2247539207&idx=1&sn=a9b09c66f440506fc898cc78113aa0da&chksm=9fdad139a8ad582f3d2a190e9ae65d9c73041483191065c2b069161a7d249217ef4b137889be&scene=21#wechat_redirect)
+
+**KaiYuanshe**
+_**KAIYUANSHE**_
+
+
+
+KaiYuanshe was established in 2014. It is an open source community composed of individual volunteers dedicated to the cause of open source, based on the principles of "Contribution, Consensus, and Co-governance." KaiYuanshe always maintains the理念 (concept) of "Vendor-Neutral, Public Welfare, Non-Profit," with the愿景 (vision) of "Based in China, Contributing to the World, Promoting Open Source as a Lifestyle in the New Era," and the使命 (mission) of "Open Source Governance, International Integration, Community Development, Project Incubation," aiming to co-create a healthy and sustainable open source ecosystem.
+
+KaiYuanshe actively cooperates closely with open-source-supporting communities, universities, enterprises, and relevant government units. It is also the first Chinese member of the Open Source Initiative (OSI), the global open source license certification organization.
+
+Since 2016, KaiYuanshe has continuously hosted the China Open Source Conference (COSCon), released the "China Open Source Annual Report," jointly launched initiatives like the "China Open Source Pioneer List" and "China Open Source Code Power List," generating widespread influence both domestically and internationally.
+
+COSCon'24 29
\ No newline at end of file
diff --git a/src/activity/dev-ai-frontier-meet.md b/src/activity/dev-ai-frontier-meet.md
new file mode 100644
index 0000000000..f2cfa5fb64
--- /dev/null
+++ b/src/activity/dev-ai-frontier-meet.md
@@ -0,0 +1,20 @@
+---
+title: '@Developers|GitCode AI Exploration Day: An Opportunity to Meet Industry Experts Face-to-Face...'
+author: GitCode Open Source Exploration
+date: 2024-10-10
+cover: /assets/img/activity/dev-ai-frontier-meet-0.gif
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+AI technology is leading the technological revolution, driving transformation and upgrading across all industries. To delve into the frontiers of AI and foster developer exchange, as the 1024 Programmer's Day approaches, GitCode is joining hands with the Songshan Lake Developer Village to host a special event in the picturesque Songshan Lake European Town. On **October 20th**, the AI Future Exploration Day officially opens!
+
+The event specially invites multiple distinguished guests with profound expertise in the AI field. They will share exciting insights on cutting-edge topics such as large model applications, breaking new ground in the AGI era, and AI-assisted coding. Additionally, GitCode's AI algorithm engineers will take you on an in-depth exploration of the **GitCode AI Community**. The event also features a lucky draw session, giving developers the chance to gain unexpected surprises and rewards during the exchange.
+
+Join us as we embark on a journey of deep dialogue and exploration about the future of AI~
+
+
+
\ No newline at end of file
diff --git a/src/activity/dromara-COSCon-letter.md b/src/activity/dromara-COSCon-letter.md
new file mode 100644
index 0000000000..0c0ffc653a
--- /dev/null
+++ b/src/activity/dromara-COSCon-letter.md
@@ -0,0 +1,43 @@
+---
+title: A Letter from Dromara Open Source Organization Participating in COSCon
+author: Dromara Secretariat
+date: 2024-09-27
+cover: /assets/img/activity/dromara-COSCon-letter-0.png
+head:
+ - - meta
+ - name: Activity
+---
+
+**About**
+
+COSCon
+
+
+
+China Open Source Conference (COSCon) is one of the most influential open source events in the industry. Since its inception in 2015 by KaiYuanShe, this year marks its ninth edition. With its unique positioning and growing influence, COSCon has not only garnered strong support from an increasing number of domestic and international companies, universities, open source organizations, and communities but has also successfully attracted widespread attention and active participation from open source developers, contributors, and submitters worldwide. Compared to industry conferences typically organized by enterprises, IT media, or industry associations, COSCon boasts broader cross-organizational, cross-project, and cross-community coverage.
+
+**Location**
+
+
+
+Zhongguancun National Independent Innovation Demonstration Zone Conference Center, Beijing
+
+**A Passion for Open Source in Late Autumn**
+
+dromara
+
+
+
+Dozens of open source authors from the Dromara open source organization will participate in this event. Among them are veterans of open source from the 80s and 90s, as well as rising open source stars from the 00s and 10s. We hope to meet and fully communicate with developers, corporate leaders, and all those who follow the Dromara open source organization offline, sparking the flame of China’s open source atmosphere.
+
+**Information Collection**
+
+
+
+
+
+We look forward to meeting everyone offline. Participants are kindly requested to fill in their personal information. Based on the number of attendees, we will select a venue and customize Dromara open source peripheral gifts.
+
+Information submission link: https://docs.qq.com/sheet/DSmNjU1dma3JEVFRk?is_no_hook_redirect=1&tab=BB08J2
+
+
\ No newline at end of file
diff --git a/src/activity/dromara-activites-introduce.md b/src/activity/dromara-activites-introduce.md
index 297e2db897..5ae9a59732 100644
--- a/src/activity/dromara-activites-introduce.md
+++ b/src/activity/dromara-activites-introduce.md
@@ -1,47 +1,47 @@
----
-title: Dromara Dream Code Book Club Introduction
-author: xiaoyu
-date: 2020-12-27
-tag:
- - DreamCode
- - Dromara
- - GateWay
-cover: /assets/img/activity/dromara-open-soul-01.jpg
-head:
- - - meta
- - name: Activity
----
-
-
-
-### Dromara Dream Code Book Club(Dromara 2020 event introduction)
-
-- Date: Sunday, December 27, 2020
-
-### Activity background
-
-- In order to increase the enthusiasm of community participants, promote the construction of the Dromara community, exercise everyone's expressive ability and improve the core strength of technology, the community organized this event in the form of source code reading.
-
-### Activity purpose, meaning and goal
-
-- Increase motivation
-- Improve technical strength and expand everyone's horizons
-- Exercise language skills
-- Promote the harmony, unity and progress of the community
-- Make the Dromara community bigger and bigger
-
-### Activity development
-
-- The activity is divided into multiple phases. First, twelve members are selected for a 12-day source code reading, and two online sharing is carried out during the period.
-- In order to improve everyone's consciousness, we have set up a punishment system. First hand over 500 yuan to the administrator. If homework is not submitted at 8 am the next day, 100 yuan will be deducted for sharing latecomers. Those who ask for leave in advance do not need to be punished.
-- Each person writes to their homework submission area in text based on the content they read every day.
-
-### Activity leader and main participants
-
-#### Principal
-
-- Cui, Kimming, Xiaoyu
-
-#### The main participants
-
-- Dromara community member
+---
+title: Dromara Dream Code Book Club Introduction
+author: xiaoyu
+date: 2020-12-27
+tag:
+ - DreamCode
+ - Dromara
+ - GateWay
+cover: /assets/img/activity/dromara-open-soul-01.jpg
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+### Dromara Dream Code Book Club(Dromara 2020 event introduction)
+
+- Date: Sunday, December 27, 2020
+
+### Activity background
+
+- In order to increase the enthusiasm of community participants, promote the construction of the Dromara community, exercise everyone's expressive ability and improve the core strength of technology, the community organized this event in the form of source code reading.
+
+### Activity purpose, meaning and goal
+
+- Increase motivation
+- Improve technical strength and expand everyone's horizons
+- Exercise language skills
+- Promote the harmony, unity and progress of the community
+- Make the Dromara community bigger and bigger
+
+### Activity development
+
+- The activity is divided into multiple phases. First, twelve members are selected for a 12-day source code reading, and two online sharing is carried out during the period.
+- In order to improve everyone's consciousness, we have set up a punishment system. First hand over 500 yuan to the administrator. If homework is not submitted at 8 am the next day, 100 yuan will be deducted for sharing latecomers. Those who ask for leave in advance do not need to be punished.
+- Each person writes to their homework submission area in text based on the content they read every day.
+
+### Activity leader and main participants
+
+#### Principal
+
+- Cui, Kimming, Xiaoyu
+
+#### The main participants
+
+- Dromara community member
diff --git a/src/activity/dromara-cloud-native-meet-02.md b/src/activity/dromara-cloud-native-meet-02.md
index f97c5c2b4d..35fb50b0e9 100644
--- a/src/activity/dromara-cloud-native-meet-02.md
+++ b/src/activity/dromara-cloud-native-meet-02.md
@@ -1,43 +1,43 @@
----
-title: Dromara Soul Source Code 01 Reading Sharing Session 02
-author: xiaoyu
-date: 2021-02-06
-tag:
- - Soul
- - Dromara
- - Reactor
-cover: /assets/img/activity/dromara-open-soul-03.jpg
-head:
- - - meta
- - name: Activity
----
-
-
-
-### Dromara source code reading (Soul 2021 first activity)
-
-- Date: Sunday, February 6, 2021
-- Time:20:00 – 23:00
-- Location: Tencent Meeting
-
-### Activity Details
-
-**20:00 - 20:10 The opening introduces the recent dream code sharing situation by kimming & 崔**
-
-**20:10 - 20:25 [Introduction to SPI and how Soul SPI is enhanced](https://blog.csdn.net/zm469568595/article/details/113362044) by zhuming**
-
-**20:25 - 20:50 [Introduction to Reactive Programming](https://zhoutzzz.com/archives/xiang-ying-shi-bian-cheng-reactiveprogramming) by Ztzzz**
-
-**20:50 - 21:10 [Soul Unit Test](https://www.yuque.com/docs/share/27992671-8d47-4bba-b2dc-c0e39074d649?#) by yangze**
-
-**21:10 - 21:25 [Fault-tolerant design](http://icyfenix.cn/distribution/traffic-management/failure.html) by jiangwenbo**
-
-**21:25 - 21:40 [Soul Web Flux loading process and processing request analysis](https://blog.csdn.net/u012180773?t=1) by rwby**
-
-**21:40 - 21:55 [Soul current limiting and fusing analysis](https://redick01.github.io/redick.github.io/#/blog/sourcecode/soul/soul_19) by liupenghui**
-
-**21:55 - 22:05 Summary of common Java problems by muou**
-
-**22:05 - 22:20 How to open a social interface by weikai**
-
-**22:20 - 22:30 Summary and Community Development Prospects by Xiaoyu**
+---
+title: Dromara Soul Source Code 01 Reading Sharing Session 02
+author: xiaoyu
+date: 2021-02-06
+tag:
+ - Soul
+ - Dromara
+ - Reactor
+cover: /assets/img/activity/dromara-open-soul-03.jpg
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+### Dromara source code reading (Soul 2021 first activity)
+
+- Date: Sunday, February 6, 2021
+- Time:20:00 – 23:00
+- Location: Tencent Meeting
+
+### Activity Details
+
+**20:00 - 20:10 The opening introduces the recent dream code sharing situation by kimming & 崔**
+
+**20:10 - 20:25 [Introduction to SPI and how Soul SPI is enhanced](https://blog.csdn.net/zm469568595/article/details/113362044) by zhuming**
+
+**20:25 - 20:50 [Introduction to Reactive Programming](https://zhoutzzz.com/archives/xiang-ying-shi-bian-cheng-reactiveprogramming) by Ztzzz**
+
+**20:50 - 21:10 [Soul Unit Test](https://www.yuque.com/docs/share/27992671-8d47-4bba-b2dc-c0e39074d649?#) by yangze**
+
+**21:10 - 21:25 [Fault-tolerant design](http://icyfenix.cn/distribution/traffic-management/failure.html) by jiangwenbo**
+
+**21:25 - 21:40 [Soul Web Flux loading process and processing request analysis](https://blog.csdn.net/u012180773?t=1) by rwby**
+
+**21:40 - 21:55 [Soul current limiting and fusing analysis](https://redick01.github.io/redick.github.io/#/blog/sourcecode/soul/soul_19) by liupenghui**
+
+**21:55 - 22:05 Summary of common Java problems by muou**
+
+**22:05 - 22:20 How to open a social interface by weikai**
+
+**22:20 - 22:30 Summary and Community Development Prospects by Xiaoyu**
diff --git a/src/activity/dromara-cloud-native-meet.md b/src/activity/dromara-cloud-native-meet.md
index ea8a9e1250..4e9c5de5e3 100644
--- a/src/activity/dromara-cloud-native-meet.md
+++ b/src/activity/dromara-cloud-native-meet.md
@@ -1,43 +1,43 @@
----
-title: Dromara Soul source code 01 reading sharing session 01
-author: xiaoyu
-date: 2021-01-21
-tag:
- - Soul
- - Dromara
- - Reactor
-cover: /assets/img/activity/dromara-open-soul-02.jpg
-head:
- - - meta
- - name: Activity
----
-
-
-
-### Dromara source code reading (Soul 2021 first activity)
-
-- Date: Sunday, January 24, 2021
-- Time: 15:00 – 17:00
-- Location: Tencent Meeting
-
-### Activity Details
-
-**15:00-15:10 Opening introduction of dream code sharing process by kimming & Cui**
-
-**15:10-15:25 Soul data synchronization websocket by Ting**
-
-**15:25-15:50 Http Discovery Sharing by Zhu Ming**
-
-**15:50-16:10 Analysis based on the Sofa-Rpc protocol by Dongdong**
-
-**16:10-16:25 Metrics Monitoring by Ge Tianye**
-
-**16:25-16:40 Http Long Polling Sharing by Du Yuhang**
-
-**16:40-16:55 Sharing and introducing the overall architecture of data synchronization by Wentao Xia**
-
-**16:55-17:05 Microkernel Architecture Sharing by Shen Xiangjun**
-
-**17:05-17:20 Sharing the experience and insights of reading source code by JinZe**
-
-**17:20-17:30 Summary and Community Development Prospects by Xiaoyu**
+---
+title: Dromara Soul source code 01 reading sharing session 01
+author: xiaoyu
+date: 2021-01-21
+tag:
+ - Soul
+ - Dromara
+ - Reactor
+cover: /assets/img/activity/dromara-open-soul-02.jpg
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+### Dromara source code reading (Soul 2021 first activity)
+
+- Date: Sunday, January 24, 2021
+- Time: 15:00 – 17:00
+- Location: Tencent Meeting
+
+### Activity Details
+
+**15:00-15:10 Opening introduction of dream code sharing process by kimming & Cui**
+
+**15:10-15:25 Soul data synchronization websocket by Ting**
+
+**15:25-15:50 Http Discovery Sharing by Zhu Ming**
+
+**15:50-16:10 Analysis based on the Sofa-Rpc protocol by Dongdong**
+
+**16:10-16:25 Metrics Monitoring by Ge Tianye**
+
+**16:25-16:40 Http Long Polling Sharing by Du Yuhang**
+
+**16:40-16:55 Sharing and introducing the overall architecture of data synchronization by Wentao Xia**
+
+**16:55-17:05 Microkernel Architecture Sharing by Shen Xiangjun**
+
+**17:05-17:20 Sharing the experience and insights of reading source code by JinZe**
+
+**17:20-17:30 Summary and Community Development Prospects by Xiaoyu**
diff --git a/src/activity/dromara-coscon-market.md b/src/activity/dromara-coscon-market.md
new file mode 100644
index 0000000000..c6114160af
--- /dev/null
+++ b/src/activity/dromara-coscon-market.md
@@ -0,0 +1,252 @@
+---
+title: COSCon'24 Open Source Bazaar Officially Unveiled, Inviting You to Play at the Market Stalls~
+author: November 02, 2024 08:02
+date: 2024-11-02
+cover: /assets/img/activity/dromara-coscon-market-0.webp
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+The Open Source Bazaar is a highlight of this year's conference, aiming to build a platform to showcase open source projects and promote cooperation and exchange among open source communities and organizations. Here, you can:
+
+* Showcase excellent open source projects/activities to enhance the visibility of your project/community/organization;
+* Expand partnerships to promote the development of communities/projects/organizations;
+* Exchange insights on open source and discuss the development of the open source cause;
+* Experience open source culture and share in the open source feast.
+
+**Open Source Bazaar Stalls**
+
+**KCC KaiYuanshe City Community**
+
+
+
+KCC is the abbreviation for Kaiyuanshe City Community. It is composed of open source enthusiasts from various cities or regions at home and abroad who identify with KaiYuanshe's philosophy, referred to as "KCC". Each city or region can apply to establish a KCC. Currently, KCCs have been established in **Shenzhen, Nanjing, Shanghai, Beijing, Chengdu, Hangzhou, Dalian, Guangzhou**, Singapore, and Silicon Valley. More KCCs are expected to be established in the future.
+
+**SegmentFault & Apache Answer**
+
+
+
+
+SegmentFault, a leading technical Q&A community in China.
+Apache Answer, an open-source Q&A software initiated by the SegmentFault team.
+
+**Stall Activity:** Distribution of interactive peripherals.
+
+**HyperAI 超神经**
+
+
+
+HyperAI (hyper.ai) is a leading domestic open-source artificial intelligence and high-performance computing community. It aims to assist developers and enthusiasts in data science and AI to learn, understand, and practice by providing services such as accelerated dataset downloads, online tutorial demonstrations, in-depth paper interpretations, and top conference calendar integration, building the future of AI together with the community.
+
+**Stall Activity:** Interactive lucky draw.
+
+**Asynchronous Community Carnival**
+
+
+
+"Asynchronous Community" (www.epubit.com) is an IT professional book community founded by Posts & Telecom Press. Launched in August 2015, it is dedicated to publishing and sharing high-quality content, providing readers with premium learning materials and offering professional publishing services for authors and translators. It enables online interaction between authors and readers and the integrated development of traditional and digital publishing.
+
+**Stall Activities:**
+1. Book giveaway lottery;
+2. Book gift for Rubik's cube restoration;
+3. New book signing session.
+
+**COSCUP Open Source Contributor Conference**
+
+
+
+In 2014, several Taiwanese open source communities jointly initiated the Open Culture Foundation (OCF), hoping to promote open culture with the strength of a legal entity and spark open collaboration among industry, government, academia, and the public.
+
+**Stall Activity:** Introduce trends in technical open source topics and interactive games.
+
+**Apache IoTDB**
+
+
+
+Apache IoTDB (Internet of Things Database) is an integrated software system for collecting, storing, managing, and analyzing IoT time-series data. Apache IoTDB adopts a lightweight architecture with high performance and rich features.
+
+**Stall Interaction:** Interactive lucky draw.
+
+**SQLE (Actionopen Open Source Community)**
+
+
+
+A profound MySQL open source community. Established in 2017, the community is committed to open-sourcing high-quality operation tools, sharing technical干货 (substantive content) daily, and evangelizing database technology; currently open-sourced products include: SQL audit tool SQLE, distributed middleware DBLE, and data transmission component DTLE.
+
+**Stall Activity:** Interactive lucky draw.
+
+**PowerData Data Power Community**
+
+
+
+We are a community of data practitioners, united by passion, based on the spirit of open source, forming the PowerData Data Power Community.
+
+**Stall Activity:** Interactive lucky draw.
+
+**Dromara Open Source Community Face-to-Face**
+
+
+
+Dromara is an open-source community composed of top open-source project authors in China. It provides a series of open-source products, solutions, consulting, technical support, and training certification services, including distributed transactions, popular tools, enterprise-level authentication, microservices RPC, operation and maintenance monitoring, Agent monitoring, distributed logs, and scheduling orchestration. The technology stack is fully open-source and collaboratively built, maintaining community neutrality, and is dedicated to providing microservices cloud-native solutions for global users. It aims to allow every participating open-source enthusiast to experience the joy of open-source.
+
+**Stall Activities:**
+1. Interactive Q&A: Visitors to the stall can answer questions correctly to receive Dromara peripherals.
+2. Hands-on operation: Visitors can choose to write code hands-on and experience the fun of practical operation.
+
+**ModelScope Open Source Community**
+
+
+
+ModelScope community was established in June 2022 and is a model open-source community and innovation platform.
+
+**Stall Activities:**
+Showcase ModelScope's open-source journey, partner wall, and community experience interactions.
+
+**deepin**
+
+
+
+The deepin open-source community is a root community for desktop operating systems and also China's active desktop OS community—committed to providing everyone with a free, open communication platform and the best open-source operating system through community development and collaboration. Currently, the domestic forum has 127,000 registered users, total posts have exceeded 1 million, and code contribution participants have reached 2,000.
+
+**Stall Interaction:**
+Set up corresponding challenge tasks; users can collect stamps after completing tasks and receive different peripheral gifts based on the number of stamps.
+
+* Level 1: Basic open source general knowledge Q&A
+ Description: e.g., Who developed the Linux OS, list some popular Linux distributions, list several common open-source licenses, etc.
+* Level 2: deepin-IDE basic code challenge
+ Description: Complete simple programming tasks, such as sorting algorithms, basic data processing.
+* Level 3: deepin system interactive experience
+ Description: Experience deepin 23 on-site and leave your product feedback and suggestions through the experience.
+
+**Rainbond**
+
+
+
+Rainbond is a cloud-native application management platform. The core is 100% open-source, easy to use, does not require knowledge of containers and Kubernetes, supports managing multiple Kubernetes clusters, and provides enterprise-level application full lifecycle management.
+
+**Stall Activities:**
+1. Interactive giveaway of community stickers, e.g., following the WeChat official account, Github Star, etc.
+2. In-depth interaction giveaway of tote bags or notebooks.
+
+**Banff China**
+
+
+
+The Banff Mountain Film Festival began in 1976 and is the world's most authoritative and largest outdoor adventure, mountain culture, and environmental theme film festival. It is known as the outdoor "Oscars." Our company, Banff (Beijing) Culture Communication Co., Ltd., was established in 2010. In 2019, we introduced "Free Solo," which won the Oscar for Best Feature Documentary that same year, setting a box office record for documentaries in China. This year, Banff China joins hands with KaiYuanshe to co-create the True·Hackathon segment, contributing to promoting open source as a lifestyle.
+
+**Stall Activity:** Sports knowledge popularization, peripheral interaction.
+
+**Intelligent Robot Interactive Experience**
+
+
+
+At the COSCon'24 Open Source Bazaar display stall, we will bring an interactive experience based on intelligent robot technology. Participants can personally experience how intelligent robots use gesture control and human body following technology to achieve intelligent control of physical and virtual cars. The display includes a fully open-source physical car project, from circuit design to hardware implementation, allowing participants to deeply understand its working principle. Meanwhile, the display of the virtual car will同步 (simultaneously) open source,方便 (facilitating) participants to explore and adjust control schemes.
+
+**Stall Interaction:**
+1. Gesture Control & Human Following Race: Participants can control two physical cars for a race through gestures or human following, experiencing the fun of intelligent control technology.
+2. Path Drawing Interaction: Use gesture control to draw paths with a virtual car; source code interfaces are provided for on-site adjustments; participants who complete the task will receive精美礼物 (exquisite gifts).
+3. Algorithm Q&A Session: Q&A challenge on intelligent robot technology and algorithms; those who answer correctly also have a chance to win gifts.
+4. Through these fun interactive activities, participants can not only experience intelligent control but also further understand the underlying technical principles. We also provide detailed source code and learning materials for participants to reference after the event.
+
+**Apache Doris Quick Q&A**
+
+
+
+Apache Doris is a modern, high-performance, real-time analytical database based on MPP. Known for its extreme speed and ease of use, it can return query results on massive data with sub-second response time, supporting both high-concurrency point query scenarios and high-throughput complex analysis scenarios.
+
+**Stall Interaction:**
+Bring Apache Doris promotional materials, including project introduction, core features, application scenarios, etc. Ask the audience five random questions. Gifts for answering 1, 2-3, 4-5 questions correctly respectively: stickers, laptop stands, T-shirts, etc. Limited quantity daily, first come first served.
+
+**openInula**
+
+
+
+openInula is a compile-based reactive front-end development framework. Developers can use familiar JSX syntax, enjoy the flexibility of native JavaScript, while achieving higher page performance, less memory usage, and smaller bundle size.
+
+**Stall Interaction:** The exhibition area can combine sub-forum topics to experience hands-on operation of openInula API2.0. Small gifts prepared to reward developers.
+
+**Sermant Open Source Community**
+
+
+
+Sermant is an agentless service mesh based on Java bytecode enhancement technology. It uses Java bytecode enhancement technology to provide service governance functions for host applications, solving service governance problems in large-scale microservices scenarios.
+
+**Stall Interaction:**
+* Sermant Plugin Development Experience: Develop plugins based on the template file provided by Sermant official, dynamically implementing bytecode enhancement for Java microservices.
+* Share Sermant technical evangelism articles or check-in photos on social media (朋友圈).
+* On-site project introduction and Q&A.
+* On-site participants can receive prizes provided by the Sermant community, such as tote bags, by participating in the above activities.
+
+**OpenFDE Open Source Linux Desktop**
+
+
+
+OpenFDE is an open-source Linux desktop. Differently, it is based on the AOSP graphics stack, providing a consistent window graphics environment for Android and Linux applications.
+
+**Stall Interaction:** Open source knowledge Q&A: Prepare several open source & community-related questions; gifts for correct answers.
+
+**ZenTao Open Source Edition Project Management Software**
+
+
+
+ZenTao is an open-source full-lifecycle project management software designed based on Agile and CMMI management concepts. It integrates product management, project management, quality management, document management, organizational management, and affair management, fully covering the core processes of R&D project management.
+
+**Stall Interaction:** Prepare open source questions; give corresponding small gifts based on the participant's correct answer rate.
+
+**OpenBuild**
+
+
+
+OpenBuild is an open-source community for Web3 developers, dedicated to connecting Web2 and Web3, helping developers transition to the decentralized web. Through open-source collaboration, it provides systematic educational content, open-source tools, and community resources, helping developers build a reputation system and create business opportunities.
+
+**Stall Interaction:** Interactive lucky draw.
+
+**NebulaGraph**
+
+
+
+NebulaGraph is an open-source, distributed, easily scalable native graph database capable of handling ultra-large-scale datasets containing hundreds of billions of vertices and trillions of edges, providing millisecond-level queries.
+
+**Stall Interaction:** Answer basic questions about NebulaGraph based on the roller banner and official account content. Correct answers win any peripheral; incorrect answers receive a small rubber duck.
+
+**AIGCOPEN Community x Microsoft Reactor**
+
+
+
+
+AIGC aims to create China's AIGC ecosystem landing.
+
+**Stall Interaction:** Showcase Microsoft Reactor developer community activities on-site, distribute promotional leaflets for Microsoft AI developer certification guidance, guide downloads of the latest OpenAI Chinese whitepaper, and distribute free learning materials from the AIGCOPEN community.
+
+**隐语 SecretFlow**
+
+
+
+SecretFlow is an open-source unified framework for privacy-preserving data analysis and machine learning.
+
+**Stall Interaction:** Privacy Computing Quick Q&A—An activity initiated by the SecretFlow open-source community, "SecretFlow·Privacy Computing Quick Q&A," focusing on basic knowledge of privacy computing, aiming to promote data security, privacy computing, and related knowledge.
+
+**IvorySQL Open Source Community**
+
+
+
+IvorySQL is an open-source Oracle-compatible PostgreSQL led by HighGo. It is dedicated to providing enterprises and developers with a high-performance, scalable, and secure best-in-class solution for Oracle migration. As an international open-source project, the IvorySQL China community has now grown to 2000+ members, completed over 270 commits, and contributed over 900,000+ lines of open-source code.
+
+**Stall Interaction:**
+Participants只需 (only need to) participate in the dart lottery and community interaction for a chance to join an exciting lottery and win generous prizes.
+
+**Apollo Developer Community**
+
+
+
+The Apollo Developer Community is dedicated to providing a learning and exchange platform for global autonomous driving developers and partners, helping developers quickly understand and use autonomous driving technology.
+
+**Stall Interaction:** Autonomous driving technology科普互动 (popular science interaction); peripheral interaction.
+
+The above are the stalls at this Open Source Bazaar. Welcome!
+
+
+
\ No newline at end of file
diff --git a/src/activity/dromara-coscon-showcase.md b/src/activity/dromara-coscon-showcase.md
new file mode 100644
index 0000000000..483f27e693
--- /dev/null
+++ b/src/activity/dromara-coscon-showcase.md
@@ -0,0 +1,95 @@
+---
+title: Dromara Showcases at COSCon'24 China Open Source Conference, Discussing the Future of Domestic Open Source
+author: achao
+date: 2024-10-17
+cover: /assets/img/activity/dromara-coscon-showcase-0.jpg
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+**Time**: November 2-3, 2024
+
+**Location**: Zhongguancun National Independent Innovation Demonstration Zone Exhibition Center, Beijing
+
+The COSCon'24 China Open Source Conference & KaiYuanshe's 10th Anniversary Carnival, hosted by KaiYuanshe, will be grandly held in Beijing. As an annual event in the domestic open-source community, this conference brings together a large number of open-source enthusiasts, technical experts, and industry leaders to discuss the future development of open-source technology. The conference features multiple sub-forums, with the Middleware/Microservices sub-forum presented by the Dromara open-source organization. The Dromara community will bring exciting technical shares from multiple members, showcasing the strength of domestic open source.
+
+Dromara is an open-source community composed of top open-source project authors in China. It provides a range of open-source products including popular tools, enterprise-level authentication, microservices, operation and maintenance monitoring, scheduling orchestration, and distributed systems. The technology stack is fully open-source and collaboratively built, maintaining community neutrality, allowing every participating open-source enthusiast to experience the joy of open source. The community currently boasts over 15 GVP projects, with a total star count exceeding 300K, has built an open-source community of tens of thousands of members, and has thousands of individuals and teams using Dromara's open-source projects.
+
+At this forum, at least three members of the Dromara community will deliver speeches covering popular technical fields such as AI, data operations, and front-end animation engines. Meanwhile, core authors of many other open-source projects from the Dromara community will also be present on-site for the Dromara community offline gathering, to communicate face-to-face with everyone, share their insights and experiences in their respective fields, and promote the development of open-source technology.
+
+### 1. Li Dapeng: EasyAi - The Open-Source Java AI Framework with Top-Tier Domestic Influence
+
+**Speaker**: Li Dapeng, Author of Easy-Ai, holder of multiple AI algorithm invention patents, member of the Dromara Open Source Community Project Committee, Algorithm Engineer at Shandong Well Data Co., Ltd.
+As a promoter of domestic AI frameworks, Li Dapeng is dedicated to building out-of-the-box AI solutions with Java, empowering enterprise-level development teams. His work not only fills the gap of Java in the AI field but also provides great convenience and flexibility for developers. This time, he will bring an exciting sharing:
+
+**Presentation Topic**: 《EasyAi - A 100% JAVA Code Domestic Open-Source AI Framework - The Huge Advantages and Role of Using the Easy Framework to Develop AI Micro-Models Under the Complete JAVA System in Enterprise Business》
+
+**Presentation Time**: November 3, 2024
+
+**Introduction**: Java, as the main language for enterprise-level development, has always been known for its rich ecosystem, shaped by the mobile internet era. However, with the advent of the artificial intelligence era, it has lost its ecosystem in the AI field. To solve this problem, EasyAi emerged. Its significance for Java is equivalent to the role of Spring in the JavaWeb field—to provide an out-of-the-box solution that enables every developer to use EasyAi to develop small-scale models that meet their artificial intelligence business needs. This is its mission! EasyAi has no dependencies. It is a native Java artificial intelligence algorithm framework. Firstly, it can be seamlessly integrated into our Java projects with Maven in one step, without any additional environment configuration or dependencies, achieving out-of-the-box usability. Secondly, it includes pre-packaged modules for image target detection and AI customer service, as well as underlying algorithm tools for deep learning, machine learning, reinforcement learning, heuristic learning, matrix operations, derivative functions, partial derivative functions, etc. Developers can, with minimal learning, develop small-scale models that deeply align with their business needs.
+
+**Open Source Project Links**:
+Gitee Address:
+https://gitee.com/dromara/easyAi
+Project Honors: Gitee GVP Project, GitCode G-Star Project
+
+
+
+* * *
+
+### 2. A Chao: Stream-Query Unlocks New Ways of Data Operation for Javaers
+
+**Speaker**: A Chao, 00s open-source enthusiast, Deputy Secretary-General of the Dromara Open Source Organization, 33rd Gitee Cover Person, Author of Stream-Query, Apache StreamPark Committer, Apache ShenYu Committer, Dromara Hutool Member, Baomidou Mybatis-Plus Member, Deputy Technical Director of Heshisiwei (Beijing) Technology Co., Ltd.
+A Chao is a highly innovative young developer whose contributions have been widely recognized in multiple open-source projects, especially in the field of simplifying Java data operations. The introduction of Stream-Query enables Java developers to perform data queries more efficiently, reducing development costs. This time, he will share:
+
+**Presentation Topic**: 《From Complexity to Simplicity: Stream-Query Unlocks New Ways of Data Operation for Javaers》
+
+**Presentation Time**: November 3, 2024
+
+**Introduction**: Dromara Stream-Query, as an innovative tool library, aims to simplify the data query process and improve development efficiency. It includes two core modules: Stream-Core and Stream-Plugin, which now enhance MybatisPlus functionality, support dynamic Mapper implementation, eliminating the need to manually create Mapper classes, and easily complete CRUD operations. This presentation will detail the core functions, technical architecture, and practical application of Stream-Query in real projects, helping Java developers make full use of this tool to improve work efficiency.
+
+**Open Source Project Links**:
+Gitee Address:
+https://gitee.com/dromara/stream-query
+Project Honors: Gitee Recommended Project, GitCode G-Star Project, Trusted Open Source Project of China Academy of Information and Communications Technology (CAICT) Trusted Open Source Community Consortium (TWOS)
+
+
+
+* * *
+
+### 3. Liu Chenyang: Exploring Programmatic Animation and Open-Source Front-End Engines
+
+**Speaker**: Liu Chenyang, 05s suspended high school freshman, co-founder of the BugDuck team, member of the Dromara Open Source Community Project Committee, author of the Newcar animation engine.
+As a young generation open-source contributor, Liu Chenyang, with his creative development work in the field of front-end animation engines, has become a shining star in the open-source community. Whether it's Newcar or VueMotion, he has demonstrated profound technical skills and unique insights. This time, he will bring three topic shares:
+
+* • **Topic 1: 《From Newcar to VueMotion: Exploration of Front-End Animation Engines》**
+ **Introduction**: Introducing Newcar and VueMotion as modern animation engines. VueMotion is an animation engine based on VueJs, providing a variety of components, similar to Manim but with a wider application range than Manim. This speech will discuss the concepts, development stories, and future prospects.
+* • **Topic 2: 《LLMVision: The Combination of Programmatic Animation and Large Model Capabilities》**
+ **Introduction**: Introducing LLMVision as a mathematics and statistics demonstration animation generator based on VueMotion.
+* • **Topic 3: 《Four Years of a High School Student with Open Source》**
+ **Introduction**: I will share my experience with open source and my story of struggling against an unfair fate using open source.
+
+**Open Source Project Links**:
+Gitee Address:
+https://gitee.com/dromara/newcar
+Project Honors: Gitee Recommended Project
+
+
+
+* * *
+
+This COSCon'24 China Open Source Conference, the shares from the Dromara community will bring cutting-edge technical thinking and practical experience to Java developers, AI technology explorers, and front-end enthusiasts.
+
+Even more excitingly, core authors of multiple projects from the Dromara community will be present on-site to communicate face-to-face with everyone, sharing their development journeys and insights. This is a rare opportunity for open-source enthusiasts to engage in in-depth discussions with these excellent open-source contributors and jointly promote the prosperity and development of the domestic open-source ecosystem!
+
+**Event Details Link**:
+https://kaiyuanshe.cn/activity/COSCon-2024
+
+**Registration Link**:
+https://www.bagevent.com/event/coscon24
+
+**Dromara COSCon**:
+https://coscon.dromara.org/
\ No newline at end of file
diff --git a/src/activity/dromara-coscon24-countdown.md b/src/activity/dromara-coscon24-countdown.md
new file mode 100644
index 0000000000..793498ead9
--- /dev/null
+++ b/src/activity/dromara-coscon24-countdown.md
@@ -0,0 +1,11 @@
+---
+title: Countdown 3 Days! We're Waiting for You at the 9th China Open Source Conference & KaiYuanshe's 10th Anniversary Carnival!
+author:
+date: 2024-10-31
+cover: /assets/img/activity/dromara-coscon24-countdown-0.jpg
+head:
+ - - meta
+ - name: Activity
+---
+
+
\ No newline at end of file
diff --git a/src/activity/dromara-coscon24-forum.md b/src/activity/dromara-coscon24-forum.md
new file mode 100644
index 0000000000..919d1425e7
--- /dev/null
+++ b/src/activity/dromara-coscon24-forum.md
@@ -0,0 +1,85 @@
+---
+title: Agenda Introduction|Open Source Middleware/Microservices Sub-Forum
+author: COSCon'24 Organizing Committee
+date: 2024-10-31
+cover: /assets/img/activity/dromara-coscon24-forum-13.webp
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+The COSCon'24 9th China Open Source Conference & KaiYuanshe's 10th Anniversary Carnival will be held on November 2-3, 2024, at the Zhongguancun National Independent Innovation Demonstration Zone Conference Center. As one of the most influential open-source events in the industry, COSCon was first launched by KaiYuanshe in 2016 and is now in its ninth edition.
+
+With its unique positioning and growing influence, COSCon has attracted increasing support from domestic and international enterprises, universities, and open-source organizations/communities. It boasts broad coverage across organizations, projects, and communities, drawing the attention and participation of numerous open-source developers and enthusiasts worldwide. In 2024, coinciding with KaiYuanshe's 10th anniversary, many communities will join this grand event, jointly presenting thematic forums on various topics, covering technologies such as web application development, cloud computing, big data, artificial intelligence, and Web3.0, as well as areas like open-source evaluation standards, open-source governance, and open-source talent education.
+
+**1**
+
+**Conference & Forum Information**
+
+**⏰ Conference Time**: November 2-3, 2024. The main forum will be held in the morning, followed by parallel thematic forums in the afternoon.
+
+**📍 Location**: Zhongguancun National Independent Innovation Demonstration Zone Conference Center, Beijing
+
+**🙌🏻 Registration**: Scan the QR code below to register
+http://coscon24.bagevent.com
+
+
+
+At the COSCon'24 9th China Open Source Conference, the Dromara open-source community will serve as a co-organizing community, presenting a thematic forum on open-source middleware/microservices during the event.
+
+**2**
+
+**Forum Co-Organizer**
+
+
+
+
+
+Xiao Yu, Mianbi Intelligence
+
+
+
+Founder of the Dromara open-source organization
+
+
+
+
+
+
+**3**
+
+**Forum Agenda Introduction**
+
+
+
+**4**
+
+**About the Event**
+
+The COSCon'24 9th China Open Source Conference & KaiYuanshe's 10th Anniversary Carnival is an annual open-source extravaganza. Over the two-day conference, there will not only be exciting Keynote speeches but also diverse thematic forums/hands-on labs/community gatherings. It is expected that over 2,000 participants will attend this event on-site, with more than 10,000 viewers joining online via live stream.
+
+
+
+Produced by | COSCon'24 Organizing Committee
+Edited by | Wang Jun
+
+**Related Reading | Related Reading**
+
+[First Look at the Main Forum! Open Source, Open Life - Experience the New Open Source Lifestyle at COSCon'24](http://mp.weixin.qq.com/s?__biz=MzA4NTM4NDc4NQ==&mid=2247539577&idx=1&sn=12c6cf318e39bf583b8c746a4cdc8123&chksm=9fdad047a8ad595125aab0b3fb10da55e1d3bfff18b7c3f9f97c0e88242fd52342556ad60dca&scene=21#wechat_redirect)
+
+[Forum Introduction | Open Source Education Sub-Forum (Open Source Summer and Open Source Talent Cultivation)](http://mp.weixin.qq.com/s?__biz=MzA4NTM4NDc4NQ==&mid=2247539807&idx=1&sn=fa057571874df6208346e394f2a3cbed&chksm=9fdad761a8ad5e779b8467ba4cc5df3119c211b831148503fe9e3a3e0880a33aed9f8e94cfd8&scene=21#wechat_redirect)
+
+**KaiYuanshe**
+_**KAIYUANSHE**_
+
+
+
+KaiYuanshe was established in 2014. It is an open-source community composed of individual volunteers dedicated to the cause of open source, based on the principles of "Contribution, Consensus, and Co-governance." KaiYuanshe always maintains the concept of "Vendor-Neutral, Public Welfare, Non-Profit," with the vision of "Based in China, Contributing to the World, Promoting Open Source as a Lifestyle in the New Era," and the mission of "Open Source Governance, International Integration, Community Development, Project Incubation," aiming to co-create a healthy and sustainable open-source ecosystem.
+
+KaiYuanshe actively cooperates closely with open-source-supporting communities, universities, enterprises, and relevant government units. It is also the first Chinese member of the Open Source Initiative (OSI), the global open-source license certification organization.
+
+Since 2016, KaiYuanshe has continuously hosted the China Open Source Conference (COSCon), released the "China Open Source Annual Report," and jointly launched initiatives like the "China Open Source Pioneer List" and "China Open Source Code Power List," generating widespread influence both domestically and internationally.
+
+We look forward to meeting you on November 2-3 at the COSCon'24 9th China Open Source Conference!
\ No newline at end of file
diff --git a/src/activity/dromara-milvusplus-live-preview.md b/src/activity/dromara-milvusplus-live-preview.md
new file mode 100644
index 0000000000..5d9e9b2ffa
--- /dev/null
+++ b/src/activity/dromara-milvusplus-live-preview.md
@@ -0,0 +1,32 @@
+---
+title: "Live Stream Preview丨Dromara MilvusPlus: A Vector Database Operations Library Built for Java Developers"
+author: December 11, 2024 08:41
+date: 2024-12-11
+cover: /assets/img/activity/dromara-milvusplus-live-preview-0.png
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+
+
+This episode of the 【User Tech】live stream will kick off on **December 12 at 8:00 PM** on the Zilliz live stream channel. We are honored to invite **Dromara Community Committee Member** Xiong Guochao, who will bring a sharing titled 《Dromara MilvusPlus: A Vector Database Operations Library Built for Java Developers》. MilvusPlus greatly simplifies the process of integrating vector databases into Java applications. This project offers a MybatisPlus-like calling style, enabling developers to manage and query vector data more efficiently and familiarly.
+
+
+
+Click below to schedule your reminder now. Please stay tuned for our live stream and join us on this journey of exploration!
+
+**Recommended Reading**
+
+How to Use RAG Technology for Document Q&A? Milvus × NetEase Youdao QAnything Reveals the Secrets!
+
+
+
+User Tech|First Session of the New Year! Milvus and Ctrip's Vector Exploration Publicly Revealed
+
+
+
+
+
\ No newline at end of file
diff --git a/src/activity/dromara-wuzhen-summit-2024.md b/src/activity/dromara-wuzhen-summit-2024.md
new file mode 100644
index 0000000000..9f6cceb896
--- /dev/null
+++ b/src/activity/dromara-wuzhen-summit-2024.md
@@ -0,0 +1,57 @@
+---
+title: Dromara Among the List! 30 Chinese Open Source Achievements Showcased at the 2024 World Internet Conference Wuzhen Summit
+author: China Economic Net
+date: 2024-11-22
+cover: /assets/img/activity/dromara-wuzhen-summit-2024-0.jpg
+head:
+ - - meta
+ - name: Activity
+---
+
+**Great News!** The Dromara open-source community won the **Second Prize** in the "2024 China Internet Development Innovation and Investment Competition (Open Source)" guided by the Cyberspace Administration of China's Information Development Bureau, jointly organized by the China Internet Development Foundation, the China Academy of Cyberspace Studies, and the China Internet Investment Fund, and undertaken by Beijing Chuxin Open Source Technology Co., Ltd. Several core members of the community were recognized!
+
+Thanks to all the friends in the community for your support, and thank you to the competition organizing committee for your recognition!
+
+
+
+Below is the report from China Economic Net.
+
+**Mr. Song, the head of the competition organizing committee, commented on Dromara: Some open-source communities spontaneously formed by grassroots developers, such as Dromara, are truly touching with their culture of "contributing for the love of open source."**
+
+On November 21, 2024, at the Internet Public Welfare and Charity Forum of the 2024 World Internet Conference Wuzhen Summit, the 2024 China Internet Development Innovation and Investment Competition (Open Source) concluded successfully. This competition is part of the brand public welfare project "China Internet Development Innovation and Investment Competition." Guided by the Cyberspace Administration of China's Information Development Bureau, it is the second national open-source public welfare competition jointly organized by the China Internet Development Foundation, the China Academy of Cyberspace Studies, and the China Internet Investment Fund. After multiple stages, including preliminary selection, code composition analysis, and final on-site evaluation, the competition finally selected 10 first prizes and 20 second prizes from 97 participating projects. The winning projects cover fields such as RISC-V, operating systems, databases, cloud computing, big data, artificial intelligence, and supply chain security, with some projects reaching internationally advanced levels.
+
+
+
+Since its official launch in April 2024, the 2024 China Internet Development Innovation and Investment Competition (Open Source) has attracted widespread attention from professionals in the open-source field. This year's competition was undertaken by Beijing Chuxin Open Source Technology Co., Ltd., with co-organizers including: China Open Source Software Promotion Alliance, Kechuang China Open Source Innovation Consortium, China Software Testing Center, China Engineers Joint Body, China Electronics Technology Group Corporation, China Mobile Communications Group Co., Ltd., China United Network Communications Group Co., Ltd., National Innovation Center of Intelligent and Connected Vehicles, Beijing Open Source Innovation Committee, Shanghai Open Source Technology Information Association, Huawei, ZTE, Tencent, Baidu, Alibaba Damo Academy, WeBank, Ant Group, ByteDance, JD.com, Xiaomi, QiAnXin, and dozens of other industry institutions, as well as research institutes such as the Institute of Computing Technology of the Chinese Academy of Sciences, the Institute of Software of the Chinese Academy of Sciences, the Institutes of Science and Development of the Chinese Academy of Sciences, Tsinghua University, Peking University, Zhejiang University, National University of Defense Technology, Beihang University, Beijing Institute of Technology, Beijing University of Posts and Telecommunications, University of Electronic Science and Technology of China, Sichuan University, East China Normal University, Southern University of Science and Technology, and Southwest Jiaotong University. As the main platform for the competition's publicity, China Economic Net set up a special section to report on the competition's news throughout the event. The Linux Foundation AI&DATA Community, the Apache Software Foundation, and domestic developer communities such as CSDN, InfoQ, and SegmentFault actively contributed to the online publicity of this year's competition and offline promotion activities in Beijing, Shanghai, Chengdu, Guangzhou, Hangzhou, Shenzhen, and the Greater Bay Area.
+
+Professor Lu Shouqun, a leading figure in China's open-source industry and honorary chairman of the China Open Source Software Promotion Alliance, stated: "China's cybersecurity and information infrastructure and underlying technologies benefit from the global proliferation of open-source technology. World-renowned open-source projects such as Linux, Kubernetes, and RISC-V have cultivated a group of world-class open-source technical talents in China. Talent is the foundational support for achieving a technologically strong nation, national rejuvenation, and winning the initiative in international competition. The open-source competitions continuously held by relevant units of the Cyberspace Administration of China are of extraordinary significance. Open-source competitions are an important channel for discovering, selecting, and cultivating young open-source talents. Enterprises, universities, and research institutions in the open-source ecosystem should regard this as their responsibility, actively support and participate in it, and contribute to cultivating open-source talent and enhancing technological innovation in China."
+
+It is reported that the competition established a review committee consisting of 60 technical experts and intellectual property experts, setting up a comprehensive indicator evaluation system across four dimensions: "technological innovation, open-source compliance, community operation, and commercial development." During the final stage, all participating projects underwent professional code composition analysis by the China Software Testing Center, QiAnXin Group, and Suzhou Prism Seven Color Information Technology Co., Ltd., providing an objective basis for the final expert review.
+
+Professor Chen Zhong of the Peking University School of Computer Science, chairman of this year's competition review committee, stated: "Promoting development through competitions has played an extremely important role in many innovative fields, and the open-source field is no exception. It even has broader exemplary significance. It can both encourage developers and related projects to stand out and promote the spirit and culture of open source, killing two birds with one stone."
+
+**Song Kewei, head of the competition organizing committee and general manager of Beijing Chuxin Open Source Technology Co., Ltd., said: "The diversity of participating projects is a significant feature of this year's competition. Compared to previous competitions, many individual open-source projects emerged in this year's competition. Although their influence may not be extensive, the maturity of their projects and community activity are very high; some open-source communities spontaneously formed by grassroots developers, such as Dromara, are truly touching with their culture of 'contributing for the love of open source'; there are also projects originating from leading tech companies (e.g., Tencent) and donated to the OpenAtom Open Source Foundation, such as OpenTenbase, which are particularly noteworthy."**
+
+Since open source was incorporated into the national "14th Five-Year Plan and 2035 Long-Range Objectives" in 2021, China's open source has officially entered an "accelerated" development stage. China has become the country with the fastest growth in the number of open-source developers globally and the second-largest in terms of total numbers. Open-source innovation has become an important development path for China's core and cutting-edge technologies.
+
+The "Decision" of the Third Plenary Session of the 20th Central Committee of the Communist Party of China proposed "improving the systems and mechanisms for developing new quality productive forces according to local conditions. Promoting revolutionary breakthroughs in technology, innovative allocation of production factors, and in-depth transformation and upgrading of industries, promoting the optimized combination and renewal leap of laborers, means of labor, and objects of labor, giving birth to new industries, new models, and new momentum, and developing productive forces characterized by high technology, high efficiency, and high quality."
+
+The China Internet Development Foundation, the organizer of the competition, has deeply implemented the strategic deployment of building a strong cyber nation, fully utilized the leveraging effect of public welfare funds, and carried out a series of innovation and investment competitions focusing on key technical talent and project innovation. It has successively included an open-source track in the China Internet Development Innovation and Investment Competition in 2022 and 2024. By hosting a series of open-source innovation and investment competitions, it continues to focus on the development of the open-source industry, assists in incubating outstanding open-source projects in China, and contributes to promoting China's information development and consolidating the digital economy.
+
+"Compared to the 2022 competition, the organizing committee expanded the review committee and included the adaptation of open-source projects to domestic chips and operating systems as one of the assessment items, fully demonstrating the competition's strong support for China's信创 (IT application innovation) industry ecosystem. In addition, unlike previous training on investment and financing for winning projects, this year's competition has added financing roadshow promotion activities for high-quality open-source projects, further reflecting the attributes of an 'innovation and investment competition,'" Song Kewei added. "Since 2024, local governments across the country have纷纷出台 (successively introduced) policies to support the open-source industry, providing targeted support for open-source enterprises, open-source projects, and open-source communities. Through these two open-source competitions, we have selected 120 outstanding Chinese open-source projects and more than 500 outstanding open-source talents, distributed in more than 20 cities across the country. Subsequently, we will continue to focus on the cultivation of open-source talent and the incubation of open-source projects."
+
+For the detailed list of winners, see: https://bs.bjos.club/hjgg-n183.html
+
+(Project supported by funds from the China Internet Development Foundation)
+
+
+
+
+
+---
+
+About Dromara
+
+Dromara is an open-source community composed of top open-source project authors in China. It provides a series of open-source products, solutions, consulting, technical support, and training certification services, including distributed transactions, popular tools, enterprise-level authentication, microservices RPC, operation and maintenance monitoring, Agent monitoring, distributed logs, and scheduling orchestration. The technology stack is fully open-source and collaboratively built, maintaining community neutrality, and is dedicated to providing microservices cloud-native solutions for global users. It aims to allow every participating open-source enthusiast to experience the joy of open-source.
+
+The Dromara open-source community currently boasts over 10 GVP (Gitee Most Valuable Project) projects, with a total star count exceeding 100,000. It has built an open-source community of tens of thousands of members, with thousands of individuals and teams using Dromara's open-source projects.
\ No newline at end of file
diff --git a/src/activity/gstar-reward-submit.md b/src/activity/gstar-reward-submit.md
new file mode 100644
index 0000000000..d5ea148541
--- /dev/null
+++ b/src/activity/gstar-reward-submit.md
@@ -0,0 +1,25 @@
+---
+title: G-Star Light Guide Initiative Launched | Submit Your GitCode Project Story to Win AirPods Pro, Guaranteed JD.com Gift Card upon Entry!
+author: GitCode
+date: 2024-11-12
+cover: /assets/img/activity/gstar-reward-submit-0.gif
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/activity/gstar-shenzhen-developers.md b/src/activity/gstar-shenzhen-developers.md
new file mode 100644
index 0000000000..ae9211db7e
--- /dev/null
+++ b/src/activity/gstar-shenzhen-developers.md
@@ -0,0 +1,35 @@
+---
+title: 【Event Registration】G-Star Gathering Day Shenzhen Stop|A Exclusive Party for Developers, Coming to Shenzhen!
+author: November 13, 2024 08:30
+date: 2024-11-13
+cover: /assets/img/activity/gstar-shenzhen-developers-0.gif
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+**G-Star Gathering Day** is a face-to-face gathering crafted by GitCode for open-source project developers. We have prepared a venue with a great view, delicious pizza, and beer to freely discuss the passion and joy of open-source development, share experiences, and spark inspiration. This is not only a technical exchange event but also a beautiful testament to the friendship among developers, working together to contribute to the prosperity of the open-source ecosystem.
+
+**G-Star Gathering Day Shenzhen Stop** is about to kick off! We sincerely invite every developer who loves open source to gather in Shenzhen on **November 30** to explore the infinite possibilities of open-source technology together. Continue reading to learn more about the event details and join us for this exclusive party for developers!
+
+
+
+**G-Star Gathering Day Shenzhen Stop**
+📅 Event Time: November 30, 14:00
+📍 Event Location: Nanshan District, Shenzhen
+
+“
+
+**Event Details and Registration Method**
+
+
+
+This event not only invites several industry experts to bring diverse technical sharing but also includes interactive sessions and lucky draws! It is a rare opportunity for technical exchange and networking, where both newcomers and veterans can gain a lot. Scan the QR code above or click the mini-program below to register and embark on a journey of open-source technology exploration! Limited spots available, don’t miss out!
+
+As China’s hub of technological innovation, Shenzhen has always been at the forefront of technological innovation and exchange, and it has also given birth to many high-quality G-Star projects. We believe that this G-Star Gathering Day Shenzhen Stop will undoubtedly inspire more innovative ideas!
+
+**November 30, see you in Shenzhen!**
+
+
\ No newline at end of file
diff --git a/src/activity/harmonyos-tool-gift.md b/src/activity/harmonyos-tool-gift.md
new file mode 100644
index 0000000000..b1f30a3146
--- /dev/null
+++ b/src/activity/harmonyos-tool-gift.md
@@ -0,0 +1,33 @@
+---
+title: HarmonyOS Developers, Assemble! Contribute Your Development Tools and Win Gifts at the Grand Bazaar
+author: January 23, 2025 10:24
+date: 2025-01-23
+cover: /assets/img/activity/harmonyos-tool-gift-0.gif
+head:
+ - - meta
+ - name: Activity
+---
+
+
+
+Amid the wave of technology, the HarmonyOS ecosystem is advancing rapidly with breakthrough momentum, flourishing vigorously. The HarmonyOS Development Tools Square Community, as a gathering place for practical HarmonyOS development tools, has always been committed to creating a resource-rich, efficient, and convenient exchange platform for developers.
+
+At the beginning of 2025, we sincerely invite developers to participate in the "**HarmonyOS Development Tools Grand Bazaar**" activity. Turn your usual HarmonyOS development experience into tools to help more developers efficiently build HarmonyOS applications. Welcome to **scan the QR code in the poster below to join this event** and contribute to the prosperity of the HarmonyOS ecosystem together!
+
+
+
+
+
+**Recommended Reading**
+
+
+
+[](https://mp.weixin.qq.com/s?__biz=MzkyNjY0MDY1Ng==&mid=2247488718&idx=1&sn=5da5632275a58f1b743e875e2ad9ffc3&scene=21#wechat_redirect)
+
+Announcement of Winners for the Cangjie Programming Language Essay Contest
+
+[](https://mp.weixin.qq.com/s?__biz=MzkyNjY0MDY1Ng==&mid=2247488488&idx=1&sn=d5cb681cc3eec11f0e56785dbab75f3e&scene=21#wechat_redirect)
+
+CodeMaster Featured Personality - Issue 1
+
+
\ No newline at end of file
diff --git a/src/activity/secretflow-3rd-upgrade-showcase.md b/src/activity/secretflow-3rd-upgrade-showcase.md
new file mode 100644
index 0000000000..f7425af79e
--- /dev/null
+++ b/src/activity/secretflow-3rd-upgrade-showcase.md
@@ -0,0 +1,138 @@
+---
+title: Three-Year Promise, Gathering in Beijing! Join Us to Witness the All-New SecretFlow Evolving into a "Full-Stack Technology Community for Data Circulation"
+author: SecretFlow Open Source Community
+date: 2025-07-29
+cover: /assets/img/activity/secretflow-3rd-upgrade-showcase-0.webp
+head:
+ - - meta
+ - name: Activity
+---
+
+At a critical stage of the in-depth development of the digital economy, national data infrastructure serves as a vital carrier for implementing data foundation systems and advanced technologies. It requires the integration and co-creation of technological and industrial ecosystems to break down barriers to interoperability. Seizing this period of accelerated development, the SecretFlow open source community is celebrating its third anniversary—a significant milestone!
+
+Three years ago, the SecretFlow open source community started with privacy-preserving computing technology, embarking on a journey of exploration and practice to ensure secure and trustworthy data circulation. Today, the community has gathered over 20,000 followers and developers, collaborated with 60+ academic and research institutions, and partnered with 70+ industrial organizations, collectively advancing key technologies for data element circulation and their application in various industries.
+
+In response to national data infrastructure policies, the SecretFlow open source community is not only born from the trend but also evolves with the times! We look forward to having you join us in witnessing SecretFlow’s transformation from a "privacy-preserving computing technology community" to a "full-stack technology community for data circulation."
+
+
+
+
+
+On August 14, 2025, the event "Technical Interconnection · Infinite Value—SecretFlow Third Anniversary & Community Upgrade Ceremony" will take place! This event is jointly hosted by Ant Cryptocomputing Technology Co., Ltd., the Key Laboratory of Blockchain Technology and Data Security of the Ministry of Industry and Information Technology, the State Key Laboratory of Blockchain and Data Security at Zhejiang University, and China Electronics Data Industry Group Co., Ltd. It is co-organized by the Beijing International Big Data Exchange and the Digital Economy Professional Committee of the China Electronics Information Industry Federation.
+
+The aim is to collaborate with partners from industry, academia, and research to advance the construction of data element circulation infrastructure, accelerate innovation in data element application scenarios, and unleash industrial value.
+
+Scan the QR code to reserve your【on-site seat】
+
+
+
+A three-year promise—SecretFlow meets you in Beijing!
+
+
+
+**🌟 Event Highlights: A Sneak Peek**
+
+**1. SecretFlow Community Upgrade: Technological Ecosystem Unites for Progress**
+
+A single tree does not make a forest. The SecretFlow community will "break boundaries" beyond privacy-preserving computing, uniting the technological and industrial ecosystem to build an integrated technological ecosystem covering trusted data spaces, data components, data networks, blockchain, and privacy-preserving computing. This evolution from a privacy-preserving computing technology community to a full-stack technology community for data element circulation will foster a collaborative ecosystem for national data infrastructure.
+
+Simultaneously, the community will promote interoperability across the data element industry. Leveraging the open-source nature of SecretFlow, it will build an ecological community involving multiple stakeholders from industry, academia, research, and application, breaking down industry silos, facilitating cross-domain technological collaboration and standard co-creation, enabling efficient cross-domain data element circulation, and stimulating innovation in industrial data. Stay tuned for the inaugural lineup!
+
+**2. Technology + Industry: A Feast of Colliding Ideas Driven by Dual Forces**
+
+We believe that cutting-edge technology is not a "solo performance." To bring hardcore code into real business scenarios, this event will feature exchanges and collisions through keynote sharing and in-depth roundtable discussions. Topics will include the technological evolution of data circulation infrastructure construction and benchmark scenarios of industrial data circulation, exploring how open source drives innovation, how technology empowers business, and how industries accelerate incubation.
+
+When上层建筑 (superstructure) meets基层建设 (infrastructure), and when tech geeks meet industry practitioners, we will break down barriers between cognition and practice,碰撞出 (collide to generate) practical paths for driving data value mining and industrial application落地 (implementation).
+
+**3. Data x Industries: Insights into Real Industrial Application Challenges**
+
+Industry is the best training ground for technology, and application scenarios are the fundamental drivers of data value. At this event, we will invite industry pioneers from various fields such as government, finance, insurance, and transportation to share practical cases of data element circulation and value release.
+
+We look forward to industrial organizations playing a leading role, jointly exploring innovative applications of data in various scenarios, promoting value mining and cross-entity reuse of data elements, and achieving complementary advantages, experience sharing, knowledge diffusion, and value multiplication through ecological exchange and cooperation.
+
+
+
+**🎓 Agenda: First Public Release**
+
+
+
+**🌟 Event Highlights: A Sneak Peek**
+
+Sign up now for the "Technical Interconnection · Infinite Value—SecretFlow Third Anniversary Special Event" to secure your exclusive seat! Gain in-depth insights into the latest technologies and practices in data circulation infrastructure, meet industry experts face-to-face, and unlock limited-edition community gifts!
+
+🔍 **How to Register**
+
+Scan the QR code above or below to register. Add SecretFlow Assistant Calor (WeChat ID: SecretFlow04) and reply with "Third Anniversary" to check your registration status.
+
+
+
+🎁 **Support Benefits**
+
+**1. Participation Period: From now until 23:59 on August 13, 2025**
+
+**2. Participation Rules and Reward Mechanism**:
+
+① Invite friends for online support:
+
+Step 1: Scan the registration QR code via WeChat to enter the "Support SecretFlow" page.
+
+
+
+Step 2: Share the support link with friends, and invited friends click to support online.
+
+Step 3: After the inviter checks in at the event, they can receive designated gifts based on the cumulative number of friends invited.
+
+Reward Mechanism:
+
+- 10 ≤ Cumulative number of friends invited < 20: Receive a SecretFlow custom umbrella.
+- Cumulative number of friends invited ≥ 20: Receive a SecretFlow custom travel cup.
+
+
+
+② Invite friends to register and attend onsite:
+
+Step 1: Scan the registration QR code via WeChat to enter the "Support SecretFlow" page.
+
+Step 2: Share the support link with friends, and invited friends register for the event and check in onsite.
+
+Step 3: The inviter can receive designated gifts based on the number of friends who check in onsite (Note: The inviter does not need to be onsite; the invited friends' onsite check-ins will count toward the cumulative number).
+
+Reward Mechanism:
+
+a) 3 ≤ Number of friends who check in onsite < 5: Receive a SecretFlow third-anniversary custom T-shirt.
+
+b) 5 ≤ Number of friends who check in onsite < 8: Receive a SecretFlow custom umbrella and a nap blanket.
+
+c) Number of friends who check in onsite ≥ 8: Receive a SecretFlow third-anniversary custom T-shirt and an eye massager.
+
+
+
+Note: In this support activity, users with the same ID number, mobile phone number, or WeChat account are considered the same user. Please refrain from using any unfair means that undermine the fairness of the activity. Violators will be disqualified from rewards.
+
+
+
+**Open-Source Trusted Privacy-Preserving Computing Framework—SecretFlow**
+
+Supports mainstream privacy-preserving computing technologies such as MPC, FL, and TEE. Integrates the co-creation capabilities of industry, academia, and research ecosystems to facilitate the broader application of privacy-preserving computing in AI, data analysis, and other scenarios, addressing pain points such as privacy protection and data silos across industries.
+
+**Website**: https://www.secretflow.org.cn
+
+**View Source Code**: https://github.com/secretflow/secretflow
+
+
+
+**Open-Source Secure and Trusted System Software Stack—Asterinas**
+
+An open-source community focused on secure and trusted technology software stacks, providing a solid security foundation for general computing and confidential computing scenarios, safeguarding emerging safety-critical and privacy-sensitive applications.
+
+**Website**: https://asterinas.github.io/
+
+**View Source Code**: https://github.com/asterinas
+
+
+
+**Contact Us**
+
+**Official Account**: SecretFlow’s Little Theater (隐语的小剧场)
+
+**Assistant**: SecretFlow04
\ No newline at end of file
diff --git a/src/blog/ApacheShenYu-2.6.1.md b/src/blog/ApacheShenYu-2.6.1.md
index 9a00d83565..8d88e4a4a2 100644
--- a/src/blog/ApacheShenYu-2.6.1.md
+++ b/src/blog/ApacheShenYu-2.6.1.md
@@ -2,8 +2,6 @@
title: Apache ShenYu 2.6.1 Released
author: Ho Fung Eun
date: 2024-01-24
-tag:
- -
cover: /assets/img/blog/ApacheShenYu-2.6.1-0.png
head:
- - meta
diff --git a/src/blog/DyJava.md b/src/blog/DyJava.md
index abae02fffc..b19b7eaaf6 100644
--- a/src/blog/DyJava.md
+++ b/src/blog/DyJava.md
@@ -3,7 +3,7 @@ title: Here it comes! TikTok Development Tool DyJava Joins Dromara Open Source C
author: danmo
date: 2024-04-29
tag:
- -
+ - DyJava
cover: /assets/img/blog/DyJava-0.png
head:
- - meta
@@ -68,6 +68,6 @@ For businesses that want to open a store on TikTok, DyJava provides a complete b
> as the 1 Java development kit specially built for tremolo, DyJava will undoubtedly become the preferred tool for tremolo back-end development with its rich functional modules, simple API design, efficient performance and perfect support system. Let's join hands with DyJava to open a new chapter in the back-end development of Douyin!
-**In this era full of infinite possibilities, let us use DyJava to create more wonderful Douyin applications and bring users a richer entertainment experience! * *
+\*_In this era full of infinite possibilities, let us use DyJava to create more wonderful Douyin applications and bring users a richer entertainment experience! _ \*
-**Warehouse address: https://gitee.com/dromara/dy-java**
\ No newline at end of file
+**Warehouse address: https://gitee.com/dromara/dy-java**
diff --git a/src/blog/Fast Request-0.md b/src/blog/Fast Request-0.md
index e93206ab2b..062f99b4b4 100644
--- a/src/blog/Fast Request-0.md
+++ b/src/blog/Fast Request-0.md
@@ -2,8 +2,6 @@
title: Script library syntax hint support, Fast Request 2024.1.5 release
author: Kings
date: 2024-05-15
-tag:
- -
cover: /assets/img/blog/Fast Request-0-1.webp
head:
- - meta
diff --git a/src/blog/FastRequest.md b/src/blog/FastRequest.md
index 279d815e94..737c6241da 100644
--- a/src/blog/FastRequest.md
+++ b/src/blog/FastRequest.md
@@ -2,8 +2,6 @@
title: Multi-tab open API,Fast Request 2024.1.6 release
author: Kings
date: 2024-06-26
-tag:
- -
cover: /assets/img/blog/FastRequest-1.webp
head:
- - meta
diff --git a/src/blog/HertzBea-collection-works.md b/src/blog/HertzBea-collection-works.md
new file mode 100644
index 0000000000..1e6058ce08
--- /dev/null
+++ b/src/blog/HertzBea-collection-works.md
@@ -0,0 +1,130 @@
+---
+title: "Behind the Scenes of HertzBeat: How Metric Collection Works"
+author: 2025年04月03日 11:19
+date: 2025-04-03
+cover: /assets/img/blog/HertzBea-collection-works-0.png
+head:
+ - - meta
+ - name: 博客
+---
+
+
+
+> 来自Apache HertzBeat 社区韩国朋友 @JuJinPark 的文章,写的很棒,这里就直接贴原文不翻译了.
+
+> HertzBeat is an open-source, real-time monitoring system designed for flexibility and ease of use. But how exactly does it collect, process, and store metrics from various systems?
+
+In this post, we’ll walk through the internal architecture behind **HertzBeat’s metric collection pipeline** — from job distribution to alerting and storage — with the help of a high-level system diagram.
+
+* * *
+
+### HertzBeat’s Metric Collection Architecture
+
+
+
+
+
+> **Figure**: High-level architecture of HertzBeat's metric collection system. The Manager handles job scheduling, alerting, and storage, while Collectors (external or internal) perform the actual metric collection. Communication between the Manager and Collectors uses a custom Netty TCP protocol.
+
+* * *
+
+### 1\. Job Distribution: Assigning What to Monitor
+
+When the **Manager** component starts, it loads monitoring targets from the database. These targets define the host, collection interval, and other parameters.
+
+To distribute the workload, the Manager sends jobs to **external Collectors** over a custom **Netty-based TCP protocol**. The module handles this logic using **consistent hashing**, ensuring jobs are evenly distributed across collectors.`CollectJobScheduling`
+
+> 💡 HertzBeat also includes a built-in **main collector** (identified as ) that runs directly inside the Manager. This allows HertzBeat to operate in **standalone mode** without requiring any external collectors.`MAIN_COLLECTOR_NODE`
+
+* * *
+
+### 2\. Task Scheduling: When to Monitor
+
+Once a Collector receives a job, it registers it with the **`TimerDispatch`** system.
+
+* For **external collectors**, the Manager sends the task via the TCP connection.
+
+* For the **main collector**, the Manager directly invokes within the same process.`CollectJobService`
+
+
+Each Collector runs a **`Timer`** in a background thread, which schedules tasks according to their configured intervals. When the time is up, the timer triggers a to begin metric collection.`TimerTask`
+
+* * *
+
+### 3\. Task Execution: How Metrics Are Collected
+
+When a is triggered, it creates a task and passes it to , which places it in the **`MetricsCollectorQueue`**.`TimerTask``MetricsCollect``MetricsTaskDispatch`
+
+* A dedicated thread () continuously polls this queue.`CommonDispatcher`
+
+* Tasks are executed by a **worker thread pool**, allowing multiple metric collections to run concurrently.
+
+* Each task uses a specific **collector strategy** (e.g., HTTP, JDBC, SSH) to fetch metrics from the target system.
+
+
+* * *
+
+### 4\. Result Processing: What Happens to Collected Data
+
+Once metrics are collected, the results are processed by the **`CollectDataDispatch`** module.
+
+* If the task is recurring, it is rescheduled via .`TimerDispatch`
+
+* Results are added to a **`CommonDataQueue`** for further handling.
+
+
+For external collectors, results are sent **back to the Manager** via the Netty TCP connection. For the main collector, results are forwarded **directly** to the next processing stage without network overhead.
+
+* * *
+
+### 5\. Alerting & Storage: Making Metrics Useful
+
+The Manager receives metric data and pushes it into the , where it is processed through two main pipelines:`MetricsDataToAlertQueue`
+
+#### 🔔 Alerting
+
+* The consumes metrics from the alert queue.`RealTimeAlertCalculator`
+
+* It checks each metric against user-defined alert rules and triggers alerts if conditions are met.
+
+
+#### 🧠 Storage
+
+* After alert evaluation, metrics are added to the .`MetricsDataToStorageQueue`
+
+* A background thread () processes this queue and stores the metrics in a database for long-term analysis and dashboard visualization.`DataStorageDispatch`
+
+
+* * *
+
+### Standalone Mode: No External Collectors Required
+
+Thanks to the built-in **main collector**, HertzBeat can operate entirely in standalone mode. This is especially useful for testing, small deployments, or quick setup. All core components — job scheduling, collection, alerting, and storage — run within a single process.
+
+* * *
+
+### 🧠 Conclusion
+
+HertzBeat’s metric collection system is designed for **performance, scalability, and flexibility**. With its:
+
+* **Queue-based, multi-threaded architecture**
+
+* **Persistent TCP connections** for reliable job/result flow
+
+* **Built-in main collector** for standalone operation
+
+
+it handles large-scale monitoring workloads with minimal overhead and high efficiency.
+
+* * *
+
+### 🙌 What’s Next?
+
+If you're curious to explore more:
+
+* ⭐️ Star the project on GitHub
+
+* 🤝 Contribute or open an issue
+
+
+https://github.com/apache/hertzbeat
\ No newline at end of file
diff --git a/src/blog/HertzBeat-v1.6.0 .md b/src/blog/HertzBeat-v1.6.0 .md
index 8b4beaccbe..154ef52b4f 100644
--- a/src/blog/HertzBeat-v1.6.0 .md
+++ b/src/blog/HertzBeat-v1.6.0 .md
@@ -2,8 +2,6 @@
title: The first Apache version v1.6.0 was released in HertzBeat!
author: tom
date: 2024-06-17
-tag:
- -
cover: /assets/img/blog/HertzBeat-v1.6.0-0.png
head:
- - meta
diff --git a/src/blog/LiteFlow-2.12.0.md b/src/blog/LiteFlow-2.12.0.md
index 56b1f6e843..57fb81f44e 100644
--- a/src/blog/LiteFlow-2.12.0.md
+++ b/src/blog/LiteFlow-2.12.0.md
@@ -2,8 +2,6 @@
title: Decision routing features hit, LiteFlow release of version 2.12.0, Make your code amaing!
author: Platinum East
date: 2024-04-16
-tag:
- -
cover: /assets/img/blog/LiteFlow-2.12.0-0.png
head:
- - meta
diff --git a/src/blog/LiteFlow-v2.12.1.md b/src/blog/LiteFlow-v2.12.1.md
index bfb0a0fd66..b5acc80ed8 100644
--- a/src/blog/LiteFlow-v2.12.1.md
+++ b/src/blog/LiteFlow-v2.12.1.md
@@ -2,8 +2,6 @@
title: The rule engine LiteFlow released the v2.12.1 version. How hard did it use to know
author: Platinum East
date: 2024-06-04
-tag:
- -
cover: /assets/img/qrcode_zsxq.webp
head:
- - meta
diff --git a/src/blog/README.md b/src/blog/README.md
index bdea2cba06..df3b36c555 100644
--- a/src/blog/README.md
+++ b/src/blog/README.md
@@ -1,33 +1,9 @@
----
-title: Blog
-pageInfo: false
-contributors: false
-editLink: false
-lastUpdated: false
----
-
-
-
-
-
-
+---
+title: Blog
+pageInfo: false
+contributors: false
+editLink: false
+sidebar: false
+lastUpdated: false
+layout: siteLayout
+---
diff --git a/src/blog/RuoYi-Vue-Plus-5.2.0.md b/src/blog/RuoYi-Vue-Plus-5.2.0.md
index 59ec604318..8f8e87fcb4 100644
--- a/src/blog/RuoYi-Vue-Plus-5.2.0.md
+++ b/src/blog/RuoYi-Vue-Plus-5.2.0.md
@@ -2,8 +2,6 @@
title: RuoYi-Vue-Plus Releases 5.2.0-BETA Public Test Version Workflow!
author: RuoYi
date: 2024-05-22
-tag:
- -
cover: /assets/img/blog/RuoYi-Vue-Plus-5.2.0-0.png
head:
- - meta
diff --git a/src/blog/Sa-Token-v1.38.0.md b/src/blog/Sa-Token-v1.38.0.md
index cf1af1974c..1a8b3d1e81 100644
--- a/src/blog/Sa-Token-v1.38.0.md
+++ b/src/blog/Sa-Token-v1.38.0.md
@@ -2,8 +2,6 @@
title: Sa-Token v1.38.0 Release, heavily refactor SSO modules
author: click33
date: 2024-05-13
-tag:
- -
cover: /assets/img/blog/Sa-Token-v1.38.0-0.png
head:
- - meta
diff --git a/src/blog/Sa-Token.md b/src/blog/Sa-Token.md
index 5f41cca2de..7cca7c883f 100644
--- a/src/blog/Sa-Token.md
+++ b/src/blog/Sa-Token.md
@@ -2,8 +2,6 @@
title: Commemoration:1541Days, Sa-Token Top 1 of Gitee Recommendation List
author: click33
date: 2024-04-25
-tag:
- -
cover: /assets/img/blog/Sa-Token-0.png
head:
- - meta
diff --git a/src/blog/architect-software-craft.md b/src/blog/architect-software-craft.md
new file mode 100644
index 0000000000..55b3290c4f
--- /dev/null
+++ b/src/blog/architect-software-craft.md
@@ -0,0 +1,101 @@
+---
+title: "The Architect: Master Craftsmen in the Software Edifice"
+author: Cat Lord is Annoyed
+date: 2024-12-26
+cover: /assets/img/blog/architect-software-craft-0.webp
+head:
+ - - meta
+ - name: Blog
+---
+
+In this era driven by software and artificial intelligence, our daily lives are inextricably linked with software. In this skyscraper of the software world, architects are like those master builders. They are not only proficient in stacking code but also understand how to use the bricks and tiles of code and logic to construct skyscrapers that are both sturdy and aesthetically pleasing, ensuring these structures can withstand the test of time. This article explores how architects, wielding keyboards and mice in the software world, play multiple roles in the process of building software. Let's read with a light heart, think with a smile, and feel free to offer your valuable opinions after a knowing chuckle.
+
+## Requirements Analysis (Project Manager)
+
+When a new engineering project is initiated, the project manager first needs to clarify the project's budget, timeline, and requirements. At this stage, objective, rational, and scientific analysis is crucial.
+The project manager needs to negotiate for more resources with the boss based on facts and the timeline, and determine reasonable deliverables and deadlines.
+
+* Humorous Case
+
+
+Boss: "We need to build an e-commerce skyscraper, just like Taobao would be best."
+Xiao Wang: "Boss, what's your budget?"
+Boss: "100,000, not a penny more. Finish it within a month."
+Xiao Wang: "Okay, got it, OKK, guaranteed completion." *Mutters to himself: Guess I'll have to use plastic and foam as building materials (Better buy a set online or find one in an open-source community to modify).*
+Boss: "Can you show me the design drawings later?"
+Xiao Wang: "What, we need design drawings too? Okay, no problem," *turns around and sends over some materials found online.*
+Boss: "Xiao Wang, quality and safety must be guaranteed; we need maintenance later too."
+Xiao Wang: "OKK, no problem"
+After the project goes live: Quality? Impossible. Safety? Nonsense. Maintenance? Dream on. Unit tests, integration tests, performance tests? Not a chance.
+Further development? Impossible. Lack the skill and energy. Just delete the database and run away.
+
+## Site Selection & Technology Selection (Feng Shui Master)
+
+When a new engineering project is initiated, the project manager first needs to clarify the project's budget, timeline, and requirements.
+At this stage, objective, rational, and scientific analysis is crucial. The project manager needs to negotiate for more resources with the boss based on facts and the timeline, and determine reasonable deliverables and deadlines.
+For example:
+
+* Programming Language Selection: Choose Java for its stability and mature ecosystem, Python for its flexibility, or PHP, hailed as the best language in the world?
+* Database Selection: Choose MySQL, reputed to struggle beyond 20 million rows per table, or PostgreSQL with its more powerful features and higher scalability?
+* Message Queue Selection: Choose Kafka, known for being extremely fast but potentially losing messages, or the recently popular Pulsar?
+
+Every selection needs concrete implementation, guided by the `first principle of pursuing efficiency`.
+For instance, can using Java enable rapid development and easier maintenance later?
+Does MySQL really fail at 20 million rows per table?
+Why is Kafka so fast, what's its principle, does it really lose messages, what features does it have?
+These questions require in-depth study. Don't follow blindly; you must delve into the principles, consult experts, and even examine the source code implementation—after all, there are no secrets in front of the source code.
+
+## Design Drawings & Architecture Diagrams (Architect)
+
+Architects use pencils and rulers to draw blueprints, while software architects use code and logic to build architecture diagrams.
+This is not random scribbling; every stroke relates to the software's survival.
+If the diagram is wrong, the disaster is unimaginable. Architects should have a high degree of technical foresight in their designs, adhering to the principle of `staying one step ahead`.
+
+During the design process, it's not about immediately designing for trillions-level traffic or multi-site disaster recovery from the start. Instead, architecture diagrams should be drawn according to the actual situation and team resources, adapting to local conditions.
+Every component in the diagram—its purpose, the scenarios for its use, its fault tolerance, the interaction protocols and interfaces between them—needs careful consideration.
+Then, draw the lines and trajectories; plainly put, it's a logical flow diagram of data movement.
+
+Of course, during this process, the architect also needs to reserve some space to consider the system's extensibility for adding more features later. Secondly, in case the boss is dissatisfied one day, the cost of replacement can be minimized as much as possible.
+Finally, the architect needs to communicate and review with the project team members in an easy-to-understand manner, ensuring everyone understands what they are doing and how to do it.
+
+## Construction & Management (Foreman)
+
+If developing a software edifice is compared to a small battle, then the architect is undoubtedly the general on the battlefield.
+Understanding the combat effectiveness, technical level, and temperaments of the people below is key to deploying troops and utilizing their strengths.
+This way, even when faced with constantly changing requirements, flexible adjustments can be made. When encountering technical challenges, the architect's motto should be: `"Brothers, follow me!" (not "Brothers, charge for me!")`, leading the charge to overcome technical difficulties.
+Don't浮躁 (be impetuous) floating on "PPT," drawing a diagram here, connecting a line there, and thinking the problem is solved. When it comes to actual development, you'll find it's completely different—how difficult it is and how much time it really takes.
+
+* During the development process:
+ 1. First, the architect should estimate the工期 (timeline) for each module reasonably and scientifically based on the requirements and architectural design. Always reserve some buffer time. (Sometimes, encountering a single problem can take developers days to solve).
+ 2. Write core code, establish coding standards, complete interface protocol design, define unit tests, integration tests, E2E tests, etc. Simultaneously, communicate with team members about why this is done and what the benefits are.
+ 3. Secondly, the architect should constantly monitor the project's progress. When bricklaying (coding) is slow, consider encapsulating some wheels (reusable components) or leveraging AI capabilities to speed up the bricklaying.
+ 4. Finally,验收 (acceptance) needs to be done for each feature, code needs to be reviewed, and an obsession with quality must be maintained.
+ When there are disagreements in understanding the code, you might even need to personally refactor the code and then communicate and discuss with the team members.
+
+* In management and communication:
+ 1. A good technical team doesn't really need management. The ancients said, 文人相轻 (scholars tend to despise each other). Actually, the programmer community is similar. They won't follow you because of your title or various management tactics,
+ but rather, based on what they can learn and how they can improve by working with you. Only then will they truly unite and genuinely love the work.
+ 2. Regarding communication: internally, you are a whole, a team. Problems must be communicated clearly face-to-face, ensuring every member understands your thoughts. Externally,
+ you must have your own technical convictions and personality. When faced with unreasonable demands, communicate promptly, even argue, to ensure the project stays on track. Don't let people think you are a
+ pushover or a soft persimmon (easy to bully). Of course, this might also give the impression that you are difficult to communicate with. So what? The responsibility is to get the project done well.
+
+## Delivery & Operations (Property Management)
+
+Now the building is constructed. How to safely hand over the house to the owner and let the owner live安心 (with peace of mind)? In this process, the architect should play
+the role of property management and should clarify the following:
+
+* Documentation
+ Documentation is the most critical and core deliverable in the entire handover process.
+ First, be responsible for managing documentation and knowledge, ensuring all important architectural decisions, design documents, and operational manuals are properly recorded and managed.
+ Ensure the documentation `is like a recipe; even if the chef changes, the same flavor can be produced`.
+
+* DevOps
+ Imagine if the building needs repairs or parts replacement,
+ architects can quickly respond to needs and iterate continuously through automated CI/CD (Continuous Integration/Continuous Deployment) processes.
+ If encountering a problem like a broken elevator, besides considering fault-tolerant strategies, you also need to consider how to perform rolling, canary updates.
+
+* Observability
+ The community's operational status needs to be constantly monitored and observed. Ensure that everything that should be monitored *is* monitored, and that monitoring data can reconstruct the entire chain when needed.
+ Predict system risks in advance and proactively avoid abnormal situations.
+
+In this era full of changes, architects need to continuously learn new technologies, adapt to new environments, and solve new problems. They also need to maintain curiosity, be full of desire to explore the unknown, and be passionate about new technologies. Be brave in innovation, dare to try, and don't fear failure. This work is full of challenges but also full of fun. This architects' alliance is a collision of ideas,更像 (even more like) a meeting of heroes. Borrowing the quote from Master Yi in League of Legends (LOL): `"A true master is an eternal student."` Hope all architects and engineers aspiring to become architects can contribute their bricks and tiles to China's software edifice.
\ No newline at end of file
diff --git a/src/blog/dromara-open-source-community.md b/src/blog/dromara-open-source-community.md
new file mode 100644
index 0000000000..9e24c6257d
--- /dev/null
+++ b/src/blog/dromara-open-source-community.md
@@ -0,0 +1,149 @@
+---
+title: How to Join an Open Source Community and Enjoy Open Source
+author: tom
+date: 2022-11-16
+cover: /logo.svg
+head:
+ - - meta
+ - name: Blog
+---
+
+Many students have the idea of participating in open source communities or projects. A well-structured open source community with a clear growth path can be particularly appealing.
+
+Growing together with a community—while one may walk fast alone, a group will walk farther together.
+
+### First, an Introduction to the Dromara Community
+
+The Dromara community is an open source community formed by top open source project authors in China. It offers a range of open source products, solutions, consulting, technical support, and training certification services, including distributed transactions, logging, popular tools, enterprise-level authentication, microservices RPC, operation and maintenance monitoring, Agent monitoring, scheduling orchestration, and more. The community is committed to comprehensive open source collaboration and maintaining community neutrality, aiming to provide global users with microservices and cloud-native solutions. It allows every open source enthusiast involved to experience the joy of open source.
+
+The community currently boasts more than 10 GVP (Gitee Most Valuable Project) projects, with a total star count exceeding 100,000. It has built an open source community of tens of thousands of members, with thousands of individuals and teams using Dromara’s open source projects.
+
+### Community Philosophy
+
+**Let every open source enthusiast involved experience the joy of open source**.
+
+We understand that open source contribution is a voluntary act—no one pays open source project developers. Therefore, the open source community will never force you to undertake any tasks or features you are not interested in. Contributors voluntarily pick up tasks that interest them, or when tasks need to be assigned, the PMC will consult contributors to see if they are interested, fully respecting contributors' opinions. We cannot guarantee that participating in open source will be 100% joyful, but we can guarantee that it will never be 100% unpleasant.
+
+Although no one is paying, the open source community strives to provide as many benefits as possible to developers, such as community gifts and merchandise, and this year’s "Open Source Summer" event by the Chinese Academy of Sciences (with a 12,000 RMB reward).
+
+You might participate purely out of a love for open source, or because you believe in the project’s growth potential and want to grow together, or to enhance your resume with open source experience, or for internal enterprise development, or to realize self-worth. We highly respect all motivations for participating in open source projects and do our best to provide the utmost support within our means.
+
+### Open Source Project Growth Path
+
+**Contributor** (code, documentation, etc.) -> **Committer** (sustained project contribution or outstanding contributors, nominated by the PMC) -> **PMC** (after being a Committer for 2+ months, with sustained contributions and active maintenance of the open source project, nominated by the PMC)
+
+Community projects have a well-defined growth path. Contributions are not limited to **code**—a **unit test case**, **documentation improvement**, or even fixing a **punctuation error** in the documentation can count as a project contribution. Your first successful contribution makes you a **Project Contributor**. Those who contribute consistently or make significant feature contributions can be nominated by the PMC to become a **Project Committer** (with no objections), automatically becoming a **Dromara Community Member**. Committers who continue to contribute and actively maintain the project can be nominated by the PMC and, after a vote, become a **Project PMC**. After becoming a **Project PMC**, they can be voted into the **Dromara Committee** by the **Dromara Council**.
+
+## Joining the Organization
+
+**Dromara** welcomes all open source enthusiasts to join. We provide a well-rounded platform for community governance development and member growth.
+
+### Dromara Community Members
+
+#### How to Become a Community Member?
+
+1. You can contribute to open source projects under the Dromara community (through code, documentation, examples, and other forms of contribution). Once elected as a **Project Committer**, you automatically become a **Dromara Community Member**.
+
+#### Community Member Rights and Responsibilities
+
+**Community Member Rights**
+
+1. The community will display member information and honors on the official website, repositories, etc.
+2. Dromara community exclusive email, e.g., `lili@dromara.org`.
+3. Free invitation as a guest to Dromara’s paid knowledge planet.
+4. Participation in community internal meetings, development plans, events, offline gatherings, etc.
+5. Annual community gifts and merchandise (e.g., sweatshirts and wrist rests in 2022).
+6. Various types of community support in open source, work, etc. (there are many experts in the community!).
+
+**Community Member Responsibilities**
+
+1. Do not engage in illegal activities or actions that harm the community and open source projects.
+2. Maintain the community’s image and actively promote it.
+
+### Dromara Committee Members
+
+1. You can contribute consistently to open source projects under the Dromara community, become a **Project PMC**, and after being voted in by the Dromara Committee, become a **Dromara Committee Member**.
+2. Alternatively, you can donate your open source project directly. Upon successful donation, you automatically become a **Dromara Committee Member**.
+
+**Committee Member Rights**
+
+1. All rights enjoyed by the above `Community Members`.
+2. Voting rights on community matters.
+3. Listing on the official website, repositories, etc., as a Dromara Committee Member.
+4. Nominations for new open source projects, committee member nominations, voting, etc.
+5. Opportunities for presentations, project promotions, and other community resource support.
+
+**Committee Member Responsibilities**
+
+1. Do not engage in illegal activities or actions that harm the community and open source projects.
+2. Proactively maintain and promote the community.
+
+## Step-by-Step Guide to Participating in Open Source
+
+> Here, we use the open source project HertzBeat under the Dromara community as an example.
+
+#### Understand and Get Familiar with the Open Source Project
+
+- Visit the project repository https://github.com/dromara/hertzbeat or the official website https://hertzbeat.com/ to learn about the project.
+- Use or start the project based on the documentation to familiarize yourself with its features.
+
+#### Find Tasks That Interest You
+
+- Visit the project repository’s Issue list and find tasks that interest you or are marked as [TASK]. If you’d like to try one, simply comment below to claim it, and you can get started! Of course, you can also directly join the communication group or contact WeChat tan-cloud to express your interest in participating, and they will recommend tasks that suit you.
+- We recommend starting with small tasks for your first contribution, such as writing unit test cases.
+
+#### Submit a Pull Request
+
+1. First, you need to Fork the target repository hertzbeat: https://github.com/dromara/hertzbeat.
+2. Then, use the git command to clone the code locally:
+ ```
+ git clone git@github.com:${YOUR_USERNAME}/hertzbeat.git
+ ```
+3. After cloning, refer to the target repository’s getting started guide or README file to initialize the project.
+4. Next, you can use the following commands to commit code, switch to a new branch, and start development:
+ ```
+ git checkout -b a-feature-branch
+ ```
+5. Commit your changes. The commit message should follow the convention: [module name or type name]feature or bugfix or doc: custom message.
+ ```
+ git add
+ git commit -m '[docs]feature: necessary instructions'
+ ```
+6. Push to the remote repository:
+ ```
+ git push origin a-feature-branch
+ ```
+7. Then, you can create a new PR (Pull Request) on GitHub.
+
+Please ensure the PR title and content include necessary information to help Committers and other contributors with code review.
+
+#### Wait for the PR to Be Merged
+
+After submitting a PR, Committers or community members will review your code (Code Review), provide feedback, or engage in discussions. Please keep an eye on your PR.
+
+Note: **If后续需要改动,不需要发起一个新的 PR,在原有的分支上提交 commit 并推送到远程仓库后,PR会自动更新**。(If后续需要改动,不需要发起一个新的 PR,在原有的分支上提交 commit 并推送到远程仓库后,PR会自动更新。)
+
+Additionally, the project has a standardized CI check process. After submitting a PR, CI will be triggered. Please ensure it passes the CI checks.
+
+Finally, a Committer can merge the PR into the **main DEV branch**.
+
+#### After the Code Is Merged
+
+Once the code is merged, you can delete the development branch locally and remotely:
+```
+git branch -d a-dev-branch
+git push origin --delete a-dev-branch
+```
+
+On the main branch, you can sync with the upstream repository using:
+```
+git remote add upstream https://github.com/dromara/hertzbeat.git #Bind the remote warehouse, if it has been executed, it does not need to be executed again
+git checkout master
+git pull upstream master
+```
+
+By following these steps, you become a HertzBeat contributor. Repeat the process, stay active in the community, and persist—you can become a Committer -> PMC!
+
+## Final Thoughts
+
+When it comes to programmers, the stereotype often includes plaid shirts, being earnest and a bit dull—we are often the ones behind the scenes. The open source community hopes that through open source, developers can have the opportunity to step into the spotlight and showcase themselves, using open source code to "package" themselves. Imagine the project you contributed to being used or deployed by thousands of teams—that’s really cool. When interviewing, you won’t need to spend a month cramming stereotyped essay writing to prove yourself to the interviewer; your Github/Gitee account will already show them that you are awesome and reliable!
\ No newline at end of file
diff --git a/src/blog/dromara-warmflow-assignee-guide.md b/src/blog/dromara-warmflow-assignee-guide.md
new file mode 100644
index 0000000000..0011ff60f7
--- /dev/null
+++ b/src/blog/dromara-warmflow-assignee-guide.md
@@ -0,0 +1,55 @@
+---
+title: Dromara WarmFlow Workflow Dynamically Assigning Approvers
+author: WarmFlow
+date: 2024-12-03
+cover: /assets/img/blog/dromara-warmflow-assignee-guide-0.webp
+head:
+ - - meta
+ - name: Blog
+---
+
+### Background:
+
+The assignee of an approval task is usually predefined in the process designer. But what if you need to assign an approver dynamically during the process? This is where Dromara's WarmFlow workflow comes into play. It effectively addresses this issue through its assignee variable expressions.
+
+##### Solution Approach
+
+* 1. During process design, configure the assignee variable expression `${handler1}` for nodes that require dynamic assignee assignment.
+* 2. When the previous task is being processed, pass the value of `${handler1}` in the process variables.
+* 3. Once the task is completed, the current node task will be generated, achieving dynamic assignment.
+
+
+
+Backend code to set variables:
+
+```java
+// Process variables
+Map variable = new HashMap<>();
+variable.put("handler1", "100");
+flowParams.variable(variable);
+
+Instance instance = insService.skipByInsId(testLeave.getInstanceId(), flowParams);
+```
+
+##### Advanced Usage
+
+* Supports dynamically assigning a group of people.
+* Supports SpEL expressions.
+* Supports expression extensions.
+
+Change the code from `"100"` to `Arrays.asList(4, "5", 100L)` to dynamically assign a group of people:
+
+```java
+// Process variables
+Map variable = new HashMap<>();
+variable.put("handler1", Arrays.asList(4, "5", 100L));
+flowParams.variable(variable);
+
+Instance instance = insService.skipByInsId(testLeave.getInstanceId(), flowParams);
+```
+
+##### Conclusion
+
+Doesn't WarmFlow workflow make it easy to manage your approval processes? Don't hesitate to give it a try! ^v^
+
+Additionally, the Dromara organization offers many other useful projects. Feel free to explore them!
\ No newline at end of file
diff --git a/src/blog/hmily_current.md b/src/blog/hmily_current.md
index e2206ae8f5..877c267f0b 100644
--- a/src/blog/hmily_current.md
+++ b/src/blog/hmily_current.md
@@ -1,172 +1,172 @@
----
-title: Hmily:Easy Handle Highly Concurrent Distributed Transactions
-author: xiaoyu
-date: 2018-11-14
-tag:
- - hmily
-cover: /assets/img/architecture/hmily-framework.png
-head:
- - - meta
- - name: Blog
----
-
-### Handling Highly Concurrent Transactions with Hmily
-
-Let's start with a quick advertisement. Hmily is participating in the Open Source China Annual Popularity Poll at [this link](https://www.oschina.net/project/top_cn_2018?origin=zhzd). Click the link, search for Hmily, and cast your vote. It's the second one in the 11th row. Thank you, everyone, for your support! Feel free to follow us and submit pull requests to make Hmily even better and more perfect.
-
-GitHub: [https://github.com/yu199195/hmily]
-Gitee: [https://gitee.com/dromara/hmily]
-
-Now, let's address some questions from the community and clear up some areas of confusion.
-
-### 1. Performance Issues with Hmily?
-
-Answer: Hmily uses AOP aspect to bind with your RPC methods. It essentially saves logs (using asynchronous disruptor) and passes some parameters when you make an RPC call. Both confirm and cancel operations are now asynchronous, so its performance is similar to that of your RPC. Remember, Hmily doesn't create transactions; it's just a facilitator for distributed transactions. In the past, Hmily had a performance drop due to a locking mechanism in the AOP aspect, as discussed in an article by the Spring Cloud China community. This issue has been resolved now, and everything is asynchronous. The testing scenario was somewhat unreasonable, as it was a demo under default configurations. In the following sections, I'll explain how to improve Hmily's performance.
-
-### 2. How Does Hmily Handle RPC Call Timeouts?
-
-Answer: In a distributed environment, when you invoke an RPC method and it exceeds the timeout, let's say the Dubbo timeout is set to 100ms but your method takes 140ms, your method has succeeded, but for the caller, it appears as a failure. In this case, a rollback is needed. Hmily's approach is as follows: if the caller thinks the operation failed due to a timeout, it won't include the operation in the rollback chain. So, for an RPC interface that times out, it handles its own rollback. A scheduled task handles the rollback because the log is in the "try" phase, and the cancel method is invoked for rollback, achieving eventual consistency.
-
-### 3. Hmily's Support for Cluster Deployment and Log Recovery in Cluster Environments?
-
-Answer: Hmily is naturally compatible with cluster deployment as it's bound to your application via AOP aspect. Log recovery in a clustered environment is rarely an issue, unless your entire cluster crashes simultaneously. If your cluster goes down simultaneously and recovers, the logs have a version field; only those that are successfully updated undergo recovery.
-
-### 4. Hmily Asynchronously Saves Logs, What If a Drastic Event Occurs Before Logging?
-
-Answer: If you're having such thoughts, you probably haven't delved into the source code or didn't fully understand it. In the AOP aspect, logs are first saved asynchronously, with the state being PRE_TRY. After the try phase execution completes, it's updated to "try". Even in scenarios like sudden JVM exit or power loss right after this line of code is executed, the mechanism stands. Even if you're testing scenarios like stopping the JVM abruptly or killing the service, keep in mind that Hmily can't account for all accidental events. It's best not to put excessive effort into solving these rare occurrences; the ideal solution is to not focus on them.
-
-### Hmily Configuration Tuning for High-Concurrency Scenarios
-
-The following parameters can be optimized for high-concurrency scenarios in Hmily:
-
-- `serializer`: I recommend using Kryo. Hmily also supports Hessian, Protostuff, and JDK serialization. In our tests, the performance was in the order: Kryo > Hessian > Protostuff > JDK.
-
-- `recoverDelayTime`: Delay time for the recovery task (in seconds, default is 120). This parameter should be greater than the timeout set for your RPC calls.
-
-- `retryMax`: Maximum retry count (default is 3). When your service goes down, the recovery task will execute your cancel or confirm method for a maximum of retryMax times.
-
-- `bufferSize`: Disruptor's buffer size. Increase this for high-concurrency scenarios; it should be a power of 2.
-
-- `consumerThreads`: Number of threads for Disruptor's consumer. Increase this for high-concurrency scenarios.
-
-- `started`: Set this to true for the initiator side and false for the participant side.
-
-- `asyncThreads`: Size of the thread pool for asynchronous execution of confirm and cancel methods. Increase this for high-concurrency scenarios.
-
-Next, the most important aspect: configuring the storage of transaction logs. In our stress tests, I recommend using MongoDB, where the performance ranked as follows: MongoDB > Redis Cluster > MySQL > ZooKeeper.
-
-- If you're using MongoDB for log storage, configure as follows:
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-- Here, I recommend using Kryo. Of course, Hmily also supports Hessian, Protostuff, and JDK serialization. In our tests, the performance was in the order: Kryo > Hessian > Protostuff > JDK.
-
-- recoverDelayTime :Delay time for the recovery task (in seconds, default is 120). This parameter should be greater than the timeout set for your RPC calls.
-
-- retryMax : Maximum retry count (default is 3). When your service goes down, the recovery task will execute your cancel or confirm method for a maximum of retryMax times.
-
-- Disruptor's buffer size. Increase this for high-concurrency scenarios; it should be a power of 2.
-
-- consumerThreads: Disruptor's consumer thread count. Increase this for high-concurrency scenarios.
-
-- started: Set this to true for the initiator side and false for the participant side.
-
-- asyncThreads: Size of the thread pool for asynchronous execution of confirm and cancel methods. Increase this for high-concurrency scenarios.
-
-- Next is the most important aspect: configuring the storage of transaction logs. In our stress tests, I recommend using MongoDB, where the performance ranked as follows: MongoDB > Redis Cluster > MySQL > ZooKeeper.
-
-- If you're using MongoDB for log storage, configure as follows:
-
-```xml
-
-
-
-
-
-
-
-
-
-```
-
-- If you're using Redis for log storage, configure as follows:
-
- - Single node Redis:
-
-```xml
-
-
-
-
-
-
-
-
-```
-
-- Redis sentinel mode cluster:
-
-```xml
-
-
-
-
-
-
-
-
-
-```
-
-- Redis cluster mode:
-
-```xml
-
-
-
-
-
-
-
-
-```
-
-- If you're using ZooKeeper for log storage, configure as follows:
-
-```xml
-
-
-
-
-
-
-
-
-```
-
-- The database configuration has been provided above, and I won't introduce the file-based storage approach.
-- The above is the content shared today, an annotation, and a few configuration lines to easily handle high-concurrency distributed transactions!
+---
+title: Hmily:Easy Handle Highly Concurrent Distributed Transactions
+author: xiaoyu
+date: 2018-11-14
+tag:
+ - hmily
+cover: /assets/img/architecture/hmily-framework.png
+head:
+ - - meta
+ - name: Blog
+---
+
+### Handling Highly Concurrent Transactions with Hmily
+
+Let's start with a quick advertisement. Hmily is participating in the Open Source China Annual Popularity Poll at [this link](https://www.oschina.net/project/top_cn_2018?origin=zhzd). Click the link, search for Hmily, and cast your vote. It's the second one in the 11th row. Thank you, everyone, for your support! Feel free to follow us and submit pull requests to make Hmily even better and more perfect.
+
+GitHub: [https://github.com/yu199195/hmily]
+Gitee: [https://gitee.com/dromara/hmily]
+
+Now, let's address some questions from the community and clear up some areas of confusion.
+
+### 1. Performance Issues with Hmily?
+
+Answer: Hmily uses AOP aspect to bind with your RPC methods. It essentially saves logs (using asynchronous disruptor) and passes some parameters when you make an RPC call. Both confirm and cancel operations are now asynchronous, so its performance is similar to that of your RPC. Remember, Hmily doesn't create transactions; it's just a facilitator for distributed transactions. In the past, Hmily had a performance drop due to a locking mechanism in the AOP aspect, as discussed in an article by the Spring Cloud China community. This issue has been resolved now, and everything is asynchronous. The testing scenario was somewhat unreasonable, as it was a demo under default configurations. In the following sections, I'll explain how to improve Hmily's performance.
+
+### 2. How Does Hmily Handle RPC Call Timeouts?
+
+Answer: In a distributed environment, when you invoke an RPC method and it exceeds the timeout, let's say the Dubbo timeout is set to 100ms but your method takes 140ms, your method has succeeded, but for the caller, it appears as a failure. In this case, a rollback is needed. Hmily's approach is as follows: if the caller thinks the operation failed due to a timeout, it won't include the operation in the rollback chain. So, for an RPC interface that times out, it handles its own rollback. A scheduled task handles the rollback because the log is in the "try" phase, and the cancel method is invoked for rollback, achieving eventual consistency.
+
+### 3. Hmily's Support for Cluster Deployment and Log Recovery in Cluster Environments?
+
+Answer: Hmily is naturally compatible with cluster deployment as it's bound to your application via AOP aspect. Log recovery in a clustered environment is rarely an issue, unless your entire cluster crashes simultaneously. If your cluster goes down simultaneously and recovers, the logs have a version field; only those that are successfully updated undergo recovery.
+
+### 4. Hmily Asynchronously Saves Logs, What If a Drastic Event Occurs Before Logging?
+
+Answer: If you're having such thoughts, you probably haven't delved into the source code or didn't fully understand it. In the AOP aspect, logs are first saved asynchronously, with the state being PRE_TRY. After the try phase execution completes, it's updated to "try". Even in scenarios like sudden JVM exit or power loss right after this line of code is executed, the mechanism stands. Even if you're testing scenarios like stopping the JVM abruptly or killing the service, keep in mind that Hmily can't account for all accidental events. It's best not to put excessive effort into solving these rare occurrences; the ideal solution is to not focus on them.
+
+### Hmily Configuration Tuning for High-Concurrency Scenarios
+
+The following parameters can be optimized for high-concurrency scenarios in Hmily:
+
+- `serializer`: I recommend using Kryo. Hmily also supports Hessian, Protostuff, and JDK serialization. In our tests, the performance was in the order: Kryo > Hessian > Protostuff > JDK.
+
+- `recoverDelayTime`: Delay time for the recovery task (in seconds, default is 120). This parameter should be greater than the timeout set for your RPC calls.
+
+- `retryMax`: Maximum retry count (default is 3). When your service goes down, the recovery task will execute your cancel or confirm method for a maximum of retryMax times.
+
+- `bufferSize`: Disruptor's buffer size. Increase this for high-concurrency scenarios; it should be a power of 2.
+
+- `consumerThreads`: Number of threads for Disruptor's consumer. Increase this for high-concurrency scenarios.
+
+- `started`: Set this to true for the initiator side and false for the participant side.
+
+- `asyncThreads`: Size of the thread pool for asynchronous execution of confirm and cancel methods. Increase this for high-concurrency scenarios.
+
+Next, the most important aspect: configuring the storage of transaction logs. In our stress tests, I recommend using MongoDB, where the performance ranked as follows: MongoDB > Redis Cluster > MySQL > ZooKeeper.
+
+- If you're using MongoDB for log storage, configure as follows:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+- Here, I recommend using Kryo. Of course, Hmily also supports Hessian, Protostuff, and JDK serialization. In our tests, the performance was in the order: Kryo > Hessian > Protostuff > JDK.
+
+- recoverDelayTime :Delay time for the recovery task (in seconds, default is 120). This parameter should be greater than the timeout set for your RPC calls.
+
+- retryMax : Maximum retry count (default is 3). When your service goes down, the recovery task will execute your cancel or confirm method for a maximum of retryMax times.
+
+- Disruptor's buffer size. Increase this for high-concurrency scenarios; it should be a power of 2.
+
+- consumerThreads: Disruptor's consumer thread count. Increase this for high-concurrency scenarios.
+
+- started: Set this to true for the initiator side and false for the participant side.
+
+- asyncThreads: Size of the thread pool for asynchronous execution of confirm and cancel methods. Increase this for high-concurrency scenarios.
+
+- Next is the most important aspect: configuring the storage of transaction logs. In our stress tests, I recommend using MongoDB, where the performance ranked as follows: MongoDB > Redis Cluster > MySQL > ZooKeeper.
+
+- If you're using MongoDB for log storage, configure as follows:
+
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+- If you're using Redis for log storage, configure as follows:
+
+ - Single node Redis:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+- Redis sentinel mode cluster:
+
+```xml
+
+
+
+
+
+
+
+
+
+```
+
+- Redis cluster mode:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+- If you're using ZooKeeper for log storage, configure as follows:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+- The database configuration has been provided above, and I won't introduce the file-based storage approach.
+- The above is the content shared today, an annotation, and a few configuration lines to easily handle high-concurrency distributed transactions!
diff --git a/src/blog/hmily_introduction.md b/src/blog/hmily_introduction.md
index cb786963fc..56d650d482 100644
--- a/src/blog/hmily_introduction.md
+++ b/src/blog/hmily_introduction.md
@@ -1,628 +1,627 @@
----
-title: Hmily:High-Performance Asynchronous Distributed Transaction TCC Framework
-author: xiaoyu
-date: 2018-09-25
-tag:
- - hmily
- - TCC
-cover: /assets/img/architecture/hmily-framework.png
-head:
- - - meta
- - name: Blog
----
-
-# Hmily Framework Features [https://github.com/yu199195/hmily]
-
-- Seamlessly integrates with Spring and Spring Boot.
-
-- Seamlessly integrates with Dubbo, Spring Cloud, Motan, and other RPC frameworks.
-
-- Supports various transaction log storage methods (Redis, MongoDB, MySQL, etc.).
-
-- Offers multiple serialization methods for different types of logs (Kryo, Protostuff, Hessian).
-
-- Provides automatic transaction recovery.
-
-- Supports embedded transaction dependency propagation.
-
-- Zero-intrusion code and flexible configuration.
-
-# Why is Hmily So High-Performance?
-
-### 1. Asynchronous Read/Write of Transaction Logs Using Disruptor (Disruptor is a Lock-Free, GC-Free Concurrency Framework)
-
-```java
-package com.hmily.tcc.core.disruptor.publisher;
-
-import com.hmily.tcc.common.bean.entity.TccTransaction;
-import com.hmily.tcc.common.enums.EventTypeEnum;
-import com.hmily.tcc.core.concurrent.threadpool.HmilyThreadFactory;
-import com.hmily.tcc.core.coordinator.CoordinatorService;
-import com.hmily.tcc.core.disruptor.event.HmilyTransactionEvent;
-import com.hmily.tcc.core.disruptor.factory.HmilyTransactionEventFactory;
-import com.hmily.tcc.core.disruptor.handler.HmilyConsumerDataHandler;
-import com.hmily.tcc.core.disruptor.translator.HmilyTransactionEventTranslator;
-import com.lmax.disruptor.BlockingWaitStrategy;
-import com.lmax.disruptor.IgnoreExceptionHandler;
-import com.lmax.disruptor.RingBuffer;
-import com.lmax.disruptor.dsl.Disruptor;
-import com.lmax.disruptor.dsl.ProducerType;
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * event publisher.
- *
- * @author xiaoyu(Myth)
- */
-@Component
-public class HmilyTransactionEventPublisher implements DisposableBean {
-
- private Disruptor disruptor;
-
- private final CoordinatorService coordinatorService;
-
- @Autowired
- public HmilyTransactionEventPublisher(final CoordinatorService coordinatorService) {
- this.coordinatorService = coordinatorService;
- }
-
- /**
- * disruptor start.
- *
- * @param bufferSize this is disruptor buffer size.
- * @param threadSize this is disruptor consumer thread size.
- */
- public void start(final int bufferSize, final int threadSize) {
- disruptor = new Disruptor<>(new HmilyTransactionEventFactory(), bufferSize, r -> {
- AtomicInteger index = new AtomicInteger(1);
- return new Thread(null, r, "disruptor-thread-" + index.getAndIncrement());
- }, ProducerType.MULTI, new BlockingWaitStrategy());
-
- final Executor executor = new ThreadPoolExecutor(threadSize, threadSize, 0, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<>(),
- HmilyThreadFactory.create("hmily-log-disruptor", false),
- new ThreadPoolExecutor.AbortPolicy());
-
- HmilyConsumerDataHandler[] consumers = new HmilyConsumerDataHandler[threadSize];
- for (int i = 0; i < threadSize; i++) {
- consumers[i] = new HmilyConsumerDataHandler(executor, coordinatorService);
- }
- disruptor.handleEventsWithWorkerPool(consumers);
- disruptor.setDefaultExceptionHandler(new IgnoreExceptionHandler());
- disruptor.start();
- }
-
- /**
- * publish disruptor event.
- *
- * @param tccTransaction {@linkplain com.hmily.tcc.common.bean.entity.TccTransaction }
- * @param type {@linkplain EventTypeEnum}
- */
- public void publishEvent(final TccTransaction tccTransaction, final int type) {
- final RingBuffer ringBuffer = disruptor.getRingBuffer();
- ringBuffer.publishEvent(new HmilyTransactionEventTranslator(type), tccTransaction);
- }
-
- @Override
- public void destroy() {
- disruptor.shutdown();
- }
-}
-```
-
-- The default value of bufferSize here is 4094 \* 4, which can be configured based on the user's requirements.
-
-```java
-
- HmilyConsumerDataHandler[] consumers = new HmilyConsumerDataHandler[threadSize];
- for (int i = 0; i < threadSize; i++) {
- consumers[i] = new HmilyConsumerDataHandler(executor, coordinatorService);
- }
- disruptor.handleEventsWithWorkerPool(consumers);
-```
-
-- Multiple consumers are employed here to process tasks in the queue.
-
-### 2.Asynchronous Execution of Confirm and Cancel Methods
-
-```java
-package com.hmily.tcc.core.service.handler;
-
-import com.hmily.tcc.common.bean.context.TccTransactionContext;
-import com.hmily.tcc.common.bean.entity.TccTransaction;
-import com.hmily.tcc.common.enums.TccActionEnum;
-import com.hmily.tcc.core.concurrent.threadpool.HmilyThreadFactory;
-import com.hmily.tcc.core.service.HmilyTransactionHandler;
-import com.hmily.tcc.core.service.executor.HmilyTransactionExecutor;
-import org.aspectj.lang.ProceedingJoinPoint;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * this is transaction starter.
- *
- * @author xiaoyu
- */
-@Component
-public class StarterHmilyTransactionHandler implements HmilyTransactionHandler {
-
- private static final int MAX_THREAD = Runtime.getRuntime().availableProcessors() << 1;
-
- private final HmilyTransactionExecutor hmilyTransactionExecutor;
-
- private final Executor executor = new ThreadPoolExecutor(MAX_THREAD, MAX_THREAD, 0, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<>(),
- HmilyThreadFactory.create("hmily-execute", false),
- new ThreadPoolExecutor.AbortPolicy());
-
- @Autowired
- public StarterHmilyTransactionHandler(final HmilyTransactionExecutor hmilyTransactionExecutor) {
- this.hmilyTransactionExecutor = hmilyTransactionExecutor;
- }
-
- @Override
- public Object handler(final ProceedingJoinPoint point, final TccTransactionContext context)
- throws Throwable {
- Object returnValue;
- try {
- TccTransaction tccTransaction = hmilyTransactionExecutor.begin(point);
- try {
- //execute try
- returnValue = point.proceed();
- tccTransaction.setStatus(TccActionEnum.TRYING.getCode());
- hmilyTransactionExecutor.updateStatus(tccTransaction);
- } catch (Throwable throwable) {
- //if exception ,execute cancel
- final TccTransaction currentTransaction = hmilyTransactionExecutor.getCurrentTransaction();
- executor.execute(() -> hmilyTransactionExecutor
- .cancel(currentTransaction));
- throw throwable;
- }
- //execute confirm
- final TccTransaction currentTransaction = hmilyTransactionExecutor.getCurrentTransaction();
- executor.execute(() -> hmilyTransactionExecutor.confirm(currentTransaction));
- } finally {
- hmilyTransactionExecutor.remove();
- }
- return returnValue;
- }
-}
-```
-
-- When an exception occurs in the try method's AOP aspect, the cancel method is executed asynchronously using a thread pool. If there is no exception, the confirm method is executed.
-
-### A question might arise: What if the cancel or confirm methods themselves throw exceptions?
-
-Answer: This scenario is quite rare because you've just finished executing the try phase moments ago. Moreover, if such an exception arises, the framework has a built-in scheduling thread pool for recovery, so there's no need to worry.
-
-### Another question might arise: What if there's an exception during log storage?
-
-Answer: First, this is an edge case; second, the log configuration parameters are required during framework startup. Even if log storage fails during runtime, the framework will utilize cached logs, ensuring correct program execution. Lastly, if log storage fails and the system crashes under extremely rare circumstances, well, congratulations, you can consider buying a lottery ticket. The best solution is to not overly concern yourself with such a scenario.
-
-### 3.Use of ThreadLocal Cache
-
-```java
- /**
- * transaction begin.
- *
- * @param point cut point.
- * @return TccTransaction
- */
- public TccTransaction begin(final ProceedingJoinPoint point) {
- LogUtil.debug(LOGGER, () -> "......hmily transaction!start....");
- //build tccTransaction
- final TccTransaction tccTransaction = buildTccTransaction(point, TccRoleEnum.START.getCode(), null);
- //save tccTransaction in threadLocal
- CURRENT.set(tccTransaction);
- //publishEvent
- hmilyTransactionEventPublisher.publishEvent(tccTransaction, EventTypeEnum.SAVE.getCode());
- //set TccTransactionContext this context transfer remote
- TccTransactionContext context = new TccTransactionContext();
- //set action is try
- context.setAction(TccActionEnum.TRYING.getCode());
- context.setTransId(tccTransaction.getTransId());
- context.setRole(TccRoleEnum.START.getCode());
- TransactionContextLocal.getInstance().set(context);
- return tccTransaction;
- }
-```
-
-- It's important to understand that the ThreadLocal cache holds transaction information for the initiator method. RPC calls form a chain of invocation, ensuring proper storage.
-
-```java
-
-/**
- * add participant.
- *
- * @param participant {@linkplain Participant}
- */
- public void enlistParticipant(final Participant participant) {
- if (Objects.isNull(participant)) {
- return;
- }
- Optional.ofNullable(getCurrentTransaction())
- .ifPresent(c -> {
- c.registerParticipant(participant);
- updateParticipant(c);
- });
- }
-```
-
-### 4.Usage of Guava Cache
-
-```java
-package com.hmily.tcc.core.cache;
-
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
-import com.google.common.cache.Weigher;
-import com.hmily.tcc.common.bean.entity.TccTransaction;
-import com.hmily.tcc.core.coordinator.CoordinatorService;
-import com.hmily.tcc.core.helper.SpringBeanUtils;
-import org.apache.commons.lang3.StringUtils;
-
-import java.util.Optional;
-import java.util.concurrent.ExecutionException;
-
-/**
- * use google guava cache.
- * @author xiaoyu
- */
-public final class TccTransactionCacheManager {
-
- private static final int MAX_COUNT = 10000;
-
- private static final LoadingCache LOADING_CACHE =
- CacheBuilder.newBuilder().maximumWeight(MAX_COUNT)
- .weigher((Weigher) (string, tccTransaction) -> getSize())
- .build(new CacheLoader() {
- @Override
- public TccTransaction load(final String key) {
- return cacheTccTransaction(key);
- }
- });
-
- private static CoordinatorService coordinatorService = SpringBeanUtils.getInstance().getBean(CoordinatorService.class);
-
- private static final TccTransactionCacheManager TCC_TRANSACTION_CACHE_MANAGER = new TccTransactionCacheManager();
-
- private TccTransactionCacheManager() {
-
- }
-
- /**
- * TccTransactionCacheManager.
- *
- * @return TccTransactionCacheManager
- */
- public static TccTransactionCacheManager getInstance() {
- return TCC_TRANSACTION_CACHE_MANAGER;
- }
-
- private static int getSize() {
- return (int) LOADING_CACHE.size();
- }
-
- private static TccTransaction cacheTccTransaction(final String key) {
- return Optional.ofNullable(coordinatorService.findByTransId(key)).orElse(new TccTransaction());
- }
-
- /**
- * cache tccTransaction.
- *
- * @param tccTransaction {@linkplain TccTransaction}
- */
- public void cacheTccTransaction(final TccTransaction tccTransaction) {
- LOADING_CACHE.put(tccTransaction.getTransId(), tccTransaction);
- }
-
- /**
- * acquire TccTransaction.
- *
- * @param key this guava key.
- * @return {@linkplain TccTransaction}
- */
- public TccTransaction getTccTransaction(final String key) {
- try {
- return LOADING_CACHE.get(key);
- } catch (ExecutionException e) {
- return new TccTransaction();
- }
- }
-
- /**
- * remove guava cache by key.
- * @param key guava cache key.
- */
- public void removeByKey(final String key) {
- if (StringUtils.isNotEmpty(key)) {
- LOADING_CACHE.invalidate(key);
- }
- }
-
-}
-```
-
-- Among the participants, we used ThreadLocal, but why don't we use it among the participants?
- There are actually two reasons: First, because try and confirm will not be in the same thread, which will cause ThreadLocal to fail. When considering RPC clusters, it may be load balanced to different machines. Here is a detail:
-
-```java
- private static TccTransaction cacheTccTransaction(final String key) {
- return Optional.ofNullable(coordinatorService.findByTransId(key)).orElse(new TccTransaction());
- }
-```
-
-When the Guava Cache doesn't have a particular entry, it queries the log for that entry, ensuring support for clustered environments.
-
-### These four aspects collectively make Hmily an asynchronous, high-performance distributed TCC framework.
-
-### How to Use Hmily?(https://github.com/yu199195/hmily/tree/master/hmily-tcc-demo)
-
-Due to package naming issues, the framework package hasn't been uploaded to the Maven Central Repository. Therefore, users need to clone the code, compile it, and deploy it to their private repository.
-
-### 1.For Dubbo Users
-
-- Include in your API project:
-
-```xml
-
-
- com.hmily.tcc
- hmily-tcc-annotation
- {you version}
-
-```
-
-- Include in your service provider project:
-
-```xml
-
- com.hmily.tcc
- hmily-tcc-dubbo
- {you version}
-
-```
-
-- Configure the startup bean in your XML configuration.
-
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-- Of course there are many configuration properties, here I only gave a demo. For details, you can refer to this class:
-
-```java
-package com.hmily.tcc.common.config;
-
-import com.hmily.tcc.common.enums.RepositorySupportEnum;
-import lombok.Data;
-
-/**
- * hmily config.
- *
- * @author xiaoyu
- */
-@Data
-public class TccConfig {
-
-
- /**
- * Resource suffix this parameter please fill in about is the transaction store path.
- * If it's a table store this is a table suffix, it's stored the same way.
- * If this parameter is not filled in, the applicationName of the application is retrieved by default
- */
- private String repositorySuffix;
-
- /**
- * log serializer.
- * {@linkplain com.hmily.tcc.common.enums.SerializeEnum}
- */
- private String serializer = "kryo";
-
- /**
- * scheduledPool Thread size.
- */
- private int scheduledThreadMax = Runtime.getRuntime().availableProcessors() << 1;
-
- /**
- * scheduledPool scheduledDelay unit SECONDS.
- */
- private int scheduledDelay = 60;
-
- /**
- * retry max.
- */
- private int retryMax = 3;
-
- /**
- * recoverDelayTime Unit seconds
- * (note that this time represents how many seconds after the local transaction was created before execution).
- */
- private int recoverDelayTime = 60;
-
- /**
- * Parameters when participants perform their own recovery.
- * 1.such as RPC calls time out
- * 2.such as the starter down machine
- */
- private int loadFactor = 2;
-
- /**
- * repositorySupport.
- * {@linkplain RepositorySupportEnum}
- */
- private String repositorySupport = "db";
-
- /**
- * disruptor bufferSize.
- */
- private int bufferSize = 4096 * 2 * 2;
-
- /**
- * this is disruptor consumerThreads.
- */
- private int consumerThreads = Runtime.getRuntime().availableProcessors() << 1;
-
- /**
- * db config.
- */
- private TccDbConfig tccDbConfig;
-
- /**
- * mongo config.
- */
- private TccMongoConfig tccMongoConfig;
-
- /**
- * redis config.
- */
- private TccRedisConfig tccRedisConfig;
-
- /**
- * zookeeper config.
- */
- private TccZookeeperConfig tccZookeeperConfig;
-
- /**
- * file config.
- */
- private TccFileConfig tccFileConfig;
-
-}
-```
-
-### SpringCloud Users
-
-```xml
-
- com.hmily.tcc
- hmily-tcc-springcloud
- {you version}
-
-```
-
-### Motan Users
-
-```xml
-
- com.hmily.tcc
- hmily-tcc-motan
- {you version}
-
-```
-
-### hmily-spring-boot-start - this makes it even easier, you just need to import different jar packages according to your RPC framework.
-
-- For Dubbo users, add:
-
-```xml
-
- com.hmily.tcc
- hmily-tcc-spring-boot-starter-dubbo
- ${your version}
-
-```
-
-- For Spring Cloud users, add:
-
-```xml
-
- com.hmily.tcc
- hmily-tcc-spring-boot-starter-springcloud
- ${your version}
-
-```
-
-- For Motan users, add:
-
-```xml
-
- com.hmily.tcc
- hmily-tcc-spring-boot-starter-motan
- ${your version}
-
-```
-
-- Next, configure the settings in your YML file:
-
-```yml
-hmily:
- tcc:
- serializer: kryo
- recoverDelayTime: 128
- retryMax: 3
- scheduledDelay: 128
- scheduledThreadMax: 10
- repositorySupport: db
- tccDbConfig:
- driverClassName: com.mysql.jdbc.Driver
- url: jdbc:mysql://192.168.1.98:3306/tcc?useUnicode=true&characterEncoding=utf8
- username: root
- password: 123456
-
- #repositorySupport : redis
- #tccRedisConfig:
- #masterName: mymaster
- #sentinel : true
- #sentinelUrl : 192.168.1.91:26379;192.168.1.92:26379;192.168.1.93:26379
- #password : foobaredbbexONE123
-
- # repositorySupport : zookeeper
- # host : 92.168.1.73:2181
- # sessionTimeOut : 100000
- # rootPath : /tcc
-
- # repositorySupport : mongodb
- # mongoDbUrl : 192.168.1.68:27017
- # mongoDbName : happylife
- # mongoUserName : xiaoyu
- # mongoUserPwd : 123456
-
- # repositorySupport : file
- # path : /account
- # prefix : account
-```
-
-- Using Hmily is simple. Just annotate your interface methods with @Tcc, and you're good to go.
-
-- Please note that due to space constraints, some intricate details have been summarized. For those interested, you can star and fork the project on GitHub and join the WeChat group or QQ group for discussions.
-
-- GitHub repository: https://github.com/yu199195/hmily
-
-- Thank you once again! If you're interested, you're welcome to provide any valuable PR contributions."
+---
+title: Hmily:High-Performance Asynchronous Distributed Transaction TCC Framework
+author: xiaoyu
+date: 2018-09-25
+tag:
+ - hmily
+ - TCC
+cover: /assets/img/architecture/hmily-framework.png
+head:
+ - - meta
+ - name: Blog
+---
+
+# Hmily Framework Features [https://github.com/yu199195/hmily]
+
+- Seamlessly integrates with Spring and Spring Boot.
+
+- Seamlessly integrates with Dubbo, Spring Cloud, Motan, and other RPC frameworks.
+
+- Supports various transaction log storage methods (Redis, MongoDB, MySQL, etc.).
+
+- Offers multiple serialization methods for different types of logs (Kryo, Protostuff, Hessian).
+
+- Provides automatic transaction recovery.
+
+- Supports embedded transaction dependency propagation.
+
+- Zero-intrusion code and flexible configuration.
+
+# Why is Hmily So High-Performance?
+
+### 1. Asynchronous Read/Write of Transaction Logs Using Disruptor (Disruptor is a Lock-Free, GC-Free Concurrency Framework)
+
+```java
+package com.hmily.tcc.core.disruptor.publisher;
+
+import com.hmily.tcc.common.bean.entity.TccTransaction;
+import com.hmily.tcc.common.enums.EventTypeEnum;
+import com.hmily.tcc.core.concurrent.threadpool.HmilyThreadFactory;
+import com.hmily.tcc.core.coordinator.CoordinatorService;
+import com.hmily.tcc.core.disruptor.event.HmilyTransactionEvent;
+import com.hmily.tcc.core.disruptor.factory.HmilyTransactionEventFactory;
+import com.hmily.tcc.core.disruptor.handler.HmilyConsumerDataHandler;
+import com.hmily.tcc.core.disruptor.translator.HmilyTransactionEventTranslator;
+import com.lmax.disruptor.BlockingWaitStrategy;
+import com.lmax.disruptor.IgnoreExceptionHandler;
+import com.lmax.disruptor.RingBuffer;
+import com.lmax.disruptor.dsl.Disruptor;
+import com.lmax.disruptor.dsl.ProducerType;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * event publisher.
+ *
+ * @author xiaoyu(Myth)
+ */
+@Component
+public class HmilyTransactionEventPublisher implements DisposableBean {
+
+ private Disruptor disruptor;
+
+ private final CoordinatorService coordinatorService;
+
+ @Autowired
+ public HmilyTransactionEventPublisher(final CoordinatorService coordinatorService) {
+ this.coordinatorService = coordinatorService;
+ }
+
+ /**
+ * disruptor start.
+ *
+ * @param bufferSize this is disruptor buffer size.
+ * @param threadSize this is disruptor consumer thread size.
+ */
+ public void start(final int bufferSize, final int threadSize) {
+ disruptor = new Disruptor<>(new HmilyTransactionEventFactory(), bufferSize, r -> {
+ AtomicInteger index = new AtomicInteger(1);
+ return new Thread(null, r, "disruptor-thread-" + index.getAndIncrement());
+ }, ProducerType.MULTI, new BlockingWaitStrategy());
+
+ final Executor executor = new ThreadPoolExecutor(threadSize, threadSize, 0, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<>(),
+ HmilyThreadFactory.create("hmily-log-disruptor", false),
+ new ThreadPoolExecutor.AbortPolicy());
+
+ HmilyConsumerDataHandler[] consumers = new HmilyConsumerDataHandler[threadSize];
+ for (int i = 0; i < threadSize; i++) {
+ consumers[i] = new HmilyConsumerDataHandler(executor, coordinatorService);
+ }
+ disruptor.handleEventsWithWorkerPool(consumers);
+ disruptor.setDefaultExceptionHandler(new IgnoreExceptionHandler());
+ disruptor.start();
+ }
+
+ /**
+ * publish disruptor event.
+ *
+ * @param tccTransaction {@linkplain com.hmily.tcc.common.bean.entity.TccTransaction }
+ * @param type {@linkplain EventTypeEnum}
+ */
+ public void publishEvent(final TccTransaction tccTransaction, final int type) {
+ final RingBuffer ringBuffer = disruptor.getRingBuffer();
+ ringBuffer.publishEvent(new HmilyTransactionEventTranslator(type), tccTransaction);
+ }
+
+ @Override
+ public void destroy() {
+ disruptor.shutdown();
+ }
+}
+```
+
+- The default value of bufferSize here is 4094 \* 4, which can be configured based on the user's requirements.
+
+```java
+
+ HmilyConsumerDataHandler[] consumers = new HmilyConsumerDataHandler[threadSize];
+ for (int i = 0; i < threadSize; i++) {
+ consumers[i] = new HmilyConsumerDataHandler(executor, coordinatorService);
+ }
+ disruptor.handleEventsWithWorkerPool(consumers);
+```
+
+- Multiple consumers are employed here to process tasks in the queue.
+
+### 2.Asynchronous Execution of Confirm and Cancel Methods
+
+```java
+package com.hmily.tcc.core.service.handler;
+
+import com.hmily.tcc.common.bean.context.TccTransactionContext;
+import com.hmily.tcc.common.bean.entity.TccTransaction;
+import com.hmily.tcc.common.enums.TccActionEnum;
+import com.hmily.tcc.core.concurrent.threadpool.HmilyThreadFactory;
+import com.hmily.tcc.core.service.HmilyTransactionHandler;
+import com.hmily.tcc.core.service.executor.HmilyTransactionExecutor;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * this is transaction starter.
+ *
+ * @author xiaoyu
+ */
+@Component
+public class StarterHmilyTransactionHandler implements HmilyTransactionHandler {
+
+ private static final int MAX_THREAD = Runtime.getRuntime().availableProcessors() << 1;
+
+ private final HmilyTransactionExecutor hmilyTransactionExecutor;
+
+ private final Executor executor = new ThreadPoolExecutor(MAX_THREAD, MAX_THREAD, 0, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<>(),
+ HmilyThreadFactory.create("hmily-execute", false),
+ new ThreadPoolExecutor.AbortPolicy());
+
+ @Autowired
+ public StarterHmilyTransactionHandler(final HmilyTransactionExecutor hmilyTransactionExecutor) {
+ this.hmilyTransactionExecutor = hmilyTransactionExecutor;
+ }
+
+ @Override
+ public Object handler(final ProceedingJoinPoint point, final TccTransactionContext context)
+ throws Throwable {
+ Object returnValue;
+ try {
+ TccTransaction tccTransaction = hmilyTransactionExecutor.begin(point);
+ try {
+ //execute try
+ returnValue = point.proceed();
+ tccTransaction.setStatus(TccActionEnum.TRYING.getCode());
+ hmilyTransactionExecutor.updateStatus(tccTransaction);
+ } catch (Throwable throwable) {
+ //if exception ,execute cancel
+ final TccTransaction currentTransaction = hmilyTransactionExecutor.getCurrentTransaction();
+ executor.execute(() -> hmilyTransactionExecutor
+ .cancel(currentTransaction));
+ throw throwable;
+ }
+ //execute confirm
+ final TccTransaction currentTransaction = hmilyTransactionExecutor.getCurrentTransaction();
+ executor.execute(() -> hmilyTransactionExecutor.confirm(currentTransaction));
+ } finally {
+ hmilyTransactionExecutor.remove();
+ }
+ return returnValue;
+ }
+}
+```
+
+- When an exception occurs in the try method's AOP aspect, the cancel method is executed asynchronously using a thread pool. If there is no exception, the confirm method is executed.
+
+### A question might arise: What if the cancel or confirm methods themselves throw exceptions?
+
+Answer: This scenario is quite rare because you've just finished executing the try phase moments ago. Moreover, if such an exception arises, the framework has a built-in scheduling thread pool for recovery, so there's no need to worry.
+
+### Another question might arise: What if there's an exception during log storage?
+
+Answer: First, this is an edge case; second, the log configuration parameters are required during framework startup. Even if log storage fails during runtime, the framework will utilize cached logs, ensuring correct program execution. Lastly, if log storage fails and the system crashes under extremely rare circumstances, well, congratulations, you can consider buying a lottery ticket. The best solution is to not overly concern yourself with such a scenario.
+
+### 3.Use of ThreadLocal Cache
+
+```java
+ /**
+ * transaction begin.
+ *
+ * @param point cut point.
+ * @return TccTransaction
+ */
+ public TccTransaction begin(final ProceedingJoinPoint point) {
+ LogUtil.debug(LOGGER, () -> "......hmily transaction!start....");
+ //build tccTransaction
+ final TccTransaction tccTransaction = buildTccTransaction(point, TccRoleEnum.START.getCode(), null);
+ //save tccTransaction in threadLocal
+ CURRENT.set(tccTransaction);
+ //publishEvent
+ hmilyTransactionEventPublisher.publishEvent(tccTransaction, EventTypeEnum.SAVE.getCode());
+ //set TccTransactionContext this context transfer remote
+ TccTransactionContext context = new TccTransactionContext();
+ //set action is try
+ context.setAction(TccActionEnum.TRYING.getCode());
+ context.setTransId(tccTransaction.getTransId());
+ context.setRole(TccRoleEnum.START.getCode());
+ TransactionContextLocal.getInstance().set(context);
+ return tccTransaction;
+ }
+```
+
+- It's important to understand that the ThreadLocal cache holds transaction information for the initiator method. RPC calls form a chain of invocation, ensuring proper storage.
+
+```java
+
+/**
+ * add participant.
+ *
+ * @param participant {@linkplain Participant}
+ */
+ public void enlistParticipant(final Participant participant) {
+ if (Objects.isNull(participant)) {
+ return;
+ }
+ Optional.ofNullable(getCurrentTransaction())
+ .ifPresent(c -> {
+ c.registerParticipant(participant);
+ updateParticipant(c);
+ });
+ }
+```
+
+### 4.Usage of Guava Cache
+
+```java
+package com.hmily.tcc.core.cache;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.Weigher;
+import com.hmily.tcc.common.bean.entity.TccTransaction;
+import com.hmily.tcc.core.coordinator.CoordinatorService;
+import com.hmily.tcc.core.helper.SpringBeanUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Optional;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * use google guava cache.
+ * @author xiaoyu
+ */
+public final class TccTransactionCacheManager {
+
+ private static final int MAX_COUNT = 10000;
+
+ private static final LoadingCache LOADING_CACHE =
+ CacheBuilder.newBuilder().maximumWeight(MAX_COUNT)
+ .weigher((Weigher) (string, tccTransaction) -> getSize())
+ .build(new CacheLoader() {
+ @Override
+ public TccTransaction load(final String key) {
+ return cacheTccTransaction(key);
+ }
+ });
+
+ private static CoordinatorService coordinatorService = SpringBeanUtils.getInstance().getBean(CoordinatorService.class);
+
+ private static final TccTransactionCacheManager TCC_TRANSACTION_CACHE_MANAGER = new TccTransactionCacheManager();
+
+ private TccTransactionCacheManager() {
+
+ }
+
+ /**
+ * TccTransactionCacheManager.
+ *
+ * @return TccTransactionCacheManager
+ */
+ public static TccTransactionCacheManager getInstance() {
+ return TCC_TRANSACTION_CACHE_MANAGER;
+ }
+
+ private static int getSize() {
+ return (int) LOADING_CACHE.size();
+ }
+
+ private static TccTransaction cacheTccTransaction(final String key) {
+ return Optional.ofNullable(coordinatorService.findByTransId(key)).orElse(new TccTransaction());
+ }
+
+ /**
+ * cache tccTransaction.
+ *
+ * @param tccTransaction {@linkplain TccTransaction}
+ */
+ public void cacheTccTransaction(final TccTransaction tccTransaction) {
+ LOADING_CACHE.put(tccTransaction.getTransId(), tccTransaction);
+ }
+
+ /**
+ * acquire TccTransaction.
+ *
+ * @param key this guava key.
+ * @return {@linkplain TccTransaction}
+ */
+ public TccTransaction getTccTransaction(final String key) {
+ try {
+ return LOADING_CACHE.get(key);
+ } catch (ExecutionException e) {
+ return new TccTransaction();
+ }
+ }
+
+ /**
+ * remove guava cache by key.
+ * @param key guava cache key.
+ */
+ public void removeByKey(final String key) {
+ if (StringUtils.isNotEmpty(key)) {
+ LOADING_CACHE.invalidate(key);
+ }
+ }
+
+}
+```
+
+- Among the participants, we used ThreadLocal, but why don't we use it among the participants?
+ There are actually two reasons: First, because try and confirm will not be in the same thread, which will cause ThreadLocal to fail. When considering RPC clusters, it may be load balanced to different machines. Here is a detail:
+
+```java
+ private static TccTransaction cacheTccTransaction(final String key) {
+ return Optional.ofNullable(coordinatorService.findByTransId(key)).orElse(new TccTransaction());
+ }
+```
+
+When the Guava Cache doesn't have a particular entry, it queries the log for that entry, ensuring support for clustered environments.
+
+### These four aspects collectively make Hmily an asynchronous, high-performance distributed TCC framework.
+
+### How to Use Hmily?(https://github.com/yu199195/hmily/tree/master/hmily-tcc-demo)
+
+Due to package naming issues, the framework package hasn't been uploaded to the Maven Central Repository. Therefore, users need to clone the code, compile it, and deploy it to their private repository.
+
+### 1.For Dubbo Users
+
+- Include in your API project:
+
+```xml
+
+ com.hmily.tcc
+ hmily-tcc-annotation
+ {you version}
+
+```
+
+- Include in your service provider project:
+
+```xml
+
+ com.hmily.tcc
+ hmily-tcc-dubbo
+ {you version}
+
+```
+
+- Configure the startup bean in your XML configuration.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+- Of course there are many configuration properties, here I only gave a demo. For details, you can refer to this class:
+
+```java
+package com.hmily.tcc.common.config;
+
+import com.hmily.tcc.common.enums.RepositorySupportEnum;
+import lombok.Data;
+
+/**
+ * hmily config.
+ *
+ * @author xiaoyu
+ */
+@Data
+public class TccConfig {
+
+
+ /**
+ * Resource suffix this parameter please fill in about is the transaction store path.
+ * If it's a table store this is a table suffix, it's stored the same way.
+ * If this parameter is not filled in, the applicationName of the application is retrieved by default
+ */
+ private String repositorySuffix;
+
+ /**
+ * log serializer.
+ * {@linkplain com.hmily.tcc.common.enums.SerializeEnum}
+ */
+ private String serializer = "kryo";
+
+ /**
+ * scheduledPool Thread size.
+ */
+ private int scheduledThreadMax = Runtime.getRuntime().availableProcessors() << 1;
+
+ /**
+ * scheduledPool scheduledDelay unit SECONDS.
+ */
+ private int scheduledDelay = 60;
+
+ /**
+ * retry max.
+ */
+ private int retryMax = 3;
+
+ /**
+ * recoverDelayTime Unit seconds
+ * (note that this time represents how many seconds after the local transaction was created before execution).
+ */
+ private int recoverDelayTime = 60;
+
+ /**
+ * Parameters when participants perform their own recovery.
+ * 1.such as RPC calls time out
+ * 2.such as the starter down machine
+ */
+ private int loadFactor = 2;
+
+ /**
+ * repositorySupport.
+ * {@linkplain RepositorySupportEnum}
+ */
+ private String repositorySupport = "db";
+
+ /**
+ * disruptor bufferSize.
+ */
+ private int bufferSize = 4096 * 2 * 2;
+
+ /**
+ * this is disruptor consumerThreads.
+ */
+ private int consumerThreads = Runtime.getRuntime().availableProcessors() << 1;
+
+ /**
+ * db config.
+ */
+ private TccDbConfig tccDbConfig;
+
+ /**
+ * mongo config.
+ */
+ private TccMongoConfig tccMongoConfig;
+
+ /**
+ * redis config.
+ */
+ private TccRedisConfig tccRedisConfig;
+
+ /**
+ * zookeeper config.
+ */
+ private TccZookeeperConfig tccZookeeperConfig;
+
+ /**
+ * file config.
+ */
+ private TccFileConfig tccFileConfig;
+
+}
+```
+
+### SpringCloud Users
+
+```xml
+
+ com.hmily.tcc
+ hmily-tcc-springcloud
+ {you version}
+
+```
+
+### Motan Users
+
+```xml
+
+ com.hmily.tcc
+ hmily-tcc-motan
+ {you version}
+
+```
+
+### hmily-spring-boot-start - this makes it even easier, you just need to import different jar packages according to your RPC framework.
+
+- For Dubbo users, add:
+
+```xml
+
+ com.hmily.tcc
+ hmily-tcc-spring-boot-starter-dubbo
+ ${your version}
+
+```
+
+- For Spring Cloud users, add:
+
+```xml
+
+ com.hmily.tcc
+ hmily-tcc-spring-boot-starter-springcloud
+ ${your version}
+
+```
+
+- For Motan users, add:
+
+```xml
+
+ com.hmily.tcc
+ hmily-tcc-spring-boot-starter-motan
+ ${your version}
+
+```
+
+- Next, configure the settings in your YML file:
+
+```yml
+hmily:
+ tcc:
+ serializer: kryo
+ recoverDelayTime: 128
+ retryMax: 3
+ scheduledDelay: 128
+ scheduledThreadMax: 10
+ repositorySupport: db
+ tccDbConfig:
+ driverClassName: com.mysql.jdbc.Driver
+ url: jdbc:mysql://192.168.1.98:3306/tcc?useUnicode=true&characterEncoding=utf8
+ username: root
+ password: 123456
+
+ #repositorySupport : redis
+ #tccRedisConfig:
+ #masterName: mymaster
+ #sentinel : true
+ #sentinelUrl : 192.168.1.91:26379;192.168.1.92:26379;192.168.1.93:26379
+ #password : foobaredbbexONE123
+
+ # repositorySupport : zookeeper
+ # host : 92.168.1.73:2181
+ # sessionTimeOut : 100000
+ # rootPath : /tcc
+
+ # repositorySupport : mongodb
+ # mongoDbUrl : 192.168.1.68:27017
+ # mongoDbName : happylife
+ # mongoUserName : xiaoyu
+ # mongoUserPwd : 123456
+
+ # repositorySupport : file
+ # path : /account
+ # prefix : account
+```
+
+- Using Hmily is simple. Just annotate your interface methods with @Tcc, and you're good to go.
+
+- Please note that due to space constraints, some intricate details have been summarized. For those interested, you can star and fork the project on GitHub and join the WeChat group or QQ group for discussions.
+
+- GitHub repository: https://github.com/yu199195/hmily
+
+- Thank you once again! If you're interested, you're welcome to provide any valuable PR contributions."
diff --git a/src/blog/milvus-easyai-face-java.md b/src/blog/milvus-easyai-face-java.md
new file mode 100644
index 0000000000..2c82019600
--- /dev/null
+++ b/src/blog/milvus-easyai-face-java.md
@@ -0,0 +1,131 @@
+---
+title: "Milvus × EasyAi: How to Build a Face Recognition Application from Scratch with Java"
+author: December 26, 2024, 08:36
+date: 2024-12-26
+cover: /assets/img/blog/milvus-easyai-face-java-0.webp
+head:
+ - - meta
+ - name: Blog
+---
+
+
+
+
+
+**How to build a face recognition application from scratch? Try the combination of native Java artificial intelligence algorithms: EasyAi + Milvus**.
+
+**The software and tools used in this article include**:
+
+* EasyAi: Face feature vector extraction
+* Milvus: Vector database for efficient storage and retrieval of data.
+
+**01**.
+
+**EasyAi: The Most Popular Java AI Algorithm Framework in China**
+
+As a pure Java framework for developing AI applications, EasyAi has no dependencies. It is a native Java artificial intelligence algorithm framework. First, it can be seamlessly integrated into our Java project via Maven with one click, requiring no additional environment configuration or dependencies, making it ready to use out of the box. Furthermore, it includes pre-packaged modules for image target detection and AI customer service, as well as underlying algorithm tools for deep learning, machine learning, reinforcement learning, heuristic learning, matrix operations, derivative functions, partial derivative functions, and more. With minimal learning, developers can deeply customize micro-models that fit their business needs.
+
+**02**.
+
+**EasyAi-Face: A Face Recognition Application Based on Easy-Ai**
+
+**1. Generate an average human face**: Scale all face samples to a uniform size, crop excess parts from the top and bottom, pad zeros where insufficient, sum all pixel channels, calculate the average, and output the average face.
+
+**2. Use a pre-trained face localization fastYolo model** to locate faces in the target photo, setting a threshold so that only detections with confidence above this threshold are considered faces.
+
+**3. Obtain the face bounding box with the highest confidence in the target photo** and perform a secondary correction on the face position based on this bounding box.
+
+_Secondary correction solution:_
+
+* Use particle swarm optimization with four feature dimensions to seek the optimal solution: the x and y coordinates of the top-left corner of the face and the width and height. The adaptive function returns the value set to minimize for optimality. The activity range of the xy and width-height four-dimensional particles is adjusted, with upper and lower limits set to ±50 pixels around the coordinates and dimensions from the initial localization (adjustable).
+* The adaptation function calculation process involves cropping the face based on the coordinates locked by the four-dimensional particles, scaling it to a specified smaller size using the same scaling method as before, and converting their grayscale channels into probability distributions via softMax for the entire matrix.
+* Compare the Euclidean distance between the average face and the grayscale probability image of the face locked by the particles at this moment, and return it. Let the particles explore (within a specified number of iterations) for the minimum optimal solution.
+
+**4. Extract facial features**: Obtain the optimal coordinates found by the particles, crop the image based on these coordinates, and take the top 70% of the height (discarding the mouth area, as it is less stable). Extract the LBP (Local Binary Pattern) texture features of the resulting image.
+
+**03**.
+
+**Building a Face Recognition Application with EasyAi-Face + Milvus**
+
+**3.1 Extracting Facial Features**
+
+**Add Dependency**
+```xml
+
+ org.dromara.easyai
+ seeFace
+ 1.0.5
+
+```
+**Initialize Face**
+```java
+@Bean
+public Face face(FaceConfig faceConfig ){
+ if (StringUtils.isNotBlank(faceConfig.getAvgFace()) && StringUtils.isNotBlank(faceConfig.getFaceModel())){
+ return FaceFactory.getFace(faceConfig.getAvgFace(), faceConfig.getFaceModel());
+ }
+ return FaceFactory.getFace();
+}
+```
+**Extract Facial Features**
+```java
+private List getFloats(InputStream inputStream) {
+ ThreeChannelMatrix m = Picture.getThreeMatrix(inputStream, false);
+ ErrorMessage errorMessage = face.look(m, idWorker.nextId(), 30);
+ final Matrix feature = errorMessage.getFaceMessage().getFeature();
+ return MatrixUtil.matrixToFloatList(feature);
+}
+```
+**3.2 Store in Vector Database**
+```java
+public void initUserVector(UserDTO userDTO, List features) {
+ List names = Collections.singletonList(userDTO.getUserName());
+ List userIds = Collections.singletonList(userDTO.getUserId());
+ List getFaceUrl = Collections.singletonList(userDTO.getFaceUrl());
+ List getFaceFeatureUrl = Collections.singletonList(userDTO.getFaceFeatureUrl());
+ List> vectors = Collections.singletonList(features);
+ List fields = new ArrayList();
+ fields.add(new Field("vector", vectors));
+ fields.add(new Field("face_url", getFaceUrl));
+ fields.add(new Field("face_feature_url", getFaceFeatureUrl));
+ fields.add(new Field("user_id", userIds));
+ fields.add(new Field("user_name", names));
+ InsertParam insertParam = InsertParam.newBuilder().withCollectionName(milvusConfig.getCollectionName()).withFields(fields).build();
+ this.milvusClient.insert(insertParam);
+}
+```
+**3.3 [Recognize Faces] L2 Similarity Search for Facial Features**
+```java
+public List search(List floatList, Integer topK) {
+ final List idScoreList = vectorService.search(floatList, topK);
+ List list = new ArrayList<>();
+ idScoreList.forEach(idScore -> {
+ UserDTO imageDTO = new UserDTO();
+ final float score = idScore.getScore();
+ final Map fieldValues = idScore.getFieldValues();
+ imageDTO.setAutoId(Long.valueOf(String.valueOf( fieldValues.getOrDefault("Auto_id", "-1"))));
+ imageDTO.setUserId(Long.valueOf(String.valueOf( fieldValues.getOrDefault("user_id", "-1"))));
+ imageDTO.setUserName(String.valueOf((fieldValues.getOrDefault("user_name", ""))));
+ imageDTO.setFaceUrl(String.valueOf((fieldValues.getOrDefault("face_url", ""))));
+ imageDTO.setFaceFeatureUrl(String.valueOf((fieldValues.getOrDefault("face_feature_url", ""))));
+ imageDTO.setScore(Math.sqrt(score));
+ list.add(imageDTO);
+ });
+ return list;
+}
+```
+**04**.
+
+**Conclusion**
+
+This article demonstrates how to build a face recognition application using **EasyAi and Milvus**. By leveraging the strengths of the Java ecosystem's EasyAi and Milvus's vector search capabilities, we can quickly set up our own face recognition project using Java. We hope this article has been helpful. At the same time, we encourage you to use EasyAi and vector search in your own projects to explore more possibilities. The code involved in this article can be obtained via **Gitee**: Easy-Ai-Face_(https://gitee.com/fushoujiang/easy-ai-face)_.
+
+**Recommended Reading**
+
+[](https://mp.weixin.qq.com/s?__biz=MzUzMDI5OTA5NQ==&mid=2247507230&idx=1&sn=3dc46b15f39e6ac1666614bb7aa338d6&scene=21#wechat_redirect)
+
+[](https://mp.weixin.qq.com/s?__biz=MzUzMDI5OTA5NQ==&mid=2247507157&idx=1&sn=0ac4343d81b7ef655e358e49c496e37b&scene=21#wechat_redirect)
+
+
+
+
\ No newline at end of file
diff --git a/src/blog/northstar-7.0.md b/src/blog/northstar-7.0.md
index 5790ea59fa..a281a07b61 100644
--- a/src/blog/northstar-7.0.md
+++ b/src/blog/northstar-7.0.md
@@ -2,8 +2,6 @@
title: Quantitative software industry spoiler, northstar 7.0 official version of the strong debut
author: northstar
date: 2024-02-01
-tag:
- -
cover: /assets/img/blog/northstar-7.0-0.png
head:
- - meta
diff --git a/src/blog/os-data-community-team.md b/src/blog/os-data-community-team.md
new file mode 100644
index 0000000000..a3853a0dff
--- /dev/null
+++ b/src/blog/os-data-community-team.md
@@ -0,0 +1,221 @@
+---
+title: China's Open Source Data Community Operations Dream Team
+author: December 20, 2024, 13:59
+date: 2024-12-20
+cover: /assets/img/blog/os-data-community-team-0.gif
+head:
+ - - meta
+ - name: Blog
+---
+
+
+
+**PowerData**
+
+The Power of Data, Extraordinary Imagination
+
+■ ■ ■
+
+Think • Exchange • Contribute • Win-Win
+
+○
+
+○
+
+**Full text: 4632 words | Suggested reading time: 10 minutes**
+
+**Article Guide /** Company Nature
+
+Recently, I read an article by Teacher Yin Haiwen titled ["Database Operations Dream Team Plus"](https://mp.weixin.qq.com/s?__biz=Mzg3MTk4MzYyMQ==&mid=2247486110&idx=1&sn=dd60d01a97e6f8aaa1fbbc9315009959&scene=21#wechat_redirect), which sparked an idea. The open-source data community also has many capable, good-looking, and thoughtful operations colleagues. Hence, I wrote this introduction to China's open-source data operations dream team.
+
+Author: PowerData - Li Qifeng | Editor: PowerData - Li Zhao
+
+**Chapter 01**
+
+**DolphinScheduler**
+
+Zeng Hui
+
+Apache DolphinScheduler Committer, mainly responsible for the community operations of Apache DolphinScheduler and SeaTunnel. Leads the project's ecosystem development, maintains developer relations, enhances the global influence of the "open-source project," and focuses on internal community building. Dedicated to spreading open-source culture. If you're interested in open-source big data technology events, 🙏 welcome 👏👏 to contact me to collaborate! Usually enjoy music, outdoor activities, and playing badminton!
+
+**Operations Insight**: The core of community operations lies in activating developers, creating value, and building connections. First, understand developer needs and attract user participation through high-quality content and activities, such as technical sharing, case studies, and contribution incentives. Second, lower the barrier to entry by providing clear contribution guidelines and tutorials to help newcomers quickly get started and integrate into the community. It is also particularly important to focus on cultivating and incentivizing core contributors, giving users a sense of identity and honor to form long-term motivation for contribution. Regularly collect user feedback, respond to issues quickly, and enhance the sense of belonging and trust in the community. Finally, maintain synergy with technology and the ecosystem, allowing the community to bring practical value to products and business, forming a virtuous cycle.
+
+
+
+**Brother Hui is widely recognized in the industry for his great voice in the open-source community (I remember at an open-source dinner, his performance of "海阔天空" directly won over the entire room). In my heart, he is the great open-source operations big brother—professional, passionate, and humble. Since the founding of PowerData, I have been organizing events with Brother Hui. Until now, although we have never met in person, I truly admire him. Brother Hui now has both a son and a daughter, a happy and fulfilling family, and a thriving open-source career. It's really enviable!**
+
+**Chapter 02**
+
+**SelectDB**
+
+Ye Zi (Coconut)
+
+An event operations specialist, a 24g internet surfer who loves researching and paying attention to fun and interesting activities from various communities. My favorite thing is collecting souvenirs from offline events!
+
+**Operations Insight**: Operations must have a human touch. Based on clear community goals and user needs, organize activities to create unique community labels. Focus on interaction with users, encourage active participation in community activities, and provide certain incentives.
+
+**I met Xiran同学 offline at China Open Source Conference and even asked her to hold a PD sign for a photo to endorse PD. Getting back on topic, I have collaborated with Xiran同学 on many online and offline events. She is a very fun, professional, and enthusiastic operations colleague. (She says she is socially anxious, but actually she's not—she's somewhere between an introvert and an extrovert).**
+
+Yang Suli
+
+Apache Doris is the first database open-source project I have truly been involved in operating. Having been involved with the project for nearly three years, I am responsible for the content operations of the Doris community & Flywheel Tech, including technical analysis, user case studies, solution articles, as well as the planning, compilation, and promotion of e-books and case collections. So far, I have planned and produced 100+ original articles, garnering 500w+ reads across all channels, and planned a WeChat article with 40k+ reads. Over these three years, I have grown alongside the community. The strength of the Doris community has made me who I am, and I have done my best to contribute to the community's prosperity.
+
+**Operations Insight**: It might be too early to talk about insights. Operations work requires both innovation and insight, as well as strong execution and communication skills. Understanding the business and basing actions on user needs and experiences are also fundamental abilities for operations. The open-source user segment, with its strong technical literacy and unique perspectives, requires operations to be more open, inclusive, practical, and respectful. In content operations, addressing user needs and solving practical problems should be the top priority. Creating valuable, high-quality content, combined with effective operational strategies and persistence, is the way to achieve goals.
+
+
+
+
+
+**Suli同学 is too modest. As SelectDB's content operations specialist, when I lack inspiration while typesetting the official WeChat account, I look at SelectDB's official account (for reference, not plagiarism, manual doge head). I met her in person at Doris Summit in 2023—she is very approachable and enthusiastic. I hope to have the opportunity to communicate offline again in the future.**
+
+**Chapter 03**
+
+**NebulaGraph**
+
+Zhang Hui
+
+NebulaGraph Developer Relations Lead, CNCF Ambassador. Technical background, skilled in engaging with developers from their perspective within the community.
+
+**Operations Insight**: People-oriented, Trust is everything.
+
+
+
+
+
+
+
+**I just met Sister Hui recently. During our exchanges, I felt her passion for open source and her sense of responsibility towards community operations. We can organize more interesting open-source activities together in the future. While compiling this article, Sister Hui proactively helped me invite TiDB community operations representative '表妹' (cousin). Thank you very much~**
+
+**Chapter 04**
+
+**IoTDB**
+
+Qin Chuqing
+
+Hello everyone, I'm Chuqing. I handle the operations for the IoTDB community and am also the operations lead for Timecho Tech. IoTDB is a time-series database initiated and developed by Tsinghua University. It graduated smoothly from the Apache Incubator and became a top-level project in 2020. Having operated the IoTDB community for over three years, we have organized or participated in nearly fifty events, connected and communicated with thousands of open-source users, and are committed to making IoTDB a warm and rewarding open-source community.
+
+**Operations Insight**: A community is a magical group. Partners scattered across the country, even the globe, collaborate to form a community—a spontaneously formed, even weakly connected behavior. Operating a community, in my opinion, involves two key tasks: targeting the audience and identifying needs. Step one, targeting the audience, means deeply understanding the community group and analyzing its characteristics. Clearly defining the audience and having a deep understanding of the community is the foundation of community operations. Step two is identifying needs. This means recognizing where the community needs help from the operators. For example, if community users need a deeper understanding of the product architecture, then relevant sharing sessions or blog writing can be organized.
+
+
+
+**Sister Chuqing, Tsinghua top student,气质美女 (elegant beauty), operations expert. This year, I invited Sister Chuqing to share and communicate at the Open Source Conference. She has a thorough understanding of the technical details related to IoTDB and shoulders responsibilities in both market and operations—very comprehensive. PD has collaborated with IoTDB multiple times this year. I feel that the IoTDB team members are very humble, professional, and communicating with them is like a breath of fresh air.**
+
+**Chapter 05**
+
+**Pulsar**
+
+Fu Teng
+
+Hi,大家好 (hello everyone), I'm Fu Teng. I'm currently working at Anliu Tech on Pulsar commercialization and community operations. It's been 12 years in the industry in a flash, already whitening the young man's head. I started firmly on the Programmer path, later moved into products and projects, and now I'm doing operations and support. I love open source, am passionate about running and fitness, reject internal strife, and hope for peace. Nearing forty, what I value most is my declining health and my hope for a vibrant family. Of course, I still really like working—truly and effectively. Hope to get to know everyone.
+
+**Operations Insight**: As a rapidly developing open-source messaging and stream processing platform (well, it actually has a 12-year history), Apache Pulsar has penetrated many leading large companies domestically and internationally. Open source and the community have played a crucial role here. Since the open-source wave began in 2000, it has swept across the global software infrastructure like a whirlwind. I wonder, how does everyone view open source? From our perspective in open-source commercialization, infrastructure, due to its complexity, has actually achieved collaboration among global domain experts through open source, rapidly淘汰 (phasing out) many closed-source "black boxes" with unparalleled efficiency. This大大降低了 (greatly reduces) sales costs. Therefore, operating an open-source community is actually a job that combines branding, marketing, and product into one. It's really a tough job because you have to do everything. But it's also a great job because through open source, you can reach big shots in various industries. Everyone can benefit each other and get through the winter. If I had to say what insights I have, it's that you must give first, consistently and steadfastly!
+
+
+
+**Brother Teng, always pushing me to work: "Qifeng, don't forget to repost the article," "Qifeng, come support the Beijing event," "Qifeng, endorse Pulsar." And he's always very polite. I've said we're buddies,哎 (ai), after all, Brother Teng is getting older, but he has been trying hard to integrate into the PD atmosphere recently. This year, I organized many events with Brother Teng, and the collaboration was particularly pleasant. He has also been very supportive of PD. Let's continue this!**
+
+**Chapter 06**
+
+**KAIYUANSHE (Open Source Society)**
+
+Dong Jifu
+
+Initiator of KCC@Nanjing (KAIYUANSHE Nanjing City Community). Upholding the宗旨 (purpose) of KAIYUANSHE, with the philosophy of "Open Source Knowledge and Action, Steadfast and Robust," he promotes open-source culture in Nanjing, jointly organizes offline events with various communities, and co-builds the open-source ecosystem and inheritance.
+
+**Operations Insight**: Everything comes from open source, everything goes back to open source. I have made many friends through open source, and at the same time, I have learned a lot of new knowledge by participating in open-source activities. Take action, reap joy.
+
+
+
+
+
+
+
+**Brother Jifu, my close open-source partner in Nanjing, along with Brother Mazheng and Brother Daqing in the picture above—we are the Nanjing open-source task force. Since 2023, we have organized an event in Nanjing every year. Brother Jifu, as the lead organizer and recorder, writes post-event summary articles that are, in my heart, the no.1 open-source event summary articles. They are detailed, share materials, and incorporate his own thoughts, making it feel like listening to the sharing session again. Attached is one of Brother Jifu's event summary articles: [AI开源南京分享会回顾录 (AI Open Source Nanjing Sharing Session Review)](https://mp.weixin.qq.com/s?__biz=MzA4NTM4NDc4NQ==&mid=2247542008&idx=1&sn=6209251a4a343932e5451cc04d35461e&scene=21#wechat_redirect)**
+
+**Chapter 07**
+
+**Dromara**
+
+Li Nan
+
+Hello everyone, I am Li Nan (Nannan) from KAIYUANSHE & Dromara open-source community. I like comics, binge-watching web dramas, am very curious, and enjoy tinkering around—a "free-spirited" woman.
+
+**Operations Insight**: Interact more, listen more, encourage everyone to communicate and share, and respond to questions promptly. Promote more to attract more people to join. Also, maintain community rules well, keep the atmosphere friendly, so everyone can find a sense of belonging.
+
+
+
+**I met Li Nan同学 offline during PD's Hangzhou Open Source Tour. She is a very lovely and enthusiastic girl. Li Nan同学 does not have a technical background but has been volunteering in open-source operations. This year's PD Open Source Tour also received a lot of support from the community. I hope Li Nan同学 can stay lovely forever~**
+
+**Logically, this article is an introduction to operations personnel in the open-source data community, but I still included Brother Jifu and Li Nan同学 here. I want everyone to see that there are such sincere and lovely people in the open-source field.**
+
+**Chapter 08**
+
+**ByConity**
+
+Liu Xiaomi
+
+An open-source operations girl, sometimes a bit socially anxious ENFP. I've been working in the open-source circle since my internship before graduation. Over the years, I've been exposed to various big shots and learn a lot of knowledge every day. I hope to continue contributing my part to community building in the future 🐮
+
+**Operations Insight**: In my opinion, the most important aspect of community operations is maintaining developer relations. By building strong relationships with developers, the community can leverage extensive developer expertise, increase product iteration speed, and quickly obtain community feedback. By analyzing the project's status from aspects like maturity and infrastructure, and then using the right operational methods to maintain developer relations, a better environment can be created for the community's developers.
+
+
+
+**Xiaomi同学, we haven't met in person, but we have collaborated on many events. This year, for PD's city tour, Xiaomi同学 provided venue, speaker, and souvenir support for multiple events. She is very sincere, no wonder she excels in maintaining developer relations. Not having communicated offline with Xiaomi同学 is one of my regrets. I hope to have the opportunity to请教 (seek advice from) her in person next year.**
+
+**Chapter 09**
+
+**TiDB**
+
+Huang Manshen (Biǎo mèi - Cousin)
+
+In this era of rapid digital development, open-source communities have become important platforms for technical exchange and knowledge sharing. In my heart, the ideal domestic open-source community should be a vibrant, open, inclusive, and collaborative win-win ecosystem. It is not just a gathering place for technology but also a convergence point for dreams and passion.
+
+**Operations Insight**: In the TiDB community, all decision-making processes, technology roadmaps, and development plans will be公开 (open) to community members, allowing everyone to understand how the community operates and its future direction. This transparency not only enhances the trust of community members but also attracts more outstanding talents to join. Everyone is encouraged to提出 (put forward) their ideas and suggestions, whether it's improvements to existing features or visions for future development—all can become valuable assets for the community's development.
+
+
+
+**In the field of open-source data community operations, the figure of TiDB's Biǎo mèi (I should probably call her Biǎo jiě - older cousin sister) is indispensable. The活跃 (activeness) of the TiDB community is离不开 (inseparable from) Cousin's professionalism and dedication. As an outsider, I also learned a lot of practical and actionable operational knowledge from her article ["What is the Ideal Open Source Community Like? A Shallow Understanding from TiDB Community Operations Cousin"](https://mp.weixin.qq.com/s?__biz=MzA4MTgzNDQzMQ==&mid=2651502145&idx=1&sn=5225ec33313a4284d72143296441a8aa&scene=21#wechat_redirect). I hope to have the opportunity to seek advice from Cousin in person in the future~**
+
+**Chapter 10**
+
+**ActionTech**
+
+Guan Changlong
+
+ActionTech Open Source Community Operations Manager. From server rooms to training podiums, passionate about open-source technology evangelism.
+
+**Operations Insight**: As a community operator, passion and patience are required. Establish efficient communication channels to facilitate developer exchange. Actively respond to member questions and create a friendly atmosphere. If there is an opportunity for offline communication, the effect is definitely the best.
+
+I met Brother Long at an open-source event in Shanghai and had in-depth exchanges at the China Open Source Conference, where PD's and ActionTech's booths were next to each other. As an operations expert with a technical background, he has a natural advantage in technology evangelism. Combined with Brother Long's eloquent expression and patient, steady tone, you will unconsciously be attracted to him. Moreover, he is very helpful and took many handsome photos of me during the event. Brother Long is also somewhere between an introvert and an extrovert—an introvert when无事 (nothing's happening), but特别 e (very extroverted) when there's work to be done.
+
+<<< Summary >>>
+
+The open-source community is full of strong players—not only developers but also operations personnel, each with their own unique skills and extraordinary talents. I hope today's article allows you to see the professionalism, enthusiasm, handsomeness, and beauty of open-source operations personnel.
+
+**Past Wonderful Article Collections**
+
+
+
+【Open Source Figures Column】
+
+[Open Source Figure - Wang Chunsheng of ZenTao Community: Making Project Management More Convenient](https://mp.weixin.qq.com/s?__biz=MzUyMTA1NTcyOA==&mid=2247488278&idx=1&sn=f3e915780246a3e0084fb398b5f392e5&scene=21#wechat_redirect)
+
+[Open Source Figure—Qiao Jialin of IoTDB: Conquering Industrial Internet Data](https://mp.weixin.qq.com/s?__biz=MzUyMTA1NTcyOA==&mid=2247488845&idx=1&sn=101879acffe0007ab7a5db28a26b3f2f&scene=21#wechat_redirect)
+
+【Technical Articles Column】
+
+[Kafka Source Code Learning (1) Producer Source Code](https://mp.weixin.qq.com/s?__biz=MzUyMTA1NTcyOA==&mid=2247487990&idx=1&sn=5b98967588a71af796130e85801f86b4&scene=21#wechat_redirect)
+
+[Kafka Source Code Learning (2) Server Source Code](https://mp.weixin.qq.com/s?__biz=MzUyMTA1NTcyOA==&mid=2247488095&idx=1&sn=b3ae348379c7ab827e2b1828b48cc344&scene=21#wechat_redirect)
+
+[Kafka Source Code Learning (3) Consumer Source Code](https://mp.weixin.qq.com/s?__biz=MzUyMTA1NTcyOA==&mid=2247488219&idx=1&sn=01636401250533e1bd5b44a7975e8cb1&scene=21#wechat_redirect)
+
+[【Technical Practice】Doris Data Query Performance Analysis: In-Depth Application of Explain and Profile Functions](https://mp.weixin.qq.com/s?__biz=MzUyMTA1NTcyOA==&mid=2247488755&idx=1&sn=42bdf28a3ba55ddb9a6d416b315a4b44&scene=21#wechat_redirect)
+
+【Community Activities Column】
+
+[Event Review | 【Digital Economy·City Pulse】PowerData Xi'an Open Source Tour](https://mp.weixin.qq.com/s?__biz=MzUyMTA1NTcyOA==&mid=2247487699&idx=1&sn=f1ccdcf2e0681d9dcca47e91838a64a4&scene=21#wechat_redirect)
+
+[Event Review | 【Digital Economy·City Pulse】PowerData Hangzhou Open Source Tour](https://mp.weixin.qq.com/s?__biz=MzUyMTA1NTcyOA==&mid=2247487817&idx=1&sn=8e1a9e621032a95d74b9881bdb4f2498&scene=21#wechat_redirect)
+
+<<< END >>>
\ No newline at end of file
diff --git a/src/blog/soul_resource_learning_07_admin.md b/src/blog/soul_resource_learning_07_admin.md
index e750e30de6..6acd739cf6 100644
--- a/src/blog/soul_resource_learning_07_admin.md
+++ b/src/blog/soul_resource_learning_07_admin.md
@@ -1,145 +1,145 @@
----
-title: Soul Gateway Learning Admin Source Code Analysis
-author: zenglinhui
-date: 2021-01-20
-tag:
- - Soul
-cover: /img/architecture/soul-framework.png
-head:
- - - meta
- - name: Blog
----
-
-# Source Code Analysis
-
-## Page Operation Source Code Analysis
-
-Before analyzing the source code, let's take a look at the image below. The plugin list displayed on the page corresponds to requests made to the backend. Based on these backend requests, the corresponding controller class is identified.
-
-
-
-Then, we find the corresponding method. In the image above, it can be seen that here, we access the mapping that is empty by default in the "plugin". We pass in pagination-related parameters and then query the corresponding plugin records in the database.
-
-
-
-The corresponding table in the database is shown in the image below. The "divide" status is enabled. In the previous article, this plugin was used to test the gateway.
-
-
-
-At the same time, a selector is also requested. The requested controller can be seen in the image below. In the previous demonstration, we directly perform CRUD operations on the conditions in the selector on the page. These changes can be reflected in the gateway in real time without the need to restart the gateway. Therefore, in addition to the "query" method, the "create", "delete", and "update" methods have been added. After saving to the database, a "publishEvent" method is triggered. This event allows users to configure rules directly in the Soul backend, achieving real-time effectiveness.
-
-
-
-```
-public int createOrUpdate(final SelectorDTO selectorDTO) {
- int selectorCount;
- SelectorDO selectorDO = SelectorDO.buildSelectorDO(selectorDTO);
- List selectorConditionDTOs = selectorDTO.getSelectorConditions();
- if (StringUtils.isEmpty(selectorDTO.getId())) {
- selectorCount = selectorMapper.insertSelective(selectorDO);
- selectorConditionDTOs.forEach(selectorConditionDTO -> {
- selectorConditionDTO.setSelectorId(selectorDO.getId());
- selectorConditionMapper.insertSelective(SelectorConditionDO.buildSelectorConditionDO(selectorConditionDTO));
- });
- } else {
- selectorCount = selectorMapper.updateSelective(selectorDO);
- //delete rule condition then add
- selectorConditionMapper.deleteByQuery(new SelectorConditionQuery(selectorDO.getId()));
- selectorConditionDTOs.forEach(selectorConditionDTO -> {
- selectorConditionDTO.setSelectorId(selectorDO.getId());
- SelectorConditionDO selectorConditionDO = SelectorConditionDO.buildSelectorConditionDO(selectorConditionDTO);
- selectorConditionMapper.insertSelective(selectorConditionDO);
- });
- }
- publishEvent(selectorDO, selectorConditionDTOs);
- return selectorCount;
- }
-```
-
-2. **Synchronization with soul-bootstrap (WebSocket) Source Code Analysis**
-
-Previously, it was explained how data is saved to the database after performing operations on the admin page. Spring's built-in reactive programming is used to synchronize the data with the bootstrap project, achieving dynamic refreshing of gateway rules and plugins without requiring a restart.
-When soul-bootstrap starts, the following log entry is displayed:
-
-```
-2021-01-21 00:33:39.620 INFO 14276 --- [0.0-9095-exec-5] o.d.s.a.l.websocket.WebsocketCollector : websocket on open successful....
-```
-
-The question is, which entity is it connecting to using WebSockets and how does the connection happen? By examining the code that generates this log entry, we can gain insights. Here's where the log entry is generated:
-
-First, let's analyze this code:
-
-- Get the requested address from the websocketConfig configuration, which is of course configured in the location shown in the following figure.
-- After obtaining this configuration address, a timed thread pool is created with a size of urls.length, and a daemon thread with a thread name prefix of "websocket-connect" is created. Why use daemon threads? Because this is just to ensure that the websocket connections of bootstrap and admin are constantly maintained, similar to the function of a heartbeat, so a daemon thread is the best choice.
-- According to the created client, one by one, go to the address configured in the configuration file, and then print the previously found logs.
-- Finally, start a thread to check if the client is closed. If it is closed, it will reconnect (the initial interval is 10 seconds, and then it will check every 30 seconds, so if you see multiple connection success logs printed in the console, it means that reconnection has occurred).
- 
-- Next, let's take a look at how the data operated in the admin background is synchronized to bootstrap. Previously, it was mentioned that after saving or updating data in the background, the publishEvent method is called. This is a method of spring's built-in reactive programming. Since it is reactive, it is event-based, and therefore requires a listener.
- 
-
-Sure enough, the red box in the above picture is familiar, it is a listener related to websocket. If you still don't understand the connection between the listener and the previous publishEvent, then put breakpoints in the listener's code and debug it. For convenience, I clicked on this synchronization of all data here.
-
-This enters the DataChangedEventDispatcher class, calls the event-related methods, and in the lower left corner, you can see familiar methods. Yes, it is the aforementioned publishEvent.
-
-- Then it will jump to the WebsocketDataChangedListener class. Here, pay attention to the send method in the debugging method.
- 
-- Use the send method to send the updated data to bootstrap. At this point, how admin synchronizes data to bootstrap is revealed.
- 
-
-3. **Soul-bootstrap data synchronization (zookeeper) source code analysis**
-
-Without further ado, let's first look at the picture. Comment out the websocket configuration, open the zookeeper configuration, and start the local or remote zookeeper service. Then start soul-admin.
-
-First, enter the run method of the ZookeeperDataInit class. After this method is executed, the strange thing is that it jumps to the WebsocketDataChangedListener class.
-
-I don't understand this point. After the onPluginChanged method in this class is executed, it returns to the ZookeeperDataChangedListener class.
-
-If it is not deleted, the zkNode node data will be updated.
-
-Method for updating zk node.
-
-Moreover, the onSelectorChanged, onMetaDataChanged, and onRuleChanged methods will all first go to the corresponding methods in the WebsocketDataChangedListener class, and then enter the methods in the ZookeeperDataChangedListener class. If the plugin data is changed, it will go through the above steps again.
-The problem of entering two Listener classes for synchronous data has not been solved yet. Suddenly, I thought that there was a dependency on websocket in the pom file, because the websocket configuration in the application.yml file had been commented out (not enable=false), so I commented out this dependency first and then compiled the code. I found that the code did not pass the compilation. Another way is to change websocket to disabled. After the modification, I found that it would not jump to the websocket-related class again.
- 4. **Analysis of soul-bootstrap data synchronization (http) source code**
-
-As usual, modify the configuration in the yml file, and then set a breakpoint in the corresponding listener class. If http is used here, the websocket-related class will still be accessed, so it cannot be commented out directly.
-
-
-Let's take a look at the code inside:
-There is a constructor here, which instantiates a clients array blocking queue with a size of 1024. A timed task thread pool with a thread number of 1 and a name prefix of "long-polling" background daemon thread (as can be seen from the name, this is used for long polling). A related property configuration
-In the initialization method, a timed thread is started. After 5 minutes, the refreshLocalCache method for refreshing the local cache is executed every 5 minutes.
-
-
-```
- private void refreshLocalCache() {
- this.updateAppAuthCache();
- this.updatePluginCache();
- this.updateRuleCache();
- this.updateSelectorCache();
- this.updateMetaDataCache();
- }
-```
-
-If the data is manually synchronized, the following related methods will be executed, and they will also be executed through the timed thread pool, but they will be executed immediately.
-
-Five minutes later, execute the corresponding refresh method, and print the log.
-
-```
-2021-01-22 01:00:19.007 INFO 20800 --- [-long-polling-2] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh config start.
-2021-01-22 01:00:19.010 INFO 20800 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener : update config cache[APP_AUTH], old: {group='APP_AUTH', md5='d751713988987e9331980363e24189ce', lastModifyTime=1611248118794}, updated: {group='APP_AUTH', md5='d751713988987e9331980363e24189ce', lastModifyTime=1611248419010}
-2021-01-22 01:00:19.012 INFO 20800 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener : update config cache[PLUGIN], old: {group='PLUGIN', md5='70b269257d47f0f6404ae7b7e976d8f1', lastModifyTime=1611248295740}, updated: {group='PLUGIN', md5='70b269257d47f0f6404ae7b7e976d8f1', lastModifyTime=1611248419012}
-2021-01-22 01:00:19.069 INFO 20800 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener : update config cache[RULE], old: {group='RULE', md5='5811b56257e31109621976d39fc226aa', lastModifyTime=1611248301607}, updated: {group='RULE', md5='5811b56257e31109621976d39fc226aa', lastModifyTime=1611248419069}
-2021-01-22 01:00:19.075 INFO 20800 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener : update config cache[SELECTOR], old: {group='SELECTOR', md5='70bad5ebb1cf6e3fc55278eef2df42f3', lastModifyTime=1611248299419}, updated: {group='SELECTOR', md5='70bad5ebb1cf6e3fc55278eef2df42f3', lastModifyTime=1611248419075}
-2021-01-22 01:00:19.077 INFO 20800 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener : update config cache[META_DATA], old: {group='META_DATA', md5='5f79d821e3b601330631a2d53294fb34', lastModifyTime=1611248302571}, updated: {group='META_DATA', md5='5f79d821e3b601330631a2d53294fb34', lastModifyTime=1611248419077}
-2021-01-22 01:00:19.077 INFO 20800 --- [-long-polling-2] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh config success.
-```
-
-5. There are other methods for synchronizing data in soul, which will be analyzed later if there is energy. This is the end of the analysis of the soul-admin source code. If further analysis is conducted, another article will be written separately.
-
-# Summary
-
-There are still many features in soul-admin that have not been used yet, and there are many interesting things. This article will be continuously updated, and the source code inside will be analyzed in detail when it is used.
-
-1. On January 20, 2021, analyzed how soul-admin synchronizes data to soul-bootstrap using websocket.
-2. On January 21, 2021, analyzed how soul-admin synchronizes data to soul-bootstrap using zookeeper.
-3. On January 21, 2021, analyzed how soul-admin synchronizes data to soul-bootstrap using http.
+---
+title: Soul Gateway Learning Admin Source Code Analysis
+author: zenglinhui
+date: 2021-01-20
+tag:
+ - Soul
+cover: /img/architecture/soul-framework.png
+head:
+ - - meta
+ - name: Blog
+---
+
+# Source Code Analysis
+
+## Page Operation Source Code Analysis
+
+Before analyzing the source code, let's take a look at the image below. The plugin list displayed on the page corresponds to requests made to the backend. Based on these backend requests, the corresponding controller class is identified.
+
+
+
+Then, we find the corresponding method. In the image above, it can be seen that here, we access the mapping that is empty by default in the "plugin". We pass in pagination-related parameters and then query the corresponding plugin records in the database.
+
+
+
+The corresponding table in the database is shown in the image below. The "divide" status is enabled. In the previous article, this plugin was used to test the gateway.
+
+
+
+At the same time, a selector is also requested. The requested controller can be seen in the image below. In the previous demonstration, we directly perform CRUD operations on the conditions in the selector on the page. These changes can be reflected in the gateway in real time without the need to restart the gateway. Therefore, in addition to the "query" method, the "create", "delete", and "update" methods have been added. After saving to the database, a "publishEvent" method is triggered. This event allows users to configure rules directly in the Soul backend, achieving real-time effectiveness.
+
+
+
+```
+public int createOrUpdate(final SelectorDTO selectorDTO) {
+ int selectorCount;
+ SelectorDO selectorDO = SelectorDO.buildSelectorDO(selectorDTO);
+ List selectorConditionDTOs = selectorDTO.getSelectorConditions();
+ if (StringUtils.isEmpty(selectorDTO.getId())) {
+ selectorCount = selectorMapper.insertSelective(selectorDO);
+ selectorConditionDTOs.forEach(selectorConditionDTO -> {
+ selectorConditionDTO.setSelectorId(selectorDO.getId());
+ selectorConditionMapper.insertSelective(SelectorConditionDO.buildSelectorConditionDO(selectorConditionDTO));
+ });
+ } else {
+ selectorCount = selectorMapper.updateSelective(selectorDO);
+ //delete rule condition then add
+ selectorConditionMapper.deleteByQuery(new SelectorConditionQuery(selectorDO.getId()));
+ selectorConditionDTOs.forEach(selectorConditionDTO -> {
+ selectorConditionDTO.setSelectorId(selectorDO.getId());
+ SelectorConditionDO selectorConditionDO = SelectorConditionDO.buildSelectorConditionDO(selectorConditionDTO);
+ selectorConditionMapper.insertSelective(selectorConditionDO);
+ });
+ }
+ publishEvent(selectorDO, selectorConditionDTOs);
+ return selectorCount;
+ }
+```
+
+2. **Synchronization with soul-bootstrap (WebSocket) Source Code Analysis**
+
+Previously, it was explained how data is saved to the database after performing operations on the admin page. Spring's built-in reactive programming is used to synchronize the data with the bootstrap project, achieving dynamic refreshing of gateway rules and plugins without requiring a restart.
+When soul-bootstrap starts, the following log entry is displayed:
+
+```
+2021-01-21 00:33:39.620 INFO 14276 --- [0.0-9095-exec-5] o.d.s.a.l.websocket.WebsocketCollector : websocket on open successful....
+```
+
+The question is, which entity is it connecting to using WebSockets and how does the connection happen? By examining the code that generates this log entry, we can gain insights. Here's where the log entry is generated:
+
+First, let's analyze this code:
+
+- Get the requested address from the websocketConfig configuration, which is of course configured in the location shown in the following figure.
+- After obtaining this configuration address, a timed thread pool is created with a size of urls.length, and a daemon thread with a thread name prefix of "websocket-connect" is created. Why use daemon threads? Because this is just to ensure that the websocket connections of bootstrap and admin are constantly maintained, similar to the function of a heartbeat, so a daemon thread is the best choice.
+- According to the created client, one by one, go to the address configured in the configuration file, and then print the previously found logs.
+- Finally, start a thread to check if the client is closed. If it is closed, it will reconnect (the initial interval is 10 seconds, and then it will check every 30 seconds, so if you see multiple connection success logs printed in the console, it means that reconnection has occurred).
+ 
+- Next, let's take a look at how the data operated in the admin background is synchronized to bootstrap. Previously, it was mentioned that after saving or updating data in the background, the publishEvent method is called. This is a method of spring's built-in reactive programming. Since it is reactive, it is event-based, and therefore requires a listener.
+ 
+
+Sure enough, the red box in the above picture is familiar, it is a listener related to websocket. If you still don't understand the connection between the listener and the previous publishEvent, then put breakpoints in the listener's code and debug it. For convenience, I clicked on this synchronization of all data here.
+
+This enters the DataChangedEventDispatcher class, calls the event-related methods, and in the lower left corner, you can see familiar methods. Yes, it is the aforementioned publishEvent.
+
+- Then it will jump to the WebsocketDataChangedListener class. Here, pay attention to the send method in the debugging method.
+ 
+- Use the send method to send the updated data to bootstrap. At this point, how admin synchronizes data to bootstrap is revealed.
+ 
+
+3. **Soul-bootstrap data synchronization (zookeeper) source code analysis**
+
+Without further ado, let's first look at the picture. Comment out the websocket configuration, open the zookeeper configuration, and start the local or remote zookeeper service. Then start soul-admin.
+
+First, enter the run method of the ZookeeperDataInit class. After this method is executed, the strange thing is that it jumps to the WebsocketDataChangedListener class.
+
+I don't understand this point. After the onPluginChanged method in this class is executed, it returns to the ZookeeperDataChangedListener class.
+
+If it is not deleted, the zkNode node data will be updated.
+
+Method for updating zk node.
+
+Moreover, the onSelectorChanged, onMetaDataChanged, and onRuleChanged methods will all first go to the corresponding methods in the WebsocketDataChangedListener class, and then enter the methods in the ZookeeperDataChangedListener class. If the plugin data is changed, it will go through the above steps again.
+The problem of entering two Listener classes for synchronous data has not been solved yet. Suddenly, I thought that there was a dependency on websocket in the pom file, because the websocket configuration in the application.yml file had been commented out (not enable=false), so I commented out this dependency first and then compiled the code. I found that the code did not pass the compilation. Another way is to change websocket to disabled. After the modification, I found that it would not jump to the websocket-related class again.
+ 4. **Analysis of soul-bootstrap data synchronization (http) source code**
+
+As usual, modify the configuration in the yml file, and then set a breakpoint in the corresponding listener class. If http is used here, the websocket-related class will still be accessed, so it cannot be commented out directly.
+
+
+Let's take a look at the code inside:
+There is a constructor here, which instantiates a clients array blocking queue with a size of 1024. A timed task thread pool with a thread number of 1 and a name prefix of "long-polling" background daemon thread (as can be seen from the name, this is used for long polling). A related property configuration
+In the initialization method, a timed thread is started. After 5 minutes, the refreshLocalCache method for refreshing the local cache is executed every 5 minutes.
+
+
+```
+ private void refreshLocalCache() {
+ this.updateAppAuthCache();
+ this.updatePluginCache();
+ this.updateRuleCache();
+ this.updateSelectorCache();
+ this.updateMetaDataCache();
+ }
+```
+
+If the data is manually synchronized, the following related methods will be executed, and they will also be executed through the timed thread pool, but they will be executed immediately.
+
+Five minutes later, execute the corresponding refresh method, and print the log.
+
+```
+2021-01-22 01:00:19.007 INFO 20800 --- [-long-polling-2] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh config start.
+2021-01-22 01:00:19.010 INFO 20800 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener : update config cache[APP_AUTH], old: {group='APP_AUTH', md5='d751713988987e9331980363e24189ce', lastModifyTime=1611248118794}, updated: {group='APP_AUTH', md5='d751713988987e9331980363e24189ce', lastModifyTime=1611248419010}
+2021-01-22 01:00:19.012 INFO 20800 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener : update config cache[PLUGIN], old: {group='PLUGIN', md5='70b269257d47f0f6404ae7b7e976d8f1', lastModifyTime=1611248295740}, updated: {group='PLUGIN', md5='70b269257d47f0f6404ae7b7e976d8f1', lastModifyTime=1611248419012}
+2021-01-22 01:00:19.069 INFO 20800 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener : update config cache[RULE], old: {group='RULE', md5='5811b56257e31109621976d39fc226aa', lastModifyTime=1611248301607}, updated: {group='RULE', md5='5811b56257e31109621976d39fc226aa', lastModifyTime=1611248419069}
+2021-01-22 01:00:19.075 INFO 20800 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener : update config cache[SELECTOR], old: {group='SELECTOR', md5='70bad5ebb1cf6e3fc55278eef2df42f3', lastModifyTime=1611248299419}, updated: {group='SELECTOR', md5='70bad5ebb1cf6e3fc55278eef2df42f3', lastModifyTime=1611248419075}
+2021-01-22 01:00:19.077 INFO 20800 --- [-long-polling-2] o.d.s.a.l.AbstractDataChangedListener : update config cache[META_DATA], old: {group='META_DATA', md5='5f79d821e3b601330631a2d53294fb34', lastModifyTime=1611248302571}, updated: {group='META_DATA', md5='5f79d821e3b601330631a2d53294fb34', lastModifyTime=1611248419077}
+2021-01-22 01:00:19.077 INFO 20800 --- [-long-polling-2] a.l.h.HttpLongPollingDataChangedListener : http sync strategy refresh config success.
+```
+
+5. There are other methods for synchronizing data in soul, which will be analyzed later if there is energy. This is the end of the analysis of the soul-admin source code. If further analysis is conducted, another article will be written separately.
+
+# Summary
+
+There are still many features in soul-admin that have not been used yet, and there are many interesting things. This article will be continuously updated, and the source code inside will be analyzed in detail when it is used.
+
+1. On January 20, 2021, analyzed how soul-admin synchronizes data to soul-bootstrap using websocket.
+2. On January 21, 2021, analyzed how soul-admin synchronizes data to soul-bootstrap using zookeeper.
+3. On January 21, 2021, analyzed how soul-admin synchronizes data to soul-bootstrap using http.
diff --git a/src/blog/soul_source_learning_01.md b/src/blog/soul_source_learning_01.md
index a6f11cbfde..438597a215 100644
--- a/src/blog/soul_source_learning_01.md
+++ b/src/blog/soul_source_learning_01.md
@@ -1,224 +1,224 @@
----
-title: Soul Learning(1) Environment Configuration
-author: chenxi
-date: 2021-01-15
-tag:
- - Soul
-cover: /assets/img/architecture/soul-framework.png
-head:
- - - meta
- - name: Blog
----
-
-# Analysis of soul (1) Set up soul environment
-
-> soul is a High-Performance Java API Gateway
->
-> GitHub:https://github.com/dromara/soul
->
-> document:https://dromara.org/zh-cn/docs/soul/soul.html
-
-## 1. Prepare source code
-
-### 1.1. Fork [dromara/soul](https://github.com/dromara/soul.git) repository to my github [cchenxi/soul](https://github.com/cchenxi/soul.git)
-
-### 1.2. Clone the repository
-
-```shell
-git clone https://github.com/cchenxi/soul.git
-```
-
-### 1.3.Open the source code with idea
-
-### 1.4. Compile the soul source code
-
-You can compile the project as follows.
-
-```shell
-mvn clean package install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -Drat.skip=true -Dcheckstyle.skip=true
-```
-
-待补,文章内部有报错
-
-## 2. Startup `soul`
-
-### 2.1. Startup `soul-admin` module
-
-> `soul-admin` is the management system for soul.
-
-Choose to use `MySQL` to storage gateway data and modify the datasource config.
-
-待补,文章内部有报错
-
-Run `org.dromara.soul.admin.SoulAdminBootstrap`.
-
-When success, please visit the website `http://localhost:9095/`, then jump to the login page, and input the corresponding user name and password to log in.
-
-The user name is `admin` and the password is `123456`.
-
-待补,文章内部有报错
-
-待补,文章内部有报错
-
-### 2.2. Startup `soul-bootstrap` module
-
-> `soul-bootstrap` is the core of soul.
-
-Check the configuration of `soul-bootstrap`.
-
-待补,文章内部有报错
-
-Please make sure the ip and the port has been configured for `soul-admin`.
-
-If the console output as follows, it means the startup is successful.
-
-```plain text
-2021-01-14 15:01:15.832 INFO 17943 --- [ main] b.s.s.d.w.WebsocketSyncDataConfiguration : you use websocket sync soul data.......
-2021-01-14 15:01:15.924 INFO 17943 --- [ main] o.d.s.p.s.d.w.WebsocketSyncDataService : websocket connection is successful.....
-2021-01-14 15:01:16.113 INFO 17943 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
-log4j:WARN No appenders could be found for logger (com.alibaba.dubbo.common.logger.LoggerFactory).
-log4j:WARN Please initialize the log4j system properly.
-log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
-2021-01-14 15:01:17.150 INFO 17943 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 9195
-2021-01-14 15:01:17.154 INFO 17943 --- [ main] o.d.s.b.SoulBootstrapApplication : Started SoulBootstrapApplication in 5.508 seconds (JVM running for 6.762)
-```
-
-## 3. Test
-
-> Add the `soul-examples` module to soul's pom.xml for test.
-
-### 3.1. Startup an HTTP backend service
-
-Startup `soul-examples-http`
-
-You can see the dependency in `soul-examples-http`'s pom.xml.
-
-```xml
-
- org.dromara
- soul-spring-boot-starter-client-springmvc
- ${soul.version}
-
-```
-
-Configure the `application.yml`
-
-```yaml
-soul:
- http:
- adminUrl: http://localhost:9095
- port: 8188
- contextPath: /http
- appName: http
- full: false
-```
-
-If `soul.http.full`=false, you need to add the `@SoulSpringMvcClient` annotation in controller or controller method.
-
-#### 3.1.1. Test the service
-
-Visit `http://localhost:8188/test/findByUserId?userId=1` and the result as follows.
-
-待补,文章内部有报错
-
-#### 3.1.2. Test forward HTTP request
-
-Visit `http://localhost:9195/http/test/findByUserId?userId=1` and the result as follows.
-
-待补,文章内部有报错
-
-You can see the following information in the console of `soul-bootstrap`. It means the forward HTTP request is successful.
-
-```shell
-2021-01-14 20:42:57.123 INFO 29812 --- [work-threads-11] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
-2021-01-14 20:42:57.125 INFO 29812 --- [work-threads-11] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
-2021-01-14 20:42:57.126 INFO 29812 --- [work-threads-11] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8188/test/findByUserId?userId=1, retryTimes is 0
-```
-
-### 3.2. Startup two HTTP backend services to simulate load balance
-
-Choose `Allow parallel run`
-
-Change the port to `8189`
-
-Startup `soul-examples-http` again
-
-待补,文章内部有报错
-
-#### 3.2.1. Test the service
-
-Visit `http://localhost:8189/test/findByUserId?userId=1` and the result as follows.
-
-待补,文章内部有报错
-
-#### 3.2.2. Test load balance
-
-待补,文章内部有报错
-
-Configure two HTTP service in selector
-
-Visit `http://localhost:9195/http/test/findByUserId?userId=1` more and more and result as follows.
-
-待补,文章内部有报错
-You can see the following information in the console of `soul-bootstrap`. It means the load balance is successful.
-
-```shell
-2021-01-14 20:48:34.460 INFO 29812 --- [work-threads-21] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
-2021-01-14 20:48:34.460 INFO 29812 --- [work-threads-21] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
-2021-01-14 20:48:34.460 INFO 29812 --- [work-threads-21] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8189/test/findByUserId?userId=1, retryTimes is 0
-2021-01-14 20:48:35.147 INFO 29812 --- [work-threads-22] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
-2021-01-14 20:48:35.147 INFO 29812 --- [work-threads-22] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
-2021-01-14 20:48:35.147 INFO 29812 --- [work-threads-22] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8188/test/findByUserId?userId=1, retryTimes is 0
-2021-01-14 20:48:38.755 INFO 29812 --- [work-threads-23] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
-2021-01-14 20:48:38.756 INFO 29812 --- [work-threads-23] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
-2021-01-14 20:48:38.756 INFO 29812 --- [work-threads-23] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8188/test/findByUserId?userId=1, retryTimes is 0
-2021-01-14 20:48:39.609 INFO 29812 --- [work-threads-24] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
-2021-01-14 20:48:39.609 INFO 29812 --- [work-threads-24] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
-2021-01-14 20:48:39.609 INFO 29812 --- [work-threads-24] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8189/test/findByUserId?userId=1, retryTimes is 0
-2021-01-14 20:48:40.317 INFO 29812 --- [work-threads-25] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
-2021-01-14 20:48:40.317 INFO 29812 --- [work-threads-25] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
-2021-01-14 20:48:40.317 INFO 29812 --- [work-threads-25] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8188/test/findByUserId?userId=1, retryTimes is 0
-2021-01-14 20:48:40.976 INFO 29812 --- [-work-threads-1] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
-2021-01-14 20:48:40.976 INFO 29812 --- [-work-threads-1] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
-2021-01-14 20:48:40.977 INFO 29812 --- [-work-threads-1] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8188/test/findByUserId?userId=1, retryTimes is 0
-```
-
-#### 3.2.3. Press test
-
-Use `wrk` to press test and compare the two ways as follows.
-
-1. Visit the backend service directly.
-2. Visit the service via soul.
-
-The performance drops slightly after using the gateway, probably because of the extra layer of forwarding.
-
-```shell
-➜ soul git:(master) ✗ wrk -t8 -c40 -d30s http://localhost:8189/test/findByUserId\?userId\=1
-Running 30s test @ http://localhost:8189/test/findByUserId?userId=1
- 8 threads and 40 connections
- Thread Stats Avg Stdev Max +/- Stdev
- Latency 6.06ms 28.81ms 442.25ms 98.22%
- Req/Sec 2.05k 493.86 2.84k 74.82%
- 486269 requests in 30.05s, 51.01MB read
-Requests/sec: 16179.68
-Transfer/sec: 1.70MB
-➜ soul git:(master) ✗ wrk -t8 -c40 -d30s http://localhost:9195/http/test/findByUserId\?userId\=1
-Running 30s test @ http://localhost:9195/http/test/findByUserId?userId=1
- 8 threads and 40 connections
- Thread Stats Avg Stdev Max +/- Stdev
- Latency 14.37ms 18.11ms 255.66ms 93.06%
- Req/Sec 459.41 139.11 1.01k 74.23%
- 109533 requests in 30.09s, 11.49MB read
-Requests/sec: 3639.60
-Transfer/sec: 390.98KB
-```
-
-#### 3.2.4. Problem in the process
-
-When startup the port of `8189`,but the output of console is still `8188`.
-
-待补,文章内部有报错
-
-After modify the value of `soul.http.port`, the problem solved.
-
-待补,文章内部有报错
+---
+title: Soul Learning(1) Environment Configuration
+author: chenxi
+date: 2021-01-15
+tag:
+ - Soul
+cover: /assets/img/architecture/soul-framework.png
+head:
+ - - meta
+ - name: Blog
+---
+
+# Analysis of soul (1) Set up soul environment
+
+> soul is a High-Performance Java API Gateway
+>
+> GitHub:https://github.com/dromara/soul
+>
+> document:https://dromara.org/zh-cn/docs/soul/soul.html
+
+## 1. Prepare source code
+
+### 1.1. Fork [dromara/soul](https://github.com/dromara/soul.git) repository to my github [cchenxi/soul](https://github.com/cchenxi/soul.git)
+
+### 1.2. Clone the repository
+
+```shell
+git clone https://github.com/cchenxi/soul.git
+```
+
+### 1.3.Open the source code with idea
+
+### 1.4. Compile the soul source code
+
+You can compile the project as follows.
+
+```shell
+mvn clean package install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -Drat.skip=true -Dcheckstyle.skip=true
+```
+
+待补,文章内部有报错
+
+## 2. Startup `soul`
+
+### 2.1. Startup `soul-admin` module
+
+> `soul-admin` is the management system for soul.
+
+Choose to use `MySQL` to storage gateway data and modify the datasource config.
+
+待补,文章内部有报错
+
+Run `org.dromara.soul.admin.SoulAdminBootstrap`.
+
+When success, please visit the website `http://localhost:9095/`, then jump to the login page, and input the corresponding user name and password to log in.
+
+The user name is `admin` and the password is `123456`.
+
+待补,文章内部有报错
+
+待补,文章内部有报错
+
+### 2.2. Startup `soul-bootstrap` module
+
+> `soul-bootstrap` is the core of soul.
+
+Check the configuration of `soul-bootstrap`.
+
+待补,文章内部有报错
+
+Please make sure the ip and the port has been configured for `soul-admin`.
+
+If the console output as follows, it means the startup is successful.
+
+```plain text
+2021-01-14 15:01:15.832 INFO 17943 --- [ main] b.s.s.d.w.WebsocketSyncDataConfiguration : you use websocket sync soul data.......
+2021-01-14 15:01:15.924 INFO 17943 --- [ main] o.d.s.p.s.d.w.WebsocketSyncDataService : websocket connection is successful.....
+2021-01-14 15:01:16.113 INFO 17943 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
+log4j:WARN No appenders could be found for logger (com.alibaba.dubbo.common.logger.LoggerFactory).
+log4j:WARN Please initialize the log4j system properly.
+log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
+2021-01-14 15:01:17.150 INFO 17943 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 9195
+2021-01-14 15:01:17.154 INFO 17943 --- [ main] o.d.s.b.SoulBootstrapApplication : Started SoulBootstrapApplication in 5.508 seconds (JVM running for 6.762)
+```
+
+## 3. Test
+
+> Add the `soul-examples` module to soul's pom.xml for test.
+
+### 3.1. Startup an HTTP backend service
+
+Startup `soul-examples-http`
+
+You can see the dependency in `soul-examples-http`'s pom.xml.
+
+```xml
+
+ org.dromara
+ soul-spring-boot-starter-client-springmvc
+ ${soul.version}
+
+```
+
+Configure the `application.yml`
+
+```yaml
+soul:
+ http:
+ adminUrl: http://localhost:9095
+ port: 8188
+ contextPath: /http
+ appName: http
+ full: false
+```
+
+If `soul.http.full`=false, you need to add the `@SoulSpringMvcClient` annotation in controller or controller method.
+
+#### 3.1.1. Test the service
+
+Visit `http://localhost:8188/test/findByUserId?userId=1` and the result as follows.
+
+待补,文章内部有报错
+
+#### 3.1.2. Test forward HTTP request
+
+Visit `http://localhost:9195/http/test/findByUserId?userId=1` and the result as follows.
+
+待补,文章内部有报错
+
+You can see the following information in the console of `soul-bootstrap`. It means the forward HTTP request is successful.
+
+```shell
+2021-01-14 20:42:57.123 INFO 29812 --- [work-threads-11] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
+2021-01-14 20:42:57.125 INFO 29812 --- [work-threads-11] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
+2021-01-14 20:42:57.126 INFO 29812 --- [work-threads-11] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8188/test/findByUserId?userId=1, retryTimes is 0
+```
+
+### 3.2. Startup two HTTP backend services to simulate load balance
+
+Choose `Allow parallel run`
+
+Change the port to `8189`
+
+Startup `soul-examples-http` again
+
+待补,文章内部有报错
+
+#### 3.2.1. Test the service
+
+Visit `http://localhost:8189/test/findByUserId?userId=1` and the result as follows.
+
+待补,文章内部有报错
+
+#### 3.2.2. Test load balance
+
+待补,文章内部有报错
+
+Configure two HTTP service in selector
+
+Visit `http://localhost:9195/http/test/findByUserId?userId=1` more and more and result as follows.
+
+待补,文章内部有报错
+You can see the following information in the console of `soul-bootstrap`. It means the load balance is successful.
+
+```shell
+2021-01-14 20:48:34.460 INFO 29812 --- [work-threads-21] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
+2021-01-14 20:48:34.460 INFO 29812 --- [work-threads-21] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
+2021-01-14 20:48:34.460 INFO 29812 --- [work-threads-21] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8189/test/findByUserId?userId=1, retryTimes is 0
+2021-01-14 20:48:35.147 INFO 29812 --- [work-threads-22] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
+2021-01-14 20:48:35.147 INFO 29812 --- [work-threads-22] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
+2021-01-14 20:48:35.147 INFO 29812 --- [work-threads-22] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8188/test/findByUserId?userId=1, retryTimes is 0
+2021-01-14 20:48:38.755 INFO 29812 --- [work-threads-23] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
+2021-01-14 20:48:38.756 INFO 29812 --- [work-threads-23] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
+2021-01-14 20:48:38.756 INFO 29812 --- [work-threads-23] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8188/test/findByUserId?userId=1, retryTimes is 0
+2021-01-14 20:48:39.609 INFO 29812 --- [work-threads-24] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
+2021-01-14 20:48:39.609 INFO 29812 --- [work-threads-24] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
+2021-01-14 20:48:39.609 INFO 29812 --- [work-threads-24] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8189/test/findByUserId?userId=1, retryTimes is 0
+2021-01-14 20:48:40.317 INFO 29812 --- [work-threads-25] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
+2021-01-14 20:48:40.317 INFO 29812 --- [work-threads-25] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
+2021-01-14 20:48:40.317 INFO 29812 --- [work-threads-25] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8188/test/findByUserId?userId=1, retryTimes is 0
+2021-01-14 20:48:40.976 INFO 29812 --- [-work-threads-1] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http
+2021-01-14 20:48:40.976 INFO 29812 --- [-work-threads-1] o.d.soul.plugin.base.AbstractSoulPlugin : divide selector success match , selector name :/http/test/**
+2021-01-14 20:48:40.977 INFO 29812 --- [-work-threads-1] o.d.s.plugin.httpclient.WebClientPlugin : The request urlPath is http://172.27.121.155:8188/test/findByUserId?userId=1, retryTimes is 0
+```
+
+#### 3.2.3. Press test
+
+Use `wrk` to press test and compare the two ways as follows.
+
+1. Visit the backend service directly.
+2. Visit the service via soul.
+
+The performance drops slightly after using the gateway, probably because of the extra layer of forwarding.
+
+```shell
+➜ soul git:(master) ✗ wrk -t8 -c40 -d30s http://localhost:8189/test/findByUserId\?userId\=1
+Running 30s test @ http://localhost:8189/test/findByUserId?userId=1
+ 8 threads and 40 connections
+ Thread Stats Avg Stdev Max +/- Stdev
+ Latency 6.06ms 28.81ms 442.25ms 98.22%
+ Req/Sec 2.05k 493.86 2.84k 74.82%
+ 486269 requests in 30.05s, 51.01MB read
+Requests/sec: 16179.68
+Transfer/sec: 1.70MB
+➜ soul git:(master) ✗ wrk -t8 -c40 -d30s http://localhost:9195/http/test/findByUserId\?userId\=1
+Running 30s test @ http://localhost:9195/http/test/findByUserId?userId=1
+ 8 threads and 40 connections
+ Thread Stats Avg Stdev Max +/- Stdev
+ Latency 14.37ms 18.11ms 255.66ms 93.06%
+ Req/Sec 459.41 139.11 1.01k 74.23%
+ 109533 requests in 30.09s, 11.49MB read
+Requests/sec: 3639.60
+Transfer/sec: 390.98KB
+```
+
+#### 3.2.4. Problem in the process
+
+When startup the port of `8189`,but the output of console is still `8188`.
+
+待补,文章内部有报错
+
+After modify the value of `soul.http.port`, the problem solved.
+
+待补,文章内部有报错
diff --git a/src/blog/soul_source_learning_02_divide_plugin.md b/src/blog/soul_source_learning_02_divide_plugin.md
index 9d34ddf4ff..dd1598ca2b 100644
--- a/src/blog/soul_source_learning_02_divide_plugin.md
+++ b/src/blog/soul_source_learning_02_divide_plugin.md
@@ -1,134 +1,134 @@
----
-title: Soul Learning(2) Use Divide Plugin
-author: yuanjie
-date: 2021-01-16
-tag:
- - Soul
-cover: /assets/img/architecture/soul-framework.png
-head:
- - - meta
- - name: Blog
----
-
-# The Divide plug-in uses
-
-## I. Initiation of the Project
-
-Start the soul-bootstrap (9195) and soul-admin (9095) modules. We can see from the bootstrap configuration file that the two modules perform data synchronization through the Web Socket protocol:
-
-
-
-You can also see from the bootstrap log:
-
-
-
-The so-called data synchronization refers to synchronizing the data configured in soul-admin to the JVM memory in the soul cluster, which is the key to the high performance of the gateway.
-
-After we start the two projects, we can test the divide plug-in through the background management system.
-
-## II. Introduction to divide Plug-in
-
-The divide plug-in is the core processing plug-in for the gateway to process HTTP protocol requests, and is also the only plug-in that is enabled by default:
-
-
-
-We can imagine what the gateway does and guess what the divide plug-in might do to handle HTTP requests?
-
-First of all, as a micro-service gateway, there must be a distributed micro-service cluster with multiple business lines behind it, and as a unified entrance to all services, the gateway must have the ability of traffic distribution/routing/load balancing, and the word divide, as its name implies, means distribution and distribution. So we can guess that the divide plug-in is to route and forward HTTP requests according to various rules, which is also the most basic capability of the gateway.
-
-When we open the list of plug-ins on the management interface, we can see that all plug-ins are composed of two parts: ** Selector ** (selector) and ** Selector rule **.
-
-The plug-in design idea is the core design idea of soul gateway, and the two concepts of selector and rule are also the soul of soul gateway. In theory, if we master it well, we can manage the traffic of any access gateway.
-
-A plug-in has multiple selectors, and one selector corresponds to multiple rules. The selector is equivalent to the first screening of traffic, and the rule is the final screening.
-
-### Selector
-
-
-
- * ** Name **: Give your selector an easily distinguishable name
- * ** Type **: custom flow is a custom flow. Full flow is full flow. Custom traffic means that the request will follow your matching methods and conditions below. Full flow does not go.
- * ** Match mode **: and or or means that the following multiple conditions are combined in the way of and or or.
- * ** Condition **:
- * URI: It means that you filter traffic according to the way of URI, and the way of match supports fuzzy matching (/**)
- * Header: refers to filtering traffic based on the fields in the request header.
- * Query: refers to filtering traffic based on the query criteria of the URI.
- * IP: Refers to filtering traffic based on the real IP you request.
- * Host: refers to filtering traffic based on the real host you request.
- * Post: Not recommended.
- * Condition matching:
- * Match: Fuzzy matching, recommended and URI condition collocation, support restful style matching. (/test/**)
- * =: The preceding and following values must be equal to match.
- * RegEx: Regular match, which means that the previous value matches the following regular expression.
- * Like: string fuzzy match.
- * ** Whether to open or not **: Open to take effect
- * ** Print the log **: When opened, the match log is printed when a match is made.
- * ** Order of execution **: When there are multiple selectors, the one with the smaller execution order is executed first.
-
-### Selector rule
-
-
-
-
-
-As you can see, the configuration of rules is similar to that of selectors, which can be understood as a more fine-grained custom configuration.
-
-## III. Use of divide plug-in
-
-Without further ado, let's just run the examples module provided by soul to demonstrate the divide plugin.
-
-
-
-Notice that we ended up running the soul-examples-http module. The configuration file can use the default or customize the contextPath and appName, as shown in the figure above.
-
-We need to note that the contextPath attribute is very important, which is equivalent to the namespace of all our HTTP requests, and the selector is aligned one by one. Generally speaking, we can configure a service to correspond to a contextPath, and multiple service instances configured with the same contextPath under a service will be automatically mapped to the same selector for load balancing.
-
-After we start the process with port 8188, we can find that the selector and rule corresponding to this instance are automatically configured in the divide plug-in list of the management console:
-
-
-
-You can see that the 8188 project address I started is automatically registered:
-
-
-
-### Test gateway routing
-
-Test the forwarding without gateway through postman first:
-
-```plain
-http://localhost:8188/order/findById?id=1
-```
-
-
-
-Then test the forwarding to this interface through the gateway:
-
-```plain
-http://localhost:9195/my-http/order/findById?id=1
-```
-
-
-
-Looking at the log, we found that it was indeed forwarded to the 8188 interface address through the gateway:
-
-
-
-### Test load balancing
-
-We change the port to 8189 and start the second process.
-
-
-
-Note that IDEA needs to remove the restriction of Single instance only:
-
-
-
-We enter the management console again and find that two configuration addresses appear under the my-http selector:
-
-
-
-At this point, we continue to test and find that the load balancing strategy does work:
-
-
-
-Today, I just demonstrated the most basic configuration of the divide plug-in, and there are various other rule configurations that can be tried later.
+---
+title: Soul Learning(2) Use Divide Plugin
+author: yuanjie
+date: 2021-01-16
+tag:
+ - Soul
+cover: /assets/img/architecture/soul-framework.png
+head:
+ - - meta
+ - name: Blog
+---
+
+# The Divide plug-in uses
+
+## I. Initiation of the Project
+
+Start the soul-bootstrap (9195) and soul-admin (9095) modules. We can see from the bootstrap configuration file that the two modules perform data synchronization through the Web Socket protocol:
+
+
+
+You can also see from the bootstrap log:
+
+
+
+The so-called data synchronization refers to synchronizing the data configured in soul-admin to the JVM memory in the soul cluster, which is the key to the high performance of the gateway.
+
+After we start the two projects, we can test the divide plug-in through the background management system.
+
+## II. Introduction to divide Plug-in
+
+The divide plug-in is the core processing plug-in for the gateway to process HTTP protocol requests, and is also the only plug-in that is enabled by default:
+
+
+
+We can imagine what the gateway does and guess what the divide plug-in might do to handle HTTP requests?
+
+First of all, as a micro-service gateway, there must be a distributed micro-service cluster with multiple business lines behind it, and as a unified entrance to all services, the gateway must have the ability of traffic distribution/routing/load balancing, and the word divide, as its name implies, means distribution and distribution. So we can guess that the divide plug-in is to route and forward HTTP requests according to various rules, which is also the most basic capability of the gateway.
+
+When we open the list of plug-ins on the management interface, we can see that all plug-ins are composed of two parts: ** Selector ** (selector) and ** Selector rule **.
+
+The plug-in design idea is the core design idea of soul gateway, and the two concepts of selector and rule are also the soul of soul gateway. In theory, if we master it well, we can manage the traffic of any access gateway.
+
+A plug-in has multiple selectors, and one selector corresponds to multiple rules. The selector is equivalent to the first screening of traffic, and the rule is the final screening.
+
+### Selector
+
+
+
+ * ** Name **: Give your selector an easily distinguishable name
+ * ** Type **: custom flow is a custom flow. Full flow is full flow. Custom traffic means that the request will follow your matching methods and conditions below. Full flow does not go.
+ * ** Match mode **: and or or means that the following multiple conditions are combined in the way of and or or.
+ * ** Condition **:
+ * URI: It means that you filter traffic according to the way of URI, and the way of match supports fuzzy matching (/**)
+ * Header: refers to filtering traffic based on the fields in the request header.
+ * Query: refers to filtering traffic based on the query criteria of the URI.
+ * IP: Refers to filtering traffic based on the real IP you request.
+ * Host: refers to filtering traffic based on the real host you request.
+ * Post: Not recommended.
+ * Condition matching:
+ * Match: Fuzzy matching, recommended and URI condition collocation, support restful style matching. (/test/**)
+ * =: The preceding and following values must be equal to match.
+ * RegEx: Regular match, which means that the previous value matches the following regular expression.
+ * Like: string fuzzy match.
+ * ** Whether to open or not **: Open to take effect
+ * ** Print the log **: When opened, the match log is printed when a match is made.
+ * ** Order of execution **: When there are multiple selectors, the one with the smaller execution order is executed first.
+
+### Selector rule
+
+
+
+
+
+As you can see, the configuration of rules is similar to that of selectors, which can be understood as a more fine-grained custom configuration.
+
+## III. Use of divide plug-in
+
+Without further ado, let's just run the examples module provided by soul to demonstrate the divide plugin.
+
+
+
+Notice that we ended up running the soul-examples-http module. The configuration file can use the default or customize the contextPath and appName, as shown in the figure above.
+
+We need to note that the contextPath attribute is very important, which is equivalent to the namespace of all our HTTP requests, and the selector is aligned one by one. Generally speaking, we can configure a service to correspond to a contextPath, and multiple service instances configured with the same contextPath under a service will be automatically mapped to the same selector for load balancing.
+
+After we start the process with port 8188, we can find that the selector and rule corresponding to this instance are automatically configured in the divide plug-in list of the management console:
+
+
+
+You can see that the 8188 project address I started is automatically registered:
+
+
+
+### Test gateway routing
+
+Test the forwarding without gateway through postman first:
+
+```plain
+http://localhost:8188/order/findById?id=1
+```
+
+
+
+Then test the forwarding to this interface through the gateway:
+
+```plain
+http://localhost:9195/my-http/order/findById?id=1
+```
+
+
+
+Looking at the log, we found that it was indeed forwarded to the 8188 interface address through the gateway:
+
+
+
+### Test load balancing
+
+We change the port to 8189 and start the second process.
+
+
+
+Note that IDEA needs to remove the restriction of Single instance only:
+
+
+
+We enter the management console again and find that two configuration addresses appear under the my-http selector:
+
+
+
+At this point, we continue to test and find that the load balancing strategy does work:
+
+
+
+Today, I just demonstrated the most basic configuration of the divide plug-in, and there are various other rule configurations that can be tried later.
diff --git a/src/blog/soul_source_learning_02_divide_plugin_source.md b/src/blog/soul_source_learning_02_divide_plugin_source.md
index 80ab7c5fcb..f7d4790d7c 100644
--- a/src/blog/soul_source_learning_02_divide_plugin_source.md
+++ b/src/blog/soul_source_learning_02_divide_plugin_source.md
@@ -1,15 +1,15 @@
----
-title: 待补,文章内部有报错
-author: 季鹏
-date: 2021-01-17
-tag:
- - Soul
-cover: /assets/img/architecture/soul-framework.png
-head:
- - - meta
- - name: Blog
----
-
-# Divide 插件如何转发 http 请求
-
-待补,文章内部有报错
+---
+title: 待补,文章内部有报错
+author: 季鹏
+date: 2021-01-17
+tag:
+ - Soul
+cover: /assets/img/architecture/soul-framework.png
+head:
+ - - meta
+ - name: Blog
+---
+
+# Divide 插件如何转发 http 请求
+
+待补,文章内部有报错
diff --git a/src/blog/soul_source_learning_02_http_client_register.md b/src/blog/soul_source_learning_02_http_client_register.md
index 87f953c944..a905b59f78 100644
--- a/src/blog/soul_source_learning_02_http_client_register.md
+++ b/src/blog/soul_source_learning_02_http_client_register.md
@@ -1,427 +1,427 @@
----
-title: Soul Gateway Learning (2) HTTP Client Access Source Code Parsing
-author: fanjinpeng
-date: 2021-01-18
-tag:
- - Soul
-cover: /assets/img/architecture/soul-framework.png
-head:
- - - meta
- - name: Blog
----
-
-# Logic Analysis of HTTP User Accessing to Soul Gateway
-
-## 1. Registration portal
-
-When the HTTP user accesses the Soul Gateway, it will call the soul-admin interface to register the interface that needs to be managed by the Soul Gateway. Let's see what we have done today.
-
-First look at the interface information called as follows:
-
-```java
-// SpringMvcClientBeanPostProcessor.java
-/**
- * Instantiates a new Soul client bean post processor.
- *
- * @param soulSpringMvcConfig the soul spring mvc config
- */
-public SpringMvcClientBeanPostProcessor(final SoulSpringMvcConfig soulSpringMvcConfig) {
- ValidateUtils.validate(soulSpringMvcConfig);
- this.soulSpringMvcConfig = soulSpringMvcConfig;
- url = soulSpringMvcConfig.getAdminUrl() + "/soul-client/springmvc-register";
- executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
-}
-```
-
-## 2. Spring mvc-register interface logic
-
-Search "springmvc-register" globally and find the SoulClientController under the soul-admin module. See here. Are we familiar with those who often write CRUD? Ha-ha
-
-```java
-// SoulClientController.java
-/**
- * Register spring mvc string.
- *
- * @param springMvcRegisterDTO the spring mvc register dto
- * @return the string
- */
-@PostMapping("/springmvc-register")
-public String registerSpringMvc(@RequestBody final SpringMvcRegisterDTO springMvcRegisterDTO) {
- return soulClientRegisterService.registerSpringMvc(springMvcRegisterDTO);
-}
-```
-
-Service layer implementation class:
-
-```java
-// SoulClientRegisterServiceImpl.java
-@Override
-@Transactional
-public String registerSpringMvc(final SpringMvcRegisterDTO dto) {
- if (dto.isRegisterMetaData()) {
- MetaDataDO exist = metaDataMapper.findByPath(dto.getPath());
- if (Objects.isNull(exist)) {
- saveSpringMvcMetaData(dto);
- }
- }
- String selectorId = handlerSpringMvcSelector(dto);
- handlerSpringMvcRule(selectorId, dto);
- return SoulResultMessage.SUCCESS;
-}
-```
-
-Dto. IsRegister MetaData () is used to determine whether to register metadata information. I don't know when to use it, and I have doubts.//TODO, go down first.
-
-### 2.1 Take a look at the method handlerSpringMvcSelector to handle the Selector.
-
-```java
-// SoulClientRegisterServiceImpl.java
-private String handlerSpringMvcSelector(final SpringMvcRegisterDTO dto) {
- String contextPath = dto.getContext();
- // 根据 contextPath 到数据库里查询,是否已经注册过。
- SelectorDO selectorDO = selectorService.findByName(contextPath);
- String selectorId;
- String uri = String.join(":", dto.getHost(), String.valueOf(dto.getPort()));
- if (Objects.isNull(selectorDO)) {
- // 还没有注册过
- selectorId = registerSelector(contextPath, dto.getRpcType(), dto.getAppName(), uri);
- } else {
- // 已经注册过,业务系统重启了会到这里
- selectorId = selectorDO.getId();
- //update upstream
- String handle = selectorDO.getHandle();
- String handleAdd;
- DivideUpstream addDivideUpstream = buildDivideUpstream(uri);
- SelectorData selectorData = selectorService.buildByName(contextPath);
- if (StringUtils.isBlank(handle)) {
- handleAdd = GsonUtils.getInstance().toJson(Collections.singletonList(addDivideUpstream));
- } else {
- List exist = GsonUtils.getInstance().fromList(handle, DivideUpstream.class);
- for (DivideUpstream upstream : exist) {
- if (upstream.getUpstreamUrl().equals(addDivideUpstream.getUpstreamUrl())) {
- return selectorId;
- }
- }
- exist.add(addDivideUpstream);
- handleAdd = GsonUtils.getInstance().toJson(exist);
- }
- selectorDO.setHandle(handleAdd);
- selectorData.setHandle(handleAdd);
- // update db
- selectorMapper.updateSelective(selectorDO);
- // submit upstreamCheck
- upstreamCheckService.submit(contextPath, addDivideUpstream);
- // publish change event.
- eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,
- Collections.singletonList(selectorData)));
- }
- return selectorId;
-}
-```
-
-#### 2.1.1 First Access to Soul Gateway
-
-For the new access, the selectorDO cannot be found in the database. Enter the registerSelector method to carefully see which database tables have been inserted with data.
-
-```java
-// SoulClientRegisterServiceImpl.java
-private String registerSelector(final String contextPath, final String rpcType, final String appName, final String uri) {
- SelectorDTO selectorDTO = SelectorDTO.builder()
- .name(contextPath)
- .type(SelectorTypeEnum.CUSTOM_FLOW.getCode())
- .matchMode(MatchModeEnum.AND.getCode())
- .enabled(Boolean.TRUE)
- .loged(Boolean.TRUE)
- .continued(Boolean.TRUE)
- .sort(1)
- .build();
- if (RpcTypeEnum.DUBBO.getName().equals(rpcType)) {
- selectorDTO.setPluginId(getPluginId(PluginEnum.DUBBO.getName()));
- } else if (RpcTypeEnum.SPRING_CLOUD.getName().equals(rpcType)) {
- selectorDTO.setPluginId(getPluginId(PluginEnum.SPRING_CLOUD.getName()));
- selectorDTO.setHandle(GsonUtils.getInstance().toJson(buildSpringCloudSelectorHandle(appName)));
- } else if (RpcTypeEnum.SOFA.getName().equals(rpcType)) {
- selectorDTO.setPluginId(getPluginId(PluginEnum.SOFA.getName()));
- selectorDTO.setHandle(appName);
- } else if (RpcTypeEnum.TARS.getName().equals(rpcType)) {
- selectorDTO.setPluginId(getPluginId(PluginEnum.TARS.getName()));
- selectorDTO.setHandle(appName);
- } else {
- //is divide
- DivideUpstream divideUpstream = buildDivideUpstream(uri);
- String handler = GsonUtils.getInstance().toJson(Collections.singletonList(divideUpstream));
- selectorDTO.setHandle(handler);
- selectorDTO.setPluginId(getPluginId(PluginEnum.DIVIDE.getName()));
- upstreamCheckService.submit(selectorDTO.getName(), divideUpstream);
- }
- SelectorConditionDTO selectorConditionDTO = new SelectorConditionDTO();
- selectorConditionDTO.setParamType(ParamTypeEnum.URI.getName());
- selectorConditionDTO.setParamName("/");
- selectorConditionDTO.setOperator(OperatorEnum.MATCH.getAlias());
- selectorConditionDTO.setParamValue(contextPath + "/**");
- selectorDTO.setSelectorConditions(Collections.singletonList(selectorConditionDTO));
- return selectorService.register(selectorDTO);
-}
-```
-
-Are you excited to see so many if else? You can think about how to optimize so many if else and PR ^-^.
-
-Having written so much, it is nothing more than encapsulating the Selector DTO object, and finally calling the selectorS ervice. Register (Selector DTO) into the library, and continuing to follow.
-
-```java
-// SelectorServiceImpl.java
-@Override
-public String register(final SelectorDTO selectorDTO) {
- SelectorDO selectorDO = SelectorDO.buildSelectorDO(selectorDTO);
- List selectorConditionDTOs = selectorDTO.getSelectorConditions();
- if (StringUtils.isEmpty(selectorDTO.getId())) {
- selectorMapper.insertSelective(selectorDO);
- selectorConditionDTOs.forEach(selectorConditionDTO -> {
- selectorConditionDTO.setSelectorId(selectorDO.getId());
- // 这里在 for 循环里调用 dao 层插入数据,是不是可以考虑挪出去一次性批量插入?
- selectorConditionMapper.insertSelective(SelectorConditionDO
- .buildSelectorConditionDO(selectorConditionDTO));
- });
- }
- publishEvent(selectorDO, selectorConditionDTOs);
- return selectorDO.getId();
-}
-```
-
-You can see that there are two warehousing methods, which insert data into the selector and selector \_ condition tables respectively. Here we will not discuss the structure and business significance of the table in detail, and we will add it later.
-
-The publishEvent method, which involves the ApplicationEventPublisher interface, is an implementation of the observer pattern. After the event is published, the subsequent operations are completed through the listener. Here, press No Table first, and then write an article for analysis.
-
-#### 2.1.2 Soul Gateway has been accessed
-
-Just like Inception, we go back two layers of dreams and go back to the other branch of inserting data. It can be imagined that the system that has been connected to the Soul gateway restarts, or the new node starts to go.
-
-Post the previous code again:
-
-```java
-// SoulClientRegisterServiceImpl.java
-private String handlerSpringMvcSelector(final SpringMvcRegisterDTO dto) {
- String contextPath = dto.getContext();
- // 根据 contextPath 到数据库里查询,是否已经注册过。
- SelectorDO selectorDO = selectorService.findByName(contextPath);
- String selectorId;
- String uri = String.join(":", dto.getHost(), String.valueOf(dto.getPort()));
- if (Objects.isNull(selectorDO)) {
- // 还没有注册过
- selectorId = registerSelector(contextPath, dto.getRpcType(), dto.getAppName(), uri);
- } else {
- // 已接入的业务系统重启,或新节点启动,会到这里
- selectorId = selectorDO.getId();
- //update upstream
- // handle 字段存储这个接口真实节点信息,可能存在多台机器需要负载均衡的场景
- String handle = selectorDO.getHandle();
- String handleAdd;
- DivideUpstream addDivideUpstream = buildDivideUpstream(uri);
- SelectorData selectorData = selectorService.buildByName(contextPath);
- if (StringUtils.isBlank(handle)) {
- // 这个接口虽然之前注册过,但第1个服务器节点接入 Soul 时会进来
- handleAdd = GsonUtils.getInstance().toJson(Collections.singletonList(addDivideUpstream));
- } else {
- // 如果已经至少有1个服务器节点已接入,会进到这里,判断是否是同一个节点(使用 upstreamUrl 区分),如果相同直接返回
- List exist = GsonUtils.getInstance().fromList(handle, DivideUpstream.class);
- for (DivideUpstream upstream : exist) {
- if (upstream.getUpstreamUrl().equals(addDivideUpstream.getUpstreamUrl())) {
- return selectorId;
- }
- }
- // 如果不是同一个节点,把新节点加入到 handle 字段中
- exist.add(addDivideUpstream);
- handleAdd = GsonUtils.getInstance().toJson(exist);
- }
- selectorDO.setHandle(handleAdd);
- selectorData.setHandle(handleAdd);
- // update db 更新数据库
- selectorMapper.updateSelective(selectorDO);
- // submit upstreamCheck
- upstreamCheckService.submit(contextPath, addDivideUpstream);
- // publish change event.
- eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,
- Collections.singletonList(selectorData)));
- }
- return selectorId;
-}
-```
-
-Because the database table structure design has not been studied, according to some known guesses, one selector corresponds to one divide plug-in, which is identified by contextPath (here is "/HTTP"), and one contextPath can deploy multiple server nodes. The node information is stored in the handle field as JSON.
-
-```json
-// handle/handleAdd 数据格式
-[
- {
- "upstreamHost": "localhost",
- "protocol": "http://",
- "upstreamUrl": "10.0.0.12:8188",
- "weight": 50,
- "status": true,
- "timestamp": 0,
- "warmup": 0
- }
-]
-```
-
-The next step is to update the database update Selective.
-
-upstreamCheckService.submit(contextPath, addDivideUpstream); The real server node information is cached in a Map (UPSTREAM \_ MAP), and there are regular tasks to detect the activity. If the service node is found to be down, it will be eliminated to prevent the request from being sent to the node that has been down.
-
-Then there is the eventPublisher. PublishEvent (), which, like the previous publishEvent method, publishes the event and completes the subsequent operations through the listener. Here, the message of data SelectorData modification is sent through the web socket long connection established with the Soul gateway. The Soul gateway modifies the data according to the message. What data is modified and how to modify it will be analyzed later.
-
-At this point, the handlerSpringMvcSelector method is finally analyzed.
-
-### 2.Let's take a look at the method handlerSpringMvcRule, which handles the Rule.
-
-```java
-// SoulClientRegisterServiceImpl.java
-private void handlerSpringMvcRule(final String selectorId, final SpringMvcRegisterDTO dto) {
- RuleDO ruleDO = ruleMapper.findByName(dto.getRuleName());
- if (Objects.isNull(ruleDO)) {
- registerRule(selectorId, dto.getPath(), dto.getRpcType(), dto.getRuleName());
- }
-}
-```
-
-First, take the name of the rule and go to the rule table to get the data. If the table name has been registered, there is no operation.
-
-Look at the database data, which is the interface address under the business system.
-
-```bash
-mysql> use soul;
-Database changed
-
-mysql> select * from rule where name = '/http/order/findById' \G
-*************************** 1. row ***************************
- id: 1349650371868782592
- selector_id: 1349650371302551552
- match_mode: 0
- name: /http/order/findById
- enabled: 1
- loged: 1
- sort: 1
- handle: {"loadBalance":"random","retry":0,"timeout":3000}
-date_created: 2021-01-14 17:31:39
-date_updated: 2021-01-14 17:31:39
-1 row in set (0.00 sec)
-```
-
-If you don't get the data, register this rule.
-
-```java
-// SoulClientRegisterServiceImpl.java
-private void registerRule(final String selectorId, final String path, final String rpcType, final String ruleName) {
- RuleHandle ruleHandle = RuleHandleFactory.ruleHandle(RpcTypeEnum.acquireByName(rpcType), path);
- RuleDTO ruleDTO = RuleDTO.builder()
- .selectorId(selectorId)
- .name(ruleName)
- .matchMode(MatchModeEnum.AND.getCode())
- .enabled(Boolean.TRUE)
- .loged(Boolean.TRUE)
- .sort(1)
- .handle(ruleHandle.toJson())
- .build();
- RuleConditionDTO ruleConditionDTO = RuleConditionDTO.builder()
- .paramType(ParamTypeEnum.URI.getName())
- .paramName("/")
- .paramValue(path)
- .build();
- if (path.indexOf("*") > 1) {
- ruleConditionDTO.setOperator(OperatorEnum.MATCH.getAlias());
- } else {
- ruleConditionDTO.setOperator(OperatorEnum.EQ.getAlias());
- }
- ruleDTO.setRuleConditions(Collections.singletonList(ruleConditionDTO));
- ruleService.register(ruleDTO);
-}
-```
-
-In the first line, the corresponding RuleHandle is obtained according to rpcType ( "HTTP"). Here, three types are built in by default. Here, HTTP corresponds to DivideRuleHandle.
-
-```java
-// RuleHandleFactory.java
-public final class RuleHandleFactory {
-
- /**
- * The RpcType to RuleHandle class map.
- */
- private static final Map> RPC_TYPE_TO_RULE_HANDLE_CLASS = new ConcurrentHashMap<>();
-
- /**
- * The default RuleHandle.
- */
- private static final Class extends RuleHandle> DEFAULT_RULE_HANDLE = SpringCloudRuleHandle.class;
-
- static {
- RPC_TYPE_TO_RULE_HANDLE_CLASS.put(RpcTypeEnum.HTTP, DivideRuleHandle.class);
- RPC_TYPE_TO_RULE_HANDLE_CLASS.put(RpcTypeEnum.DUBBO, DubboRuleHandle.class);
- RPC_TYPE_TO_RULE_HANDLE_CLASS.put(RpcTypeEnum.SOFA, SofaRuleHandle.class);
- }
-
- /**
- * Get a RuleHandle object with given rpc type and path.
- * @param rpcType rpc type.
- * @param path path.
- * @return RuleHandle object.
- */
- public static RuleHandle ruleHandle(final RpcTypeEnum rpcType, final String path) {
- if (Objects.isNull(rpcType)) {
- return null;
- }
- Class extends RuleHandle> clazz = RPC_TYPE_TO_RULE_HANDLE_CLASS.getOrDefault(rpcType, DEFAULT_RULE_HANDLE);
- try {
- return clazz.newInstance().createDefault(path);
- } catch (InstantiationException | IllegalAccessException e) {
- throw new SoulException(
- String.format("Init RuleHandle failed with rpc type: %s, rule class: %s, exception: %s",
- rpcType,
- clazz.getSimpleName(),
- e.getMessage()));
- }
- }
-}
-```
-
-Let's construct the RuleDTO object and register the rules.
-
-```java
-// RuleServiceImpl.java
-@Override
-public String register(final RuleDTO ruleDTO) {
- RuleDO ruleDO = RuleDO.buildRuleDO(ruleDTO);
- List ruleConditions = ruleDTO.getRuleConditions();
- if (StringUtils.isEmpty(ruleDTO.getId())) {
- ruleMapper.insertSelective(ruleDO);
- ruleConditions.forEach(ruleConditionDTO -> {
- ruleConditionDTO.setRuleId(ruleDO.getId());
- // 这里在 for 循环里调用 dao 层插入数据,是不是可以考虑挪出去一次性批量插入?
- ruleConditionMapper.insertSelective(RuleConditionDO
- .buildRuleConditionDO(ruleConditionDTO));
- });
- }
- publishEvent(ruleDO, ruleConditions);
- return ruleDO.getId();
-}
-```
-
-Insert data into the rule and rule \_ condition tables, respectively.
-
-The publishEvent () method sends RuleData data to the Soul gateway through the web socket long connection.
-
-## 3.Sum up
-
-At this point, the logical analysis of calling the "/soul-client/springmvc-register" interface is finished, and we summarize as follows:
-
-- Process the selector
- - Add or modify selector and selector \_ condition table data, and persist them to MySQL.
- - Send data change information to Soul gateway through websocket.
-- Process the rule
- - Add or modify the data of rule and rule \_ condition tables, and persist them to MySQL.
- - Send data change information to Soul gateway through websocket.
-
-The table structure and field meaning need further study and research. After the websocket is sent to the Soul gateway, what the gateway has done also needs follow-up analysis.
-
-At this point, the registration logic of the HTTP user accessing the Soul gateway is analyzed.
-
-If you have the need to use the gateway in your work, or you have the pursuit of learning the gateway, welcome to analyze and learn with me. Soul Gateway, you deserve it.
+---
+title: Soul Gateway Learning (2) HTTP Client Access Source Code Parsing
+author: fanjinpeng
+date: 2021-01-18
+tag:
+ - Soul
+cover: /assets/img/architecture/soul-framework.png
+head:
+ - - meta
+ - name: Blog
+---
+
+# Logic Analysis of HTTP User Accessing to Soul Gateway
+
+## 1. Registration portal
+
+When the HTTP user accesses the Soul Gateway, it will call the soul-admin interface to register the interface that needs to be managed by the Soul Gateway. Let's see what we have done today.
+
+First look at the interface information called as follows:
+
+```java
+// SpringMvcClientBeanPostProcessor.java
+/**
+ * Instantiates a new Soul client bean post processor.
+ *
+ * @param soulSpringMvcConfig the soul spring mvc config
+ */
+public SpringMvcClientBeanPostProcessor(final SoulSpringMvcConfig soulSpringMvcConfig) {
+ ValidateUtils.validate(soulSpringMvcConfig);
+ this.soulSpringMvcConfig = soulSpringMvcConfig;
+ url = soulSpringMvcConfig.getAdminUrl() + "/soul-client/springmvc-register";
+ executorService = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
+}
+```
+
+## 2. Spring mvc-register interface logic
+
+Search "springmvc-register" globally and find the SoulClientController under the soul-admin module. See here. Are we familiar with those who often write CRUD? Ha-ha
+
+```java
+// SoulClientController.java
+/**
+ * Register spring mvc string.
+ *
+ * @param springMvcRegisterDTO the spring mvc register dto
+ * @return the string
+ */
+@PostMapping("/springmvc-register")
+public String registerSpringMvc(@RequestBody final SpringMvcRegisterDTO springMvcRegisterDTO) {
+ return soulClientRegisterService.registerSpringMvc(springMvcRegisterDTO);
+}
+```
+
+Service layer implementation class:
+
+```java
+// SoulClientRegisterServiceImpl.java
+@Override
+@Transactional
+public String registerSpringMvc(final SpringMvcRegisterDTO dto) {
+ if (dto.isRegisterMetaData()) {
+ MetaDataDO exist = metaDataMapper.findByPath(dto.getPath());
+ if (Objects.isNull(exist)) {
+ saveSpringMvcMetaData(dto);
+ }
+ }
+ String selectorId = handlerSpringMvcSelector(dto);
+ handlerSpringMvcRule(selectorId, dto);
+ return SoulResultMessage.SUCCESS;
+}
+```
+
+Dto. IsRegister MetaData () is used to determine whether to register metadata information. I don't know when to use it, and I have doubts.//TODO, go down first.
+
+### 2.1 Take a look at the method handlerSpringMvcSelector to handle the Selector.
+
+```java
+// SoulClientRegisterServiceImpl.java
+private String handlerSpringMvcSelector(final SpringMvcRegisterDTO dto) {
+ String contextPath = dto.getContext();
+ // 根据 contextPath 到数据库里查询,是否已经注册过。
+ SelectorDO selectorDO = selectorService.findByName(contextPath);
+ String selectorId;
+ String uri = String.join(":", dto.getHost(), String.valueOf(dto.getPort()));
+ if (Objects.isNull(selectorDO)) {
+ // 还没有注册过
+ selectorId = registerSelector(contextPath, dto.getRpcType(), dto.getAppName(), uri);
+ } else {
+ // 已经注册过,业务系统重启了会到这里
+ selectorId = selectorDO.getId();
+ //update upstream
+ String handle = selectorDO.getHandle();
+ String handleAdd;
+ DivideUpstream addDivideUpstream = buildDivideUpstream(uri);
+ SelectorData selectorData = selectorService.buildByName(contextPath);
+ if (StringUtils.isBlank(handle)) {
+ handleAdd = GsonUtils.getInstance().toJson(Collections.singletonList(addDivideUpstream));
+ } else {
+ List exist = GsonUtils.getInstance().fromList(handle, DivideUpstream.class);
+ for (DivideUpstream upstream : exist) {
+ if (upstream.getUpstreamUrl().equals(addDivideUpstream.getUpstreamUrl())) {
+ return selectorId;
+ }
+ }
+ exist.add(addDivideUpstream);
+ handleAdd = GsonUtils.getInstance().toJson(exist);
+ }
+ selectorDO.setHandle(handleAdd);
+ selectorData.setHandle(handleAdd);
+ // update db
+ selectorMapper.updateSelective(selectorDO);
+ // submit upstreamCheck
+ upstreamCheckService.submit(contextPath, addDivideUpstream);
+ // publish change event.
+ eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,
+ Collections.singletonList(selectorData)));
+ }
+ return selectorId;
+}
+```
+
+#### 2.1.1 First Access to Soul Gateway
+
+For the new access, the selectorDO cannot be found in the database. Enter the registerSelector method to carefully see which database tables have been inserted with data.
+
+```java
+// SoulClientRegisterServiceImpl.java
+private String registerSelector(final String contextPath, final String rpcType, final String appName, final String uri) {
+ SelectorDTO selectorDTO = SelectorDTO.builder()
+ .name(contextPath)
+ .type(SelectorTypeEnum.CUSTOM_FLOW.getCode())
+ .matchMode(MatchModeEnum.AND.getCode())
+ .enabled(Boolean.TRUE)
+ .loged(Boolean.TRUE)
+ .continued(Boolean.TRUE)
+ .sort(1)
+ .build();
+ if (RpcTypeEnum.DUBBO.getName().equals(rpcType)) {
+ selectorDTO.setPluginId(getPluginId(PluginEnum.DUBBO.getName()));
+ } else if (RpcTypeEnum.SPRING_CLOUD.getName().equals(rpcType)) {
+ selectorDTO.setPluginId(getPluginId(PluginEnum.SPRING_CLOUD.getName()));
+ selectorDTO.setHandle(GsonUtils.getInstance().toJson(buildSpringCloudSelectorHandle(appName)));
+ } else if (RpcTypeEnum.SOFA.getName().equals(rpcType)) {
+ selectorDTO.setPluginId(getPluginId(PluginEnum.SOFA.getName()));
+ selectorDTO.setHandle(appName);
+ } else if (RpcTypeEnum.TARS.getName().equals(rpcType)) {
+ selectorDTO.setPluginId(getPluginId(PluginEnum.TARS.getName()));
+ selectorDTO.setHandle(appName);
+ } else {
+ //is divide
+ DivideUpstream divideUpstream = buildDivideUpstream(uri);
+ String handler = GsonUtils.getInstance().toJson(Collections.singletonList(divideUpstream));
+ selectorDTO.setHandle(handler);
+ selectorDTO.setPluginId(getPluginId(PluginEnum.DIVIDE.getName()));
+ upstreamCheckService.submit(selectorDTO.getName(), divideUpstream);
+ }
+ SelectorConditionDTO selectorConditionDTO = new SelectorConditionDTO();
+ selectorConditionDTO.setParamType(ParamTypeEnum.URI.getName());
+ selectorConditionDTO.setParamName("/");
+ selectorConditionDTO.setOperator(OperatorEnum.MATCH.getAlias());
+ selectorConditionDTO.setParamValue(contextPath + "/**");
+ selectorDTO.setSelectorConditions(Collections.singletonList(selectorConditionDTO));
+ return selectorService.register(selectorDTO);
+}
+```
+
+Are you excited to see so many if else? You can think about how to optimize so many if else and PR ^-^.
+
+Having written so much, it is nothing more than encapsulating the Selector DTO object, and finally calling the selectorS ervice. Register (Selector DTO) into the library, and continuing to follow.
+
+```java
+// SelectorServiceImpl.java
+@Override
+public String register(final SelectorDTO selectorDTO) {
+ SelectorDO selectorDO = SelectorDO.buildSelectorDO(selectorDTO);
+ List selectorConditionDTOs = selectorDTO.getSelectorConditions();
+ if (StringUtils.isEmpty(selectorDTO.getId())) {
+ selectorMapper.insertSelective(selectorDO);
+ selectorConditionDTOs.forEach(selectorConditionDTO -> {
+ selectorConditionDTO.setSelectorId(selectorDO.getId());
+ // 这里在 for 循环里调用 dao 层插入数据,是不是可以考虑挪出去一次性批量插入?
+ selectorConditionMapper.insertSelective(SelectorConditionDO
+ .buildSelectorConditionDO(selectorConditionDTO));
+ });
+ }
+ publishEvent(selectorDO, selectorConditionDTOs);
+ return selectorDO.getId();
+}
+```
+
+You can see that there are two warehousing methods, which insert data into the selector and selector \_ condition tables respectively. Here we will not discuss the structure and business significance of the table in detail, and we will add it later.
+
+The publishEvent method, which involves the ApplicationEventPublisher interface, is an implementation of the observer pattern. After the event is published, the subsequent operations are completed through the listener. Here, press No Table first, and then write an article for analysis.
+
+#### 2.1.2 Soul Gateway has been accessed
+
+Just like Inception, we go back two layers of dreams and go back to the other branch of inserting data. It can be imagined that the system that has been connected to the Soul gateway restarts, or the new node starts to go.
+
+Post the previous code again:
+
+```java
+// SoulClientRegisterServiceImpl.java
+private String handlerSpringMvcSelector(final SpringMvcRegisterDTO dto) {
+ String contextPath = dto.getContext();
+ // 根据 contextPath 到数据库里查询,是否已经注册过。
+ SelectorDO selectorDO = selectorService.findByName(contextPath);
+ String selectorId;
+ String uri = String.join(":", dto.getHost(), String.valueOf(dto.getPort()));
+ if (Objects.isNull(selectorDO)) {
+ // 还没有注册过
+ selectorId = registerSelector(contextPath, dto.getRpcType(), dto.getAppName(), uri);
+ } else {
+ // 已接入的业务系统重启,或新节点启动,会到这里
+ selectorId = selectorDO.getId();
+ //update upstream
+ // handle 字段存储这个接口真实节点信息,可能存在多台机器需要负载均衡的场景
+ String handle = selectorDO.getHandle();
+ String handleAdd;
+ DivideUpstream addDivideUpstream = buildDivideUpstream(uri);
+ SelectorData selectorData = selectorService.buildByName(contextPath);
+ if (StringUtils.isBlank(handle)) {
+ // 这个接口虽然之前注册过,但第1个服务器节点接入 Soul 时会进来
+ handleAdd = GsonUtils.getInstance().toJson(Collections.singletonList(addDivideUpstream));
+ } else {
+ // 如果已经至少有1个服务器节点已接入,会进到这里,判断是否是同一个节点(使用 upstreamUrl 区分),如果相同直接返回
+ List exist = GsonUtils.getInstance().fromList(handle, DivideUpstream.class);
+ for (DivideUpstream upstream : exist) {
+ if (upstream.getUpstreamUrl().equals(addDivideUpstream.getUpstreamUrl())) {
+ return selectorId;
+ }
+ }
+ // 如果不是同一个节点,把新节点加入到 handle 字段中
+ exist.add(addDivideUpstream);
+ handleAdd = GsonUtils.getInstance().toJson(exist);
+ }
+ selectorDO.setHandle(handleAdd);
+ selectorData.setHandle(handleAdd);
+ // update db 更新数据库
+ selectorMapper.updateSelective(selectorDO);
+ // submit upstreamCheck
+ upstreamCheckService.submit(contextPath, addDivideUpstream);
+ // publish change event.
+ eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.SELECTOR, DataEventTypeEnum.UPDATE,
+ Collections.singletonList(selectorData)));
+ }
+ return selectorId;
+}
+```
+
+Because the database table structure design has not been studied, according to some known guesses, one selector corresponds to one divide plug-in, which is identified by contextPath (here is "/HTTP"), and one contextPath can deploy multiple server nodes. The node information is stored in the handle field as JSON.
+
+```json
+// handle/handleAdd 数据格式
+[
+ {
+ "upstreamHost": "localhost",
+ "protocol": "http://",
+ "upstreamUrl": "10.0.0.12:8188",
+ "weight": 50,
+ "status": true,
+ "timestamp": 0,
+ "warmup": 0
+ }
+]
+```
+
+The next step is to update the database update Selective.
+
+upstreamCheckService.submit(contextPath, addDivideUpstream); The real server node information is cached in a Map (UPSTREAM \_ MAP), and there are regular tasks to detect the activity. If the service node is found to be down, it will be eliminated to prevent the request from being sent to the node that has been down.
+
+Then there is the eventPublisher. PublishEvent (), which, like the previous publishEvent method, publishes the event and completes the subsequent operations through the listener. Here, the message of data SelectorData modification is sent through the web socket long connection established with the Soul gateway. The Soul gateway modifies the data according to the message. What data is modified and how to modify it will be analyzed later.
+
+At this point, the handlerSpringMvcSelector method is finally analyzed.
+
+### 2.Let's take a look at the method handlerSpringMvcRule, which handles the Rule.
+
+```java
+// SoulClientRegisterServiceImpl.java
+private void handlerSpringMvcRule(final String selectorId, final SpringMvcRegisterDTO dto) {
+ RuleDO ruleDO = ruleMapper.findByName(dto.getRuleName());
+ if (Objects.isNull(ruleDO)) {
+ registerRule(selectorId, dto.getPath(), dto.getRpcType(), dto.getRuleName());
+ }
+}
+```
+
+First, take the name of the rule and go to the rule table to get the data. If the table name has been registered, there is no operation.
+
+Look at the database data, which is the interface address under the business system.
+
+```bash
+mysql> use soul;
+Database changed
+
+mysql> select * from rule where name = '/http/order/findById' \G
+*************************** 1. row ***************************
+ id: 1349650371868782592
+ selector_id: 1349650371302551552
+ match_mode: 0
+ name: /http/order/findById
+ enabled: 1
+ loged: 1
+ sort: 1
+ handle: {"loadBalance":"random","retry":0,"timeout":3000}
+date_created: 2021-01-14 17:31:39
+date_updated: 2021-01-14 17:31:39
+1 row in set (0.00 sec)
+```
+
+If you don't get the data, register this rule.
+
+```java
+// SoulClientRegisterServiceImpl.java
+private void registerRule(final String selectorId, final String path, final String rpcType, final String ruleName) {
+ RuleHandle ruleHandle = RuleHandleFactory.ruleHandle(RpcTypeEnum.acquireByName(rpcType), path);
+ RuleDTO ruleDTO = RuleDTO.builder()
+ .selectorId(selectorId)
+ .name(ruleName)
+ .matchMode(MatchModeEnum.AND.getCode())
+ .enabled(Boolean.TRUE)
+ .loged(Boolean.TRUE)
+ .sort(1)
+ .handle(ruleHandle.toJson())
+ .build();
+ RuleConditionDTO ruleConditionDTO = RuleConditionDTO.builder()
+ .paramType(ParamTypeEnum.URI.getName())
+ .paramName("/")
+ .paramValue(path)
+ .build();
+ if (path.indexOf("*") > 1) {
+ ruleConditionDTO.setOperator(OperatorEnum.MATCH.getAlias());
+ } else {
+ ruleConditionDTO.setOperator(OperatorEnum.EQ.getAlias());
+ }
+ ruleDTO.setRuleConditions(Collections.singletonList(ruleConditionDTO));
+ ruleService.register(ruleDTO);
+}
+```
+
+In the first line, the corresponding RuleHandle is obtained according to rpcType ( "HTTP"). Here, three types are built in by default. Here, HTTP corresponds to DivideRuleHandle.
+
+```java
+// RuleHandleFactory.java
+public final class RuleHandleFactory {
+
+ /**
+ * The RpcType to RuleHandle class map.
+ */
+ private static final Map> RPC_TYPE_TO_RULE_HANDLE_CLASS = new ConcurrentHashMap<>();
+
+ /**
+ * The default RuleHandle.
+ */
+ private static final Class extends RuleHandle> DEFAULT_RULE_HANDLE = SpringCloudRuleHandle.class;
+
+ static {
+ RPC_TYPE_TO_RULE_HANDLE_CLASS.put(RpcTypeEnum.HTTP, DivideRuleHandle.class);
+ RPC_TYPE_TO_RULE_HANDLE_CLASS.put(RpcTypeEnum.DUBBO, DubboRuleHandle.class);
+ RPC_TYPE_TO_RULE_HANDLE_CLASS.put(RpcTypeEnum.SOFA, SofaRuleHandle.class);
+ }
+
+ /**
+ * Get a RuleHandle object with given rpc type and path.
+ * @param rpcType rpc type.
+ * @param path path.
+ * @return RuleHandle object.
+ */
+ public static RuleHandle ruleHandle(final RpcTypeEnum rpcType, final String path) {
+ if (Objects.isNull(rpcType)) {
+ return null;
+ }
+ Class extends RuleHandle> clazz = RPC_TYPE_TO_RULE_HANDLE_CLASS.getOrDefault(rpcType, DEFAULT_RULE_HANDLE);
+ try {
+ return clazz.newInstance().createDefault(path);
+ } catch (InstantiationException | IllegalAccessException e) {
+ throw new SoulException(
+ String.format("Init RuleHandle failed with rpc type: %s, rule class: %s, exception: %s",
+ rpcType,
+ clazz.getSimpleName(),
+ e.getMessage()));
+ }
+ }
+}
+```
+
+Let's construct the RuleDTO object and register the rules.
+
+```java
+// RuleServiceImpl.java
+@Override
+public String register(final RuleDTO ruleDTO) {
+ RuleDO ruleDO = RuleDO.buildRuleDO(ruleDTO);
+ List ruleConditions = ruleDTO.getRuleConditions();
+ if (StringUtils.isEmpty(ruleDTO.getId())) {
+ ruleMapper.insertSelective(ruleDO);
+ ruleConditions.forEach(ruleConditionDTO -> {
+ ruleConditionDTO.setRuleId(ruleDO.getId());
+ // 这里在 for 循环里调用 dao 层插入数据,是不是可以考虑挪出去一次性批量插入?
+ ruleConditionMapper.insertSelective(RuleConditionDO
+ .buildRuleConditionDO(ruleConditionDTO));
+ });
+ }
+ publishEvent(ruleDO, ruleConditions);
+ return ruleDO.getId();
+}
+```
+
+Insert data into the rule and rule \_ condition tables, respectively.
+
+The publishEvent () method sends RuleData data to the Soul gateway through the web socket long connection.
+
+## 3.Sum up
+
+At this point, the logical analysis of calling the "/soul-client/springmvc-register" interface is finished, and we summarize as follows:
+
+- Process the selector
+ - Add or modify selector and selector \_ condition table data, and persist them to MySQL.
+ - Send data change information to Soul gateway through websocket.
+- Process the rule
+ - Add or modify the data of rule and rule \_ condition tables, and persist them to MySQL.
+ - Send data change information to Soul gateway through websocket.
+
+The table structure and field meaning need further study and research. After the websocket is sent to the Soul gateway, what the gateway has done also needs follow-up analysis.
+
+At this point, the registration logic of the HTTP user accessing the Soul gateway is analyzed.
+
+If you have the need to use the gateway in your work, or you have the pursuit of learning the gateway, welcome to analyze and learn with me. Soul Gateway, you deserve it.
diff --git a/src/blog/soul_source_learning_05_plugin.md b/src/blog/soul_source_learning_05_plugin.md
index 74ee2d6c29..259e779fa5 100644
--- a/src/blog/soul_source_learning_05_plugin.md
+++ b/src/blog/soul_source_learning_05_plugin.md
@@ -1,492 +1,492 @@
----
-title: Soul Gateway learning plugin chain and load balancing analysis
-author: zhuming
-date: 2021-01-15
-tag:
- - Soul
-cover: /assets/img/architecture/soul-framework.png
-head:
- - - meta
- - name: Blog
----
-
-# Plug-in chain summary
-
-Start with a class diagram:
-
-
-
-Two of the most basic plug-in classes are:
-
-- SoulPlugin: Defines the interface of the plug-in responsibility. The key method `execute()` is called by the upper layer. `skip()` The method can cause some plug-ins to be skipped in some requests.
-
-- AbstractPlugin: An abstract class that implements an interface `execute()`, defines a common execution process, and uses the design pattern of the template method to provide `doExecute()` an abstract method for the implementation class to write its own logic.
-
-## AbstractSoulPlugin
-
-Specific analysis of the `execute()` following `AbstractSoulPlugin` categories:
-
-```java
-public Mono execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
- String pluginName = named();
- final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
- // If pluginData.getEnabled() is false, it will skip to the next plugin, only a few plugins will enter this condition (DividePlugin, AlibabaDubboPlugin, etc.)
- if (pluginData != null && pluginData.getEnabled()) {
- // Get all selectors on the plugin
- final Collection selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
- if (CollectionUtils.isEmpty(selectors)) {
- return CheckUtils.checkSelector(pluginName, exchange, chain);
- }
- // Check whether the request path in the context matches the selector and get the only matching selector data
- final SelectorData selectorData = matchSelector(exchange, selectors);
- if (Objects.isNull(selectorData)) {
- if (PluginEnum.WAF.getName().equals(pluginName)) {
- return doExecute(exchange, chain, null, null);
- }
- return CheckUtils.checkSelector(pluginName, exchange, chain);
- }
- if (selectorData.getLoged()) {
- log.info("{} selector success match , selector name :{}", pluginName, selectorData.getName());
- }
- // Gets the individual resource rules in the selector
- final List rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
- if (CollectionUtils.isEmpty(rules)) {
- if (PluginEnum.WAF.getName().equals(pluginName)) {
- return doExecute(exchange, chain, null, null);
- }
- return CheckUtils.checkRule(pluginName, exchange, chain);
- }
- RuleData rule;
- if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
- rule = rules.get(rules.size() - 1);
- } else {
- // Match the path to obtain a unique rule
- rule = matchRule(exchange, rules);
- }
- if (Objects.isNull(rule)) {
- return CheckUtils.checkRule(pluginName, exchange, chain);
- }
- if (rule.getLoged()) {
- log.info("{} rule success match ,rule name :{}", pluginName, rule.getName());
- }
- // Execute methods of subclasses
- return doExecute(exchange, chain, selectorData, rule);
- }
- // Execute the next plug-in on the plug-in chain
- return chain.execute(exchange);
-}
-```
-
-Through code analysis, some conclusions can be drawn:
-
-- Execute () has two logics: one is the matching of the request path with the selector and the rule, which finally confirms a unique rule and calls the subclass doExecute (); The second is to execute the next plug-in in the plug-in chain.
-- The execute () actually abstracts a set of rule matching logic, which is used by all the "forwarding type" plug-ins. Currently, I know the forwarding type plug-ins are `DividePlugin` (HTTP request) and `AlibabaDubboPlugin` (dubbo request). Other types of plug-ins that do not override the execute () method will go directly to the next plug-in.
-
-## SoulPluginChain
-
-Another point here is the formation and chain call of the plug-in chain. Let's analyze `SoulPluginChain` this part:
-
-
-
-The SoulPluginChain interface also defines `execute()` methods for the caller to use, and its only subclass, DefaultSoulPluginChain, implements chained calls:
-
-```java
-public Mono execute(final ServerWebExchange exchange) {
- return Mono.defer(() -> {
- // plugins contains all plugins loaded by the gateway
- if (this.index < plugins.size()) {
- // Each time the execute() method is called, the index index increases and is called to the next plug-in
- SoulPlugin plugin = plugins.get(this.index++);
- // Determine whether the current plug-in needs to be skipped based on the context
- Boolean skip = plugin.skip(exchange);
- if (skip) {
- return this.execute(exchange);
- } else {
- return plugin.execute(exchange, this);
- }
- } else {
- return Mono.empty();
- }
- });
-}
-```
-
-It's curious `plugins` to see where this list of plug-ins comes from. Here's an explanation. DefaultSoulPluginChain is a static inner class of SoulWebHandler. Is `plugins` an attribute in the Soul Web Handle:
-
-```java
-public final class SoulWebHandler implements WebHandler {
-
- private List plugins;
-
- public SoulWebHandler(final List plugins) {
- this.plugins = plugins;
- // ...
- }
-
- @Override
- public Mono handle(@NonNull final ServerWebExchange exchange) {
- // ...
- return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
- .doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
- }
-
- private static class DefaultSoulPluginChain implements SoulPluginChain {
- }
-}
-```
-
-So where did the `plugins` SoulWeb Handler come from? You can continue to trace back to where its constructor was called:
-
-```java
-@Configuration
-public class SoulConfiguration {
-
- @Bean("webHandler")
- public SoulWebHandler soulWebHandler(final ObjectProvider> plugins) {
- List pluginList = plugins.getIfAvailable(Collections::emptyList);
- final List soulPlugins = pluginList.stream()
- .sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());
- soulPlugins.forEach(soulPlugin -> log.info("loader plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
- return new SoulWebHandler(soulPlugins);
- }
-}
-```
-
-It can be seen that the writing `plugins` is started by means of Spring Bean, that is, when the container starts, all plug-ins are loaded. Here, the entry parameter is used `ObjectProvider` to lazily load all beans of the SoulPlugin type (if none of them are used, no error will be reported) and inject them into the SoulWebHandler.
-
-** There's a little hole to watch out for! **
-
-All plug-ins, including DividePlugin, AlibabaDubboPlugin, etc., are configured by the XX PluginConfiguration class in their respective `soul-spring-boot-starter-plugin-xx` projects to register their own plug-ins as beans, similar to the following example:
-
-```java
-@Configuration
-public class DividePluginConfiguration {
-
- @Bean
- public SoulPlugin dividePlugin() {
- return new DividePlugin();
- }
-}
-```
-
-Therefore, in the gateway project `soul-bootstrap`, if you need to use a plug-in, you not only need to open the plug-in in the management background, but also need to confirm whether there is a dependency of the `soul-spring-boot-starter-plugin-xx` relevant plug-in in the following `soul-bootstrap` `pom.xml`, for example:
-
-```xml
-
- org.dromara
- soul-spring-boot-starter-plugin-divide
- ${project.version}
-
-```
-
-If you have a comment here or it doesn't exist, don't expect to see it on the plugin chain..
-
-## Plug-in project structure
-
-Finally, briefly describe the functions of each plug-in project:
-
-1. The first is the spring bean startup class project just mentioned, listing a general idea:
-
- ```
- soul-spring-boot-starter-plugin-alibaba-dubbo
- soul-spring-boot-starter-plugin-apache-dubbo
- soul-spring-boot-starter-plugin-context-path
- soul-spring-boot-starter-plugin-divide
- soul-spring-boot-starter-plugin-global
- soul-spring-boot-starter-plugin-httpclient
- soul-spring-boot-starter-plugin-hystrix
- soul-spring-boot-starter-plugin-monitor
- soul-spring-boot-starter-plugin-ratelimiter
- soul-spring-boot-starter-plugin-resilience4j
- soul-spring-boot-starter-plugin-rewrite
- soul-spring-boot-starter-plugin-sentinel
- soul-spring-boot-starter-plugin-sign
- soul-spring-boot-starter-plugin-sofa
- soul-spring-boot-starter-plugin-springcloud
- soul-spring-boot-starter-plugin-tars
- soul-spring-boot-starter-plugin-waf
- ```
-
- Their main functions have just been mentioned, registering their own SoulPlugin subclasses as spring beans, and registering spring beans to the PluginData Handler interface called in AbstractSoulPlugin. Provide its own implementation subclass, such as DividePluginDataHandler.
-
-2. Specific plug-in class project:
-
- ```
- soul-plugin-alibaba-dubbo
- soul-plugin-apache-dubbo
- soul-plugin-api
- soul-plugin-base
- soul-plugin-context-path
- soul-plugin-divide
- soul-plugin-global
- soul-plugin-httpclient
- soul-plugin-hystrix
- soul-plugin-monitor
- soul-plugin-ratelimiter
- soul-plugin-resilience4j
- soul-plugin-rewrite
- soul-plugin-sentinel
- soul-plugin-sign
- soul-plugin-sofa
- soul-plugin-springcloud
- soul-plugin-tars
- soul-plugin-waf
- ```
-
- Take the `soul-plugin-divide` DividePlugin and DividePluginDataHandler mentioned just now as examples. And the project also has node information cache manager Upstream Cache Manager, load balancing strategy class LoadBalance and so on.
-
-# DividePlugin
-
-The function of DividePlugin is to match Http requests. Since there are Http requests, there are naturally forwarding downstream and returning responses. So here we will analyze three plug-ins: DividePlugin, WebClientPlugin, WebClientResponsePlugin.
-
-Let's start with that implementation in `doExecute()` DividePlugin, where I just keep the core point:
-
-```java
-@Override
-protected Mono doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
- final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
- final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
- // Get the cluster of service nodes in the cache by selector ID
- final List upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
- // Call the load balancing method and pass in the policy type to get a unique node
- DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
- // Get the real url of the node and put it in the exchange context
- String domain = buildDomain(divideUpstream);
- String realURL = buildRealURL(domain, soulContext, exchange);
- exchange.getAttributes().put(Constants.HTTP_URL, realURL);
- // Continue to call the next plug-in
- return chain.execute(exchange);
-}
-```
-
-As you can see, after executing the DividePlugin `doExecute()` method, we already have the real path of the downstream service node in the ServerWeb Exchange context, and we just need to request it. But don't worry, the load balancing strategy here is also the key point, and then analyze.
-
-## Load balancing
-
-How to execute the load balancing of Soul Gateway involves not only various strategies (hasn, random, polling), but also the concept of "weight score". The specific configuration of the management background is as follows:
-
-待补,文章内部有报错
-
-待补,文章内部有报错
-
-待补,文章内部有报错
-
-After showing the background configuration, let's take a look at the code implementation of each strategy.
-
-## Hash
-
-```java
-public DivideUpstream doSelect(final List upstreamList, final String ip) {
- final ConcurrentSkipListMap treeMap = new ConcurrentSkipListMap<>();
- for (DivideUpstream address : upstreamList) {
- // Each node *VIRTUAL_NODE_NUM(default 5) to make the hash more uniform
- for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
- long addressHash = hash("SOUL-" + address.getUpstreamUrl() + "-HASH-" + i);
- treeMap.put(addressHash, address);
- }
- }
- // Obtain a hash value from the current ip address and compare treemap(ordered) to find a location greater than the hash value
- long hash = hash(String.valueOf(ip));
- SortedMap lastRing = treeMap.tailMap(hash);
- // As long as the service node does not increase or decrease, the node obtained by the same ip address can remain unchanged
- if (!lastRing.isEmpty()) {
- return lastRing.get(lastRing.firstKey());
- }
- return treeMap.firstEntry().getValue();
-}
-```
-
-The load balancing of the hash algorithm does not use the concept of "weight score", that is to say, for each unknown IP, the probability of each node being accessed is the same. (Of course, multiple calls to the same IP will only access the same node.)
-
-## RandomLoadBalance
-
-```java
-public DivideUpstream doSelect(final List upstreamList, final String ip) {
- // Total number
- int length = upstreamList.size();
- // Total weight
- int totalWeight = 0;
- // Whether the weights are the same
- boolean sameWeight = true;
- for (int i = 0; i < length; i++) {
- int weight = upstreamList.get(i).getWeight();
- // Cumulative total weight
- totalWeight += weight;
- if (sameWeight && i > 0
- && weight != upstreamList.get(i - 1).getWeight()) {
- // Calculate whether the ownership weight is the same
- sameWeight = false;
- }
- }
- if (totalWeight > 0 && !sameWeight) {
- // If the weights are not the same and the weights are greater than 0, random by the total weights
-int offset = RANDOM.nextInt(totalWeight);
-// and determine which segment the random value falls on
- for (DivideUpstream divideUpstream : upstreamList) {
- offset -= divideUpstream.getWeight();
- if (offset < 0) {
- return divideUpstream;
- }
- }
- }
- // Equally random if the weight is the same or if the weight is 0
- return upstreamList.get(RANDOM.nextInt(length));
-}
-```
-
-When the rule is used `random`, all the node weights are accumulated and the number is obtained randomly, depending on the weight fragment of the node; If the score is 0 or the same, it is straightforward to randomize the cluster length.
-
-## RoundRobinLoadBalance
-
-```java
-public DivideUpstream doSelect(final List upstreamList, final String ip) {
- String key = upstreamList.get(0).getUpstreamUrl();
- ConcurrentMap map = methodWeightMap.get(key);
- if (map == null) {
- methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<>(16));
- map = methodWeightMap.get(key);
- }
- int totalWeight = 0;
- long maxCurrent = Long.MIN_VALUE;
- long now = System.currentTimeMillis();
- DivideUpstream selectedInvoker = null;
- WeightedRoundRobin selectedWRR = null;
- for (DivideUpstream upstream : upstreamList) {
- String rKey = upstream.getUpstreamUrl();
- // Retrieves the node information in the cache
- WeightedRoundRobin weightedRoundRobin = map.get(rKey);
- int weight = upstream.getWeight();
- if (weightedRoundRobin == null) {
- weightedRoundRobin = new WeightedRoundRobin();
- weightedRoundRobin.setWeight(weight);
- map.putIfAbsent(rKey, weightedRoundRobin);
- }
- if (weight != weightedRoundRobin.getWeight()) {
- weightedRoundRobin.setWeight(weight);
- }
- // Here is the first key: the score in the cache increases the weight score of the current node
- long cur = weightedRoundRobin.increaseCurrent();
- weightedRoundRobin.setLastUpdate(now);
- // Select the node with a high cache score
- if (cur > maxCurrent) {
- maxCurrent = cur;
- selectedInvoker = upstream;
- selectedWRR = weightedRoundRobin;
- }
- totalWeight += weight;
- }
- if (!updateLock.get() && upstreamList.size() != map.size() && updateLock.compareAndSet(false, true)) {
- try {
- ConcurrentMap newMap = new ConcurrentHashMap<>(map);
- newMap.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > recyclePeriod);
- methodWeightMap.put(key, newMap);
- } finally {
- updateLock.set(false);
- }
- }
- if (selectedInvoker != null) {
- // Here is the second key: the score in the cache, reducing the total node weight score
- selectedWRR.sel(totalWeight);
- return selectedInvoker;
- }
- return upstreamList.get(0);
-}
-```
-
-This algorithm is a bit complicated. Let me explain the core aspect of calculating weights:
-
-- Two nodes with a score of 2 and 100 respectively enter, and each of them is kept in the cache, with the score starting from 0.
-- After the for loop, the scores of the two nodes in the cache will increase based on themselves. Assuming that the following steps are not performed, the cache will be 2 and 100 for the first time, 4 and 200 for the second time, and so on.
-- The third key step is to select the node cache with the highest score and take "punishment" measures to reduce the cumulative score of all nodes, that is, 102.
-
-According to the steps of this algorithm, nodes that have not been selected, as "growth rewards", will continue to increase on their own basis. The selected node, as a "penalty," reduces the sum of the weights of the other nodes.
-
-It can be predicted that a node with a small weight will not be selected until a long time later. However, at that moment, it will be punished with great strength, which will lead to a long accumulation of strength once it returns to the pre-liberation period. For nodes with large weight scores, the penalty for being selected each time is very small. Even if the score is too low to be selected after many times, his reward score (itself) is particularly high, and one increase far surpasses other nodes.
-
-## WebClientPlugin
-
-After the DividePlugin plug-in is called, the downstream service node path is determined, and then the Web ClientPlugin plug-in comes into play. It implements the SoulPlugin interface directly and implements the `execute()` methods (keeping only the core code):
-
-```java
-public Mono execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
- String urlPath = exchange.getAttribute(Constants.HTTP_URL);
- // Request type: Get request, orPost request, etc
- HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
- // Build a shell of the request object and inject the request type and URL
- WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(urlPath);
- return handleRequestBody(requestBodySpec, exchange, timeout, chain);
-}
-
-private Mono handleRequestBody(final WebClient.RequestBodySpec requestBodySpec,
- final ServerWebExchange exchange,
- final long timeout,
- final SoulPluginChain chain) {
- return requestBodySpec.headers(httpHeaders -> {
- // Add the request header in context... Later is also to add some attributes, do not go into details
- httpHeaders.addAll(exchange.getRequest().getHeaders());
- httpHeaders.remove(HttpHeaders.HOST);
- })
- .contentType(buildMediaType(exchange))
- .body(BodyInserters.fromDataBuffers(exchange.getRequest().getBody()))
- // Start asynchronous http calls to downstream services
- .exchange()
- .doOnError(e -> log.error(e.getMessage()))
- .timeout(Duration.ofMillis(timeout))
- // Callback receives the return value
- .flatMap(e -> doNext(e, exchange, chain));
-}
-
-// Here is an asynchronous callback method that works in another thread
-private Mono doNext(final ClientResponse res, final ServerWebExchange exchange, final SoulPluginChain chain) {
- // ...
- // Continue to complete the remaining plug-in chain calls
- return chain.execute(exchange);
-}
-```
-
-Take a quick look at `handleRequestBody()` the implementation of this method in `exchange()`, here are the key Http calls:
-
-```java
-class DefaultWebClient implements WebClient {
- @Override
- public Mono exchange() {
- ClientRequest request = (this.inserter != null ?
- initRequestBuilder().body(this.inserter).build() :
- initRequestBuilder().build());
- // Here is the critical call, which will go to spring-web-reactive
- return Mono.defer(() -> exchangeFunction.exchange(request)
- .checkpoint("Request to " + this.httpMethod.name() + " " + this.uri + " [DefaultWebClient]")
- .switchIfEmpty(NO_HTTP_CLIENT_RESPONSE_ERROR));
- }
-}
-```
-
-To sum up, the processing of Web ClientPlugin will call the downstream service asynchronously, wait for the response, and then execute the subsequent plug-in chain call in another thread.
-
-## WebClientResponseClient
-
-Finally, the plug-in chain goes to the Web ClientResponseClient link to encapsulate the response information:
-
-```java
-public Mono execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
- return chain.execute(exchange).then(Mono.defer(() -> {
- // Gets the response information stored in the context
- ServerHttpResponse response = exchange.getResponse();
- ClientResponse clientResponse = exchange.getAttribute(Constants.CLIENT_RESPONSE_ATTR);
- if (Objects.isNull(clientResponse)
- || response.getStatusCode() == HttpStatus.BAD_GATEWAY
- || response.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
- Object error = SoulResultWarp.error(SoulResultEnum.SERVICE_RESULT_ERROR.getCode(), SoulResultEnum.SERVICE_RESULT_ERROR.getMsg(), null);
- return WebFluxResultUtils.result(exchange, error);
- } else if (response.getStatusCode() == HttpStatus.GATEWAY_TIMEOUT) {
- Object error = SoulResultWarp.error(SoulResultEnum.SERVICE_TIMEOUT.getCode(), SoulResultEnum.SERVICE_TIMEOUT.getMsg(), null);
- return WebFluxResultUtils.result(exchange, error);
- }
- // Various assembly
- response.setStatusCode(clientResponse.statusCode());
- response.getCookies().putAll(clientResponse.cookies());
- response.getHeaders().putAll(clientResponse.headers().asHttpHeaders());
- return response.writeWith(clientResponse.body(BodyExtractors.toDataBuffers()));
- }));
-}
-```
+---
+title: Soul Gateway learning plugin chain and load balancing analysis
+author: zhuming
+date: 2021-01-15
+tag:
+ - Soul
+cover: /assets/img/architecture/soul-framework.png
+head:
+ - - meta
+ - name: Blog
+---
+
+# Plug-in chain summary
+
+Start with a class diagram:
+
+
+
+Two of the most basic plug-in classes are:
+
+- SoulPlugin: Defines the interface of the plug-in responsibility. The key method `execute()` is called by the upper layer. `skip()` The method can cause some plug-ins to be skipped in some requests.
+
+- AbstractPlugin: An abstract class that implements an interface `execute()`, defines a common execution process, and uses the design pattern of the template method to provide `doExecute()` an abstract method for the implementation class to write its own logic.
+
+## AbstractSoulPlugin
+
+Specific analysis of the `execute()` following `AbstractSoulPlugin` categories:
+
+```java
+public Mono execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
+ String pluginName = named();
+ final PluginData pluginData = BaseDataCache.getInstance().obtainPluginData(pluginName);
+ // If pluginData.getEnabled() is false, it will skip to the next plugin, only a few plugins will enter this condition (DividePlugin, AlibabaDubboPlugin, etc.)
+ if (pluginData != null && pluginData.getEnabled()) {
+ // Get all selectors on the plugin
+ final Collection selectors = BaseDataCache.getInstance().obtainSelectorData(pluginName);
+ if (CollectionUtils.isEmpty(selectors)) {
+ return CheckUtils.checkSelector(pluginName, exchange, chain);
+ }
+ // Check whether the request path in the context matches the selector and get the only matching selector data
+ final SelectorData selectorData = matchSelector(exchange, selectors);
+ if (Objects.isNull(selectorData)) {
+ if (PluginEnum.WAF.getName().equals(pluginName)) {
+ return doExecute(exchange, chain, null, null);
+ }
+ return CheckUtils.checkSelector(pluginName, exchange, chain);
+ }
+ if (selectorData.getLoged()) {
+ log.info("{} selector success match , selector name :{}", pluginName, selectorData.getName());
+ }
+ // Gets the individual resource rules in the selector
+ final List rules = BaseDataCache.getInstance().obtainRuleData(selectorData.getId());
+ if (CollectionUtils.isEmpty(rules)) {
+ if (PluginEnum.WAF.getName().equals(pluginName)) {
+ return doExecute(exchange, chain, null, null);
+ }
+ return CheckUtils.checkRule(pluginName, exchange, chain);
+ }
+ RuleData rule;
+ if (selectorData.getType() == SelectorTypeEnum.FULL_FLOW.getCode()) {
+ rule = rules.get(rules.size() - 1);
+ } else {
+ // Match the path to obtain a unique rule
+ rule = matchRule(exchange, rules);
+ }
+ if (Objects.isNull(rule)) {
+ return CheckUtils.checkRule(pluginName, exchange, chain);
+ }
+ if (rule.getLoged()) {
+ log.info("{} rule success match ,rule name :{}", pluginName, rule.getName());
+ }
+ // Execute methods of subclasses
+ return doExecute(exchange, chain, selectorData, rule);
+ }
+ // Execute the next plug-in on the plug-in chain
+ return chain.execute(exchange);
+}
+```
+
+Through code analysis, some conclusions can be drawn:
+
+- Execute () has two logics: one is the matching of the request path with the selector and the rule, which finally confirms a unique rule and calls the subclass doExecute (); The second is to execute the next plug-in in the plug-in chain.
+- The execute () actually abstracts a set of rule matching logic, which is used by all the "forwarding type" plug-ins. Currently, I know the forwarding type plug-ins are `DividePlugin` (HTTP request) and `AlibabaDubboPlugin` (dubbo request). Other types of plug-ins that do not override the execute () method will go directly to the next plug-in.
+
+## SoulPluginChain
+
+Another point here is the formation and chain call of the plug-in chain. Let's analyze `SoulPluginChain` this part:
+
+
+
+The SoulPluginChain interface also defines `execute()` methods for the caller to use, and its only subclass, DefaultSoulPluginChain, implements chained calls:
+
+```java
+public Mono execute(final ServerWebExchange exchange) {
+ return Mono.defer(() -> {
+ // plugins contains all plugins loaded by the gateway
+ if (this.index < plugins.size()) {
+ // Each time the execute() method is called, the index index increases and is called to the next plug-in
+ SoulPlugin plugin = plugins.get(this.index++);
+ // Determine whether the current plug-in needs to be skipped based on the context
+ Boolean skip = plugin.skip(exchange);
+ if (skip) {
+ return this.execute(exchange);
+ } else {
+ return plugin.execute(exchange, this);
+ }
+ } else {
+ return Mono.empty();
+ }
+ });
+}
+```
+
+It's curious `plugins` to see where this list of plug-ins comes from. Here's an explanation. DefaultSoulPluginChain is a static inner class of SoulWebHandler. Is `plugins` an attribute in the Soul Web Handle:
+
+```java
+public final class SoulWebHandler implements WebHandler {
+
+ private List plugins;
+
+ public SoulWebHandler(final List plugins) {
+ this.plugins = plugins;
+ // ...
+ }
+
+ @Override
+ public Mono handle(@NonNull final ServerWebExchange exchange) {
+ // ...
+ return new DefaultSoulPluginChain(plugins).execute(exchange).subscribeOn(scheduler)
+ .doOnSuccess(t -> startTimer.ifPresent(time -> MetricsTrackerFacade.getInstance().histogramObserveDuration(time)));
+ }
+
+ private static class DefaultSoulPluginChain implements SoulPluginChain {
+ }
+}
+```
+
+So where did the `plugins` SoulWeb Handler come from? You can continue to trace back to where its constructor was called:
+
+```java
+@Configuration
+public class SoulConfiguration {
+
+ @Bean("webHandler")
+ public SoulWebHandler soulWebHandler(final ObjectProvider> plugins) {
+ List pluginList = plugins.getIfAvailable(Collections::emptyList);
+ final List soulPlugins = pluginList.stream()
+ .sorted(Comparator.comparingInt(SoulPlugin::getOrder)).collect(Collectors.toList());
+ soulPlugins.forEach(soulPlugin -> log.info("loader plugin:[{}] [{}]", soulPlugin.named(), soulPlugin.getClass().getName()));
+ return new SoulWebHandler(soulPlugins);
+ }
+}
+```
+
+It can be seen that the writing `plugins` is started by means of Spring Bean, that is, when the container starts, all plug-ins are loaded. Here, the entry parameter is used `ObjectProvider` to lazily load all beans of the SoulPlugin type (if none of them are used, no error will be reported) and inject them into the SoulWebHandler.
+
+** There's a little hole to watch out for! **
+
+All plug-ins, including DividePlugin, AlibabaDubboPlugin, etc., are configured by the XX PluginConfiguration class in their respective `soul-spring-boot-starter-plugin-xx` projects to register their own plug-ins as beans, similar to the following example:
+
+```java
+@Configuration
+public class DividePluginConfiguration {
+
+ @Bean
+ public SoulPlugin dividePlugin() {
+ return new DividePlugin();
+ }
+}
+```
+
+Therefore, in the gateway project `soul-bootstrap`, if you need to use a plug-in, you not only need to open the plug-in in the management background, but also need to confirm whether there is a dependency of the `soul-spring-boot-starter-plugin-xx` relevant plug-in in the following `soul-bootstrap` `pom.xml`, for example:
+
+```xml
+
+ org.dromara
+ soul-spring-boot-starter-plugin-divide
+ ${project.version}
+
+```
+
+If you have a comment here or it doesn't exist, don't expect to see it on the plugin chain..
+
+## Plug-in project structure
+
+Finally, briefly describe the functions of each plug-in project:
+
+1. The first is the spring bean startup class project just mentioned, listing a general idea:
+
+ ```
+ soul-spring-boot-starter-plugin-alibaba-dubbo
+ soul-spring-boot-starter-plugin-apache-dubbo
+ soul-spring-boot-starter-plugin-context-path
+ soul-spring-boot-starter-plugin-divide
+ soul-spring-boot-starter-plugin-global
+ soul-spring-boot-starter-plugin-httpclient
+ soul-spring-boot-starter-plugin-hystrix
+ soul-spring-boot-starter-plugin-monitor
+ soul-spring-boot-starter-plugin-ratelimiter
+ soul-spring-boot-starter-plugin-resilience4j
+ soul-spring-boot-starter-plugin-rewrite
+ soul-spring-boot-starter-plugin-sentinel
+ soul-spring-boot-starter-plugin-sign
+ soul-spring-boot-starter-plugin-sofa
+ soul-spring-boot-starter-plugin-springcloud
+ soul-spring-boot-starter-plugin-tars
+ soul-spring-boot-starter-plugin-waf
+ ```
+
+ Their main functions have just been mentioned, registering their own SoulPlugin subclasses as spring beans, and registering spring beans to the PluginData Handler interface called in AbstractSoulPlugin. Provide its own implementation subclass, such as DividePluginDataHandler.
+
+2. Specific plug-in class project:
+
+ ```
+ soul-plugin-alibaba-dubbo
+ soul-plugin-apache-dubbo
+ soul-plugin-api
+ soul-plugin-base
+ soul-plugin-context-path
+ soul-plugin-divide
+ soul-plugin-global
+ soul-plugin-httpclient
+ soul-plugin-hystrix
+ soul-plugin-monitor
+ soul-plugin-ratelimiter
+ soul-plugin-resilience4j
+ soul-plugin-rewrite
+ soul-plugin-sentinel
+ soul-plugin-sign
+ soul-plugin-sofa
+ soul-plugin-springcloud
+ soul-plugin-tars
+ soul-plugin-waf
+ ```
+
+ Take the `soul-plugin-divide` DividePlugin and DividePluginDataHandler mentioned just now as examples. And the project also has node information cache manager Upstream Cache Manager, load balancing strategy class LoadBalance and so on.
+
+# DividePlugin
+
+The function of DividePlugin is to match Http requests. Since there are Http requests, there are naturally forwarding downstream and returning responses. So here we will analyze three plug-ins: DividePlugin, WebClientPlugin, WebClientResponsePlugin.
+
+Let's start with that implementation in `doExecute()` DividePlugin, where I just keep the core point:
+
+```java
+@Override
+protected Mono doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
+ final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
+ final DivideRuleHandle ruleHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), DivideRuleHandle.class);
+ // Get the cluster of service nodes in the cache by selector ID
+ final List upstreamList = UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selector.getId());
+ // Call the load balancing method and pass in the policy type to get a unique node
+ DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
+ // Get the real url of the node and put it in the exchange context
+ String domain = buildDomain(divideUpstream);
+ String realURL = buildRealURL(domain, soulContext, exchange);
+ exchange.getAttributes().put(Constants.HTTP_URL, realURL);
+ // Continue to call the next plug-in
+ return chain.execute(exchange);
+}
+```
+
+As you can see, after executing the DividePlugin `doExecute()` method, we already have the real path of the downstream service node in the ServerWeb Exchange context, and we just need to request it. But don't worry, the load balancing strategy here is also the key point, and then analyze.
+
+## Load balancing
+
+How to execute the load balancing of Soul Gateway involves not only various strategies (hasn, random, polling), but also the concept of "weight score". The specific configuration of the management background is as follows:
+
+待补,文章内部有报错
+
+待补,文章内部有报错
+
+待补,文章内部有报错
+
+After showing the background configuration, let's take a look at the code implementation of each strategy.
+
+## Hash
+
+```java
+public DivideUpstream doSelect(final List upstreamList, final String ip) {
+ final ConcurrentSkipListMap treeMap = new ConcurrentSkipListMap<>();
+ for (DivideUpstream address : upstreamList) {
+ // Each node *VIRTUAL_NODE_NUM(default 5) to make the hash more uniform
+ for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
+ long addressHash = hash("SOUL-" + address.getUpstreamUrl() + "-HASH-" + i);
+ treeMap.put(addressHash, address);
+ }
+ }
+ // Obtain a hash value from the current ip address and compare treemap(ordered) to find a location greater than the hash value
+ long hash = hash(String.valueOf(ip));
+ SortedMap lastRing = treeMap.tailMap(hash);
+ // As long as the service node does not increase or decrease, the node obtained by the same ip address can remain unchanged
+ if (!lastRing.isEmpty()) {
+ return lastRing.get(lastRing.firstKey());
+ }
+ return treeMap.firstEntry().getValue();
+}
+```
+
+The load balancing of the hash algorithm does not use the concept of "weight score", that is to say, for each unknown IP, the probability of each node being accessed is the same. (Of course, multiple calls to the same IP will only access the same node.)
+
+## RandomLoadBalance
+
+```java
+public DivideUpstream doSelect(final List upstreamList, final String ip) {
+ // Total number
+ int length = upstreamList.size();
+ // Total weight
+ int totalWeight = 0;
+ // Whether the weights are the same
+ boolean sameWeight = true;
+ for (int i = 0; i < length; i++) {
+ int weight = upstreamList.get(i).getWeight();
+ // Cumulative total weight
+ totalWeight += weight;
+ if (sameWeight && i > 0
+ && weight != upstreamList.get(i - 1).getWeight()) {
+ // Calculate whether the ownership weight is the same
+ sameWeight = false;
+ }
+ }
+ if (totalWeight > 0 && !sameWeight) {
+ // If the weights are not the same and the weights are greater than 0, random by the total weights
+int offset = RANDOM.nextInt(totalWeight);
+// and determine which segment the random value falls on
+ for (DivideUpstream divideUpstream : upstreamList) {
+ offset -= divideUpstream.getWeight();
+ if (offset < 0) {
+ return divideUpstream;
+ }
+ }
+ }
+ // Equally random if the weight is the same or if the weight is 0
+ return upstreamList.get(RANDOM.nextInt(length));
+}
+```
+
+When the rule is used `random`, all the node weights are accumulated and the number is obtained randomly, depending on the weight fragment of the node; If the score is 0 or the same, it is straightforward to randomize the cluster length.
+
+## RoundRobinLoadBalance
+
+```java
+public DivideUpstream doSelect(final List upstreamList, final String ip) {
+ String key = upstreamList.get(0).getUpstreamUrl();
+ ConcurrentMap map = methodWeightMap.get(key);
+ if (map == null) {
+ methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<>(16));
+ map = methodWeightMap.get(key);
+ }
+ int totalWeight = 0;
+ long maxCurrent = Long.MIN_VALUE;
+ long now = System.currentTimeMillis();
+ DivideUpstream selectedInvoker = null;
+ WeightedRoundRobin selectedWRR = null;
+ for (DivideUpstream upstream : upstreamList) {
+ String rKey = upstream.getUpstreamUrl();
+ // Retrieves the node information in the cache
+ WeightedRoundRobin weightedRoundRobin = map.get(rKey);
+ int weight = upstream.getWeight();
+ if (weightedRoundRobin == null) {
+ weightedRoundRobin = new WeightedRoundRobin();
+ weightedRoundRobin.setWeight(weight);
+ map.putIfAbsent(rKey, weightedRoundRobin);
+ }
+ if (weight != weightedRoundRobin.getWeight()) {
+ weightedRoundRobin.setWeight(weight);
+ }
+ // Here is the first key: the score in the cache increases the weight score of the current node
+ long cur = weightedRoundRobin.increaseCurrent();
+ weightedRoundRobin.setLastUpdate(now);
+ // Select the node with a high cache score
+ if (cur > maxCurrent) {
+ maxCurrent = cur;
+ selectedInvoker = upstream;
+ selectedWRR = weightedRoundRobin;
+ }
+ totalWeight += weight;
+ }
+ if (!updateLock.get() && upstreamList.size() != map.size() && updateLock.compareAndSet(false, true)) {
+ try {
+ ConcurrentMap newMap = new ConcurrentHashMap<>(map);
+ newMap.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > recyclePeriod);
+ methodWeightMap.put(key, newMap);
+ } finally {
+ updateLock.set(false);
+ }
+ }
+ if (selectedInvoker != null) {
+ // Here is the second key: the score in the cache, reducing the total node weight score
+ selectedWRR.sel(totalWeight);
+ return selectedInvoker;
+ }
+ return upstreamList.get(0);
+}
+```
+
+This algorithm is a bit complicated. Let me explain the core aspect of calculating weights:
+
+- Two nodes with a score of 2 and 100 respectively enter, and each of them is kept in the cache, with the score starting from 0.
+- After the for loop, the scores of the two nodes in the cache will increase based on themselves. Assuming that the following steps are not performed, the cache will be 2 and 100 for the first time, 4 and 200 for the second time, and so on.
+- The third key step is to select the node cache with the highest score and take "punishment" measures to reduce the cumulative score of all nodes, that is, 102.
+
+According to the steps of this algorithm, nodes that have not been selected, as "growth rewards", will continue to increase on their own basis. The selected node, as a "penalty," reduces the sum of the weights of the other nodes.
+
+It can be predicted that a node with a small weight will not be selected until a long time later. However, at that moment, it will be punished with great strength, which will lead to a long accumulation of strength once it returns to the pre-liberation period. For nodes with large weight scores, the penalty for being selected each time is very small. Even if the score is too low to be selected after many times, his reward score (itself) is particularly high, and one increase far surpasses other nodes.
+
+## WebClientPlugin
+
+After the DividePlugin plug-in is called, the downstream service node path is determined, and then the Web ClientPlugin plug-in comes into play. It implements the SoulPlugin interface directly and implements the `execute()` methods (keeping only the core code):
+
+```java
+public Mono execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
+ String urlPath = exchange.getAttribute(Constants.HTTP_URL);
+ // Request type: Get request, orPost request, etc
+ HttpMethod method = HttpMethod.valueOf(exchange.getRequest().getMethodValue());
+ // Build a shell of the request object and inject the request type and URL
+ WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(urlPath);
+ return handleRequestBody(requestBodySpec, exchange, timeout, chain);
+}
+
+private Mono handleRequestBody(final WebClient.RequestBodySpec requestBodySpec,
+ final ServerWebExchange exchange,
+ final long timeout,
+ final SoulPluginChain chain) {
+ return requestBodySpec.headers(httpHeaders -> {
+ // Add the request header in context... Later is also to add some attributes, do not go into details
+ httpHeaders.addAll(exchange.getRequest().getHeaders());
+ httpHeaders.remove(HttpHeaders.HOST);
+ })
+ .contentType(buildMediaType(exchange))
+ .body(BodyInserters.fromDataBuffers(exchange.getRequest().getBody()))
+ // Start asynchronous http calls to downstream services
+ .exchange()
+ .doOnError(e -> log.error(e.getMessage()))
+ .timeout(Duration.ofMillis(timeout))
+ // Callback receives the return value
+ .flatMap(e -> doNext(e, exchange, chain));
+}
+
+// Here is an asynchronous callback method that works in another thread
+private Mono doNext(final ClientResponse res, final ServerWebExchange exchange, final SoulPluginChain chain) {
+ // ...
+ // Continue to complete the remaining plug-in chain calls
+ return chain.execute(exchange);
+}
+```
+
+Take a quick look at `handleRequestBody()` the implementation of this method in `exchange()`, here are the key Http calls:
+
+```java
+class DefaultWebClient implements WebClient {
+ @Override
+ public Mono exchange() {
+ ClientRequest request = (this.inserter != null ?
+ initRequestBuilder().body(this.inserter).build() :
+ initRequestBuilder().build());
+ // Here is the critical call, which will go to spring-web-reactive
+ return Mono.defer(() -> exchangeFunction.exchange(request)
+ .checkpoint("Request to " + this.httpMethod.name() + " " + this.uri + " [DefaultWebClient]")
+ .switchIfEmpty(NO_HTTP_CLIENT_RESPONSE_ERROR));
+ }
+}
+```
+
+To sum up, the processing of Web ClientPlugin will call the downstream service asynchronously, wait for the response, and then execute the subsequent plug-in chain call in another thread.
+
+## WebClientResponseClient
+
+Finally, the plug-in chain goes to the Web ClientResponseClient link to encapsulate the response information:
+
+```java
+public Mono execute(final ServerWebExchange exchange, final SoulPluginChain chain) {
+ return chain.execute(exchange).then(Mono.defer(() -> {
+ // Gets the response information stored in the context
+ ServerHttpResponse response = exchange.getResponse();
+ ClientResponse clientResponse = exchange.getAttribute(Constants.CLIENT_RESPONSE_ATTR);
+ if (Objects.isNull(clientResponse)
+ || response.getStatusCode() == HttpStatus.BAD_GATEWAY
+ || response.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
+ Object error = SoulResultWarp.error(SoulResultEnum.SERVICE_RESULT_ERROR.getCode(), SoulResultEnum.SERVICE_RESULT_ERROR.getMsg(), null);
+ return WebFluxResultUtils.result(exchange, error);
+ } else if (response.getStatusCode() == HttpStatus.GATEWAY_TIMEOUT) {
+ Object error = SoulResultWarp.error(SoulResultEnum.SERVICE_TIMEOUT.getCode(), SoulResultEnum.SERVICE_TIMEOUT.getMsg(), null);
+ return WebFluxResultUtils.result(exchange, error);
+ }
+ // Various assembly
+ response.setStatusCode(clientResponse.statusCode());
+ response.getCookies().putAll(clientResponse.cookies());
+ response.getHeaders().putAll(clientResponse.headers().asHttpHeaders());
+ return response.writeWith(clientResponse.body(BodyExtractors.toDataBuffers()));
+ }));
+}
+```
diff --git a/src/blog/soul_source_learning_08_httplongpolling_01.md b/src/blog/soul_source_learning_08_httplongpolling_01.md
index 72e5f141dd..492ad9c82b 100644
--- a/src/blog/soul_source_learning_08_httplongpolling_01.md
+++ b/src/blog/soul_source_learning_08_httplongpolling_01.md
@@ -1,384 +1,384 @@
----
-title: Soul Gateway Learns Http Long Polling Analysis 01
-author: zhuming
-date: 2021-01-25
-tag:
- - Soul
-cover: /assets/img/architecture/soul-framework.png
-head:
- - - meta
- - name: Blog
----
-
-## Data synchronization between background and gateway (Http long polling)
-
-### Configuration
-
-** Background information mode switching **
-
-In the previous analysis of Zookeeper synchronization ([ Soul Gateway Source Code Analysis-Issue 11 ](https://blog.csdn.net/zm469568595/article/details/113065463)), we switched through the DataSyncConfiguration configuration class. This time, we have experience and paste the configuration directly.
-
-```yml
-soul:
- sync:
- websocket:
- enabled: false
- http:
- enabled: true
-```
-
-** Gateway information mode switching **
-
-After the background mode switching is completed, the next step is the gateway. Continue to find the parameter settings on the key configuration class. The gateway configuration is also directly posted here.
-
-```yml
-soul:
- sync:
-# websocket:
-# urls: ws://localhost:9095/websocket
- http:
- url: http://localhost:9095
-```
-
-### Data ChangedListener system
-
-Background data initialization Data SyncConfiguration configures key beans. Take a look at the Http long polling Bean here.
-
-```java
-@Configuration
-public class DataSyncConfiguration {
-
- @Configuration
- @ConditionalOnProperty(name = "soul.sync.http.enabled", havingValue = "true")
- @EnableConfigurationProperties(HttpSyncProperties.class)
- static class HttpLongPollingListener {
-
- @Bean
- @ConditionalOnMissingBean(HttpLongPollingDataChangedListener.class)
- public HttpLongPollingDataChangedListener httpLongPollingDataChangedListener(final HttpSyncProperties httpSyncProperties) {
- return new HttpLongPollingDataChangedListener(httpSyncProperties);
- }
- }
-}
-```
-
-Http LongPollingData ChangedListener inherit from AbstractData ChangedListener, which are implemented from the interface DataChangedListener.
-
-We should be very familiar with the DataChangedListener interface, which provides many methods of different data type changes for the DataChangedEventDispatcher to call, and this class is an "old friend" as a transit station. Diligent ** Handle event classification and distribution for data synchronization **
-
-```java
-public class DataChangedEventDispatcher implements ApplicationListener, InitializingBean {
- // Hold the DataChangedListener collection
- private List listeners;
-
- // Method to notify the DataChangedListener of different event types when an event changes
- public void onApplicationEvent(final DataChangedEvent event) {
- for (DataChangedListener listener : listeners) {
- switch (event.getGroupKey()) {
- case APP_AUTH:
- listener.onAppAuthChanged((List) event.getSource(), event.getEventType());
- break;
- case PLUGIN:
- listener.onPluginChanged((List) event.getSource(), event.getEventType());
- break;
- case RULE:
- listener.onRuleChanged((List) event.getSource(), event.getEventType());
- break;
- case SELECTOR:
- listener.onSelectorChanged((List) event.getSource(), event.getEventType());
- break;
- case META_DATA:
- listener.onMetaDataChanged((List) event.getSource(), event.getEventType());
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
- }
- }
- }
-}
-```
-
-```java
-public interface DataChangedListener {
-
- default void onAppAuthChanged(List changed, DataEventTypeEnum eventType) {}
-
- default void onPluginChanged(List changed, DataEventTypeEnum eventType) {}
-
- default void onSelectorChanged(List changed, DataEventTypeEnum eventType) {}
-
- default void onMetaDataChanged(List changed, DataEventTypeEnum eventType) {}
-
- default void onRuleChanged(List changed, DataEventTypeEnum eventType) {}
-}
-```
-
-After understanding the functions of these two, what does AbstractData ChangedListener do? Take an example of onPluginChanged ():
-
-```java
-public abstract class AbstractDataChangedListener implements DataChangedListener, InitializingBean {
-
- protected static final ConcurrentMap CACHE = new ConcurrentHashMap<>();
-
- @Override
- public void onPluginChanged(final List changed, final DataEventTypeEnum eventType) {
- if (CollectionUtils.isEmpty(changed)) {
- return;
- }
- this.updatePluginCache();
- this.afterPluginChanged(changed, eventType);
- }
-
- // Modify cache (overwritable)
- protected void updatePluginCache() {
- this.updateCache(ConfigGroupEnum.PLUGIN, pluginService.listAll());
- }
-
- protected void updateCache(final ConfigGroupEnum group, final List data) {
- String json = GsonUtils.getInstance().toJson(data);
- ConfigDataCache newVal = new ConfigDataCache(group.name(), json, Md5Utils.md5(json), System.currentTimeMillis());
- ConfigDataCache oldVal = CACHE.put(newVal.getGroup(), newVal);
- log.info("update config cache[{}], old: {}, updated: {}", group, oldVal, newVal);
- }
-
- // Hook, customize what to do after ending data changes (rewritable)
- protected void afterPluginChanged(final List changed, final DataEventTypeEnum eventType) {
- }
-}
-```
-
-For a plug-in data change method (onPluginChanged), AbstractDataChangedListener actually defines a template, so that the subclass can work according to the specified steps, and the details of each step can be implemented by the subclass itself.
-
-Second, if you do not override its cache updates, it is maintained by the class in CACHE.
-
-### What are the other synchronization strategies doing at this time?
-
-After the DataChange dEventDispatcher calls onPluginChanged (), how does the long polling module work? ** Think about what other synchronization methods are doing at this point. **
-
-The web socket pattern, for example, rewrites onPluginChanged () itself to send the websocket information to the holding session, which has a gateway.
-
-```java
-public class WebsocketDataChangedListener implements DataChangedListener {
-
- @Override
- public void onPluginChanged(final List pluginDataList, final DataEventTypeEnum eventType) {
- WebsocketData websocketData =
- new WebsocketData<>(ConfigGroupEnum.PLUGIN.name(), eventType.name(), pluginDataList);
- WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);
- }
-}
-```
-
-Looking at the zookeeper pattern, it also rewrites onPluginChanged () to modify the node information on the zookeeper so that the gateway side will hear their node changes.
-
-```java
-public class ZookeeperDataChangedListener implements DataChangedListener {
-
- @Override
- public void onPluginChanged(final List changed, final DataEventTypeEnum eventType) {
- for (PluginData data : changed) {
- String pluginPath = ZkPathConstants.buildPluginPath(data.getName());
- // delete
- if (eventType == DataEventTypeEnum.DELETE) {
- deleteZkPathRecursive(pluginPath);
- String selectorParentPath = ZkPathConstants.buildSelectorParentPath(data.getName());
- deleteZkPathRecursive(selectorParentPath);
- String ruleParentPath = ZkPathConstants.buildRuleParentPath(data.getName());
- deleteZkPathRecursive(ruleParentPath);
- continue;
- }
- //create or update
- insertZkNode(pluginPath, data);
- }
- }
-}
-```
-
-As you can see, at this juncture, other synchronization strategies are already busy notifying gateways, so Http long polling must also do this.
-
-These two strategies are also different in the way of notification, websocket is a good person to do to the end, directly find the session session to send the information in person. After the zookeeper changes the node information, the gateway monitors the change and then synchronizes.
-
-So how does our Http long polling now notify the gateway? Keep looking..
-
-### Thinking on the Implementation of Long Polling
-
-First think about how I can design long polling by myself?
-
-Normal long polling implementation should be actively requested by the gateway. The background receives the request and holds it. If there is an update, it will return directly. If not, it will be blocked for a certain period of time. And the background is to do a good job of updating the data, hold the time to check whether the data has changed.
-
-There are three points involved here:
-
-1. How do you know if the data has changed? Do you set a last update time and compare it with the request time of the gateway to see if there is any data modification?
-2. After holding, how does the background know whether the data is updated, repeated traversal or blocked waiting?
-3. Where is the data used for updating? In the case of caching, consider how the background cache interacts with the database.
-
-### Http LongPollingData ChangedListener Long Polling Implementation
-
-Around our thinking, look at how Http LongPollingData ChangedListener is achieved. Let's take a look at the implementation of the parent onPluginChanged ().
-
-```java
-public class HttpLongPollingDataChangedListener extends AbstractDataChangedListener {
-
- private final ScheduledExecutorService scheduler;
-
- @Override
- protected void afterPluginChanged(final List changed, final DataEventTypeEnum eventType) {
- scheduler.execute(new DataChangeTask(ConfigGroupEnum.PLUGIN));
- }
-}
-```
-
-Http long polling does not directly override onPluginChanged (), but directly uses its parent class, which means that its CACHE is used. In the end, our information acquisition must also be analyzed. Put it aside for the time being.
-
-The following logic will call the afterPluginChanged () method of our implementation, where a timed thread pool is used to run a Runnable task DataChangeTask.
-
-```java
-class DataChangeTask implements Runnable {
-
- @Override
- public void run() {
- // Iterate through clients
- for (Iterator iter = clients.iterator(); iter.hasNext();) {
- LongPollingClient client = iter.next();
- iter.remove();
- // Description Complete response The response is complete
- client.sendResponse(Collections.singletonList(groupKey));
- log.info("send response with the changed group,ip={}, group={}, changeTime={}", client.ip, groupKey, changeTime);
- }
- }
-}
-```
-
-After the data is changed, the thread pool is used to call this method, take all `clients` the elements while traversing, and call the method sendResponse (), like marking that the response has been completed.
-
-Let me guess what it does. `clients` It's likely that the request is held by the gateway, and send response () is likely to actually add response information to the request context. Another key action is to end the hold, allowing the gateway to receive the response and remove the request from the collection.
-
-We now trace the following `client` generation, which is a BlockingQueue blocking queue in the HttpLongPollingData ChangedListener, which is periodically detected in the LongPolling Client.
-
-```java
-class LongPollingClient implements Runnable {
-
- @Override
- public void run() {
- this.asyncTimeoutFuture = scheduler.schedule(() -> {
- clients.remove(LongPollingClient.this);
- List changedGroups = compareChangedGroup((HttpServletRequest) asyncContext.getRequest());
- sendResponse(changedGroups);
- }, timeoutTime, TimeUnit.MILLISECONDS);
- // Here is the key, indicating the source
- clients.add(this);
- }
-}
-```
-
-Instead of analyzing the detection code block of remove (), you can see the add () in the last sentence, which is `clients` the source of the data.
-
-Find where LongPollingClient is called. HttpLongPollingData ChangedListener # doLongPolling
-
-```java
-public void doLongPolling(final HttpServletRequest request, final HttpServletResponse response) {
-
- // ...
-
- // listen for configuration changed.
- // Enable synchronous blocking requests
- final AsyncContext asyncContext = request.startAsync();
-
- // AsyncContext.settimeout() does not timeout properly, so you have to control it yourself
- asyncContext.setTimeout(0L);
-
- // block client's thread.
- // The thread pool calls LongPollingClient#run
- scheduler.execute(new LongPollingClient(asyncContext, clientIp, HttpConstants.SERVER_MAX_HOLD_TIMEOUT));
-}
-```
-
-The last sentence here will be called and added `client`, and there is a key line of code that blocks the request:
-
-```java
-final AsyncContext asyncContext = request.startAsync();
-```
-
-In the LongPolling Client # sendResponse, it has just been analyzed that, in addition to wrapping the injected response information, the held request will also be released.
-
-```java
-class LongPollingClient implements Runnable {
-
- void sendResponse(final List changedGroups) {
- // cancel scheduler
- if (null != asyncTimeoutFuture) {
- asyncTimeoutFuture.cancel(false);
- }
- generateResponse((HttpServletResponse) asyncContext.getResponse(), changedGroups);
- // The synchronization is complete
- asyncContext.complete();
- }
-}
-```
-
-After this analysis, we go back to doLongPolling (), where the thread pool calls another key point.
-
-```java
-scheduler.execute(new LongPollingClient(asyncContext, clientIp, HttpConstants.SERVER_MAX_HOLD_TIMEOUT));
-```
-
-The timeout time of 60s is passed to the LongPolling Client here. What is it used for? Remember that piece of code we skipped over at LongPolling Client # run?
-
-```java
-class LongPollingClient implements Runnable {
-
- @Override
- public void run() {
- // Start time. The delay time is based on timeoutTime
- this.asyncTimeoutFuture = scheduler.schedule(() -> {
- // Remove the managed connection
- clients.remove(LongPollingClient.this);
- List changedGroups = compareChangedGroup((HttpServletRequest) asyncContext.getRequest());
- // This method releases the blocked request
- sendResponse(changedGroups);
- }, timeoutTime, TimeUnit.MILLISECONDS);
-
- clients.add(this);
- }
-}
-```
-
-Here we have understood the implementation of the long polling process in the background. Finally, we will see how doLongPolling () is called and find the calling class ConfigController.
-
-```java
-@ConditionalOnBean(HttpLongPollingDataChangedListener.class)
-@RestController
-@RequestMapping("/configs")
-@Slf4j
-public class ConfigController {
-
- @PostMapping(value = "/listener")
- public void listener(final HttpServletRequest request, final HttpServletResponse response) {
- longPollingListener.doLongPolling(request, response);
- }
-}
-```
-
-It is also basically clear that the background exposes the HTTP path through this Controller for the gateway to call and listen to data changes.
-
-### Sum up
-
-- The background exposes the API to the gateway through the Controller layer. When the gateway requests the background, the background does not immediately return a response (whether the data has changed), but holds the request for a maximum of 60 seconds. These held requests are added to the blocking queue as an in-memory cache.
-- If there is any data change in these 60 seconds, it will be distributed to our HttpLongPollingData ChangedListener through the DataChangedEventDispatcher. All held requests are traversed ** Invoke the thread pool immediately ** in the blocking queue, stuffed with response information and released.
-- If there is still no data change after 60 seconds, the held request will be released and the corresponding request object of the blocking queue will be removed.
-
-At this point, we have sorted out its most basic long polling logic, then corresponding to the next beginning of thinking, see what conclusions or doubts.
-
-> 1. How do you know if the data has changed? Do you set a last update time and compare it with the request time of the gateway to see if there is any data modification?
-> 2. After holding, how does the background know whether the data is updated, repeated traversal or blocked waiting?
-> 3. Where is the data used for updating? In the case of caching, consider how the background cache interacts with the database.
-
-In response to point 1, how do we know that the data has changed?
-
-- At present, the data change source of our analysis is DataChangedEventDispatcher, which not only informs us when the data changes, but also calls it immediately every time we manually click the background synchronization.
-
- Then there must be something like new and old data comparison. Otherwise, every call will directly release the blocking request of the gateway. This is not possible. White IO consumption is certainly not a good design.
-
-For the second point, we now know that the mode is blocking and waiting, which is `AsyncContext` used in this way. I have not understood this part, and I will discuss it in an extra chapter.
-
-For the third point, we know that the background configuration must be modified to the database, so the interaction between this cache and the database is also a point worth analyzing. I will continue to analyze these questions in the next chapter.
+---
+title: Soul Gateway Learns Http Long Polling Analysis 01
+author: zhuming
+date: 2021-01-25
+tag:
+ - Soul
+cover: /assets/img/architecture/soul-framework.png
+head:
+ - - meta
+ - name: Blog
+---
+
+## Data synchronization between background and gateway (Http long polling)
+
+### Configuration
+
+** Background information mode switching **
+
+In the previous analysis of Zookeeper synchronization ([ Soul Gateway Source Code Analysis-Issue 11 ](https://blog.csdn.net/zm469568595/article/details/113065463)), we switched through the DataSyncConfiguration configuration class. This time, we have experience and paste the configuration directly.
+
+```yml
+soul:
+ sync:
+ websocket:
+ enabled: false
+ http:
+ enabled: true
+```
+
+** Gateway information mode switching **
+
+After the background mode switching is completed, the next step is the gateway. Continue to find the parameter settings on the key configuration class. The gateway configuration is also directly posted here.
+
+```yml
+soul:
+ sync:
+# websocket:
+# urls: ws://localhost:9095/websocket
+ http:
+ url: http://localhost:9095
+```
+
+### Data ChangedListener system
+
+Background data initialization Data SyncConfiguration configures key beans. Take a look at the Http long polling Bean here.
+
+```java
+@Configuration
+public class DataSyncConfiguration {
+
+ @Configuration
+ @ConditionalOnProperty(name = "soul.sync.http.enabled", havingValue = "true")
+ @EnableConfigurationProperties(HttpSyncProperties.class)
+ static class HttpLongPollingListener {
+
+ @Bean
+ @ConditionalOnMissingBean(HttpLongPollingDataChangedListener.class)
+ public HttpLongPollingDataChangedListener httpLongPollingDataChangedListener(final HttpSyncProperties httpSyncProperties) {
+ return new HttpLongPollingDataChangedListener(httpSyncProperties);
+ }
+ }
+}
+```
+
+Http LongPollingData ChangedListener inherit from AbstractData ChangedListener, which are implemented from the interface DataChangedListener.
+
+We should be very familiar with the DataChangedListener interface, which provides many methods of different data type changes for the DataChangedEventDispatcher to call, and this class is an "old friend" as a transit station. Diligent ** Handle event classification and distribution for data synchronization **
+
+```java
+public class DataChangedEventDispatcher implements ApplicationListener, InitializingBean {
+ // Hold the DataChangedListener collection
+ private List listeners;
+
+ // Method to notify the DataChangedListener of different event types when an event changes
+ public void onApplicationEvent(final DataChangedEvent event) {
+ for (DataChangedListener listener : listeners) {
+ switch (event.getGroupKey()) {
+ case APP_AUTH:
+ listener.onAppAuthChanged((List) event.getSource(), event.getEventType());
+ break;
+ case PLUGIN:
+ listener.onPluginChanged((List) event.getSource(), event.getEventType());
+ break;
+ case RULE:
+ listener.onRuleChanged((List) event.getSource(), event.getEventType());
+ break;
+ case SELECTOR:
+ listener.onSelectorChanged((List) event.getSource(), event.getEventType());
+ break;
+ case META_DATA:
+ listener.onMetaDataChanged((List) event.getSource(), event.getEventType());
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
+ }
+ }
+ }
+}
+```
+
+```java
+public interface DataChangedListener {
+
+ default void onAppAuthChanged(List changed, DataEventTypeEnum eventType) {}
+
+ default void onPluginChanged(List changed, DataEventTypeEnum eventType) {}
+
+ default void onSelectorChanged(List changed, DataEventTypeEnum eventType) {}
+
+ default void onMetaDataChanged(List changed, DataEventTypeEnum eventType) {}
+
+ default void onRuleChanged(List changed, DataEventTypeEnum eventType) {}
+}
+```
+
+After understanding the functions of these two, what does AbstractData ChangedListener do? Take an example of onPluginChanged ():
+
+```java
+public abstract class AbstractDataChangedListener implements DataChangedListener, InitializingBean {
+
+ protected static final ConcurrentMap CACHE = new ConcurrentHashMap<>();
+
+ @Override
+ public void onPluginChanged(final List changed, final DataEventTypeEnum eventType) {
+ if (CollectionUtils.isEmpty(changed)) {
+ return;
+ }
+ this.updatePluginCache();
+ this.afterPluginChanged(changed, eventType);
+ }
+
+ // Modify cache (overwritable)
+ protected void updatePluginCache() {
+ this.updateCache(ConfigGroupEnum.PLUGIN, pluginService.listAll());
+ }
+
+ protected void updateCache(final ConfigGroupEnum group, final List data) {
+ String json = GsonUtils.getInstance().toJson(data);
+ ConfigDataCache newVal = new ConfigDataCache(group.name(), json, Md5Utils.md5(json), System.currentTimeMillis());
+ ConfigDataCache oldVal = CACHE.put(newVal.getGroup(), newVal);
+ log.info("update config cache[{}], old: {}, updated: {}", group, oldVal, newVal);
+ }
+
+ // Hook, customize what to do after ending data changes (rewritable)
+ protected void afterPluginChanged(final List changed, final DataEventTypeEnum eventType) {
+ }
+}
+```
+
+For a plug-in data change method (onPluginChanged), AbstractDataChangedListener actually defines a template, so that the subclass can work according to the specified steps, and the details of each step can be implemented by the subclass itself.
+
+Second, if you do not override its cache updates, it is maintained by the class in CACHE.
+
+### What are the other synchronization strategies doing at this time?
+
+After the DataChange dEventDispatcher calls onPluginChanged (), how does the long polling module work? ** Think about what other synchronization methods are doing at this point. **
+
+The web socket pattern, for example, rewrites onPluginChanged () itself to send the websocket information to the holding session, which has a gateway.
+
+```java
+public class WebsocketDataChangedListener implements DataChangedListener {
+
+ @Override
+ public void onPluginChanged(final List pluginDataList, final DataEventTypeEnum eventType) {
+ WebsocketData websocketData =
+ new WebsocketData<>(ConfigGroupEnum.PLUGIN.name(), eventType.name(), pluginDataList);
+ WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);
+ }
+}
+```
+
+Looking at the zookeeper pattern, it also rewrites onPluginChanged () to modify the node information on the zookeeper so that the gateway side will hear their node changes.
+
+```java
+public class ZookeeperDataChangedListener implements DataChangedListener {
+
+ @Override
+ public void onPluginChanged(final List changed, final DataEventTypeEnum eventType) {
+ for (PluginData data : changed) {
+ String pluginPath = ZkPathConstants.buildPluginPath(data.getName());
+ // delete
+ if (eventType == DataEventTypeEnum.DELETE) {
+ deleteZkPathRecursive(pluginPath);
+ String selectorParentPath = ZkPathConstants.buildSelectorParentPath(data.getName());
+ deleteZkPathRecursive(selectorParentPath);
+ String ruleParentPath = ZkPathConstants.buildRuleParentPath(data.getName());
+ deleteZkPathRecursive(ruleParentPath);
+ continue;
+ }
+ //create or update
+ insertZkNode(pluginPath, data);
+ }
+ }
+}
+```
+
+As you can see, at this juncture, other synchronization strategies are already busy notifying gateways, so Http long polling must also do this.
+
+These two strategies are also different in the way of notification, websocket is a good person to do to the end, directly find the session session to send the information in person. After the zookeeper changes the node information, the gateway monitors the change and then synchronizes.
+
+So how does our Http long polling now notify the gateway? Keep looking..
+
+### Thinking on the Implementation of Long Polling
+
+First think about how I can design long polling by myself?
+
+Normal long polling implementation should be actively requested by the gateway. The background receives the request and holds it. If there is an update, it will return directly. If not, it will be blocked for a certain period of time. And the background is to do a good job of updating the data, hold the time to check whether the data has changed.
+
+There are three points involved here:
+
+1. How do you know if the data has changed? Do you set a last update time and compare it with the request time of the gateway to see if there is any data modification?
+2. After holding, how does the background know whether the data is updated, repeated traversal or blocked waiting?
+3. Where is the data used for updating? In the case of caching, consider how the background cache interacts with the database.
+
+### Http LongPollingData ChangedListener Long Polling Implementation
+
+Around our thinking, look at how Http LongPollingData ChangedListener is achieved. Let's take a look at the implementation of the parent onPluginChanged ().
+
+```java
+public class HttpLongPollingDataChangedListener extends AbstractDataChangedListener {
+
+ private final ScheduledExecutorService scheduler;
+
+ @Override
+ protected void afterPluginChanged(final List changed, final DataEventTypeEnum eventType) {
+ scheduler.execute(new DataChangeTask(ConfigGroupEnum.PLUGIN));
+ }
+}
+```
+
+Http long polling does not directly override onPluginChanged (), but directly uses its parent class, which means that its CACHE is used. In the end, our information acquisition must also be analyzed. Put it aside for the time being.
+
+The following logic will call the afterPluginChanged () method of our implementation, where a timed thread pool is used to run a Runnable task DataChangeTask.
+
+```java
+class DataChangeTask implements Runnable {
+
+ @Override
+ public void run() {
+ // Iterate through clients
+ for (Iterator iter = clients.iterator(); iter.hasNext();) {
+ LongPollingClient client = iter.next();
+ iter.remove();
+ // Description Complete response The response is complete
+ client.sendResponse(Collections.singletonList(groupKey));
+ log.info("send response with the changed group,ip={}, group={}, changeTime={}", client.ip, groupKey, changeTime);
+ }
+ }
+}
+```
+
+After the data is changed, the thread pool is used to call this method, take all `clients` the elements while traversing, and call the method sendResponse (), like marking that the response has been completed.
+
+Let me guess what it does. `clients` It's likely that the request is held by the gateway, and send response () is likely to actually add response information to the request context. Another key action is to end the hold, allowing the gateway to receive the response and remove the request from the collection.
+
+We now trace the following `client` generation, which is a BlockingQueue blocking queue in the HttpLongPollingData ChangedListener, which is periodically detected in the LongPolling Client.
+
+```java
+class LongPollingClient implements Runnable {
+
+ @Override
+ public void run() {
+ this.asyncTimeoutFuture = scheduler.schedule(() -> {
+ clients.remove(LongPollingClient.this);
+ List changedGroups = compareChangedGroup((HttpServletRequest) asyncContext.getRequest());
+ sendResponse(changedGroups);
+ }, timeoutTime, TimeUnit.MILLISECONDS);
+ // Here is the key, indicating the source
+ clients.add(this);
+ }
+}
+```
+
+Instead of analyzing the detection code block of remove (), you can see the add () in the last sentence, which is `clients` the source of the data.
+
+Find where LongPollingClient is called. HttpLongPollingData ChangedListener # doLongPolling
+
+```java
+public void doLongPolling(final HttpServletRequest request, final HttpServletResponse response) {
+
+ // ...
+
+ // listen for configuration changed.
+ // Enable synchronous blocking requests
+ final AsyncContext asyncContext = request.startAsync();
+
+ // AsyncContext.settimeout() does not timeout properly, so you have to control it yourself
+ asyncContext.setTimeout(0L);
+
+ // block client's thread.
+ // The thread pool calls LongPollingClient#run
+ scheduler.execute(new LongPollingClient(asyncContext, clientIp, HttpConstants.SERVER_MAX_HOLD_TIMEOUT));
+}
+```
+
+The last sentence here will be called and added `client`, and there is a key line of code that blocks the request:
+
+```java
+final AsyncContext asyncContext = request.startAsync();
+```
+
+In the LongPolling Client # sendResponse, it has just been analyzed that, in addition to wrapping the injected response information, the held request will also be released.
+
+```java
+class LongPollingClient implements Runnable {
+
+ void sendResponse(final List changedGroups) {
+ // cancel scheduler
+ if (null != asyncTimeoutFuture) {
+ asyncTimeoutFuture.cancel(false);
+ }
+ generateResponse((HttpServletResponse) asyncContext.getResponse(), changedGroups);
+ // The synchronization is complete
+ asyncContext.complete();
+ }
+}
+```
+
+After this analysis, we go back to doLongPolling (), where the thread pool calls another key point.
+
+```java
+scheduler.execute(new LongPollingClient(asyncContext, clientIp, HttpConstants.SERVER_MAX_HOLD_TIMEOUT));
+```
+
+The timeout time of 60s is passed to the LongPolling Client here. What is it used for? Remember that piece of code we skipped over at LongPolling Client # run?
+
+```java
+class LongPollingClient implements Runnable {
+
+ @Override
+ public void run() {
+ // Start time. The delay time is based on timeoutTime
+ this.asyncTimeoutFuture = scheduler.schedule(() -> {
+ // Remove the managed connection
+ clients.remove(LongPollingClient.this);
+ List changedGroups = compareChangedGroup((HttpServletRequest) asyncContext.getRequest());
+ // This method releases the blocked request
+ sendResponse(changedGroups);
+ }, timeoutTime, TimeUnit.MILLISECONDS);
+
+ clients.add(this);
+ }
+}
+```
+
+Here we have understood the implementation of the long polling process in the background. Finally, we will see how doLongPolling () is called and find the calling class ConfigController.
+
+```java
+@ConditionalOnBean(HttpLongPollingDataChangedListener.class)
+@RestController
+@RequestMapping("/configs")
+@Slf4j
+public class ConfigController {
+
+ @PostMapping(value = "/listener")
+ public void listener(final HttpServletRequest request, final HttpServletResponse response) {
+ longPollingListener.doLongPolling(request, response);
+ }
+}
+```
+
+It is also basically clear that the background exposes the HTTP path through this Controller for the gateway to call and listen to data changes.
+
+### Sum up
+
+- The background exposes the API to the gateway through the Controller layer. When the gateway requests the background, the background does not immediately return a response (whether the data has changed), but holds the request for a maximum of 60 seconds. These held requests are added to the blocking queue as an in-memory cache.
+- If there is any data change in these 60 seconds, it will be distributed to our HttpLongPollingData ChangedListener through the DataChangedEventDispatcher. All held requests are traversed ** Invoke the thread pool immediately ** in the blocking queue, stuffed with response information and released.
+- If there is still no data change after 60 seconds, the held request will be released and the corresponding request object of the blocking queue will be removed.
+
+At this point, we have sorted out its most basic long polling logic, then corresponding to the next beginning of thinking, see what conclusions or doubts.
+
+> 1. How do you know if the data has changed? Do you set a last update time and compare it with the request time of the gateway to see if there is any data modification?
+> 2. After holding, how does the background know whether the data is updated, repeated traversal or blocked waiting?
+> 3. Where is the data used for updating? In the case of caching, consider how the background cache interacts with the database.
+
+In response to point 1, how do we know that the data has changed?
+
+- At present, the data change source of our analysis is DataChangedEventDispatcher, which not only informs us when the data changes, but also calls it immediately every time we manually click the background synchronization.
+
+ Then there must be something like new and old data comparison. Otherwise, every call will directly release the blocking request of the gateway. This is not possible. White IO consumption is certainly not a good design.
+
+For the second point, we now know that the mode is blocking and waiting, which is `AsyncContext` used in this way. I have not understood this part, and I will discuss it in an extra chapter.
+
+For the third point, we know that the background configuration must be modified to the database, so the interaction between this cache and the database is also a point worth analyzing. I will continue to analyze these questions in the next chapter.
diff --git a/src/blog/soul_source_learning_09_httplongpolling_02.md b/src/blog/soul_source_learning_09_httplongpolling_02.md
index 4b3427eb0a..e64a4420c0 100644
--- a/src/blog/soul_source_learning_09_httplongpolling_02.md
+++ b/src/blog/soul_source_learning_09_httplongpolling_02.md
@@ -1,264 +1,264 @@
----
-title: Soul Gateway Learns Http Long Polling Analysis 02
-author: zhuming
-date: 2021-01-27
-tag:
- - Soul
-cover: /assets/img/architecture/soul-framework.png
-head:
- - - meta
- - name: Blog
----
-
-## Data synchronization between background and gateway (Http long polling)
-
-The last chapter of long polling analysis summarizes the implementation of long polling on the gateway side and the way of data flow.
-
-The overall process of long polling at the gateway end is also divided into two modules: one is pulling at startup, and the other is polling to monitor changes.
-
-## Pull data on gateway startup
-
-After the gateway is started, it will call the interface provided by the background to pull data and send the data to the data processing class of each plug-in
-
-The following shows the processing flow for the gateway to start pulling data: 
-
-These several processing steps are dispersed into the method collaborations of the following classes:
-
-
-
-HttpS yncData Service # start: When the gateway is started, the HttpS yncData Service initialization will call `start()` a method, which will call the background to pull data and start multiple threads for polling and monitoring (this part will be analyzed in the next module)
-
-```java
-public class HttpSyncDataService implements SyncDataService, AutoCloseable {
-
- private void start() {
- // Prevents the CAS operation from being invoked twice
- if (RUNNING.compareAndSet(false, true)) {
- // Here is the focus of the process, calling the method to pull data
- this.fetchGroupConfig(ConfigGroupEnum.values());
- int threadSize = serverList.size();
- // This will be analyzed in the next module, which will enable thread polling listening according to the background cluster
- this.executor = new ThreadPoolExecutor(threadSize, threadSize, 60L, TimeUnit.SECONDS,
- new LinkedBlockingQueue<>(),
- SoulThreadFactory.create("http-long-polling", true));
- this.serverList.forEach(server -> this.executor.execute(new HttpLongPollingTask(server)));
- } else {
- log.info("soul http long polling was started, executor=[{}]", executor);
- }
- }
-}
-```
-
-HttpS yncData Service # fetchGroup Config: It is only used to repeatedly call the pull data method according to the data type (for the same background, it will be requested many times, and the information of a certain data type will be pulled each time). The data type here refers to plugin, rule, selector, and so on
-
-```java
-private void fetchGroupConfig(final ConfigGroupEnum... groups) throws SoulException {
- for (int index = 0; index < this.serverList.size(); index++) {
- String server = serverList.get(index);
- try {
- // Call the pull data method multiple times according to the passed data type enumeration
- this.doFetchGroupConfig(server, groups);
- break;
- } catch (SoulException e) {
- if (index >= serverList.size() - 1) {
- throw e;
- }
- log.warn("fetch config fail, try another one: {}", serverList.get(index + 1));
- }
- }
-}
-```
-
-HttpS yncData Service # doFetchGroup Config: Request the `/configs/fetch` background interface, get a certain type of data, and update the cache. Before updating the cache, it will check whether it has changed, and if it has changed, it will end. \*\* \*\* (Since it is the first time to start, the cache will definitely be updated when the data is empty, so it will end directly.)
-
-```java
-private void doFetchGroupConfig(final String server, final ConfigGroupEnum... groups) {
- StringBuilder params = new StringBuilder();
- for (ConfigGroupEnum groupKey : groups) {
- params.append("groupKeys").append("=").append(groupKey.name()).append("&");
- }
- // Construct the specific request path to fetch background data
- String url = server + "/configs/fetch?" + StringUtils.removeEnd(params.toString(), "&");
- log.info("request configs: [{}]", url);
- String json = null;
- try {
- json = this.httpClient.getForObject(url, String.class);
- } catch (RestClientException e) {
- String message = String.format("fetch config fail from server[%s], %s", url, e.getMessage());
- log.warn(message);
- throw new SoulException(message, e);
- }
- // Update cache information
- boolean updated = this.updateCacheWithJson(json);
- // If there are updates, end the process
- if (updated) {
- log.info("get latest configs: [{}]", json);
- return;
- }
- log.info("The config of the server[{}] has not been updated or is out of date. Wait for 30s to listen for changes again.", server);
- ThreadUtils.sleep(TimeUnit.SECONDS, 30);
-}
-```
-
-HttpS yncData Service # update Cache WithJson: Take out the changed data information from `data` the response information and send it to the DataRefresh Factory
-
-```java
-private DataRefreshFactory factory;
-
-public HttpSyncDataService(...){
- this.factory = new DataRefreshFactory(pluginDataSubscriber, metaDataSubscribers, authDataSubscribers);
-}
-
-private boolean updateCacheWithJson(final String json) {
- JsonObject jsonObject = GSON.fromJson(json, JsonObject.class);
- JsonObject data = jsonObject.getAsJsonObject("data");
- return factory.executor(data);
-}
-```
-
-DataRefreshFactory # executor: Send the data to all kinds of data refresh classes (the information type is not distinguished here, but all data refresh classes are notified, and optimization can be considered)
-
-```java
-public final class DataRefreshFactory {
-
- private static final EnumMap ENUM_MAP = new EnumMap<>(ConfigGroupEnum.class);
-
- public DataRefreshFactory(final PluginDataSubscriber pluginDataSubscriber,
- final List metaDataSubscribers,
- final List authDataSubscribers) {
- // 注入各类型订阅器到 MAP 中
- ENUM_MAP.put(ConfigGroupEnum.PLUGIN, new PluginDataRefresh(pluginDataSubscriber));
- ENUM_MAP.put(ConfigGroupEnum.SELECTOR, new SelectorDataRefresh(pluginDataSubscriber));
- ENUM_MAP.put(ConfigGroupEnum.RULE, new RuleDataRefresh(pluginDataSubscriber));
- ENUM_MAP.put(ConfigGroupEnum.APP_AUTH, new AppAuthDataRefresh(authDataSubscribers));
- ENUM_MAP.put(ConfigGroupEnum.META_DATA, new MetaDataRefresh(metaDataSubscribers));
- }
-
- public boolean executor(final JsonObject data) {
- final boolean[] success = {false};
- // Tureen: 所有数据类型的 DataRefresh 全调用
- ENUM_MAP.values().parallelStream().forEach(dataRefresh -> success[0] = dataRefresh.refresh(data));
- return success[0];
- }
-}
-```
-
-AbstractData Refresh # refresh: Determine whether to update the cache, and if so, call the method of each type `refresh()`
-
-```java
-@Override
-public Boolean refresh(final JsonObject data) {
- boolean updated = false;
- JsonObject jsonObject = convert(data);
- if (null != jsonObject) {
- ConfigData result = fromJson(jsonObject);
- if (this.updateCacheIfNeed(result)) {
- updated = true;
- // Turren: 调用 refresh
- refresh(result.getData());
- }
- }
- return updated;
-}
-```
-
-PluginData Refresh # refresh: Invokes the plugin's subscriber, which in turn notifies all extension related events of the change
-
-```java
-@Override
-protected void refresh(final List data) {
- if (CollectionUtils.isEmpty(data)) {
- log.info("clear all plugin data cache");
- pluginDataSubscriber.refreshPluginDataAll();
- } else {
- pluginDataSubscriber.refreshPluginDataAll();
- // Turren: HTTP synchronization is used, calling the plugin data subscriber
- data.forEach(pluginDataSubscriber::onSubscribe);
- }
-}
-```
-
-## The gateway polls to listen for changes
-
-When the gateway is started, the thread is also started to make a background monitoring request. The monitoring request makes a while endless loop to poll, and the request will be hijacked on the background side. This is specifically analyzed in the background summary ([后台与网关数据同步 (Http 长轮询篇 <二>)](https://blog.csdn.net/zm469568595/article/details/113207367)).
-
-The following shows the overall process of monitoring data changes by the gateway:
-
-
-
-The corresponding actual code implementation is as follows:
-
-
-
-The monitoring process on the ** gateway side is implemented in the HttpSyncDataService class, and will be `doFetchGroupConfig()` passed to various subscribers at the end. The following process is the same ** as that at startup
-
-HttpS yncData Service # start: Start the thread to execute the Http LongPollingTask Runnable
-
-Http LongPolling Task # run: Turn on cyclic call to poll method.
-
-```java
-@Override
-public void run() {
- while (RUNNING.get()) {
- for (int time = 1; time <= retryTimes; time++) {
- try {
- doLongPolling(server);
- } catch (Exception e) {
- if (time < retryTimes) {
- log.warn("Long polling failed, tried {} times, {} times left, will be suspended for a while! {}",
- time, retryTimes - time, e.getMessage());
- ThreadUtils.sleep(TimeUnit.SECONDS, 5);
- continue;
- }
- log.error("Long polling failed, try again after 5 minutes!", e);
- ThreadUtils.sleep(TimeUnit.MINUTES, 5);
- }
- }
- }
-}
-```
-
-Http LongPolling Task # doLongPolling: Get the response result of the listening request. If there is a changed type in the return value, call the data pulling method.
-
-```java
-private void doLongPolling(final String server) {
- // Retrieve data from the cache
- MultiValueMap params = new LinkedMultiValueMap<>(8);
- for (ConfigGroupEnum group : ConfigGroupEnum.values()) {
- ConfigData> cacheConfig = factory.cacheConfigData(group);
- String value = String.join(",", cacheConfig.getMd5(), String.valueOf(cacheConfig.getLastModifyTime()));
- params.put(group.name(), Lists.newArrayList(value));
- }
- // Build the HTTP request information
- HttpHeaders headers = new HttpHeaders();
- headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
- HttpEntity httpEntity = new HttpEntity(params, headers);
- String listenerUrl = server + "/configs/listener";
- log.debug("request listener configs: [{}]", listenerUrl);
- JsonArray groupJson = null;
- try {
- String json = this.httpClient.postForEntity(listenerUrl, httpEntity, String.class).getBody();
- groupJson = GSON.fromJson(json, JsonObject.class).getAsJsonArray("data");
- } catch (RestClientException e) {
- String message = String.format("listener configs fail, server:[%s], %s", server, e.getMessage());
- throw new SoulException(message, e);
- }
- // Obtain the changed types
- if (groupJson != null) {
- ConfigGroupEnum[] changedGroups = GSON.fromJson(groupJson, ConfigGroupEnum[].class);
- if (ArrayUtils.isNotEmpty(changedGroups)) {
- log.info("Group config changed: {}", Arrays.toString(changedGroups));
- // Retrieve data of corresponding types from the background
- this.doFetchGroupConfig(server, changedGroups);
- }
- }
-}
-```
-
-LongPollingClient#doFetchGroupConfig:
-
-This piece of code was analyzed in the previous startup, and the biggest difference between it and the startup is that \*\* \*\*..
-
-What do you mean? If the gateway goes to `fetch` the background data and takes it back for comparison, it is found that it has been cheated! There is no change. Just wait for 30s to start the next monitoring. During this period, if there is a data change in the background, there is no way to notify the gateway.
-
-Why is the gateway doing this? Naturally, in order to prevent a large number of useless pull cycles, if there is a problem in the background and the data is constantly notified to change, but there is no actual change, then the gateway will generate a large number of useless network IO and data exchange with the background without delay.
+---
+title: Soul Gateway Learns Http Long Polling Analysis 02
+author: zhuming
+date: 2021-01-27
+tag:
+ - Soul
+cover: /assets/img/architecture/soul-framework.png
+head:
+ - - meta
+ - name: Blog
+---
+
+## Data synchronization between background and gateway (Http long polling)
+
+The last chapter of long polling analysis summarizes the implementation of long polling on the gateway side and the way of data flow.
+
+The overall process of long polling at the gateway end is also divided into two modules: one is pulling at startup, and the other is polling to monitor changes.
+
+## Pull data on gateway startup
+
+After the gateway is started, it will call the interface provided by the background to pull data and send the data to the data processing class of each plug-in
+
+The following shows the processing flow for the gateway to start pulling data: 
+
+These several processing steps are dispersed into the method collaborations of the following classes:
+
+
+
+HttpS yncData Service # start: When the gateway is started, the HttpS yncData Service initialization will call `start()` a method, which will call the background to pull data and start multiple threads for polling and monitoring (this part will be analyzed in the next module)
+
+```java
+public class HttpSyncDataService implements SyncDataService, AutoCloseable {
+
+ private void start() {
+ // Prevents the CAS operation from being invoked twice
+ if (RUNNING.compareAndSet(false, true)) {
+ // Here is the focus of the process, calling the method to pull data
+ this.fetchGroupConfig(ConfigGroupEnum.values());
+ int threadSize = serverList.size();
+ // This will be analyzed in the next module, which will enable thread polling listening according to the background cluster
+ this.executor = new ThreadPoolExecutor(threadSize, threadSize, 60L, TimeUnit.SECONDS,
+ new LinkedBlockingQueue<>(),
+ SoulThreadFactory.create("http-long-polling", true));
+ this.serverList.forEach(server -> this.executor.execute(new HttpLongPollingTask(server)));
+ } else {
+ log.info("soul http long polling was started, executor=[{}]", executor);
+ }
+ }
+}
+```
+
+HttpS yncData Service # fetchGroup Config: It is only used to repeatedly call the pull data method according to the data type (for the same background, it will be requested many times, and the information of a certain data type will be pulled each time). The data type here refers to plugin, rule, selector, and so on
+
+```java
+private void fetchGroupConfig(final ConfigGroupEnum... groups) throws SoulException {
+ for (int index = 0; index < this.serverList.size(); index++) {
+ String server = serverList.get(index);
+ try {
+ // Call the pull data method multiple times according to the passed data type enumeration
+ this.doFetchGroupConfig(server, groups);
+ break;
+ } catch (SoulException e) {
+ if (index >= serverList.size() - 1) {
+ throw e;
+ }
+ log.warn("fetch config fail, try another one: {}", serverList.get(index + 1));
+ }
+ }
+}
+```
+
+HttpS yncData Service # doFetchGroup Config: Request the `/configs/fetch` background interface, get a certain type of data, and update the cache. Before updating the cache, it will check whether it has changed, and if it has changed, it will end. \*\* \*\* (Since it is the first time to start, the cache will definitely be updated when the data is empty, so it will end directly.)
+
+```java
+private void doFetchGroupConfig(final String server, final ConfigGroupEnum... groups) {
+ StringBuilder params = new StringBuilder();
+ for (ConfigGroupEnum groupKey : groups) {
+ params.append("groupKeys").append("=").append(groupKey.name()).append("&");
+ }
+ // Construct the specific request path to fetch background data
+ String url = server + "/configs/fetch?" + StringUtils.removeEnd(params.toString(), "&");
+ log.info("request configs: [{}]", url);
+ String json = null;
+ try {
+ json = this.httpClient.getForObject(url, String.class);
+ } catch (RestClientException e) {
+ String message = String.format("fetch config fail from server[%s], %s", url, e.getMessage());
+ log.warn(message);
+ throw new SoulException(message, e);
+ }
+ // Update cache information
+ boolean updated = this.updateCacheWithJson(json);
+ // If there are updates, end the process
+ if (updated) {
+ log.info("get latest configs: [{}]", json);
+ return;
+ }
+ log.info("The config of the server[{}] has not been updated or is out of date. Wait for 30s to listen for changes again.", server);
+ ThreadUtils.sleep(TimeUnit.SECONDS, 30);
+}
+```
+
+HttpS yncData Service # update Cache WithJson: Take out the changed data information from `data` the response information and send it to the DataRefresh Factory
+
+```java
+private DataRefreshFactory factory;
+
+public HttpSyncDataService(...){
+ this.factory = new DataRefreshFactory(pluginDataSubscriber, metaDataSubscribers, authDataSubscribers);
+}
+
+private boolean updateCacheWithJson(final String json) {
+ JsonObject jsonObject = GSON.fromJson(json, JsonObject.class);
+ JsonObject data = jsonObject.getAsJsonObject("data");
+ return factory.executor(data);
+}
+```
+
+DataRefreshFactory # executor: Send the data to all kinds of data refresh classes (the information type is not distinguished here, but all data refresh classes are notified, and optimization can be considered)
+
+```java
+public final class DataRefreshFactory {
+
+ private static final EnumMap ENUM_MAP = new EnumMap<>(ConfigGroupEnum.class);
+
+ public DataRefreshFactory(final PluginDataSubscriber pluginDataSubscriber,
+ final List metaDataSubscribers,
+ final List authDataSubscribers) {
+ // 注入各类型订阅器到 MAP 中
+ ENUM_MAP.put(ConfigGroupEnum.PLUGIN, new PluginDataRefresh(pluginDataSubscriber));
+ ENUM_MAP.put(ConfigGroupEnum.SELECTOR, new SelectorDataRefresh(pluginDataSubscriber));
+ ENUM_MAP.put(ConfigGroupEnum.RULE, new RuleDataRefresh(pluginDataSubscriber));
+ ENUM_MAP.put(ConfigGroupEnum.APP_AUTH, new AppAuthDataRefresh(authDataSubscribers));
+ ENUM_MAP.put(ConfigGroupEnum.META_DATA, new MetaDataRefresh(metaDataSubscribers));
+ }
+
+ public boolean executor(final JsonObject data) {
+ final boolean[] success = {false};
+ // Tureen: 所有数据类型的 DataRefresh 全调用
+ ENUM_MAP.values().parallelStream().forEach(dataRefresh -> success[0] = dataRefresh.refresh(data));
+ return success[0];
+ }
+}
+```
+
+AbstractData Refresh # refresh: Determine whether to update the cache, and if so, call the method of each type `refresh()`
+
+```java
+@Override
+public Boolean refresh(final JsonObject data) {
+ boolean updated = false;
+ JsonObject jsonObject = convert(data);
+ if (null != jsonObject) {
+ ConfigData result = fromJson(jsonObject);
+ if (this.updateCacheIfNeed(result)) {
+ updated = true;
+ // Turren: 调用 refresh
+ refresh(result.getData());
+ }
+ }
+ return updated;
+}
+```
+
+PluginData Refresh # refresh: Invokes the plugin's subscriber, which in turn notifies all extension related events of the change
+
+```java
+@Override
+protected void refresh(final List data) {
+ if (CollectionUtils.isEmpty(data)) {
+ log.info("clear all plugin data cache");
+ pluginDataSubscriber.refreshPluginDataAll();
+ } else {
+ pluginDataSubscriber.refreshPluginDataAll();
+ // Turren: HTTP synchronization is used, calling the plugin data subscriber
+ data.forEach(pluginDataSubscriber::onSubscribe);
+ }
+}
+```
+
+## The gateway polls to listen for changes
+
+When the gateway is started, the thread is also started to make a background monitoring request. The monitoring request makes a while endless loop to poll, and the request will be hijacked on the background side. This is specifically analyzed in the background summary ([后台与网关数据同步 (Http 长轮询篇 <二>)](https://blog.csdn.net/zm469568595/article/details/113207367)).
+
+The following shows the overall process of monitoring data changes by the gateway:
+
+
+
+The corresponding actual code implementation is as follows:
+
+
+
+The monitoring process on the ** gateway side is implemented in the HttpSyncDataService class, and will be `doFetchGroupConfig()` passed to various subscribers at the end. The following process is the same ** as that at startup
+
+HttpS yncData Service # start: Start the thread to execute the Http LongPollingTask Runnable
+
+Http LongPolling Task # run: Turn on cyclic call to poll method.
+
+```java
+@Override
+public void run() {
+ while (RUNNING.get()) {
+ for (int time = 1; time <= retryTimes; time++) {
+ try {
+ doLongPolling(server);
+ } catch (Exception e) {
+ if (time < retryTimes) {
+ log.warn("Long polling failed, tried {} times, {} times left, will be suspended for a while! {}",
+ time, retryTimes - time, e.getMessage());
+ ThreadUtils.sleep(TimeUnit.SECONDS, 5);
+ continue;
+ }
+ log.error("Long polling failed, try again after 5 minutes!", e);
+ ThreadUtils.sleep(TimeUnit.MINUTES, 5);
+ }
+ }
+ }
+}
+```
+
+Http LongPolling Task # doLongPolling: Get the response result of the listening request. If there is a changed type in the return value, call the data pulling method.
+
+```java
+private void doLongPolling(final String server) {
+ // Retrieve data from the cache
+ MultiValueMap params = new LinkedMultiValueMap<>(8);
+ for (ConfigGroupEnum group : ConfigGroupEnum.values()) {
+ ConfigData> cacheConfig = factory.cacheConfigData(group);
+ String value = String.join(",", cacheConfig.getMd5(), String.valueOf(cacheConfig.getLastModifyTime()));
+ params.put(group.name(), Lists.newArrayList(value));
+ }
+ // Build the HTTP request information
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+ HttpEntity httpEntity = new HttpEntity(params, headers);
+ String listenerUrl = server + "/configs/listener";
+ log.debug("request listener configs: [{}]", listenerUrl);
+ JsonArray groupJson = null;
+ try {
+ String json = this.httpClient.postForEntity(listenerUrl, httpEntity, String.class).getBody();
+ groupJson = GSON.fromJson(json, JsonObject.class).getAsJsonArray("data");
+ } catch (RestClientException e) {
+ String message = String.format("listener configs fail, server:[%s], %s", server, e.getMessage());
+ throw new SoulException(message, e);
+ }
+ // Obtain the changed types
+ if (groupJson != null) {
+ ConfigGroupEnum[] changedGroups = GSON.fromJson(groupJson, ConfigGroupEnum[].class);
+ if (ArrayUtils.isNotEmpty(changedGroups)) {
+ log.info("Group config changed: {}", Arrays.toString(changedGroups));
+ // Retrieve data of corresponding types from the background
+ this.doFetchGroupConfig(server, changedGroups);
+ }
+ }
+}
+```
+
+LongPollingClient#doFetchGroupConfig:
+
+This piece of code was analyzed in the previous startup, and the biggest difference between it and the startup is that \*\* \*\*..
+
+What do you mean? If the gateway goes to `fetch` the background data and takes it back for comparison, it is found that it has been cheated! There is no change. Just wait for 30s to start the next monitoring. During this period, if there is a data change in the background, there is no way to notify the gateway.
+
+Why is the gateway doing this? Naturally, in order to prevent a large number of useless pull cycles, if there is a problem in the background and the data is constantly notified to change, but there is no actual change, then the gateway will generate a large number of useless network IO and data exchange with the background without delay.
diff --git a/src/blog/soul_source_learning_10_websocket.md b/src/blog/soul_source_learning_10_websocket.md
index 225674d466..9ff91f79fa 100644
--- a/src/blog/soul_source_learning_10_websocket.md
+++ b/src/blog/soul_source_learning_10_websocket.md
@@ -1,782 +1,782 @@
----
-title: Soul Gateway Learns WebSocket Data Synchronization Analysis
-author: fanjinpeng,zhuming
-date: 2021-01-22
-tag:
- - Soul
-cover: /assets/img/architecture/soul-framework.png
-head:
- - - meta
- - name: Blog
----
-
-> Fan Jinpeng
-
-# 1.Previously on
-
-In Part 4, we analyzed that after the HTTP user service system accesses the Soul gateway, it will call the registration interface of soul-admin, register all the interface information that needs to be proxied by the gateway to soul-admin, and finally, it will connect through the web socket. Synchronize the interface information received by soul-admin to Soul Gateway (soul-bootstrap). Today, we will continue to analyze how the data is synchronized to soul-bootstrap.
-
-If you don't know the process, you can go out and turn left to see the fourth article.
-
-# 2.Soul-admin and soul-bootstrap data synchronization
-
-In order to verify the data synchronization process, there is no need to start the business system. You can just start the soul-admin and soul-bootstrap systems. You can open or close the plug-in on the page to see how this process is implemented.
-
-Link to the official website of data synchronization strategy
-
-## 2.1 Start 2 systems
-
-They are all started by default according to the project, and no configuration files need to be modified.
-
-## 2.2. Page operation search interface
-
-Start the divide plug-in here, F12, and see which interface soul-admin will be called in the foreground.
-
-
-
-You can see that the foreground sends a PUT request to the background: http://localhost:9095/plugin/5.
-
-## 2.3 Background interface
-
-Search for this interface in the project
-
-```java
-// PluginController.java
-@RestController
-@RequestMapping("/plugin")
-public class PluginController {
-
-...
-
- /**
- * update plugin.
- *
- * @param id primary key.
- * @param pluginDTO plugin.
- * @return {@linkplain SoulAdminResult}
- */
- @PutMapping("/{id}")
- public SoulAdminResult updatePlugin(@PathVariable("id") final String id, @RequestBody final PluginDTO pluginDTO) {
- Objects.requireNonNull(pluginDTO);
- pluginDTO.setId(id);
- final String result = pluginService.createOrUpdate(pluginDTO);
- if (StringUtils.isNoneBlank(result)) {
- return SoulAdminResult.error(result);
- }
- return SoulAdminResult.success(SoulResultMessage.UPDATE_SUCCESS);
- }
-
-...
-
-}
-```
-
-Into the implementation class.
-
-```java
-// PluginServiceImpl.java
-/**
- * create or update plugin.
- *
- * @param pluginDTO {@linkplain PluginDTO}
- * @return rows
- */
- @Override
- @Transactional(rollbackFor = Exception.class)
- public String createOrUpdate(final PluginDTO pluginDTO) {
- final String msg = checkData(pluginDTO);
- if (StringUtils.isNoneBlank(msg)) {
- return msg;
- }
- PluginDO pluginDO = PluginDO.buildPluginDO(pluginDTO);
- DataEventTypeEnum eventType = DataEventTypeEnum.CREATE;
- if (StringUtils.isBlank(pluginDTO.getId())) {
- pluginMapper.insertSelective(pluginDO);
- } else {
- eventType = DataEventTypeEnum.UPDATE;
- pluginMapper.updateSelective(pluginDO);
- }
-
- // publish change event.
- eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, eventType,
- Collections.singletonList(PluginTransfer.INSTANCE.mapToData(pluginDO))));
- return StringUtils.EMPTY;
- }
-```
-
-It can be seen here that the first half is to operate the database and persist the relevant information; the second half is to publish an event.
-
-## 2.4 Publish the event
-
-The event published here is encapsulated by DataChangedEvent, and there is an enumeration in it. There are many types here:
-
-```java
-/**
- * configuration group.
- *
- * @author huangxiaofeng
- */
-public enum ConfigGroupEnum {
-
- APP_AUTH,
-
- PLUGIN,
-
- RULE,
-
- SELECTOR,
-
- META_DATA;
-
-...
-
-}
-```
-
-Seeing these types, if you still have an impression of the fourth article, you can see that the types of events sent at that time were SELECTOR and RULE, and now it is PLUGIN. Although the types are different, it does not affect us to continue to analyze the logic behind. Let's continue.
-
-Another eventType is also an enumeration. There are five types: DELETE, CREATE, UPDATE, REFRESH, and MYSELF. In this case, it is UPDATE.
-
-```java
-/**
- * The enum Data event type.
- *
- * @author xiaoyu
- */
-public enum DataEventTypeEnum {
- /**
- * delete event.
- */
- DELETE,
- /**
- * insert event.
- */
- CREATE,
- /**
- * update event.
- */
- UPDATE,
- /**
- * REFRESH data event type enum.
- */
- REFRESH,
- /**
- * Myself data event type enum.
- */
- MYSELF;
-
-...
-
-}
-```
-
-## 2.5 Listen for events
-
-Locate the code that listens for events:
-
-```java
-// DataChangedEventDispatcher.java
-@Component
-public class DataChangedEventDispatcher implements ApplicationListener, InitializingBean {
-
- private ApplicationContext applicationContext;
-
- private List listeners;
-
- public DataChangedEventDispatcher(final ApplicationContext applicationContext) {
- this.applicationContext = applicationContext;
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void onApplicationEvent(final DataChangedEvent event) {
- for (DataChangedListener listener : listeners) {
- switch (event.getGroupKey()) {
- case APP_AUTH:
- listener.onAppAuthChanged((List) event.getSource(), event.getEventType());
- break;
- case PLUGIN:
- listener.onPluginChanged((List) event.getSource(), event.getEventType());
- break;
- case RULE:
- listener.onRuleChanged((List) event.getSource(), event.getEventType());
- break;
- case SELECTOR:
- listener.onSelectorChanged((List) event.getSource(), event.getEventType());
- break;
- case META_DATA:
- listener.onMetaDataChanged((List) event.getSource(), event.getEventType());
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
- }
- }
- }
-
- @Override
- public void afterPropertiesSet() {
- Collection listenerBeans = applicationContext.getBeansOfType(DataChangedListener.class).values();
- this.listeners = Collections.unmodifiableList(new ArrayList<>(listenerBeans));
- }
-}
-```
-
-### 2.5.1 Listener injection
-
-You can see that the DataChangedEventDispatcher implements the InitializingBean interface, overrides the afterPropertiesSet method, and uses @ Component when Spring starts. This override method is called after the container is loaded. In the After PropertiesSet method, all the beans of the DataChangedListener type are obtained and placed in the class property listeners.
-
-So the question is, when are these listeners injected into the container?
-
-First look at the definition of the Data ChangedListener interface:
-
-```java
-/**
- * Event listener, used to send notification of event changes,
- * used to support HTTP, websocket, zookeeper and other event notifications.
- *
- * @author huangxiaofeng
- * @author xiaoyu
- */
-public interface DataChangedListener {
-
- /**
- * invoke this method when AppAuth was received.
- *
- * @param changed the changed
- * @param eventType the event type
- */
- default void onAppAuthChanged(List changed, DataEventTypeEnum eventType) {
- }
-
- /**
- * invoke this method when Plugin was received.
- *
- * @param changed the changed
- * @param eventType the event type
- */
- default void onPluginChanged(List changed, DataEventTypeEnum eventType) {
- }
-
- /**
- * invoke this method when Selector was received.
- *
- * @param changed the changed
- * @param eventType the event type
- */
- default void onSelectorChanged(List changed, DataEventTypeEnum eventType) {
- }
-
- /**
- * On meta data changed.
- *
- * @param changed the changed
- * @param eventType the event type
- */
- default void onMetaDataChanged(List changed, DataEventTypeEnum eventType) {
-
- }
-
- /**
- * invoke this method when Rule was received.
- *
- * @param changed the changed
- * @param eventType the event type
- */
- default void onRuleChanged(List changed, DataEventTypeEnum eventType) {
- }
-
-}
-```
-
-It can be seen that there are five methods defined in the interface, which respectively deal with the corresponding processing methods when the data changes of appAuth, plugin, selector, metaData and rule are monitored.
-
-Its inheritance relationship:
-
-
-
-Because the websocket is used by default, the listener here corresponds to the Web socketData ChangedListener, Alt + F7, and the place where this class is instantiated is the following configuration class:
-
-```java
-// DataSyncConfiguration.java
-@Configuration
-public class DataSyncConfiguration {
-
- /**
- * http long polling.
- */
- @Configuration
- @ConditionalOnProperty(name = "soul.sync.http.enabled", havingValue = "true")
- @EnableConfigurationProperties(HttpSyncProperties.class)
- static class HttpLongPollingListener {
- @Bean
- @ConditionalOnMissingBean(HttpLongPollingDataChangedListener.class)
- public HttpLongPollingDataChangedListener httpLongPollingDataChangedListener(final HttpSyncProperties httpSyncProperties) {
- return new HttpLongPollingDataChangedListener(httpSyncProperties);
- }
- }
-
- /**
- * The type Zookeeper listener.
- */
- @Configuration
- @ConditionalOnProperty(prefix = "soul.sync.zookeeper", name = "url")
- @Import(ZookeeperConfiguration.class)
- static class ZookeeperListener {
- @Bean
- @ConditionalOnMissingBean(ZookeeperDataChangedListener.class)
- public DataChangedListener zookeeperDataChangedListener(final ZkClient zkClient) {
- return new ZookeeperDataChangedListener(zkClient);
- }
- @Bean
- @ConditionalOnMissingBean(ZookeeperDataInit.class)
- public ZookeeperDataInit zookeeperDataInit(final ZkClient zkClient, final SyncDataService syncDataService) {
- return new ZookeeperDataInit(zkClient, syncDataService);
- }
- }
-
- /**
- * The type Nacos listener.
- */
- @Configuration
- @ConditionalOnProperty(prefix = "soul.sync.nacos", name = "url")
- @Import(NacosConfiguration.class)
- static class NacosListener {
- @Bean
- @ConditionalOnMissingBean(NacosDataChangedListener.class)
- public DataChangedListener nacosDataChangedListener(final ConfigService configService) {
- return new NacosDataChangedListener(configService);
- }
- }
-
- /**
- * The WebsocketListener(default strategy).
- */
- @Configuration
- @ConditionalOnProperty(name = "soul.sync.websocket.enabled", havingValue = "true", matchIfMissing = true)
- @EnableConfigurationProperties(WebsocketSyncProperties.class)
- static class WebsocketListener {
- @Bean
- @ConditionalOnMissingBean(WebsocketDataChangedListener.class)
- public DataChangedListener websocketDataChangedListener() {
- return new WebsocketDataChangedListener();
- }
- @Bean
- @ConditionalOnMissingBean(WebsocketCollector.class)
- public WebsocketCollector websocketCollector() {
- return new WebsocketCollector();
- }
- @Bean
- @ConditionalOnMissingBean(ServerEndpointExporter.class)
- public ServerEndpointExporter serverEndpointExporter() {
- return new ServerEndpointExporter();
- }
- }
-}
-```
-
-There are four data synchronization strategies: HTTP long polling, zookeeper, nacos, and websocket (default strategy).
-
-See the web socket annotation @ ConditionalOnProperty (name = "soul. Sync. Web socket. Enabled", having Value = "true", Match (IfMissing = true), find the following configuration in the configuration file:
-
-```yaml
-soul:
- sync:
- websocket:
- enabled: true
-```
-
-This is where the truth comes out.
-
-If you do not want to use the default synchronization policy of the web socket, you can write the corresponding configuration in the configuration file.
-
-### 2.5.2 Listening event processing logic
-
-In order to prevent you from turning back and looking at it, which is inconvenient, I will post the processing logic code here:
-
-```java
-// DataChangedEventDispatcher.java
-@Override
- @SuppressWarnings("unchecked")
- public void onApplicationEvent(final DataChangedEvent event) {
- for (DataChangedListener listener : listeners) {
- switch (event.getGroupKey()) {
- case APP_AUTH:
- listener.onAppAuthChanged((List) event.getSource(), event.getEventType());
- break;
- case PLUGIN:
- listener.onPluginChanged((List) event.getSource(), event.getEventType());
- break;
- case RULE:
- listener.onRuleChanged((List) event.getSource(), event.getEventType());
- break;
- case SELECTOR:
- listener.onSelectorChanged((List) event.getSource(), event.getEventType());
- break;
- case META_DATA:
- listener.onMetaDataChanged((List) event.getSource(), event.getEventType());
- break;
- default:
- throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
- }
- }
- }
-```
-
-All listeners are traversed here. For the current web socket, there is only one listener, and it is not known when the other multiple cases will appear. It is doubtful here, and we will come back to add (//TODO) when we encounter related cases later.
-
-Different logics are used according to the type of the published event. The types here correspond to the methods defined in the DataChangedListener interface.
-
-The listener here is an instance of the Web socketData ChangedListener, which will enter the corresponding method in the class:
-
-```java
-// WebsocketDataChangedListener.java
-public class WebsocketDataChangedListener implements DataChangedListener {
-
- @Override
- public void onPluginChanged(final List pluginDataList, final DataEventTypeEnum eventType) {
- WebsocketData websocketData =
- new WebsocketData<>(ConfigGroupEnum.PLUGIN.name(), eventType.name(), pluginDataList);
- WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);
- }
-
- @Override
- public void onSelectorChanged(final List selectorDataList, final DataEventTypeEnum eventType) {
- WebsocketData websocketData =
- new WebsocketData<>(ConfigGroupEnum.SELECTOR.name(), eventType.name(), selectorDataList);
- WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);
- }
-
- @Override
- public void onRuleChanged(final List ruleDataList, final DataEventTypeEnum eventType) {
- WebsocketData configData =
- new WebsocketData<>(ConfigGroupEnum.RULE.name(), eventType.name(), ruleDataList);
- WebsocketCollector.send(GsonUtils.getInstance().toJson(configData), eventType);
- }
-
- @Override
- public void onAppAuthChanged(final List appAuthDataList, final DataEventTypeEnum eventType) {
- WebsocketData configData =
- new WebsocketData<>(ConfigGroupEnum.APP_AUTH.name(), eventType.name(), appAuthDataList);
- WebsocketCollector.send(GsonUtils.getInstance().toJson(configData), eventType);
- }
-
- @Override
- public void onMetaDataChanged(final List metaDataList, final DataEventTypeEnum eventType) {
- WebsocketData configData =
- new WebsocketData<>(ConfigGroupEnum.META_DATA.name(), eventType.name(), metaDataList);
- WebsocketCollector.send(GsonUtils.getInstance().toJson(configData), eventType);
- }
-
-}
-```
-
-As you can see in the code, the data is encapsulated as Web socketData and sent using the WebsocketController. Send method.
-
-## 2.6 Synchronize data to soul-bootstrap
-
-```java
-// WebsocketCollector.java
-@Slf4j
-@ServerEndpoint("/websocket")
-public class WebsocketCollector {
-
- private static final Set SESSION_SET = new CopyOnWriteArraySet<>();
-
- private static final String SESSION_KEY = "sessionKey";
-
- /**
- * On open.
- *
- * @param session the session
- */
- @OnOpen
- public void onOpen(final Session session) {
- log.info("websocket on open successful....");
- SESSION_SET.add(session);
- }
-
- /**
- * On message.
- *
- * @param message the message
- * @param session the session
- */
- @OnMessage
- public void onMessage(final String message, final Session session) {
- if (message.equals(DataEventTypeEnum.MYSELF.name())) {
- try {
- ThreadLocalUtil.put(SESSION_KEY, session);
- SpringBeanUtils.getInstance().getBean(SyncDataService.class).syncAll(DataEventTypeEnum.MYSELF);
- } finally {
- ThreadLocalUtil.clear();
- }
- }
- }
-
- /**
- * On close.
- *
- * @param session the session
- */
- @OnClose
- public void onClose(final Session session) {
- SESSION_SET.remove(session);
- ThreadLocalUtil.clear();
- }
-
- /**
- * On error.
- *
- * @param session the session
- * @param error the error
- */
- @OnError
- public void onError(final Session session, final Throwable error) {
- SESSION_SET.remove(session);
- ThreadLocalUtil.clear();
- log.error("websocket collection error: ", error);
- }
-
- /**
- * Send.
- *
- * @param message the message
- * @param type the type
- */
- public static void send(final String message, final DataEventTypeEnum type) {
- if (StringUtils.isNotBlank(message)) {
- if (DataEventTypeEnum.MYSELF == type) {
- try {
- Session session = (Session) ThreadLocalUtil.get(SESSION_KEY);
- if (session != null) {
- session.getBasicRemote().sendText(message);
- }
- } catch (IOException e) {
- log.error("websocket send result is exception: ", e);
- }
- return;
- }
- for (Session session : SESSION_SET) {
- try {
- session.getBasicRemote().sendText(message);
- } catch (IOException e) {
- log.error("websocket send result is exception: ", e);
- }
- }
- }
- }
-}
-```
-
-The Web socketController uses the @ ServerEndpoint ( "/web socket") annotation, opens a web socket service interface, and waits for a connection.
-
-After the soul-bootstrap is started, the web socket will be connected, and the onOpen method will be triggered to store the Session of this connection information in the Set set of the SESSION \_ SET.
-
-In the send method, it will first determine whether the DataEventTypeEnum type is MYSELF. This type can be traced back to 2.3-2.4. This time it is UPDATE. As for when it is MYSELF, it needs to be added later. It is doubtful here (//TODO).
-
-The following for loop iterates through all the web socket connection sessions to send the change data.
-
-At this point, the default web socket synchronization data strategy is clear.
-
-> Zhu Ming
-
-## Data synchronization between background and gateway (Web socket)
-
-### How to establish Web socket? In the background?
-
- Data SyncConfiguration: As the configuration factory of Spring Bean, various listeners can be constructed according to the configuration information. Including HTTP long polling mode, Zookeeper mode, Nacos mode, Web socket method.
-
-```java
-@Configuration
-public class DataSyncConfiguration {
-
- // In the configuration of the soul-admin project, use soul.sync.websocket.enabled to enable or disable WebSocket.
- @Configuration
- @ConditionalOnProperty(name = "soul.sync.websocket.enabled", havingValue = "true", matchIfMissing = true)
- @EnableConfigurationProperties(WebsocketSyncProperties.class)
- static class WebsocketListener {
-
- @Bean
- @ConditionalOnMissingBean(WebsocketCollector.class)
- public WebsocketCollector websocketCollector() {
- return new WebsocketCollector();
- }
- }
-}
-```
-
-Web socketListener: As `DataSyncConfiguration` the internal class of, it is responsible for the initialization of the web socket listener. Web socket Collector: It monitors the websocket connection and receives information. Maintain all session sessions connected to the background, and provide `send()` methods to notify session information.
-
-### How does the gateway set up a Web socket?
-
-
-
-Web socket SyncData Configuration: As the configuration factory of Spring Bean, it is the gateway's entrance to build Websocket communication. (An independent startup project `soul-spring-boot-starter-sync-data-websocket` is provided for the gateway to choose freely.)
-
-```java
-@Configuration
-@ConditionalOnClass(WebsocketSyncDataService.class)
-@ConditionalOnProperty(prefix = "soul.sync.websocket", name = "urls")
-@Slf4j
-public class WebsocketSyncDataConfiguration {
-
- // Collect all subscribers registered as Beans, such as PluginDataSubscriber, MetaDataSubscriber, AuthDataSubscriber
- @Bean
- public SyncDataService websocketSyncDataService(final ObjectProvider websocketConfig, final ObjectProvider pluginSubscriber, final ObjectProvider> metaSubscribers, final ObjectProvider> authSubscribers) {
- log.info("you use websocket sync soul data.......");
- return new WebsocketSyncDataService(websocketConfig.getIfAvailable(WebsocketConfig::new), pluginSubscriber.getIfAvailable(), metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
- }
-
- // In the configuration of the soul-bootstrap project, use soul.sync.websocket to configure the backend path for establishing connections
- @Bean
- @ConfigurationProperties(prefix = "soul.sync.websocket")
- public WebsocketConfig websocketConfig() {
- return new WebsocketConfig();
- }
-}
-```
-
-Web socket SyncData Service: Get all the registered beans `WebsocketConfig` and the various `DataSubscriber` subscribers, and build an implemented `WebsocketClient` `SoulWebsocketClient` list
-
-SoulWeb socket Client: `Websocket` The communication class monitors the websocket connection and receives information. After receiving the information from the background, it will notify each subscriber.
-
-```java
-public final class SoulWebsocketClient extends WebSocketClient {
-
- private final WebsocketDataHandler websocketDataHandler;
-
- private void handleResult(final String result) {
- WebsocketData websocketData = GsonUtils.getInstance().fromJson(result, WebsocketData.class);
- ConfigGroupEnum groupEnum = ConfigGroupEnum.acquireByName(websocketData.getGroupType());
- // Determine the event type of data changes based on the incoming information, such as refresh, update, delete, etc.
- String eventType = websocketData.getEventType();
- String json = GsonUtils.getInstance().toJson(websocketData.getData());
- websocketDataHandler.executor(groupEnum, json, eventType);
- }
-}
-```
-
-Web socketData Handler: Construct the data processing classes of various implementations `AbstractDataHandler` and cache them during initialization.
-
-```java
-public class WebsocketDataHandler {
-
- // Cache all DataHandler data change handling classes
- private static final EnumMap ENUM_MAP = new EnumMap<>(ConfigGroupEnum.class);
-
- public WebsocketDataHandler(final PluginDataSubscriber pluginDataSubscriber,
- final List metaDataSubscribers,
- final List authDataSubscribers) {
- ENUM_MAP.put(ConfigGroupEnum.PLUGIN, new PluginDataHandler(pluginDataSubscriber));
- ENUM_MAP.put(ConfigGroupEnum.SELECTOR, new SelectorDataHandler(pluginDataSubscriber));
- ENUM_MAP.put(ConfigGroupEnum.RULE, new RuleDataHandler(pluginDataSubscriber));
- ENUM_MAP.put(ConfigGroupEnum.APP_AUTH, new AuthDataHandler(authDataSubscribers));
- ENUM_MAP.put(ConfigGroupEnum.META_DATA, new MetaDataHandler(metaDataSubscribers));
- }
-
- public void executor(final ConfigGroupEnum type, final String json, final String eventType) {
- // Call the corresponding DataHandler data processing class based on the data change event type
- ENUM_MAP.get(type).handle(json, eventType);
- }
-}
-```
-
-### Gateway data change call chain
-
-After the entry class `SoulWebsocketClient` that implements Websocket communication receives the background communication, the `executor()` method called `WebsocketDataHandler` matches the information type, and calls the corresponding `DataHandler` `handler()` to process the information.
-
-
-
-AbstractDataHandler: The implementation `handler()` method calls the corresponding event abstract method according to the type of the event (such as refresh, update, create, delete, etc.).
-
-```java
-public abstract class AbstractDataHandler implements DataHandler {
-
- // Distribute to respective methods based on the data event type (eventType), these methods are implemented by subclasses since different types of metadata handlers have different ways of processing
- @Override
- public void handle(final String json, final String eventType) {
- List dataList = convert(json);
- if (CollectionUtils.isNotEmpty(dataList)) {
- DataEventTypeEnum eventTypeEnum = DataEventTypeEnum.acquireByName(eventType);
- switch (eventTypeEnum) {
- case REFRESH:
- case MYSELF:
- doRefresh(dataList);
- break;
- case UPDATE:
- case CREATE:
- doUpdate(dataList);
- break;
- case DELETE:
- doDelete(dataList);
- break;
- default:
- break;
- }
- }
- }
-}
-```
-
-XXX DataHandler: This refers to each implementation class `AbstractDataHandler` of (such as `PluginDataHandler`), whose main function is to call its subscriber.
-
-Different `DataHandler` calls have different subscription methods:
-
-- Notification of plug-in metadata changes `PluginDataHandler` is called `onSubscribe()`
-- The notification selector is `SelectorDataHandler` called `onSelectorSubscribe()` to change the metadata
-- Notification rule metadata change `RuleDataHandler` is invoked `onRuleSubscribe()`
-
-```java
-@RequiredArgsConstructor
-public class PluginDataHandler extends AbstractDataHandler {
-
- private final PluginDataSubscriber pluginDataSubscriber;
-
- @Override
- protected void doUpdate(final List dataList) {
- // Call onSubscribe() of the subscriber, sending the PluginData data object
- dataList.forEach(pluginDataSubscriber::onSubscribe);
- }
-
- // ...
-}
-```
-
-CommonPluginData Subscriber: The `onSubscribe()` method of the subscriber notifies all classes injected as beans `PluginDataHandler` (not to be confused with the previous class of the same name, which is `soul-plugin-base` the interface under. Its implementation classes are in the respective pluggable plug-in packages.
-
-
-
-```java
-public class CommonPluginDataSubscriber implements PluginDataSubscriber {
-
- // Collect and cache all registered data handlers, such as DividePluginDataHandler under the HTTP plugin divide
- private final Map handlerMap;
-
- // Called for plugin metadata changes
- @Override
- public void onSubscribe(final PluginData pluginData) {
- BaseDataCache.getInstance().cachePluginData(pluginData);
- Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.handlerPlugin(pluginData));
- }
-
- // Called for selector metadata changes
- @Override
- public void onSelectorSubscribe(final SelectorData selectorData) {
- BaseDataCache.getInstance().cacheSelectData(selectorData);
- Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.handlerSelector(selectorData));
- }
-
- // Called for rule metadata changes
- @Override
- public void onRuleSubscribe(final RuleData ruleData) {
- BaseDataCache.getInstance().cacheRuleData(ruleData);
- Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.handlerRule(ruleData));
- }
-}
-```
-
-### TIPS
-
-There are two classes PluginDataHandler with the same name under the whole project. One `soul-sync-data-websocket` of them is under the project, which is used to notify the plug-in metadata change, and the other is under the `soul-plugin-base` project, which is used to define the metadata update of each type of plug-in.
-
-To summarize the naming meaning of these two classes, ** `soul-sync-data-websocket` the "plugin" in the lower class name means that the type of metadata is a plug-in class, and `soul-plugin-base` the "plugin" in the lower class name means that the subclass that inherits it comes from each pluggable plug-in. Such as divide, dubbo plugins, etc. **
+---
+title: Soul Gateway Learns WebSocket Data Synchronization Analysis
+author: fanjinpeng,zhuming
+date: 2021-01-22
+tag:
+ - Soul
+cover: /assets/img/architecture/soul-framework.png
+head:
+ - - meta
+ - name: Blog
+---
+
+> Fan Jinpeng
+
+# 1.Previously on
+
+In Part 4, we analyzed that after the HTTP user service system accesses the Soul gateway, it will call the registration interface of soul-admin, register all the interface information that needs to be proxied by the gateway to soul-admin, and finally, it will connect through the web socket. Synchronize the interface information received by soul-admin to Soul Gateway (soul-bootstrap). Today, we will continue to analyze how the data is synchronized to soul-bootstrap.
+
+If you don't know the process, you can go out and turn left to see the fourth article.
+
+# 2.Soul-admin and soul-bootstrap data synchronization
+
+In order to verify the data synchronization process, there is no need to start the business system. You can just start the soul-admin and soul-bootstrap systems. You can open or close the plug-in on the page to see how this process is implemented.
+
+Link to the official website of data synchronization strategy
+
+## 2.1 Start 2 systems
+
+They are all started by default according to the project, and no configuration files need to be modified.
+
+## 2.2. Page operation search interface
+
+Start the divide plug-in here, F12, and see which interface soul-admin will be called in the foreground.
+
+
+
+You can see that the foreground sends a PUT request to the background: http://localhost:9095/plugin/5.
+
+## 2.3 Background interface
+
+Search for this interface in the project
+
+```java
+// PluginController.java
+@RestController
+@RequestMapping("/plugin")
+public class PluginController {
+
+...
+
+ /**
+ * update plugin.
+ *
+ * @param id primary key.
+ * @param pluginDTO plugin.
+ * @return {@linkplain SoulAdminResult}
+ */
+ @PutMapping("/{id}")
+ public SoulAdminResult updatePlugin(@PathVariable("id") final String id, @RequestBody final PluginDTO pluginDTO) {
+ Objects.requireNonNull(pluginDTO);
+ pluginDTO.setId(id);
+ final String result = pluginService.createOrUpdate(pluginDTO);
+ if (StringUtils.isNoneBlank(result)) {
+ return SoulAdminResult.error(result);
+ }
+ return SoulAdminResult.success(SoulResultMessage.UPDATE_SUCCESS);
+ }
+
+...
+
+}
+```
+
+Into the implementation class.
+
+```java
+// PluginServiceImpl.java
+/**
+ * create or update plugin.
+ *
+ * @param pluginDTO {@linkplain PluginDTO}
+ * @return rows
+ */
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String createOrUpdate(final PluginDTO pluginDTO) {
+ final String msg = checkData(pluginDTO);
+ if (StringUtils.isNoneBlank(msg)) {
+ return msg;
+ }
+ PluginDO pluginDO = PluginDO.buildPluginDO(pluginDTO);
+ DataEventTypeEnum eventType = DataEventTypeEnum.CREATE;
+ if (StringUtils.isBlank(pluginDTO.getId())) {
+ pluginMapper.insertSelective(pluginDO);
+ } else {
+ eventType = DataEventTypeEnum.UPDATE;
+ pluginMapper.updateSelective(pluginDO);
+ }
+
+ // publish change event.
+ eventPublisher.publishEvent(new DataChangedEvent(ConfigGroupEnum.PLUGIN, eventType,
+ Collections.singletonList(PluginTransfer.INSTANCE.mapToData(pluginDO))));
+ return StringUtils.EMPTY;
+ }
+```
+
+It can be seen here that the first half is to operate the database and persist the relevant information; the second half is to publish an event.
+
+## 2.4 Publish the event
+
+The event published here is encapsulated by DataChangedEvent, and there is an enumeration in it. There are many types here:
+
+```java
+/**
+ * configuration group.
+ *
+ * @author huangxiaofeng
+ */
+public enum ConfigGroupEnum {
+
+ APP_AUTH,
+
+ PLUGIN,
+
+ RULE,
+
+ SELECTOR,
+
+ META_DATA;
+
+...
+
+}
+```
+
+Seeing these types, if you still have an impression of the fourth article, you can see that the types of events sent at that time were SELECTOR and RULE, and now it is PLUGIN. Although the types are different, it does not affect us to continue to analyze the logic behind. Let's continue.
+
+Another eventType is also an enumeration. There are five types: DELETE, CREATE, UPDATE, REFRESH, and MYSELF. In this case, it is UPDATE.
+
+```java
+/**
+ * The enum Data event type.
+ *
+ * @author xiaoyu
+ */
+public enum DataEventTypeEnum {
+ /**
+ * delete event.
+ */
+ DELETE,
+ /**
+ * insert event.
+ */
+ CREATE,
+ /**
+ * update event.
+ */
+ UPDATE,
+ /**
+ * REFRESH data event type enum.
+ */
+ REFRESH,
+ /**
+ * Myself data event type enum.
+ */
+ MYSELF;
+
+...
+
+}
+```
+
+## 2.5 Listen for events
+
+Locate the code that listens for events:
+
+```java
+// DataChangedEventDispatcher.java
+@Component
+public class DataChangedEventDispatcher implements ApplicationListener, InitializingBean {
+
+ private ApplicationContext applicationContext;
+
+ private List listeners;
+
+ public DataChangedEventDispatcher(final ApplicationContext applicationContext) {
+ this.applicationContext = applicationContext;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void onApplicationEvent(final DataChangedEvent event) {
+ for (DataChangedListener listener : listeners) {
+ switch (event.getGroupKey()) {
+ case APP_AUTH:
+ listener.onAppAuthChanged((List) event.getSource(), event.getEventType());
+ break;
+ case PLUGIN:
+ listener.onPluginChanged((List) event.getSource(), event.getEventType());
+ break;
+ case RULE:
+ listener.onRuleChanged((List) event.getSource(), event.getEventType());
+ break;
+ case SELECTOR:
+ listener.onSelectorChanged((List) event.getSource(), event.getEventType());
+ break;
+ case META_DATA:
+ listener.onMetaDataChanged((List) event.getSource(), event.getEventType());
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
+ }
+ }
+ }
+
+ @Override
+ public void afterPropertiesSet() {
+ Collection listenerBeans = applicationContext.getBeansOfType(DataChangedListener.class).values();
+ this.listeners = Collections.unmodifiableList(new ArrayList<>(listenerBeans));
+ }
+}
+```
+
+### 2.5.1 Listener injection
+
+You can see that the DataChangedEventDispatcher implements the InitializingBean interface, overrides the afterPropertiesSet method, and uses @ Component when Spring starts. This override method is called after the container is loaded. In the After PropertiesSet method, all the beans of the DataChangedListener type are obtained and placed in the class property listeners.
+
+So the question is, when are these listeners injected into the container?
+
+First look at the definition of the Data ChangedListener interface:
+
+```java
+/**
+ * Event listener, used to send notification of event changes,
+ * used to support HTTP, websocket, zookeeper and other event notifications.
+ *
+ * @author huangxiaofeng
+ * @author xiaoyu
+ */
+public interface DataChangedListener {
+
+ /**
+ * invoke this method when AppAuth was received.
+ *
+ * @param changed the changed
+ * @param eventType the event type
+ */
+ default void onAppAuthChanged(List changed, DataEventTypeEnum eventType) {
+ }
+
+ /**
+ * invoke this method when Plugin was received.
+ *
+ * @param changed the changed
+ * @param eventType the event type
+ */
+ default void onPluginChanged(List changed, DataEventTypeEnum eventType) {
+ }
+
+ /**
+ * invoke this method when Selector was received.
+ *
+ * @param changed the changed
+ * @param eventType the event type
+ */
+ default void onSelectorChanged(List changed, DataEventTypeEnum eventType) {
+ }
+
+ /**
+ * On meta data changed.
+ *
+ * @param changed the changed
+ * @param eventType the event type
+ */
+ default void onMetaDataChanged(List changed, DataEventTypeEnum eventType) {
+
+ }
+
+ /**
+ * invoke this method when Rule was received.
+ *
+ * @param changed the changed
+ * @param eventType the event type
+ */
+ default void onRuleChanged(List changed, DataEventTypeEnum eventType) {
+ }
+
+}
+```
+
+It can be seen that there are five methods defined in the interface, which respectively deal with the corresponding processing methods when the data changes of appAuth, plugin, selector, metaData and rule are monitored.
+
+Its inheritance relationship:
+
+
+
+Because the websocket is used by default, the listener here corresponds to the Web socketData ChangedListener, Alt + F7, and the place where this class is instantiated is the following configuration class:
+
+```java
+// DataSyncConfiguration.java
+@Configuration
+public class DataSyncConfiguration {
+
+ /**
+ * http long polling.
+ */
+ @Configuration
+ @ConditionalOnProperty(name = "soul.sync.http.enabled", havingValue = "true")
+ @EnableConfigurationProperties(HttpSyncProperties.class)
+ static class HttpLongPollingListener {
+ @Bean
+ @ConditionalOnMissingBean(HttpLongPollingDataChangedListener.class)
+ public HttpLongPollingDataChangedListener httpLongPollingDataChangedListener(final HttpSyncProperties httpSyncProperties) {
+ return new HttpLongPollingDataChangedListener(httpSyncProperties);
+ }
+ }
+
+ /**
+ * The type Zookeeper listener.
+ */
+ @Configuration
+ @ConditionalOnProperty(prefix = "soul.sync.zookeeper", name = "url")
+ @Import(ZookeeperConfiguration.class)
+ static class ZookeeperListener {
+ @Bean
+ @ConditionalOnMissingBean(ZookeeperDataChangedListener.class)
+ public DataChangedListener zookeeperDataChangedListener(final ZkClient zkClient) {
+ return new ZookeeperDataChangedListener(zkClient);
+ }
+ @Bean
+ @ConditionalOnMissingBean(ZookeeperDataInit.class)
+ public ZookeeperDataInit zookeeperDataInit(final ZkClient zkClient, final SyncDataService syncDataService) {
+ return new ZookeeperDataInit(zkClient, syncDataService);
+ }
+ }
+
+ /**
+ * The type Nacos listener.
+ */
+ @Configuration
+ @ConditionalOnProperty(prefix = "soul.sync.nacos", name = "url")
+ @Import(NacosConfiguration.class)
+ static class NacosListener {
+ @Bean
+ @ConditionalOnMissingBean(NacosDataChangedListener.class)
+ public DataChangedListener nacosDataChangedListener(final ConfigService configService) {
+ return new NacosDataChangedListener(configService);
+ }
+ }
+
+ /**
+ * The WebsocketListener(default strategy).
+ */
+ @Configuration
+ @ConditionalOnProperty(name = "soul.sync.websocket.enabled", havingValue = "true", matchIfMissing = true)
+ @EnableConfigurationProperties(WebsocketSyncProperties.class)
+ static class WebsocketListener {
+ @Bean
+ @ConditionalOnMissingBean(WebsocketDataChangedListener.class)
+ public DataChangedListener websocketDataChangedListener() {
+ return new WebsocketDataChangedListener();
+ }
+ @Bean
+ @ConditionalOnMissingBean(WebsocketCollector.class)
+ public WebsocketCollector websocketCollector() {
+ return new WebsocketCollector();
+ }
+ @Bean
+ @ConditionalOnMissingBean(ServerEndpointExporter.class)
+ public ServerEndpointExporter serverEndpointExporter() {
+ return new ServerEndpointExporter();
+ }
+ }
+}
+```
+
+There are four data synchronization strategies: HTTP long polling, zookeeper, nacos, and websocket (default strategy).
+
+See the web socket annotation @ ConditionalOnProperty (name = "soul. Sync. Web socket. Enabled", having Value = "true", Match (IfMissing = true), find the following configuration in the configuration file:
+
+```yaml
+soul:
+ sync:
+ websocket:
+ enabled: true
+```
+
+This is where the truth comes out.
+
+If you do not want to use the default synchronization policy of the web socket, you can write the corresponding configuration in the configuration file.
+
+### 2.5.2 Listening event processing logic
+
+In order to prevent you from turning back and looking at it, which is inconvenient, I will post the processing logic code here:
+
+```java
+// DataChangedEventDispatcher.java
+@Override
+ @SuppressWarnings("unchecked")
+ public void onApplicationEvent(final DataChangedEvent event) {
+ for (DataChangedListener listener : listeners) {
+ switch (event.getGroupKey()) {
+ case APP_AUTH:
+ listener.onAppAuthChanged((List) event.getSource(), event.getEventType());
+ break;
+ case PLUGIN:
+ listener.onPluginChanged((List) event.getSource(), event.getEventType());
+ break;
+ case RULE:
+ listener.onRuleChanged((List) event.getSource(), event.getEventType());
+ break;
+ case SELECTOR:
+ listener.onSelectorChanged((List) event.getSource(), event.getEventType());
+ break;
+ case META_DATA:
+ listener.onMetaDataChanged((List) event.getSource(), event.getEventType());
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
+ }
+ }
+ }
+```
+
+All listeners are traversed here. For the current web socket, there is only one listener, and it is not known when the other multiple cases will appear. It is doubtful here, and we will come back to add (//TODO) when we encounter related cases later.
+
+Different logics are used according to the type of the published event. The types here correspond to the methods defined in the DataChangedListener interface.
+
+The listener here is an instance of the Web socketData ChangedListener, which will enter the corresponding method in the class:
+
+```java
+// WebsocketDataChangedListener.java
+public class WebsocketDataChangedListener implements DataChangedListener {
+
+ @Override
+ public void onPluginChanged(final List pluginDataList, final DataEventTypeEnum eventType) {
+ WebsocketData websocketData =
+ new WebsocketData<>(ConfigGroupEnum.PLUGIN.name(), eventType.name(), pluginDataList);
+ WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);
+ }
+
+ @Override
+ public void onSelectorChanged(final List selectorDataList, final DataEventTypeEnum eventType) {
+ WebsocketData websocketData =
+ new WebsocketData<>(ConfigGroupEnum.SELECTOR.name(), eventType.name(), selectorDataList);
+ WebsocketCollector.send(GsonUtils.getInstance().toJson(websocketData), eventType);
+ }
+
+ @Override
+ public void onRuleChanged(final List ruleDataList, final DataEventTypeEnum eventType) {
+ WebsocketData configData =
+ new WebsocketData<>(ConfigGroupEnum.RULE.name(), eventType.name(), ruleDataList);
+ WebsocketCollector.send(GsonUtils.getInstance().toJson(configData), eventType);
+ }
+
+ @Override
+ public void onAppAuthChanged(final List appAuthDataList, final DataEventTypeEnum eventType) {
+ WebsocketData configData =
+ new WebsocketData<>(ConfigGroupEnum.APP_AUTH.name(), eventType.name(), appAuthDataList);
+ WebsocketCollector.send(GsonUtils.getInstance().toJson(configData), eventType);
+ }
+
+ @Override
+ public void onMetaDataChanged(final List metaDataList, final DataEventTypeEnum eventType) {
+ WebsocketData configData =
+ new WebsocketData<>(ConfigGroupEnum.META_DATA.name(), eventType.name(), metaDataList);
+ WebsocketCollector.send(GsonUtils.getInstance().toJson(configData), eventType);
+ }
+
+}
+```
+
+As you can see in the code, the data is encapsulated as Web socketData and sent using the WebsocketController. Send method.
+
+## 2.6 Synchronize data to soul-bootstrap
+
+```java
+// WebsocketCollector.java
+@Slf4j
+@ServerEndpoint("/websocket")
+public class WebsocketCollector {
+
+ private static final Set SESSION_SET = new CopyOnWriteArraySet<>();
+
+ private static final String SESSION_KEY = "sessionKey";
+
+ /**
+ * On open.
+ *
+ * @param session the session
+ */
+ @OnOpen
+ public void onOpen(final Session session) {
+ log.info("websocket on open successful....");
+ SESSION_SET.add(session);
+ }
+
+ /**
+ * On message.
+ *
+ * @param message the message
+ * @param session the session
+ */
+ @OnMessage
+ public void onMessage(final String message, final Session session) {
+ if (message.equals(DataEventTypeEnum.MYSELF.name())) {
+ try {
+ ThreadLocalUtil.put(SESSION_KEY, session);
+ SpringBeanUtils.getInstance().getBean(SyncDataService.class).syncAll(DataEventTypeEnum.MYSELF);
+ } finally {
+ ThreadLocalUtil.clear();
+ }
+ }
+ }
+
+ /**
+ * On close.
+ *
+ * @param session the session
+ */
+ @OnClose
+ public void onClose(final Session session) {
+ SESSION_SET.remove(session);
+ ThreadLocalUtil.clear();
+ }
+
+ /**
+ * On error.
+ *
+ * @param session the session
+ * @param error the error
+ */
+ @OnError
+ public void onError(final Session session, final Throwable error) {
+ SESSION_SET.remove(session);
+ ThreadLocalUtil.clear();
+ log.error("websocket collection error: ", error);
+ }
+
+ /**
+ * Send.
+ *
+ * @param message the message
+ * @param type the type
+ */
+ public static void send(final String message, final DataEventTypeEnum type) {
+ if (StringUtils.isNotBlank(message)) {
+ if (DataEventTypeEnum.MYSELF == type) {
+ try {
+ Session session = (Session) ThreadLocalUtil.get(SESSION_KEY);
+ if (session != null) {
+ session.getBasicRemote().sendText(message);
+ }
+ } catch (IOException e) {
+ log.error("websocket send result is exception: ", e);
+ }
+ return;
+ }
+ for (Session session : SESSION_SET) {
+ try {
+ session.getBasicRemote().sendText(message);
+ } catch (IOException e) {
+ log.error("websocket send result is exception: ", e);
+ }
+ }
+ }
+ }
+}
+```
+
+The Web socketController uses the @ ServerEndpoint ( "/web socket") annotation, opens a web socket service interface, and waits for a connection.
+
+After the soul-bootstrap is started, the web socket will be connected, and the onOpen method will be triggered to store the Session of this connection information in the Set set of the SESSION \_ SET.
+
+In the send method, it will first determine whether the DataEventTypeEnum type is MYSELF. This type can be traced back to 2.3-2.4. This time it is UPDATE. As for when it is MYSELF, it needs to be added later. It is doubtful here (//TODO).
+
+The following for loop iterates through all the web socket connection sessions to send the change data.
+
+At this point, the default web socket synchronization data strategy is clear.
+
+> Zhu Ming
+
+## Data synchronization between background and gateway (Web socket)
+
+### How to establish Web socket? In the background?
+
+ Data SyncConfiguration: As the configuration factory of Spring Bean, various listeners can be constructed according to the configuration information. Including HTTP long polling mode, Zookeeper mode, Nacos mode, Web socket method.
+
+```java
+@Configuration
+public class DataSyncConfiguration {
+
+ // In the configuration of the soul-admin project, use soul.sync.websocket.enabled to enable or disable WebSocket.
+ @Configuration
+ @ConditionalOnProperty(name = "soul.sync.websocket.enabled", havingValue = "true", matchIfMissing = true)
+ @EnableConfigurationProperties(WebsocketSyncProperties.class)
+ static class WebsocketListener {
+
+ @Bean
+ @ConditionalOnMissingBean(WebsocketCollector.class)
+ public WebsocketCollector websocketCollector() {
+ return new WebsocketCollector();
+ }
+ }
+}
+```
+
+Web socketListener: As `DataSyncConfiguration` the internal class of, it is responsible for the initialization of the web socket listener. Web socket Collector: It monitors the websocket connection and receives information. Maintain all session sessions connected to the background, and provide `send()` methods to notify session information.
+
+### How does the gateway set up a Web socket?
+
+
+
+Web socket SyncData Configuration: As the configuration factory of Spring Bean, it is the gateway's entrance to build Websocket communication. (An independent startup project `soul-spring-boot-starter-sync-data-websocket` is provided for the gateway to choose freely.)
+
+```java
+@Configuration
+@ConditionalOnClass(WebsocketSyncDataService.class)
+@ConditionalOnProperty(prefix = "soul.sync.websocket", name = "urls")
+@Slf4j
+public class WebsocketSyncDataConfiguration {
+
+ // Collect all subscribers registered as Beans, such as PluginDataSubscriber, MetaDataSubscriber, AuthDataSubscriber
+ @Bean
+ public SyncDataService websocketSyncDataService(final ObjectProvider websocketConfig, final ObjectProvider pluginSubscriber, final ObjectProvider> metaSubscribers, final ObjectProvider> authSubscribers) {
+ log.info("you use websocket sync soul data.......");
+ return new WebsocketSyncDataService(websocketConfig.getIfAvailable(WebsocketConfig::new), pluginSubscriber.getIfAvailable(), metaSubscribers.getIfAvailable(Collections::emptyList), authSubscribers.getIfAvailable(Collections::emptyList));
+ }
+
+ // In the configuration of the soul-bootstrap project, use soul.sync.websocket to configure the backend path for establishing connections
+ @Bean
+ @ConfigurationProperties(prefix = "soul.sync.websocket")
+ public WebsocketConfig websocketConfig() {
+ return new WebsocketConfig();
+ }
+}
+```
+
+Web socket SyncData Service: Get all the registered beans `WebsocketConfig` and the various `DataSubscriber` subscribers, and build an implemented `WebsocketClient` `SoulWebsocketClient` list
+
+SoulWeb socket Client: `Websocket` The communication class monitors the websocket connection and receives information. After receiving the information from the background, it will notify each subscriber.
+
+```java
+public final class SoulWebsocketClient extends WebSocketClient {
+
+ private final WebsocketDataHandler websocketDataHandler;
+
+ private void handleResult(final String result) {
+ WebsocketData websocketData = GsonUtils.getInstance().fromJson(result, WebsocketData.class);
+ ConfigGroupEnum groupEnum = ConfigGroupEnum.acquireByName(websocketData.getGroupType());
+ // Determine the event type of data changes based on the incoming information, such as refresh, update, delete, etc.
+ String eventType = websocketData.getEventType();
+ String json = GsonUtils.getInstance().toJson(websocketData.getData());
+ websocketDataHandler.executor(groupEnum, json, eventType);
+ }
+}
+```
+
+Web socketData Handler: Construct the data processing classes of various implementations `AbstractDataHandler` and cache them during initialization.
+
+```java
+public class WebsocketDataHandler {
+
+ // Cache all DataHandler data change handling classes
+ private static final EnumMap ENUM_MAP = new EnumMap<>(ConfigGroupEnum.class);
+
+ public WebsocketDataHandler(final PluginDataSubscriber pluginDataSubscriber,
+ final List metaDataSubscribers,
+ final List authDataSubscribers) {
+ ENUM_MAP.put(ConfigGroupEnum.PLUGIN, new PluginDataHandler(pluginDataSubscriber));
+ ENUM_MAP.put(ConfigGroupEnum.SELECTOR, new SelectorDataHandler(pluginDataSubscriber));
+ ENUM_MAP.put(ConfigGroupEnum.RULE, new RuleDataHandler(pluginDataSubscriber));
+ ENUM_MAP.put(ConfigGroupEnum.APP_AUTH, new AuthDataHandler(authDataSubscribers));
+ ENUM_MAP.put(ConfigGroupEnum.META_DATA, new MetaDataHandler(metaDataSubscribers));
+ }
+
+ public void executor(final ConfigGroupEnum type, final String json, final String eventType) {
+ // Call the corresponding DataHandler data processing class based on the data change event type
+ ENUM_MAP.get(type).handle(json, eventType);
+ }
+}
+```
+
+### Gateway data change call chain
+
+After the entry class `SoulWebsocketClient` that implements Websocket communication receives the background communication, the `executor()` method called `WebsocketDataHandler` matches the information type, and calls the corresponding `DataHandler` `handler()` to process the information.
+
+
+
+AbstractDataHandler: The implementation `handler()` method calls the corresponding event abstract method according to the type of the event (such as refresh, update, create, delete, etc.).
+
+```java
+public abstract class AbstractDataHandler implements DataHandler {
+
+ // Distribute to respective methods based on the data event type (eventType), these methods are implemented by subclasses since different types of metadata handlers have different ways of processing
+ @Override
+ public void handle(final String json, final String eventType) {
+ List dataList = convert(json);
+ if (CollectionUtils.isNotEmpty(dataList)) {
+ DataEventTypeEnum eventTypeEnum = DataEventTypeEnum.acquireByName(eventType);
+ switch (eventTypeEnum) {
+ case REFRESH:
+ case MYSELF:
+ doRefresh(dataList);
+ break;
+ case UPDATE:
+ case CREATE:
+ doUpdate(dataList);
+ break;
+ case DELETE:
+ doDelete(dataList);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+```
+
+XXX DataHandler: This refers to each implementation class `AbstractDataHandler` of (such as `PluginDataHandler`), whose main function is to call its subscriber.
+
+Different `DataHandler` calls have different subscription methods:
+
+- Notification of plug-in metadata changes `PluginDataHandler` is called `onSubscribe()`
+- The notification selector is `SelectorDataHandler` called `onSelectorSubscribe()` to change the metadata
+- Notification rule metadata change `RuleDataHandler` is invoked `onRuleSubscribe()`
+
+```java
+@RequiredArgsConstructor
+public class PluginDataHandler extends AbstractDataHandler {
+
+ private final PluginDataSubscriber pluginDataSubscriber;
+
+ @Override
+ protected void doUpdate(final List dataList) {
+ // Call onSubscribe() of the subscriber, sending the PluginData data object
+ dataList.forEach(pluginDataSubscriber::onSubscribe);
+ }
+
+ // ...
+}
+```
+
+CommonPluginData Subscriber: The `onSubscribe()` method of the subscriber notifies all classes injected as beans `PluginDataHandler` (not to be confused with the previous class of the same name, which is `soul-plugin-base` the interface under. Its implementation classes are in the respective pluggable plug-in packages.
+
+
+
+```java
+public class CommonPluginDataSubscriber implements PluginDataSubscriber {
+
+ // Collect and cache all registered data handlers, such as DividePluginDataHandler under the HTTP plugin divide
+ private final Map handlerMap;
+
+ // Called for plugin metadata changes
+ @Override
+ public void onSubscribe(final PluginData pluginData) {
+ BaseDataCache.getInstance().cachePluginData(pluginData);
+ Optional.ofNullable(handlerMap.get(pluginData.getName())).ifPresent(handler -> handler.handlerPlugin(pluginData));
+ }
+
+ // Called for selector metadata changes
+ @Override
+ public void onSelectorSubscribe(final SelectorData selectorData) {
+ BaseDataCache.getInstance().cacheSelectData(selectorData);
+ Optional.ofNullable(handlerMap.get(selectorData.getPluginName())).ifPresent(handler -> handler.handlerSelector(selectorData));
+ }
+
+ // Called for rule metadata changes
+ @Override
+ public void onRuleSubscribe(final RuleData ruleData) {
+ BaseDataCache.getInstance().cacheRuleData(ruleData);
+ Optional.ofNullable(handlerMap.get(ruleData.getPluginName())).ifPresent(handler -> handler.handlerRule(ruleData));
+ }
+}
+```
+
+### TIPS
+
+There are two classes PluginDataHandler with the same name under the whole project. One `soul-sync-data-websocket` of them is under the project, which is used to notify the plug-in metadata change, and the other is under the `soul-plugin-base` project, which is used to define the metadata update of each type of plug-in.
+
+To summarize the naming meaning of these two classes, ** `soul-sync-data-websocket` the "plugin" in the lower class name means that the type of metadata is a plug-in class, and `soul-plugin-base` the "plugin" in the lower class name means that the subclass that inherits it comes from each pluggable plug-in. Such as divide, dubbo plugins, etc. **
diff --git a/src/blog/soul_source_learning_11_SPI.md b/src/blog/soul_source_learning_11_SPI.md
index d136bc29da..9882f9127d 100644
--- a/src/blog/soul_source_learning_11_SPI.md
+++ b/src/blog/soul_source_learning_11_SPI.md
@@ -1,828 +1,828 @@
----
-title: Soul Gateway Learning SPI
-author: zhuming
-date: 2021-01-30
-tag:
- - Soul
-cover: /assets/img/architecture/soul-framework.png
-head:
- - - meta
- - name: Blog
----
-
-# Use of SPI in SOUL
-
-When analyzing the load balancing strategy of the divide plug-in, I saw a line of code:
-
-```java
-DivideUpstream divideUpstream = LoadBalanceUtils.selector(upstreamList, ruleHandle.getLoadBalance(), ip);
-```
-
-At that time, it was easy to skip its implementation, and its function was easy to analyze, calling a method that looked like a tool class, passing in a cluster of multiple nodes, and returning a node. This is a load balancer..
-
-But there are a lot of details, the most important of which is the use of the SPI to select specific implementation classes. Take a look at the code for this method:
-
-```java
-public class LoadBalanceUtils {
-
- public static DivideUpstream selector(final List upstreamList, final String algorithm, final String ip) {
- // 调用自定义的 SPI 得到一个子类
- LoadBalance loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getJoin(algorithm);
- return loadBalance.select(upstreamList, ip);
- }
-}
-```
-
-The latter is to call the `select()` specific subclass method, according to the different implementation of the subclass, will eventually show a variety of forms. The current subclass implementations are:
-
-- HashLoadBalance
-- RandomLoadBalance
-- RoundRobinLoadBalance
-
-The key is `ExtensionLoader.getExtensionLoader(LoadBalance.class).getJoin(algorithm);` this line of work.
-
-Before we look at it, let's take a look at the SPI mechanism provided by Java.
-
-## Java SPI
-
-There is such a definition _<<高可用可伸缩微服务架构>> 第 3 章 Apache Dubbo 框架的原理与实现_ in.
-
-> The full name of SPI is Service Provider Interface, which is a built-in service provider discovery function of JDK and a dynamic replacement discovery mechanism. For example, to dynamically add an implementation to an interface at runtime, you only need to add an implementation.
-
-There is also a very vivid brain map in the book, which shows the use of SPI:
-
-
-
-That is to say, in the implementation of our code, there is no need to write a Factory, use MAP to wrap some subclasses, and the final return type is the parent interface. You only need to define the resource file and specify the parent interface and its subclasses in the file, and then you can get all the defined subclass objects by setting them:
-
-```java
-ServiceLoader loaders = ServiceLoader.load(Interface.class)
-for(Interface interface : loaders){
- System.out.println(interface.toString());
-}
-```
-
-Compared with the ordinary factory pattern, this method is definitely more in line with the principle of opening and closing, adding a new subclass without modifying the factory method, but editing the resource file.
-
-### Start with a Demo
-
-According to the specification of SPI, I built a demo to see the specific implementation effect.
-
-
-
-
-
-A `run()` method is defined in Animal, and a subclass implements it.
-
-```java
-public interface Animal {
- void run();
-}
-
-public class Dog implements Animal {
- @Override
- public void run() {
- System.out.println("狗在跑");
- }
-}
-
-public class Horse implements Animal {
- @Override
- public void run() {
- System.out.println("马在跑");
- }
-}
-```
-
-Use the loading class of SPI to get the execution result of the subclass:
-
-```java
-private static void test() {
- final ServiceLoader load = ServiceLoader.load(Animal.class);
-
- for (Animal animal : load) {
- System.out.println(animal);
- animal.run();
- }
-}
-```
-
-
-
-After the call, we get the implementation classes previously written in the resource file and successfully invoke their respective `run()` methods.
-
-At this point, I have a question **, does each call `ServiceLoader.load(Animal.class)` return the same object? ** If it is, I guess it is loaded into the cache at startup, if not, it may be using reflection at the bottom, and each call has a certain consumption. Let's look at the following experiment:
-
-```java
-public static void main(String[] args) {
- for (int i = 0; i < 2; i++) {
- test();
- System.out.println("----------");
- }
-}
-
-private static void test() {
- final ServiceLoader load = ServiceLoader.load(Animal.class);
- for (Animal animal : load) {
- System.out.println(animal);
- animal.run();
- }
-}
-```
-
-
-
-The objects in the two calls are different, which makes me worry about its performance, so let's analyze its code first and see how to implement it.
-
-### Implementation of SPI
-
-To find `java.util,ServiceLoaders` this class, the most striking thing is the directory where we placed the resource files according to the specifications before.
-
-```java
-public final class ServiceLoader implements Iterable {
-
- private static final String PREFIX = "META-INF/services/";
-}
-```
-
-When the debug `PREFIX` attribute is called, it is found that `ServiceLoader.load` the lazy loading method is actually used, and the actual return class is not found when it is called, but when it is traversed.
-
-Its lazy loading is implemented in the following code:
-
-```java
-public final class ServiceLoader implements Iterable {
-
- public static ServiceLoader load(Class service) {
- // 获取当前的类加载器 (我们自己的通常是弟中弟 AppClassLoader )
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
- return ServiceLoader.load(service, cl);
- }
-
- public static ServiceLoader load(Class service, ClassLoader loader) {
- // 调用构造器初始化对象 (说明每次调用都使用新的 ServiceLoader 对象)
- return new ServiceLoader<>(service, loader);
- }
-
- private ServiceLoader(Class svc, ClassLoader cl) {
- service = Objects.requireNonNull(svc, "Service interface cannot be null");
- loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
- acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
- // 上面都是将信息放入对象实例属性中, 这行才是关键调用
- reload();
- }
-
- public void reload() {
- providers.clear();
- // 创建懒加载迭代器, 传入关键的接口 Class 以及加载器
- lookupIterator = new LazyIterator(service, loader);
- }
-}
-
-```
-
-After the call `ServiceLoader.load`, the key thing is not done, just pass the interface class and loader to LazyIterator, the implementation class of the iterator.
-
-Seeing this, we can guess that when the object returned by the real iteration call is called, the iterator must be required to complete the search and initialization of the implementation class, while the parameter passing is Class information and loader, and the initialization of the implementation class will obviously be reflection.
-
-Take a look at how LazyIterator is implemented, starting with where it will be called `hasNext()` in the first place:
-
-```java
-private class LazyIterator implements Iterator {
-
- public boolean hasNext() {
- if (acc == null) {
- return hasNextService();
- } else {
- // ...
- }
- }
-
- private boolean hasNextService() {
- if (nextName != null) {
- return true;
- }
- if (configs == null) {
- try {
- String fullName = PREFIX + service.getName();
- if (loader == null)
- configs = ClassLoader.getSystemResources(fullName);
- else
- // 加载资源文件
- configs = loader.getResources(fullName);
- } catch (IOException x) {
- fail(service, "Error locating configuration files", x);
- }
- }
- while ((pending == null) || !pending.hasNext()) {
- if (!configs.hasMoreElements()) {
- return false;
- }
- // 解析出资源文件中写入的实现类类名
- pending = parse(service, configs.nextElement());
- }
- // 获取一个类名
- nextName = pending.next();
- return true;
- }
-}
-```
-
-
-
-`hasNext()` The call can get the name of the class in our resource, write it to the instance property `nextName`, and return it `true` so that the iterator can make `next()` the call.
-
-```java
-public S next() {
- if (acc == null) {
- return nextService();
- } else {
- // ...
- }
-}
-
-private S nextService() {
- if (!hasNextService()) throw new NoSuchElementException();
- String cn = nextName;
- nextName = null;
- Class> c = null;
- try {
- // 反射得到 Class 对象
- c = Class.forName(cn, false, loader);
- } catch (ClassNotFoundException x) {
- fail(service, "Provider " + cn + " not found");
- }
- if (!service.isAssignableFrom(c)) {
- fail(service, "Provider " + cn + " not a subtype");
- }
- try {
- // 初始化对象, 并判断是否与接口符合
- S p = service.cast(c.newInstance());
- // 将初始化的对象放入hash缓存 (关键步骤)
- providers.put(cn, p);
- return p;
- } catch (Throwable x) {
- fail(service, "Provider " + cn + " could not be instantiated", x);
- }
- throw new Error(); // This cannot happen
-}
-```
-
-Here we understand that after initialization, the object will be put into the cache, and the key is the interface class. There will be no reflection consumption in the second call.
-
-So why do we produce different object instances in the way we test before? The reason is that each call `ServiceLoader.load()` produces a new `ServiceLoader` object. We will improve the test method:
-
-```java
-public static void main(String[] args) {
- // 复用 ServiceLoaders
- final ServiceLoader load = ServiceLoader.load(Animal.class);
- for (int i = 0; i < 2; i++) {
- test(load);
- System.out.println("----------");
- }
-}
-
-private static void test(ServiceLoader load) {
- for (Animal animal : load) {
- System.out.println(animal);
- animal.run();
- }
-}
-```
-
-
-
-### Java SPI Thinking
-
-There are a lot of details that we haven't described in the Java SPI, but that's the main process. Our two previous questions about how to implement and performance can also be answered:
-
-1. How to implement: Read the resource file through the IO stream, load the corresponding path by reflection and generate a Class object, and put it into the cache after initialization
-2. Performance: The first iteration call will have a reflection call, but when used multiple times, as long as the same ServiceLoader object is used, multiple reflections can be avoided, because the objects in the cache will be reused directly.
-
-At this point, I have a very confused place, before I thought it was very similar to the factory method, but it has an advantage over it, because after adding a subclass, you only need to change the resource file without changing the factory class.
-
-But when I tried to use Java SPI to implement it, I found that it could not achieve this effect. An important reason is ** The individual implementation classes in the resource file are not differentiated ** that I could not filter out the implementation class that I needed to cache in `ServiceLoaders`.
-
-So where is its usage scenario?
-
-## JDBC SPI Usage
-
-According to the information, the most critical pluggable driver design in JDBC is implemented by SPI.
-
-### Mysql driver package SPI
-
-In each database connection package, the implementation of JDBC mode needs to implement its Driver interface. The practical one is the SPI mode. Let's take a look.
-
-
-
-So how do the JDBC-related classes in the JDK implement this? The key class is DriverManager
-
-```java
-public class DriverManager {
-
- static {
- loadInitialDrivers();
- }
-
- private static void loadInitialDrivers() {
- // ...
-
- AccessController.doPrivileged(new PrivilegedAction() {
- public Void run() {
-
- // 这里就是 SPI 的实现, 迭代时实际会 Class.forName() 初始化实现类
- ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
- Iterator driversIterator = loadedDrivers.iterator();
- try{
- while(driversIterator.hasNext()) {
- driversIterator.next();
- }
- } catch(Throwable t) {
- // Do nothing
- }
- return null;
- }
- });
-
- // ...
- }
-}
-```
-
-If the static method of DriverManager is called in the code, the above code will be triggered, and what does the initialization of the ** Its function is to initialize all the Driver implementation classes in the SPI resource file. ** implementation class do? Keep looking `com.mysql.jdbc.Driver`
-
-```java
-public class Driver extends NonRegisteringDriver implements java.sql.Driver {
- static {
- try {
- // 调用 DriverManager 的注册方法, 将此 Driver 实现类注册到 JDBC 的 Driver 管理器中
- java.sql.DriverManager.registerDriver(new Driver());
- } catch (SQLException E) {
- throw new RuntimeException("Can't register driver!");
- }
- }
-}
-```
-
-The registration method of DriverManager is very simple, that is, the input parameters are put into static variables as a global cache.
-
-```java
-public class DriverManager {
- // 缓存 Driver 实现类
- private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();
-
- public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
- registerDriver(driver, null);
- }
-
- public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException {
- if(driver != null) {
- // 注册到变量中
- registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
- } else {
- throw new NullPointerException();
- }
- }
-}
-```
-
-### Filter Driver: Contract is greater than configuration
-
-In normal use, we will get the connection directly `DriverManager.getConnection(url, user, passwd)`, but there is a question here. We have registered multiple drivers in DriverManager. Why can we determine a unique Driver here?
-
-To find the `getConnection()` DriverManager first:
-
-```java
-public static Connection getConnection(String url, String user, String password) throws SQLException {
- // ...
- return (getConnection(url, info, Reflection.getCallerClass()));
-}
-
-private static Connection getConnection(
- String url, java.util.Properties info, Class> caller) throws SQLException {
-
- // ...
-
- for(DriverInfo aDriver : registeredDrivers) {
- // isDriverAllowed() 仅是通过 Class.forName() 初始化, 没有甄别作用
- if(isDriverAllowed(aDriver.driver, callerCL)) {
- try {
- // 最关键的点在这行, 筛选工作其实在实现类自身的 connect() 方法中, 会根据传入的 url 筛选
- Connection con = aDriver.driver.connect(url, info);
- if (con != null) {
- return (con);
- }
- } catch (SQLException ex) {
- }
- } else {
- }
-
- }
-
- // ...
-}
-```
-
-See how filtering is implemented in the all-important Mysql Driver (which inherits from NonRegisteringDriver)
-
-```java
-public class NonRegisteringDriver implements java.sql.Driver {
- private static final String URL_PREFIX = "jdbc:mysql://";
- private static final String REPLICATION_URL_PREFIX = "jdbc:mysql:replication://";
- private static final String MXJ_URL_PREFIX = "jdbc:mysql:mxj://";
- public static final String LOADBALANCE_URL_PREFIX = "jdbc:mysql:loadbalance://";
-
- public java.sql.Connection connect(String url, Properties info) throws SQLException {
- // ...
- // parseURL() 会匹配 url 是否符合其所在 Driver 的连接方式
- // 这里就是采用"约定大于配置"的思想, 通过匹配路径头做筛选
- if ((props = parseURL(url, info)) == null) {
- return null;
- }
-
- // ...
- }
-
- public Properties parseURL(String url, Properties defaults) throws java.sql.SQLException {
- // ...
- // 如果 url 不匹配此 Driver 的路径则返回null, 最外层会继续尝试下个 Driver
- if (!StringUtils.startsWithIgnoreCase(url, URL_PREFIX) && !StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX)
- && !StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX) && !StringUtils.startsWithIgnoreCase(url, REPLICATION_URL_PREFIX)) {
- return null;
- }
-
- // ...
- }
-}
-```
-
-### Summary MySQL & JDBC
-
-See here, I think you already understand the implementation of SPI in MySQL & JDBC. Summarize a few points.
-
-- The DriverManager in JDBC loads the SPI resource file and `java.sql.Driver` initializes all the implementation classes.
-- In fact, when the class is initialized, it will create its own object and inject it into DriverManager for unified management.
-- The DriverManager filters the managed Drivers by the Driver implementation class itself, which is only responsible for traversing and taking out the available Drivers
-- The Driver implementation class determines whether it should return itself by passing in the database URL header. If not, return `null`.. JDBC's DriverManager receives the `null` call that will continue with the next Driver implementation class.
-- The MySql driver actual selection scheme is path header matching, which is one of
-
-### JDBC Demo
-
-After writing these analyses, let's look at how to implement a simple demo.
-
-Let's share the way I wrote it before.
-
-```java
-static {
- try {
- // 反射, 该类加载时会在静态块中, 向 DriverManager 注册 Driver
- Class.forName("com.mysql.jdbc.Driver");
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
-}
-
-public static void main(String[] args) {
- try (
- final Connection conn = DriverManager.getConnection(url, user, passwd);
- final Statement stmt = conn.createStatement();
- final ResultSet rs = stmt.executeQuery("select count(1) from test")
- ) {
- while (rs.next()) {
- int count = rs.getInt("count(1)");
- System.out.println(count);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
-}
-```
-
-Although this can be used, don't you think there is extra code? Look at my new way of writing.
-
-```java
-public static void main(String[] args) throws ClassNotFoundException {
- try (
- final Connection conn = DriverManager.getConnection(url, user, passwd);
- final Statement stmt = conn.createStatement();
- final ResultSet rs = stmt.executeQuery("select count(1) from test")
- ) {
- while (rs.next()) {
- int count = rs.getInt("count(1)");
- System.out.println(count);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
-}
-```
-
-Only these simple codes are needed. `DriverManager.getConnection()` When called, the DriverManager will automatically load the implementation class in the SPI, and we do not need to `Class.forName()` manually call `java.mysql.Driver` the initialization.
-
-** See here I think you still understand the most important role of SPI. There is no need to explicitly write out the implementation class corresponding to the interface **
-
-So we also have a problem in "Java SPI Thinking" that has been solved. ** How do you distinguish the implementation classes to be used in the SPI? Let the implementation class decide for itself, and the outer call simply iterates over all. **
-
-## SOUL SPI implementation
-
-We have a thorough understanding of the use of SPI in Java, while the SPI in Soul is designed by ourselves, using the design idea of SPI in Dubbo. You can see the associated annotation on the `org.dromara.soul.spi.SPI` annotation class.
-
-```java
-/**
- * SPI Extend the processing.
- * All spi system reference the apache implementation of
- * https://github.com/apache/dubbo/blob/master/dubbo-common/src/main/java/org/apache/dubbo/common/extension.
- */
-```
-
-### Java SPI bug
-
-When analyzing the use of Java SPI in the last two modules, some shortcomings were found:
-
-1. If the ServiceLoader is used improperly ** Does not properly utilize its caching mechanism **, it will cause the class object to be reflected and the instance object to be initialized every time the concrete implementation class is obtained. Not to mention that the performance is over, the object obtained every time is different, which may cause program problems.
-2. That is to say, every time you look for a specific implementation class, you have to iterate over it. Although the use of fewer subclasses has no effect, this way is still silly. In addition, referring to the implementation of JDBC in MySQL driver, we also need to design a more complex filtering mechanism.
-
-So how does the implementation of Soul SPI solve these two problems? The key lies in the next two sub-modules.
-
-- Optimized Extension Loader
-- Enhanced getJoin ()
-
-### Optimized Extension Loader
-
-Let's first look at the overall picture of the SPI implementation project, which is `soul-spi`:
-
-
-
-The core class is the Extension Loader, which can be said to be the Soul version of the ServiceLoader. It also defines the path location of the SPI resource file.
-
-```java
-public final class ExtensionLoader {
- private static final String SOUL_DIRECTORY = "META-INF/soul/";
-}
-```
-
-By examining the callers of its methods, we find the entry method.
-
-```java
-public final class ExtensionLoader