[{"content":"图文讲解：分布式系统、微服务架构与服务器集群的区别与联系 前言 目前越来越多的公司都在用微服务架构，可以说微服务就是未来的主流，所以对其了解乃至深入理解是很有必要的，本文采用图文结合的方式系统地介绍了什么是分布式系统、微服务架构、服务器集群，方便初学者了解，同时也介绍了三者之间的区别与联系，并给出了作者的见解，方便进阶读者深入理解。\n由于作者水平有限，所以如本文有什么错误欢迎指出，读者若有什么不同的观点也欢迎评论区或者私信交流。\n基础概念讲解 什么是分布式系统? 分布式系统概念\n由多台独立计算机通过网络协同工作、共同完成同一任务，对外表现为一个整体的系统。\n由概念我们不难理解，分布式系统就是多台计算机合作完成同一项任务，通俗理解来讲就是*\u0026ldquo;一个篱笆三个桩，一个好汉三个帮”*，一个计算机完成不了的任务通过多个计算机来完成。\n分布式系统图\n分布式优势\n相信从图中可以看出，现在6个计算机负责同一个任务，那么每个计算机所承受的压力变为了原来的六分之一,若性能还是不够的话还可以再加计算机，可见分布式系统的出现主要是为了在性能方面分散压力同时提高可拓展性。\n需注意各个计算机负责的点不同，否则就变成了服务器集群。（你可以先跳过此段话看完后面再来理解）\n该图为类比到项目上的分布式架构（大家多注意区分分布式系统和分布式架构，下文分布式多代表分布式架构）。\n什么是微服务架构？ 微服务概念\n微服务是指将一个大型的单体应用系统按照业务功能拆分成一组小型、独立运行的服务。\n概念也很明确了，就是将一个应用的复杂业务拆分成多个单独的服务，通俗来理解就是同样是看病的地方，小诊所一个屋子负责很多病症，而大医院则分成了各个科室，不同科室负责不同的病症。所以微服务架构与单体架构的不同点在于同样是很多功能，微服务架构将各个功能拆分开来了。\n单体架构VS微服务架构\n需注意：单体架构是模块，微服务架构是服务，区别在于各个模块互相依赖，而各个服务之间则是互相联系，也就是说对于单体架构，若模块1出问题可能其他模块也都崩溃，但若服务1出问题（若分离的够好）并不影响其他服务。\n关于API网关的讲解具体可见我其他博客。\n微服务优势\n由上述可以看出，微服务架构相较于单体架构耦合度低了很多，对于大型复杂业务，若采用单体架构，则很容易写成“屎山”，后期极难维护，而若一个地方写错了，则整个程序可能都会崩溃，对于大型复杂业务根本不可取，但是若采用微服务架构，将不同业务分离开来，架构清晰很多的同时也便于团队合作开发，且若某个地方写错了虽然很难避免影响到其他服务，但可以最小化影响，同时可以快速定位到错误的服务，微服务架构主要是为了降低项目业务耦合度和增加协作效率。\n但凡事都有两面性，对于简单项目则更建议采用单体架构，否则便是徒增开发难度和复杂度，且如果代码水平不够的话很容易把微服务写成“屎山”，反倒不如单体架构优雅。架构没有绝对优劣，合适才是最优解。\n什么是服务器集群？ 服务器集群概念\n同一个服务的多台服务器同时运行，用于提高性能和可用性。\n从概念就可以直接理解什么是服务器集群，通俗理解就是你开了个店，生意太好了，所以你又开了几个分店。\n这里很容易和分布式系统混淆，大家先不必纠结，先理解服务器集群即可，下面会进行论述。\n服务器集群图\n关于负载均衡以及各种负载均衡策略的讲解具体可见我其他博客。\n服务器集群的优势\n其实由图可以很容易看出，现在你有了三个运行服务A的服务器，现在你拥有了三倍的性能，同时若其中某个服务器宕机，整个服务仍然正常运行，所以我们可以知道服务器集群的优势是提高项目的性能和稳定性。\n三者的区别与联系 很多人把三者看为A包含B，B包含C的集合形式，这样其实是不正确的。\n需明确，三者虽关联密切，但并不是同种类型的东西，它们关注的角度不同，无法用集合的形式表示，就像对于字母、数字、符号，你无法用A包含B，B包含C的集合形式来解释他们的关系。\n作者见解：可以将三者套入医院，分布式就是一个医院有不同场所，各个场所做着不同的事情但共同完成整个流程（系统层面），微服务就是医院有很多科室，各个科室负责不同的病症（业务方面），服务器集群就是多个科室负责相同的病症。\n区别 关注点不同\n分布式系统 是一种系统形态，强调多节点协同工作来完成同一任务；\n微服务架构 是一种设计思想，强调将业务拆分成多个独立的小服务；\n服务器集群 则是一种部署方式，强调同一服务运行在多台服务器上以提升性能和可靠性。\n解决的问题不同\n分布式系统解决了性能和可拓展性的问题。 微服务架构解决了业务复杂度和耦合度的问题。 分布式系统解决了高并发和高可用的问题。 联系 微服务架构既然拆分了业务，那若将各个业务分别部署到不同的单个服务器岂不是即提升了性能又降低了耦合度，这便是微服务和分布式的结合。\n而若将微服务的每个业务分别部署到不同的多个服务器上岂不是又提升了并发性能和高可用，这便是微服务和分布式和服务器集群的结合。\n对于单纯的服务器集群来说，每个服务器运行是同一模块，则不能算为分布式，但是若运行不同的模块（各个模块之间不以业务划分）则可以称之为服务器集群+分布式，而若运行不同的服务（各个服务之间以业务划分），则可称为服务器集群+分布式+微服务架构。\n两两之间论述 分布式和微服务的论述 分布式更强调系统层面的模块分离和物理的部署，将不同模块部署到不同服务器，重点在于提升性能和可拓展性。\n微服务更强调业务上的解耦，将整个项目拆分为不同的服务，重点在于降低业务复杂度和耦合度。\n但现实中一般的微服务架构都是分布式，因为一般大型公司将服务拆分后往往会部署到不同的服务器。\n而若项目拆分后依旧将全部服务部署到同一服务器，则不属于分布式。\n是微服务但并不是分布式的例子\n分布式和集群的论述 分布式架构是将不同模块部署到不同的服务器。\n服务器集群是将同一业务部署到不同的服务器。\n不难看出，对于服务器集群，各个服务器运行的是同一业务，而分布式的各个服务器则是不同模块。\n所以单纯的服务器集群并不是分布式，单纯的分布式也不是集群，而若将分布式的各个模块都部署到多台服务器上，则即是服务器集群，又是\n分布式。\n单独的分布式系统\n单独的服务器集群\n即是分布式架构又是服务器集群\n微服务和集群的论述 其实两者很容易区分，并无太多关联，一般的微服务也不是集群，一般的集群也不是微服务。\n但是现实中应该采用微服务的项目都进行了集群部署，因为这样便兼顾了降低业务耦合度和提高高并发、高可用能力。\n微服务非集群\n微服务集群\n一些加深理解的问题 1.分布式和微服务各自的目的?\n分布式是为了分散物理压力和增加可拓展性。\n微服务是为了降低业务的复杂度和耦合度。\n2.分布式一定是微服务吗？\n并不一定，若分布式的各部分是按照业务划分的则属于微服务，反之则不属于。\n3.微服务一定是分布式吗？\n并不一定，若微服务部署到不同的服务器则属于分布式，反之不属于。\n4.如何区分分布式和集群？\n只需要看服务器上部署的是不是同一模块，若是同一模块则是集群，若是不同模块则是分布式，若有多个服务器相同模块，同时多个又不同，则是分布式又是集群。\n5.要给作者点赞吗？\n既然你都看到这里了，说明对作者所说还是有点认可的，那么点个吧，又不花钱(手动滑稽)。\n写于文尾:本文章仅为作者个人理解，若有遗漏或错误欢迎点出，若有不同观点欢迎交流，若哪部分不是特别理解可以在评论区指出或者私信作者。最后祝愿大家都有一个好的未来，每天开心。\n","date":"2025-11-05T00:00:00Z","image":"https://leonincs.github.io/p/%E5%9B%BE%E6%96%87%E8%AE%B2%E8%A7%A3%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E4%B8%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%9B%86%E7%BE%A4%E7%9A%84%E5%8C%BA%E5%88%AB%E4%B8%8E%E8%81%94%E7%B3%BB/face_hu_86b90ae6f9d5b65b.png","permalink":"https://leonincs.github.io/p/%E5%9B%BE%E6%96%87%E8%AE%B2%E8%A7%A3%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E4%B8%8E%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%9B%86%E7%BE%A4%E7%9A%84%E5%8C%BA%E5%88%AB%E4%B8%8E%E8%81%94%E7%B3%BB/","title":"图文讲解：分布式系统、微服务架构与服务器集群的区别与联系"},{"content":"常用C++算法模板与例题(偏自用) 该博客准备了一些常用的C++算法模板与相应的例题，采用class的格式(偏自用)，为ICPC区域赛而准备。\n基础模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; using pii = pair\u0026lt;int,int\u0026gt;; using pll = pair\u0026lt;i64,i64\u0026gt;; void solve() { } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T = 1; //cin \u0026gt;\u0026gt; T; while(T--) { solve(); } return 0; } 快速幂 模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 复杂度：O(log2(b)) #include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; i64 qmi(i64 a, i64 b, i64 mod) { i64 res = 1; while(b) { if(b \u0026amp; 1) res = (res * a) % mod; a = (a * a) % mod; b \u0026gt;\u0026gt;= 1; } return res; } 例题 【模板】快速幂 - 洛谷\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; using pii = pair\u0026lt;int,int\u0026gt;; using pll = pair\u0026lt;i64,i64\u0026gt;; // ---------------------------------------------------------------------------------------------- //快速幂 i64 qmi(i64 a, i64 b, i64 mod) { i64 res = 1; while(b) { if(b \u0026amp; 1) res = (res * a) % mod; a = (a * a) % mod; b \u0026gt;\u0026gt;= 1; } return res; } // ---------------------------------------------------------------------------------------------- void solve() { i64 a, b, mod; cin \u0026gt;\u0026gt; a \u0026gt;\u0026gt; b \u0026gt;\u0026gt; mod; i64 ans = qmi(a, b, mod); cout \u0026lt;\u0026lt; a \u0026lt;\u0026lt; \u0026#34;^\u0026#34; \u0026lt;\u0026lt; b \u0026lt;\u0026lt; \u0026#34; mod \u0026#34; \u0026lt;\u0026lt; mod \u0026lt;\u0026lt; \u0026#34;=\u0026#34; \u0026lt;\u0026lt; ans \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T = 1; //cin \u0026gt;\u0026gt; T; while(T--) { solve(); } return 0; } 逆元 模板 快速幂+费马小定理（要求mod为质数） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 复杂度：O(log2(mod)) #include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; i64 mod = 1e9 + 7; //快速幂 i64 qmi(i64 a, i64 b) { i64 res = 1; while(b) { if(b \u0026amp; 1) res = (res * a) % mod; a = (a * a) % mod; b \u0026gt;\u0026gt;= 1; } return res; } //费马小定理 i64 inv(i64 a) { return qmi(a, mod - 2); } 线性推（要求mod为质数） 1 2 3 4 5 6 7 8 9 10 11 12 13 // 复杂度：O(n) #include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; i64 mod = 1e9 + 7; vector\u0026lt;i64\u0026gt; init(int n) { vector\u0026lt;i64\u0026gt; inv(n + 1, 0); inv[1] = 1; for (int i = 2; i \u0026lt;= n; ++i) inv[i] = (i64)(mod - mod / i) * inv[mod % i] % mod; return inv; } 扩展欧拉定理（mod可以不为质数） 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; using u64 = unsigned long long; using i128 = __int128_t; // 快速幂 (a^e % mod)，支持 64 位乘法防溢出 static inline i64 mul_mod(i64 a, i64 b, i64 mod) { return (i64)((i128)a * b % mod); } static inline i64 pow_mod(i64 a, i64 e, i64 mod) { a %= mod; if (a \u0026lt; 0) a += mod; i64 r = 1 % mod; while (e \u0026gt; 0) { if (e \u0026amp; 1) r = mul_mod(r, a, mod); a = mul_mod(a, a, mod); e \u0026gt;\u0026gt;= 1; } return r; } // 试除法求 phi(m)（适合中等 m；很大 m 可换分解或线筛预处理） i64 phi(i64 m) { i64 r = m, x = m; for (i64 p = 2; p * p \u0026lt;= x; ++p) { if (x % p == 0) { while (x % p == 0) x /= p; r = r / p * (p - 1); } } if (x \u0026gt; 1) r = r / x * (x - 1); return r; } // 扩展欧拉定理：计算 a^b mod m i64 mod_pow_ext_euler(i64 a, i64 b, i64 m) { if (m == 1) return 0; a %= m; if (a \u0026lt; 0) a += m; i64 g = std::gcd((i64)llabs(a), (i64)llabs(m)); i64 t = phi(m); i64 e; if (g == 1) { // 互质，直接降幂到 φ(m) e = (t == 0 ? 0 : (b % t + t) % t); } else { // 不互质：b \u0026lt; φ 用 b；否则用 (b % φ + φ) if (b \u0026lt; t) e = b; else e = (t == 0 ? b : (b % t + t)); } return pow_mod(a, e, m); } 例题 【模板】模意义下的乘法逆元 - 洛谷\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; using pii = pair\u0026lt;int,int\u0026gt;; using pll = pair\u0026lt;i64,i64\u0026gt;; i64 mod = 1e9 + 7; // ---------------------------------------------------------------------------------------------- // inv[i] = i^{-1} mod p，要求 p 为素数、n \u0026lt; p vector\u0026lt;i64\u0026gt; init(int n) { vector\u0026lt;i64\u0026gt; inv(n + 1, 0); inv[1] = 1; for (int i = 2; i \u0026lt;= n; ++i) inv[i] = (i64)(mod - mod / i) * inv[mod % i] % mod; return inv; } // ---------------------------------------------------------------------------------------------- void solve() { int n; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; mod; vector\u0026lt;i64\u0026gt; ans = init(n); for(int i = 1; i \u0026lt;= n; i++) { cout \u0026lt;\u0026lt; ans[i] \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T = 1; //cin \u0026gt;\u0026gt; T; while(T--) { solve(); } return 0; } 筛质数 模板 埃氏筛 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 复杂度：O(n log log n)，适合一次性筛到 n #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; vector\u0026lt;int\u0026gt; primes; vector\u0026lt;bool\u0026gt; is_prime; void sieve(int n) { is_prime.assign(n + 1, true); if(n \u0026gt;= 0) is_prime[0] = false; if(n \u0026gt;= 1) is_prime[1] = false; for(int i = 2; i * 1LL * i \u0026lt;= n; ++i) { if (is_prime[i]) { for (i64 j = 1LL * i * i; j \u0026lt;= n; j += i) is_prime[j] = false; } } primes.clear(); for(int i = 2; i \u0026lt;= n; ++i) { if(is_prime[i]) primes.push_back(i); } } 线性筛 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 // 复杂度：O(n) #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; vector\u0026lt;int\u0026gt; primes; vector\u0026lt;bool\u0026gt; is_prime; void sieve(int n) { is_prime.assign(n + 1, true); if(n \u0026gt;= 0) is_prime[0] = false; if(n \u0026gt;= 1) is_prime[1] = false; for(int i = 2; i \u0026lt;= n; i++) { if(is_prime[i]) primes.push_back(i); for(int p : primes) { if(i * p \u0026gt; n) break; is_prime[i * p] = false; if(i % p == 0) break; } } } 例题 【模板】线性筛素数 - 洛谷\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; using pii = pair\u0026lt;int,int\u0026gt;; using pll = pair\u0026lt;i64,i64\u0026gt;; i64 mod = 1e9 + 7; // ---------------------------------------------------------------------------------------------- vector\u0026lt;int\u0026gt; primes; vector\u0026lt;bool\u0026gt; is_prime; void sieve(int n) { is_prime.assign(n + 1, true); if(n \u0026gt;= 0) is_prime[0] = false; if(n \u0026gt;= 1) is_prime[1] = false; for(int i = 2; i \u0026lt;= n; i++) { if(is_prime[i]) primes.push_back(i); for(int p : primes) { if(i * p \u0026gt; n) break; is_prime[i * p] = false; if(i % p == 0) break; } } } // ---------------------------------------------------------------------------------------------- void solve() { int n, m; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; sieve(n); while(m--) { int x; cin \u0026gt;\u0026gt; x; x--; cout \u0026lt;\u0026lt; primes[x] \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T = 1; //cin \u0026gt;\u0026gt; T; while(T--) { solve(); } return 0; } ST表 模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 //预处理复杂度：O(nlogn) //查询复杂度：O(1) #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; template\u0026lt;class T\u0026gt; class ST { public: ST() : n(0), K(0) {} ST(const vector\u0026lt;T\u0026gt;\u0026amp; a) { build(a); } void build(const vector\u0026lt;T\u0026gt;\u0026amp; a) { n = (int)a.size(); K = n ? (32 - __builtin_clz(n)) : 1; lg.assign(n + 1, 0); for (int i = 2; i \u0026lt;= n; ++i) lg[i] = lg[i \u0026gt;\u0026gt; 1] + 1; mn.assign(K, vector\u0026lt;T\u0026gt;(n)); mx.assign(K, vector\u0026lt;T\u0026gt;(n)); if (n == 0) return; mn[0] = a; mx[0] = a; for (int k = 1; k \u0026lt; K; ++k) { int len = 1 \u0026lt;\u0026lt; k, half = len \u0026gt;\u0026gt; 1; for (int i = 0; i + len \u0026lt;= n; ++i) { mn[k][i] = std::min(mn[k-1][i], mn[k-1][i + half]); mx[k][i] = std::max(mx[k-1][i], mx[k-1][i + half]); } } } // 闭区间最小值 [l, r] T queryMin(int l, int r) const { int k = lg[r - l + 1]; return std::min(mn[k][l], mn[k][r - (1 \u0026lt;\u0026lt; k) + 1]); } // 闭区间最大值 [l, r] T queryMax(int l, int r) const { int k = lg[r - l + 1]; return std::max(mx[k][l], mx[k][r - (1 \u0026lt;\u0026lt; k) + 1]); } private: int n, K; vector\u0026lt;int\u0026gt; lg; vector\u0026lt;vector\u0026lt;T\u0026gt;\u0026gt; mn, mx; }; 例题 【模板】ST 表 \u0026amp; RMQ 问题 - 洛谷\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; using pii = pair\u0026lt;int,int\u0026gt;; using pll = pair\u0026lt;i64,i64\u0026gt;; i64 mod = 1e9 + 7; // ---------------------------------------------------------------------------------------------- template\u0026lt;class T\u0026gt; class ST { public: ST() : n(0), K(0) {} ST(const vector\u0026lt;T\u0026gt;\u0026amp; a) { build(a); } void build(const vector\u0026lt;T\u0026gt;\u0026amp; a) { n = (int)a.size(); K = n ? (32 - __builtin_clz(n)) : 1; lg.assign(n + 1, 0); for (int i = 2; i \u0026lt;= n; ++i) lg[i] = lg[i \u0026gt;\u0026gt; 1] + 1; mn.assign(K, vector\u0026lt;T\u0026gt;(n)); mx.assign(K, vector\u0026lt;T\u0026gt;(n)); if (n == 0) return; mn[0] = a; mx[0] = a; for (int k = 1; k \u0026lt; K; ++k) { int len = 1 \u0026lt;\u0026lt; k, half = len \u0026gt;\u0026gt; 1; for (int i = 0; i + len \u0026lt;= n; ++i) { mn[k][i] = std::min(mn[k-1][i], mn[k-1][i + half]); mx[k][i] = std::max(mx[k-1][i], mx[k-1][i + half]); } } } // 闭区间最小值 [l, r] T queryMin(int l, int r) const { int k = lg[r - l + 1]; return std::min(mn[k][l], mn[k][r - (1 \u0026lt;\u0026lt; k) + 1]); } // 闭区间最大值 [l, r] T queryMax(int l, int r) const { int k = lg[r - l + 1]; return std::max(mx[k][l], mx[k][r - (1 \u0026lt;\u0026lt; k) + 1]); } private: int n, K; vector\u0026lt;int\u0026gt; lg; vector\u0026lt;vector\u0026lt;T\u0026gt;\u0026gt; mn, mx; }; // ---------------------------------------------------------------------------------------------- void solve() { int n, m; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; vector\u0026lt;int\u0026gt; a(n); for (int i = 0; i \u0026lt; n; ++i) cin \u0026gt;\u0026gt; a[i]; ST\u0026lt;int\u0026gt; st(a); while(m--) { int l, r; cin \u0026gt;\u0026gt; l \u0026gt;\u0026gt; r; l--, r--; cout \u0026lt;\u0026lt; st.queryMax(l, r) \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T = 1; //cin \u0026gt;\u0026gt; T; while(T--) { solve(); } return 0; } 并查集(0-based) 模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // DSU (路径压缩) // 复杂度：O(n) #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; class DSU { public: DSU(int n) { init(n); } void init(int n) { par.resize(n); iota(par.begin(), par.end(), 0); } int find(int x) { if (par[x] == x) return x; return par[x] = find(par[x]); // 路径压缩 } void merge(int a, int b) { a = find(a); b = find(b); if(a \u0026gt; b) swap(a, b); par[b] = a; } bool same(int a, int b) { return find(a) == find(b); } int size(int x) { int sz = 0; for(int i = 0; i \u0026lt; par.size(); i++){ if(find(i) == x) sz++; } return sz; } private: vector\u0026lt;int\u0026gt; par; }; 例题 【模板】并查集 - 洛谷\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; using pii = pair\u0026lt;int,int\u0026gt;; using pll = pair\u0026lt;i64,i64\u0026gt;; // ---------------------------------------------------------------------------------------------- class DSU { public: DSU(int n) { init(n); } void init(int n) { par.resize(n); iota(par.begin(), par.end(), 0); } int find(int x) { if (par[x] == x) return x; return par[x] = find(par[x]); // 路径压缩 } void merge(int a, int b) { a = find(a); b = find(b); if(a \u0026gt; b) swap(a, b); par[b] = a; } bool same(int a, int b) { return find(a) == find(b); } int size() { int sz = 0; for(int i = 0; i \u0026lt; par.size(); i++){ if(find(i) == i) sz++; } return sz; } private: vector\u0026lt;int\u0026gt; par; }; // ---------------------------------------------------------------------------------------------- void solve() { int n, m; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; DSU dsu(n); while (m--) { int choice, a, b; cin \u0026gt;\u0026gt; choice \u0026gt;\u0026gt; a \u0026gt;\u0026gt; b; a--, b--; if (choice == 1) { dsu.merge(a, b); } else { cout \u0026lt;\u0026lt; (dsu.same(a, b) ? \u0026#34;Y\u0026#34; : \u0026#34;N\u0026#34;) \u0026lt;\u0026lt; endl; } } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T = 1; //cin \u0026gt;\u0026gt; T; while(T--) { solve(); } return 0; } 字典树 模板 a-z版 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 //复杂度：O(n) #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; class Trie { static constexpr int ALPHA = 26; static constexpr char BASE = \u0026#39;a\u0026#39;; struct Node { array\u0026lt;int, ALPHA\u0026gt; next; int pass = 0, end = 0; Node() { next.fill(-1); } }; vector\u0026lt;Node\u0026gt; t; public: Trie() { t.emplace_back(); } void insert(string_view s) { int u = 0; t[u].pass++; for (char ch : s) { int c = ch - BASE; // 如果保证输入是 a-z，可以删掉以下两行检查 if (c \u0026lt; 0 || c \u0026gt;= ALPHA) continue; if (t[u].next[c] == -1) { t[u].next[c] = (int)t.size(); t.emplace_back(); } u = t[u].next[c]; t[u].pass++; } t[u].end++; } bool contains(string_view s) const { int u = findNode(s); return u != -1 \u0026amp;\u0026amp; t[u].end \u0026gt; 0; } bool startsWith(string_view s) const { int u = findNode(s); return u != -1 \u0026amp;\u0026amp; t[u].pass \u0026gt; 0; } int countPrefix(string_view s) const { int u = findNode(s); return u == -1 ? 0 : t[u].pass; } int countExact(string_view s) const { // 原 countWordsWithPrefix 更名 int u = findNode(s); return u == -1 ? 0 : t[u].end; } void erase(string_view s) { if (!contains(s)) return; int u = 0; t[u].pass--; for (char ch : s) { int c = ch - BASE; if (c \u0026lt; 0 || c \u0026gt;= ALPHA) continue; u = t[u].next[c]; t[u].pass--; } t[u].end--; } private: int findNode(string_view s) const { int u = 0; for (char ch : s) { int c = ch - BASE; if (c \u0026lt; 0 || c \u0026gt;= ALPHA) return -1; int v = t[u].next[c]; if (v == -1) return -1; u = v; } return u; } }; ASCII版 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 // 稀疏版 Trie：7-bit ASCII，避免 MLE #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; class Trie { struct Node { unordered_map\u0026lt;unsigned char, int\u0026gt; next; // 只存实际存在的子边 int pass = 0, end = 0; }; vector\u0026lt;Node\u0026gt; t; static inline int idx(unsigned char ch) { return (ch \u0026lt; 128) ? int(ch) : -1; // 7-bit ASCII } public: Trie() { t.reserve(1 \u0026lt;\u0026lt; 20); // 可按数据量调整（可选） t.emplace_back(); } void insert(string_view s) { int u = 0; t[u].pass++; for (char ch : s) { int c = idx(static_cast\u0026lt;unsigned char\u0026gt;(ch)); if (c == -1) continue; // 忽略非 ASCII auto it = t[u].next.find((unsigned char)c); if (it == t[u].next.end()) { int v = (int)t.size(); t[u].next[(unsigned char)c] = v; t.emplace_back(); u = v; } else { u = it-\u0026gt;second; } t[u].pass++; } t[u].end++; } bool contains(string_view s) const { int u = findNode(s); return u != -1 \u0026amp;\u0026amp; t[u].end \u0026gt; 0; } bool startsWith(string_view s) const { int u = findNode(s); return u != -1 \u0026amp;\u0026amp; t[u].pass \u0026gt; 0; } int countPrefix(string_view s) const { int u = findNode(s); return u == -1 ? 0 : t[u].pass; } int countExact(string_view s) const { int u = findNode(s); return u == -1 ? 0 : t[u].end; } void erase(string_view s) { if (!contains(s)) return; int u = 0; t[u].pass--; for (char ch : s) { int c = idx(static_cast\u0026lt;unsigned char\u0026gt;(ch)); if (c == -1) continue; u = t[u].next.at((unsigned char)c); t[u].pass--; } t[u].end--; // 注：未做物理回收，通常不必；若需要可做懒惰删除标记或GC } private: int findNode(string_view s) const { int u = 0; for (char ch : s) { int c = idx(static_cast\u0026lt;unsigned char\u0026gt;(ch)); if (c == -1) return -1; auto it = t[u].next.find((unsigned char)c); if (it == t[u].next.end()) return -1; u = it-\u0026gt;second; } return u; } }; 例题 【模板】字典树 - 洛谷\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; using pii = pair\u0026lt;int,int\u0026gt;; using pll = pair\u0026lt;i64,i64\u0026gt;; // ---------------------------------------------------------------------------------------------- // 稀疏版 Trie：7-bit ASCII，避免 MLE class Trie { struct Node { unordered_map\u0026lt;unsigned char, int\u0026gt; next; // 只存实际存在的子边 int pass = 0, end = 0; }; vector\u0026lt;Node\u0026gt; t; static inline int idx(unsigned char ch) { return (ch \u0026lt; 128) ? int(ch) : -1; // 7-bit ASCII } public: Trie() { t.reserve(1 \u0026lt;\u0026lt; 20); // 可按数据量调整（可选） t.emplace_back(); } void insert(string_view s) { int u = 0; t[u].pass++; for (char ch : s) { int c = idx(static_cast\u0026lt;unsigned char\u0026gt;(ch)); if (c == -1) continue; // 忽略非 ASCII auto it = t[u].next.find((unsigned char)c); if (it == t[u].next.end()) { int v = (int)t.size(); t[u].next[(unsigned char)c] = v; t.emplace_back(); u = v; } else { u = it-\u0026gt;second; } t[u].pass++; } t[u].end++; } bool contains(string_view s) const { int u = findNode(s); return u != -1 \u0026amp;\u0026amp; t[u].end \u0026gt; 0; } bool startsWith(string_view s) const { int u = findNode(s); return u != -1 \u0026amp;\u0026amp; t[u].pass \u0026gt; 0; } int countPrefix(string_view s) const { int u = findNode(s); return u == -1 ? 0 : t[u].pass; } int countExact(string_view s) const { int u = findNode(s); return u == -1 ? 0 : t[u].end; } void erase(string_view s) { if (!contains(s)) return; int u = 0; t[u].pass--; for (char ch : s) { int c = idx(static_cast\u0026lt;unsigned char\u0026gt;(ch)); if (c == -1) continue; u = t[u].next.at((unsigned char)c); t[u].pass--; } t[u].end--; // 注：未做物理回收，通常不必；若需要可做懒惰删除标记或GC } private: int findNode(string_view s) const { int u = 0; for (char ch : s) { int c = idx(static_cast\u0026lt;unsigned char\u0026gt;(ch)); if (c == -1) return -1; auto it = t[u].next.find((unsigned char)c); if (it == t[u].next.end()) return -1; u = it-\u0026gt;second; } return u; } }; // ---------------------------------------------------------------------------------------------- void solve() { int n, m; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; Trie t; for(int i = 0; i \u0026lt; n; i++){ string s; cin \u0026gt;\u0026gt; s; t.insert(s); } for(int i = 0; i \u0026lt; m; i++){ string s; cin \u0026gt;\u0026gt; s; cout \u0026lt;\u0026lt; t.countPrefix(s) \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T = 1; cin \u0026gt;\u0026gt; T; while(T--) { solve(); } return 0; } 树状数组(0-based) 模板 点更新+区间求和 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; class Fenwick { public: Fenwick(): n(0) {} explicit Fenwick(int n_) { init(n_); } explicit Fenwick(const vector\u0026lt;long long\u0026gt;\u0026amp; a) { build(a); } void init(int n_) { n = n_; bit.assign(n, 0); } void build(const vector\u0026lt;long long\u0026gt;\u0026amp; a) { n = (int)a.size(); bit = a; for (int i = 0; i \u0026lt; n; ++i) { int j = i | (i + 1); if (j \u0026lt; n) bit[j] += bit[i]; } } void add(int i, long long delta) { for (; i \u0026lt; n; i = i | (i + 1)) bit[i] += delta; } // 前缀和：A[0] + ... + A[i]，若 i \u0026lt; 0 返回 0 long long sumPrefix(int i) const { if (i \u0026lt; 0) return 0; long long res = 0; for (; i \u0026gt;= 0; i = (i \u0026amp; (i + 1)) - 1) res += bit[i]; return res; } // 区间和：A[l] + ... + A[r]（要求 0\u0026lt;=l\u0026lt;=r\u0026lt;n） long long sumRange(int l, int r) const { if (l \u0026gt; r) return 0; return sumPrefix(r) - sumPrefix(l - 1); } int size() const { return n; } private: int n; vector\u0026lt;long long\u0026gt; bit; }; 区间更新+区间求和 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; class Fenwick { public: Fenwick() : n(0) {} explicit Fenwick(int n_) { init(n_); } explicit Fenwick(const vector\u0026lt;long long\u0026gt;\u0026amp; a) { build(a); } void init(int n_) { n = n_; bit1.assign(n + 2, 0); bit2.assign(n + 2, 0); } // 从数组构建（0-based）。如需 O(n) 构建可改成差分构建，这里用简洁的 O(n log n) 版本。 void build(const vector\u0026lt;long long\u0026gt;\u0026amp; a) { init((int)a.size()); for (int i = 0; i \u0026lt; n; ++i) addRange(i, i, a[i]); } // 区间加：A[l..r] += delta void addRange(int l, int r, long long delta) { // 将 0-based 转为 1-based int L = l + 1, R = r + 1; internalAdd(bit1, L, +delta); internalAdd(bit1, R + 1, -delta); internalAdd(bit2, L, +delta * (L - 1)); internalAdd(bit2, R + 1, -delta * R); } // 前缀和：sum A[0..i]；若 i \u0026lt; 0 返回 0 long long sumPrefix(int i) const { if (i \u0026lt; 0) return 0; int x = i + 1; // 1-based return x * internalSum(bit1, x) - internalSum(bit2, x); } // 区间和：sum A[l..r] long long sumRange(int l, int r) const { if (l \u0026gt; r) return 0; return sumPrefix(r) - sumPrefix(l - 1); } int size() const { return n; } private: int n; vector\u0026lt;long long\u0026gt; bit1, bit2; // 双树 static void internalAdd(vector\u0026lt;long long\u0026gt;\u0026amp; bit, int idx, long long delta) { for (int i = idx; i \u0026lt; (int)bit.size(); i += i \u0026amp; -i) bit[i] += delta; } static long long internalSum(const vector\u0026lt;long long\u0026gt;\u0026amp; bit, int idx) { long long res = 0; for (int i = idx; i \u0026gt; 0; i -= i \u0026amp; -i) res += bit[i]; return res; } }; 例题 【模板】树状数组 1 - 洛谷 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; using pii = pair\u0026lt;int,int\u0026gt;; using pll = pair\u0026lt;i64,i64\u0026gt;; // ---------------------------------------------------------------------------------------------- class Fenwick { public: Fenwick(): n(0) {} explicit Fenwick(int n_) { init(n_); } explicit Fenwick(const vector\u0026lt;long long\u0026gt;\u0026amp; a) { build(a); } void init(int n_) { n = n_; bit.assign(n, 0); } void build(const vector\u0026lt;long long\u0026gt;\u0026amp; a) { n = (int)a.size(); bit = a; for (int i = 0; i \u0026lt; n; ++i) { int j = i | (i + 1); if (j \u0026lt; n) bit[j] += bit[i]; } } void add(int i, long long delta) { for (; i \u0026lt; n; i = i | (i + 1)) bit[i] += delta; } // 前缀和：A[0] + ... + A[i]，若 i \u0026lt; 0 返回 0 long long sumPrefix(int i) const { if (i \u0026lt; 0) return 0; long long res = 0; for (; i \u0026gt;= 0; i = (i \u0026amp; (i + 1)) - 1) res += bit[i]; return res; } // 区间和：A[l] + ... + A[r]（要求 0\u0026lt;=l\u0026lt;=r\u0026lt;n） long long sumRange(int l, int r) const { if (l \u0026gt; r) return 0; return sumPrefix(r) - sumPrefix(l - 1); } int size() const { return n; } private: int n; vector\u0026lt;long long\u0026gt; bit; }; // ---------------------------------------------------------------------------------------------- void solve() { int n, m; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; vector\u0026lt;i64\u0026gt; a(n); for (int i = 0; i \u0026lt; n; ++i) cin \u0026gt;\u0026gt; a[i]; Fenwick fen(a); while(m--) { int choose; cin \u0026gt;\u0026gt; choose; if(choose == 1) { int idx, val; cin \u0026gt;\u0026gt; idx \u0026gt;\u0026gt; val; idx--; fen.add(idx, val); } else { int l, r; cin \u0026gt;\u0026gt; l \u0026gt;\u0026gt; r; l--, r--; cout \u0026lt;\u0026lt; fen.sumRange(l, r) \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T = 1; //cin \u0026gt;\u0026gt; T; while(T--) { solve(); } return 0; } 【模板】树状数组 2 - 洛谷 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; using pii = pair\u0026lt;int,int\u0026gt;; using pll = pair\u0026lt;i64,i64\u0026gt;; // ---------------------------------------------------------------------------------------------- class Fenwick { public: Fenwick() : n(0) {} explicit Fenwick(int n_) { init(n_); } explicit Fenwick(const vector\u0026lt;long long\u0026gt;\u0026amp; a) { build(a); } void init(int n_) { n = n_; bit1.assign(n + 2, 0); bit2.assign(n + 2, 0); } void build(const vector\u0026lt;long long\u0026gt;\u0026amp; a) { init((int)a.size()); for (int i = 0; i \u0026lt; n; ++i) addRange(i, i, a[i]); } // 区间加：A[l..r] += delta void addRange(int l, int r, long long delta) { // 将 0-based 转为 1-based int L = l + 1, R = r + 1; internalAdd(bit1, L, +delta); internalAdd(bit1, R + 1, -delta); internalAdd(bit2, L, +delta * (L - 1)); internalAdd(bit2, R + 1, -delta * R); } // 前缀和：sum A[0..i]；若 i \u0026lt; 0 返回 0 long long sumPrefix(int i) const { if (i \u0026lt; 0) return 0; int x = i + 1; // 1-based return x * internalSum(bit1, x) - internalSum(bit2, x); } // 区间和：sum A[l..r] long long sumRange(int l, int r) const { if (l \u0026gt; r) return 0; return sumPrefix(r) - sumPrefix(l - 1); } int size() const { return n; } private: int n; vector\u0026lt;long long\u0026gt; bit1, bit2; // 双树 static void internalAdd(vector\u0026lt;long long\u0026gt;\u0026amp; bit, int idx, long long delta) { for (int i = idx; i \u0026lt; (int)bit.size(); i += i \u0026amp; -i) bit[i] += delta; } static long long internalSum(const vector\u0026lt;long long\u0026gt;\u0026amp; bit, int idx) { long long res = 0; for (int i = idx; i \u0026gt; 0; i -= i \u0026amp; -i) res += bit[i]; return res; } }; // ---------------------------------------------------------------------------------------------- void solve() { int n, m; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; vector\u0026lt;i64\u0026gt; a(n); for (int i = 0; i \u0026lt; n; ++i) cin \u0026gt;\u0026gt; a[i]; Fenwick fen(a); while(m--) { int choose; cin \u0026gt;\u0026gt; choose; if(choose == 1) { int l, r, val; cin \u0026gt;\u0026gt; l \u0026gt;\u0026gt; r \u0026gt;\u0026gt; val; l--, r--; fen.addRange(l, r, val); } else { int idx; cin \u0026gt;\u0026gt; idx; idx--; cout \u0026lt;\u0026lt; fen.sumRange(idx, idx) \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T = 1; //cin \u0026gt;\u0026gt; T; while(T--) { solve(); } return 0; } 最近公共祖先 模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; class LCA { public: LCA(int n = 0) { init(n); } void init(int n_) { n = n_; LOG = (n \u0026lt;= 1 ? 1 : 32 - __builtin_clz(n)); g.assign(n, {}); depth.assign(n, 0); tin.assign(n, 0); tout.assign(n, 0); up.assign(LOG, vector\u0026lt;int\u0026gt;(n, 0)); timer = 0; } // 无向树 void addEdge(int u, int v) { g[u].push_back(v); g[v].push_back(u); } void dfs(int u, int p) { tin[u] = ++timer; up[0][u] = (p == -1 ? u : p); for (int k = 1; k \u0026lt; LOG; ++k) { up[k][u] = up[k-1][ up[k-1][u] ]; } for (int v : g[u]) if (v != p) { depth[v] = depth[u] + 1; dfs(v, u); } tout[u] = ++timer; } // 预处理 LCA void build(int root = 0) { depth[root] = 0; dfs(root, -1); } // 查询 u 是否为 v 的祖先 inline bool is_ancestor(int u, int v) const { return tin[u] \u0026lt;= tin[v] \u0026amp;\u0026amp; tout[v] \u0026lt;= tout[u]; } int lca(int a, int b) const { if (is_ancestor(a, b)) return a; if (is_ancestor(b, a)) return b; int u = a; for (int k = LOG - 1; k \u0026gt;= 0; --k) { int nu = up[k][u]; if (!is_ancestor(nu, b)) u = nu; } return up[0][u]; } // 第 k 级祖先（k=0 返回自身；越界停在根） int kth_ancestor(int v, int k) const { for (int i = 0; i \u0026lt; LOG; ++i) if (k \u0026amp; (1 \u0026lt;\u0026lt; i)) v = up[i][v]; return v; } // 边数距离 int dist(int a, int b) const { int c = lca(a, b); return depth[a] + depth[b] - 2 * depth[c]; } private: int n, LOG; vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; g; vector\u0026lt;int\u0026gt; depth; vector\u0026lt;int\u0026gt; tin, tout; vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; up; int timer = 0; }; 例题 【模板】最近公共祖先（LCA） - 洛谷\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; using i64 = long long; using pii = pair\u0026lt;int,int\u0026gt;; using pll = pair\u0026lt;i64,i64\u0026gt;; // ---------------------------------------------------------------------------------------------- class LCA { public: LCA(int n = 0) { init(n); } void init(int n_) { n = n_; LOG = (n \u0026lt;= 1 ? 1 : 32 - __builtin_clz(n)); g.assign(n, {}); depth.assign(n, 0); tin.assign(n, 0); tout.assign(n, 0); up.assign(LOG, vector\u0026lt;int\u0026gt;(n, 0)); timer = 0; } // 无向树 void addEdge(int u, int v) { g[u].push_back(v); g[v].push_back(u); } void dfs(int u, int p) { tin[u] = ++timer; up[0][u] = (p == -1 ? u : p); for (int k = 1; k \u0026lt; LOG; ++k) { up[k][u] = up[k-1][ up[k-1][u] ]; } for (int v : g[u]) if (v != p) { depth[v] = depth[u] + 1; dfs(v, u); } tout[u] = ++timer; } // 预处理 LCA void build(int root = 0) { depth[root] = 0; dfs(root, -1); } // 查询 u 是否为 v 的祖先 inline bool is_ancestor(int u, int v) const { return tin[u] \u0026lt;= tin[v] \u0026amp;\u0026amp; tout[v] \u0026lt;= tout[u]; } int lca(int a, int b) const { if (is_ancestor(a, b)) return a; if (is_ancestor(b, a)) return b; int u = a; for (int k = LOG - 1; k \u0026gt;= 0; --k) { int nu = up[k][u]; if (!is_ancestor(nu, b)) u = nu; } return up[0][u]; } // 第 k 级祖先（k=0 返回自身；越界停在根） int kth_ancestor(int v, int k) const { for (int i = 0; i \u0026lt; LOG; ++i) if (k \u0026amp; (1 \u0026lt;\u0026lt; i)) v = up[i][v]; return v; } // 边数距离 int dist(int a, int b) const { int c = lca(a, b); return depth[a] + depth[b] - 2 * depth[c]; } private: int n, LOG; vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; g; vector\u0026lt;int\u0026gt; depth; vector\u0026lt;int\u0026gt; tin, tout; vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; up; int timer = 0; }; // ---------------------------------------------------------------------------------------------- void solve() { int n, m, s; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m \u0026gt;\u0026gt; s; LCA lca(n); for(int i = 0; i \u0026lt; n - 1; ++i) { int u, v; cin \u0026gt;\u0026gt; u \u0026gt;\u0026gt; v; u--, v--; lca.addEdge(u, v); } lca.build(s - 1); while(m--) { int u, v; cin \u0026gt;\u0026gt; u \u0026gt;\u0026gt; v; u--, v--; cout \u0026lt;\u0026lt; lca.lca(u, v) + 1 \u0026lt;\u0026lt; \u0026#34;\\n\u0026#34;; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int T = 1; //cin \u0026gt;\u0026gt; T; while(T--) { solve(); } return 0; } pbds平衡树 模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include\u0026lt;bits/stdc++.h\u0026gt; #include\u0026lt;ext/pb_ds/assoc_container.hpp\u0026gt; using namespace std; using namespace __gnu_pbds; using i64 = long long; using pll = pair\u0026lt;i64,i64\u0026gt;; using ordered_set = tree\u0026lt;pll,null_type,less\u0026lt;pll\u0026gt;,rb_tree_tag,tree_order_statistics_node_update\u0026gt;; //注意，ost里面存的是pair\u0026lt;i64,i64\u0026gt; void solve() { ordered_set ost; //ost.find_by_order(k); 返回下标为k的对象的指针 //ost.order_of_key(val); 返回小于等于val的数的个数， val可以不存在 判断逻辑:小于val.first或者等于val.first且x.second小于val.second,如果first和second都相同则不计入。 //ost.lower_bound(); //ost.upper_bound(); //ost.insert(val); //ost.erase(val); //ost.erase(ost.lower_bound(val)) } 滚动哈希 模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include \u0026lt;bits/stdc++.h\u0026gt; using namespace std; struct RH64 { using ull = unsigned long long; static constexpr ull FIXED_RANDOM = 0x9e3779b97f4a7c15ULL; ull B; // base vector\u0026lt;ull\u0026gt; p; // B^i vector\u0026lt;ull\u0026gt; h; // 前缀哈希：h[i] = s[0..i-1] 的哈希，h[0]=0 RH64(const string\u0026amp; s, ull base = 1315423911ULL /* 随机奇数更好 */) { // 基数最好随机化（运行时随机也可），避免对抗。 B = base ^ (chrono::steady_clock::now().time_since_epoch().count() + FIXED_RANDOM); int n = (int)s.size(); p.resize(n + 1); h.resize(n + 1); p[0] = 1; h[0] = 0; for (int i = 0; i \u0026lt; n; ++i) { p[i + 1] = p[i] * B; h[i + 1] = h[i] * B + (unsigned char)s[i] + 1; // +1 避免前导零 } } // 取得子串 s[l..r] 的哈希（闭区间，0-indexed） ull get(int l, int r) const { // 返回：h[r+1] - h[l] * B^(r-l+1) return h[r + 1] - h[l] * p[r - l + 1]; } }; ","date":"2025-10-29T00:00:00Z","image":"https://leonincs.github.io/p/%E5%B8%B8%E7%94%A8c-%E7%AE%97%E6%B3%95%E6%A8%A1%E6%9D%BF%E4%B8%8E%E4%BE%8B%E9%A2%98%E5%81%8F%E8%87%AA%E7%94%A8/face_hu_adc78b0cf862be55.png","permalink":"https://leonincs.github.io/p/%E5%B8%B8%E7%94%A8c-%E7%AE%97%E6%B3%95%E6%A8%A1%E6%9D%BF%E4%B8%8E%E4%BE%8B%E9%A2%98%E5%81%8F%E8%87%AA%E7%94%A8/","title":"常用C++算法模板与例题(偏自用)"},{"content":"Go微服务网关开发（3）：负载均衡功能的实现 本章在Day1路由转发的基础上，实现了负载均衡功能，支持轮询算法分发请求到多个后端服务\n完整项目地址(已开发80%的功能)\n30天开发地址(将项目分为30天开发，附详细技术文档与代码解析)\n欢迎star✨\n什么是负载均衡功能 负载均衡功能是指一个服务有多个实例，当有请求过来时，网关会根据负载均衡算法（如轮询）将请求分发到不同的实例上，以实现负载均衡和高可用性。\n负载均衡功能示意图\n与路由转发的区别：路由转发是将请求匹配到相应的服务，而负载均衡则是将请求分发到同一服务的不同实例上。（可避免一个服务实例过载或者故障导致整个服务不可用）\n与路由转发的区别：路由转发是将请求匹配到相应的服务，而负载均衡则是将请求分发到同一服务的不同实例上。（可避免一个服务实例过载或者故障导致整个服务不可用）\n实现第一步：理解Day2的架构变化 Day2在Day1的基础上新增了以下模块\n负载均衡器接口和实现\n后端服务管理模块\n多目标路由配置支持\n之所以负载均衡器采用接口的方式而非直接实现，是为了解耦和可扩展性**，既未来可以方便地替换其他负载均衡算法。**\nDay2的文件结构变化\n在internal/core添加了loadbalancer\nbackend.go：定义后端服务管理模块\nloadbalancer.go：定义负载均衡器接口和轮询实现\nround_robin.go：实现轮询负载均衡算法\n实现第二步：实现负载均衡功能 1.负载均衡器主要功能就是选择一个后端服务实例来处理请求，因此首先定义后端服务管理模块（backend.go），用于存储和管理后端服务实例。\n定义结构体，包括后端服务实例的ID、URL、状态（是否存活）和互斥锁（用于处理并发）。\n1 2 3 4 5 6 7 8 9 10 11 12 type Backend struct { ID string URL *url.URL mu sync.Mutex Alive bool } 定义两个方法，一个用于设置后端服务实例的存活状态，另一个用于获取后端服务实例的存活状态。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func (b *Backend) SetAlive(alive bool) { b.mu.Lock() defer b.mu.Unlock() b.Alive = alive } func (b *Backend) IsAlive() bool { b.mu.Lock() defer b.mu.Unlock() return b.Alive } 2.定义负载均衡器接口（loadbalancer.go），包括添加后端服务实例、删除后端服务实例、选择后端服务实例等方法。\n1 2 3 4 5 6 7 8 9 10 11 12 type LoadBalancer interface { GetNextTarget() *Backend // 获取下一个目标后端服务实例 AddBackend(backend *Backend) // 添加后端服务实例 RemoveBackend(backend *Backend) // 删除后端服务实例 GetBackends() []*Backend // 获取所有后端服务实例 } 3.开始实现轮询负载均衡算法（round_robin.go），即按顺序选择下一个活着的后端服务实例。\n定义结构体，主要包括后端服务实例列表、当前选择的后端索引（打上标记便于选择下一个）和互斥锁（用于处理并发）。\n1 2 3 4 5 6 7 8 9 10 type RoundRobinLoadBalancer struct { backends []*Backend current int mutex sync.Mutex } 开始实现具体的方法\n实现构造函数，用于创建一个新的轮询负载均衡器实例。\n实现获取下一个目标后端服务实例的方法，按顺序选择下一个活着的后端服务实例。\n实现添加后端服务实例的方法，将新的后端服务实例添加到列表中。\n实现删除后端服务实例的方法，将指定的后端服务实例从列表中移除。\n实现获取所有后端服务实例的方法，返回当前所有后端服务实例的列表。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 func NewRoundRobinLoadBalancer() *RoundRobinLoadBalancer { return \u0026amp;RoundRobinLoadBalancer{ backends: make([]*Backend, 0), current: -1, } } // 跳转到下一个可用的后端 func (r *RoundRobinLoadBalancer) GetNextTarget() *Backend { r.mutex.Lock() defer r.mutex.Unlock() if len(r.backends) == 0 { return nil } r.current = (r.current + 1) % len(r.backends) for !r.backends[r.current].IsAlive() { r.current = (r.current + 1) % len(r.backends) } return r.backends[r.current] } // 添加后端服务实例 func (r *RoundRobinLoadBalancer) AddBackend(backend *Backend) { r.mutex.Lock() defer r.mutex.Unlock() r.backends = append(r.backends, backend) } // 删除后端服务实例 func (r *RoundRobinLoadBalancer) RemoveBackend(backend *Backend) { r.mutex.Lock() defer r.mutex.Unlock() for i, b := range r.backends { if b.ID == backend.ID { r.backends = append(r.backends[:i], r.backends[i+1:]...) return } } } // 获取所有后端服务实例 func (r *RoundRobinLoadBalancer) GetBackends() []*Backend { r.mutex.Lock() defer r.mutex.Unlock() return r.backends } 实现第三步，修改其他部分使整个项目可以运行 修改路由模块（route.go），添加负载均衡器字段，用于选择后端服务实例。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 routes: # 路由service-a - id: service-a path: /service-a targets: - http://localhost:8081 - http://localhost:8082 # 路由service-b - id: service-b path: /service-b targets: - http://localhost:8083 - http://localhost:8084 对比Day1,可以看到仅仅是每个服务从一个target变成了多个target，这是为了支持负载均衡功能。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 routes: # 路由service-a - id: service-a path: /service-a target: http://localhost:8081 # 路由service-b - id: service-b path: /service-b target: http://localhost:8082 修改config.go,使其正确映射（修改Route结构体即可） 1 2 3 4 5 6 7 8 9 10 type Route struct { ID string `yaml:\u0026#34;id\u0026#34;` Path string `yaml:\u0026#34;path\u0026#34;` Targets []string `yaml:\u0026#34;targets\u0026#34;` } 3.修改proxy.go,添加负载均衡器字段，用于选择后端服务实例。\n首先在结构体里面添加一个字段，用于存储每个服务的负载均衡器实例。\n1 2 3 4 5 6 type Proxy struct { loadbalancers map[string]loadbalancer.LoadBalancer } 实现构造函数，用于创建一个新的代理实例（初始化负载混衡器并将所有后端服务实例添加到负载混衡器中）。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 func NewProxy(backends []*config.Route) *Proxy { loadbalancers := make(map[string]loadbalancer.LoadBalancer) for _, backend := range backends { lb := loadbalancer.NewRoundRobinLoadBalancer() for _, target := range backend.Targets { backendURL, _ := url.Parse(target) lb.AddBackend(\u0026amp;loadbalancer.Backend{ ID: backend.ID, URL: backendURL, Alive: true, }) } loadbalancers[backend.ID] = lb } return \u0026amp;Proxy{ loadbalancers: loadbalancers, } } 修改ServeHTTP方法，使用负载均衡器选择后端服务实例。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 //修改点 backend := p.loadbalancers[route.ID].GetNextTarget() if backend == nil { http.Error(w, \u0026#34;无可用后端服务\u0026#34;, http.StatusServiceUnavailable) return } // 创建反向代理 proxy := httputil.NewSingleHostReverseProxy(backend.URL) //后面一样 此时应该已经可以正确运行，若有报错大家自行修改，应该只是些类似于传参的低级错误，因为文章不便讲解每个修改的部分，但已将重要的修改点都标注出来了，其中一些低级的错误大家可以自己修改，若实在有问题也可以参考完整项目（已完成测试）或者联系作者。\n最后一步 测试网关 首先启动8081,8082,8083,8084端口的服务端，然后启动网关（8080），最后使用curl或postman测试网关是否正常工作。(注意：大家先勿过度测试，确保负载均衡功能可以正常运行即可，还有很多问题接下来会一一解决。)\n启动8081,8082,8083,8084端口的服务端的命令\n1 2 3 4 5 6 7 8 python3 -m http.server 8081 python3 -m http.server 8082 python3 -m http.server 8083 python3 -m http.server 8084 启动网关开始测试\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # 启动网关 cd /home/leon/GoCode/30daysGateway/day2 go run cmd/main.go # 在另一个终端测试路由转发 curl http://localhost:8080/service-a curl http://localhost:8080/service-a curl http://localhost:8080/service-a curl http://localhost:8080/service-b curl http://localhost:8080/service-b curl http://localhost:8080/service-b 可见第一次访问http://localhost:8080/service-a，返回了8081端口的响应，第二次访问返回了8082端口的响应，第三次访问返回了8081端口的响应，说明负载均衡功能正常工作。service-b也同理。\n本章结束。\n","date":"2025-10-14T00:00:00Z","image":"https://leonincs.github.io/p/go%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%BD%91%E5%85%B3%E5%BC%80%E5%8F%913%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E5%8A%9F%E8%83%BD%E7%9A%84%E5%AE%9E%E7%8E%B0/face_hu_782ac07a16f74397.png","permalink":"https://leonincs.github.io/p/go%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%BD%91%E5%85%B3%E5%BC%80%E5%8F%913%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E5%8A%9F%E8%83%BD%E7%9A%84%E5%AE%9E%E7%8E%B0/","title":"Go微服务网关开发（3）：负载均衡功能的实现"},{"content":"Go微服务网关开发（2）：路由转发功能的实现 本章实现了API网关的路由转发功能\n完整项目地址(已开发80%的功能)\n30天开发地址(将项目分为30天开发，附详细技术文档与代码解析)\n欢迎star✨\n什么是路由转发功能 路由转发功能是指API网关将客户端请求转发到后端服务的功能。\n直观理解就是访问API网关端口（8080），会将请求转发到后端服务端口（8081、8082）等。\n路由转发功能示例图\n实现第一步：搭好文件结构 首先，我们需要知道网关分为以下模块\n配置文件\n解析配置文件模块\n网关核心模块\n反向代理模块\n路由模块\n服务器模块\n其次，我们需要了解Go的常见架构\n├── cmd\n├── internal\n└── pkg\ncmd：负责网关的入口\ninternal：负责网关的内部模块，不暴露给外部使用\npkg：负责暴露给外部使用的模块\n最后，我们结合上面的模块与Go的常见架构，搭好文件结构\n├── cmd\n│ └── main.go //网关的入口\n├── configs\n│ └── config.yaml //网关的配置文件\n├── internal\n│ ├── config\n│ │ └── config.go //解析配置文件模块\n│ └── core //核心模块\n│ ├── gateway.go //网关核心模块\n│ ├── proxy.go //反向代理模块\n│ ├── route.go //路由模块\n│ └── server.go //服务器模块\n├── go.mod\n├── go.sum\n└── 文档\n实现第二步：理解程序运行过程 首先，先加载配置文件（config.yaml）并保存（这里命名为cfg）\n根据cfg配置网关核心模块（这里命名为gw）\n根据cfg和gw配置服务器（这里命名为srv）\n启动服务器\n运行服务\n优雅关闭服务器\n流程图\n实现第三步：开始用代码实现 首先，我们需要写好配置文件（config.yaml） 思路为声明客户端端口并声明路由转发规则并配置要转发的路由路径与目标服务端口（本项目采用yaml文件配置，如果第一次接触可以花十分钟先去了解一下）\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 # 配置文件 server: port: 8080 # 网关客户端的端口 # 路由配置 routes: # 路由service-a - id: service-a # 路由service-a的ID path: /service-a # 路由service-a的路径 target: http://localhost:8081 # 路由service-a的目标服务端口 # 路由service-b - id: service-b path: /service-b target: http://localhost:8082 # 通过上述配置，我们可以实现以下路由转发功能 # 当访问http://localhost:8080/service-a时，会将请求转发到http://localhost:8081/service-a # 当访问http://localhost:8080/service-b时，会将请求转发到http://localhost:8082/service-b 其次，我们需要写解析配置文件模块（config.go）来解析配置文件（config.yaml） 思路为定义结构体并利用yaml标签映射配置文件中的字段，然后写LoadConfig函数根据传入的配置文件地址参数来解析配置文件（解析方法采用yaml.v3库） 定义结构体并利用yaml标签映射配置文件中的字段\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 type Config struct { Server Server `yaml:\u0026#34;server\u0026#34;` Routes []*Route `yaml:\u0026#34;routes\u0026#34;` } type Server struct { Port string `yaml:\u0026#34;port\u0026#34;` } type Route struct { ID string `yaml:\u0026#34;id\u0026#34;` Path string `yaml:\u0026#34;path\u0026#34;` Target string `yaml:\u0026#34;target\u0026#34;` } LoadConfig函数\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func LoadConfig(config string) (*Config, error) { var cfg Config data, err := os.ReadFile(config) if err != nil { return nil, err } if err := yaml.Unmarshal(data, \u0026amp;cfg); err != nil { return nil, err } return \u0026amp;cfg, nil } 3.然后，我们需要写网关核心模块（gateway.go）、反向代理模块（proxy.go）和路由模块（route.go）\n我们需要知道反向代理模块和路由模块的都是网关的核心功能，但为了代码的可维护性，我们将它们分别写在不同的文件中，通过依赖注入的方式将它们注入到网关核心模块中。\n先写route.go\n只需要定义Route结构体，其中routes为所有所要转发的路由，并写一个NewRoute函数用于创建结构体，再写一个FindRoute用于查找匹配路由（可采用字典树查询方式，本项目先采用遍历的方式。）\n定义结构体并写出NewRoute函数\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type Router struct { routes []*config.Route } func NewRouter(routes []*config.Route) *Router { return \u0026amp;Router{ routes: routes, } } FindRoute函数\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func (r *Router) FindRoute(path string) *config.Route { // 遍历查找匹配的路由 for _, route := range r.routes { // 检查路径是否匹配 if strings.HasPrefix(path, route.Path) { return route } } return nil } 再写proxy.go\n定义结构体并写出NewProxy函数(目前未额外添加其他功能，只是将结构分离便于拓展)，并实现ServeHTTP方法使其成为一个Handler。\n定义结构体并写出NewProxy函数\n1 2 3 4 5 6 7 8 9 10 11 12 type Proxy struct { } func NewProxy() *Proxy { return \u0026amp;Proxy{} } 实现反向代理的核心功能 - ServeHTTP方法（核心） ServeHTTP是Go语言中http.Handler接口的核心方法，它是HTTP请求处理的入口点，实现了ServeHTTP方法就相当于把对象变成了Handler。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request, route *config.Route) { // 解析目标服务URL targetURL, err := url.Parse(route.Target) if err != nil { http.Error(w, \u0026#34;无效的目标服务地址\u0026#34;, http.StatusInternalServerError) return } // 创建反向代理 proxy := httputil.NewSingleHostReverseProxy(targetURL) // 保存原始路径用于日志记录 originalPath := r.URL.Path // 设置反向代理的Director proxy.Director = func(req *http.Request) { // 设置目标服务的协议和主机 req.URL.Scheme = targetURL.Scheme req.URL.Host = targetURL.Host // 处理路径映射 req.URL.Path = targetURL.Path + strings.TrimPrefix(req.URL.Path, route.Path) } // 记录转发日志 log.Printf(\u0026#34;转发请求: %s %s -\u0026gt; %s://%s%s\u0026#34;, r.Method, originalPath, targetURL.Scheme, targetURL.Host, r.URL.Path) // 转发请求 proxy.ServeHTTP(w, r) } 最后，我们需要将反向代理模块和路由模块注入到网关核心模块中。\n定义Gateway结构体并注入反向代理模块和路由模块，并写出NewGateway函数，同时实现ServeHTTP方法使其成为一个Handler。\n定义结构体并注入反向代理模块和路由模块\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 type Gateway struct { config *config.Config router *Router proxy *Proxy } func NewGateway(config *config.Config) *Gateway { return \u0026amp;Gateway{ config: config, router: NewRouter(config.Routes), proxy: NewProxy(), } } 实现网关核心功能 - ServeHTTP方法（核心）\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func (g *Gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) { // 查找匹配的路由 route := g.router.FindRoute(r.URL.Path) if route == nil { http.Error(w, \u0026#34;路由未找到\u0026#34;, http.StatusNotFound) return } // 转发请求 g.proxy.ServeHTTP(w, r, route) } 4.上述实现完成后我们开始写服务器\n写服务器的思路很直接，就是定义一个Server结构体（其中封装了一个http.Server）并给出NewServer函数用于创建结构体，同时定义一个Start方法用于启动服务器。\n定义结构体并写出NewServer函数\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 type Server struct { httpServer *http.Server } func NewServer(addr string, handler http.Handler) (*Server, error) { srv := \u0026amp;http.Server{ Addr: addr, Handler: handler, } return \u0026amp;Server{ httpServer: srv, }, nil } 实现启动服务器的Start方法（重点在于优雅关闭）\n服务器启动时，我们需要在一个goroutine中启动服务器以便能监听中断信号，同时在主goroutine中等待中断信号，收到信号后我们需要优雅关闭服务器（优雅关闭确保服务器立即停止接收新连接请求，但会等待所有已建立的连接和正在处理的请求正常完成，避免用户请求被强制中断和数据丢失，同时设置30秒超时保护机制，超时后强制关闭以保证系统资源能够及时释放）。\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 func (s *Server) Start() error { // 在goroutine中启动服务器以便能监听中断信号 go func() { if err := s.httpServer.ListenAndServe(); err != nil \u0026amp;\u0026amp; err != http.ErrServerClosed { log.Fatalf(\u0026#34;服务器启动失败: %v\u0026#34;, err) } }() log.Printf(\u0026#34;服务器正在监听地址 %s\u0026#34;, s.httpServer.Addr) // 等待中断信号 quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) \u0026lt;-quit log.Println(\u0026#34;正在关闭服务器...\u0026#34;) // 优雅关闭 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := s.httpServer.Shutdown(ctx); err != nil { log.Fatal(\u0026#34;服务器关闭错误:\u0026#34;, err) } log.Println(\u0026#34;服务器已关闭\u0026#34;) return nil } 5.最后，我们在main函数中创建网关、路由模块、反向代理模块、服务器模块，并启动服务器即可。\n顺序为：加载配置 -\u0026gt; 创建网关 -\u0026gt; 创建服务器 -\u0026gt; 启动服务器\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package main import ( \u0026#34;gateway/internal/config\u0026#34; \u0026#34;gateway/internal/core\u0026#34; \u0026#34;log\u0026#34; ) func main() { // 加载配置 log.Println(\u0026#34;开始加载配置\u0026#34;) cfg, err := config.LoadConfig(\u0026#34;configs/config.yaml\u0026#34;) if err != nil { log.Fatal(\u0026#34;加载配置失败:\u0026#34;, err) } log.Println(\u0026#34;加载配置成功\u0026#34;) // 创建网关 log.Println(\u0026#34;开始创建网关\u0026#34;) gw := core.NewGateway(cfg) log.Println(\u0026#34;网关创建成功\u0026#34;) // 创建服务器 log.Println(\u0026#34;开始创建服务器\u0026#34;) // 修复：确保端口地址格式正确 addr := \u0026#34;:\u0026#34; + cfg.Server.Port srv, err := core.NewServer(addr, gw) if err != nil { log.Fatal(\u0026#34;创建服务器失败:\u0026#34;, err) } log.Println(\u0026#34;服务器创建成功\u0026#34;) // 启动服务器 log.Printf(\u0026#34;服务器正在监听端口 %s\u0026#34;, cfg.Server.Port) if err := srv.Start(); err != nil { log.Fatal(\u0026#34;服务器启动失败:\u0026#34;, err) } log.Println(\u0026#34;服务器已启动\u0026#34;) } 最后一步 测试网关 首先启动8081和8082端口的服务端，然后启动网关（8080），最后使用curl或postman测试网关是否正常工作。\n启动8081和8082端口的命令\n1 2 3 4 python3 -m http.server 8081 python3 -m http.server 8082 启动网关开始测试\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 启动网关 cd /home/leon/GoCode/30daysGateway/day1 go run cmd/main.go # 在另一个终端测试路由转发 curl http://localhost:8080/service-a curl http://localhost:8080/service-b 可见输入访问http://localhost:8080/service-a，返回了8081端口的响应，访问http://localhost:8080/service-b也返回了8082端口的响应，说明网关正常工作。\n本章结束。\n","date":"2025-10-10T00:00:00Z","image":"https://leonincs.github.io/p/go%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%BD%91%E5%85%B32%E8%B7%AF%E7%94%B1%E8%BD%AC%E5%8F%91%E5%8A%9F%E8%83%BD%E7%9A%84%E5%AE%9E%E7%8E%B0/face_hu_782ac07a16f74397.png","permalink":"https://leonincs.github.io/p/go%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%BD%91%E5%85%B32%E8%B7%AF%E7%94%B1%E8%BD%AC%E5%8F%91%E5%8A%9F%E8%83%BD%E7%9A%84%E5%AE%9E%E7%8E%B0/","title":"Go微服务网关（2）：路由转发功能的实现"},{"content":"Go微服务网关开发（1） 本章内容主要介绍了微服务网关是什么，以及微服务网关的核心功能。\n完整项目地址(已开发80%的功能)\n30天开发地址(将项目分为30天开发，附详细技术文档与代码解析)\n欢迎star✨\n什么是微服务？它有什么优势？ 微服务架构介绍 官方表述：\u0026ldquo;微服务是一种架构风格，将复杂应用程序分解为一组小的、松耦合的服务，每个服务专注于单一业务功能，围绕业务能力构建，可独立部署扩展。\u0026rdquo;\n通俗易懂解释：小诊所是一个整体，属于单体架构，而大型医院则将不同功能的服务分到不同科室，属于微服务架构。\n单体架构图\n\n微服务架构图\n\n微服务的优势 官方表述：微服务适合高并发、复杂业务、多团队协作的场景，通过解耦带来极致的灵活性和扩展性，但需配套完善的DevOps体系支撑其运维复杂度。\n通俗易懂解释：小诊所医生请假诊所关门便暂停服务，而医院的一个医生请假医院依旧正常运转，且小诊所医生需要掌握各种常发病的治疗方法，而医院的各个医生只需要负责对于的病症，效率更高，服务更好。\n什么是微服务网关？为什么要有微服务网关？ 微服务网关介绍 官方表述：微服务网关（Microservice Gateway） 是部署在微服务架构边缘的服务入口层，负责接收所有外部请求，并将其智能路由到后端对应的微服务实例。它作为服务边界的统一管控点，提供路由转发、负载均衡、安全认证、流量控制、协议转换等核心能力，是微服务架构中不可或缺的流量协调中心。\n通俗易懂的解释：把一个大型项目想象成一个一所医院，医院有许多科室，每个科室都有不同的功能（微服务），而网关就是医院的前台，若没了前台，你便很难快速地找到相应的科室，且你每到一个科室都要登记，还有很大可能该科室已经有挂满号，让你空跑一趟，效率极低，而前台（网关）的出现便帮我们解决了这些痛点，护士会根据你的症状为你指引正确的科室（路由转发 ），在前台登记后你便不用再重复登记（安全认证），在挂号时尽可能将病人分配到同一功能的不同科室以防止某个科室人数过多（负载均衡），若所有科室号都已挂满便会不再挂该科室的号（流量控制），同时前台可以监控各科室的情况（日志/监控），同时前台也在负责协调各个科室之间的通信（协议转换）。\n微服务网关必要性 必要性：若无微服务网关，则整个微服务项目因耦合度过低，各个模块相互独立，客户端请求一个服务需要访问多个模块，且各个模块功能交互较为混乱，微服务的优势变成了劣势。\n微服务网关的核心功能 这里我们先提供一个架构图\n\n1.统一入口（API聚合） 作用：所有客户端请求通过网关统一访问，避免直接暴露微服务细节。 场景：客户端（如前端/移动端）只需与网关交互，无需知道内部服务地址（如 /api/users → 路由到用户服务）。 实现：通过路由规则将请求分发到对应微服务。 通俗易懂理解：去医院挂号不用去各个科室挂号，前台挂号即可。\n2.路由转发 作用：根据请求规则将流量精准分发到后端微服务。 核心能力： 路径匹配：如 /orders/* → 订单服务。 权重路由：按比例分配流量（如80%新版本，20%旧版本）。 动态路由：基于服务注册中心动态更新路由表。 通俗易懂理解：去医院看病，前台会指引你去正确的科室。\n3. 安全认证 认证（Authentication）：验证请求合法性（如 JWT、OAuth2.0、API Key）。 授权（Authorization）：检查用户权限（如 VIP用户才能访问特定接口）。 限流（Rate Limiting）：防止恶意请求或流量激增（如每秒100次/用户）。 HTTPS/TLS终止：解密请求流量，减轻微服务安全负担。 通俗易懂理解：看病无需去各个科室登记身份，前台登记即可\n4. 流量控制 负载均衡：在多个服务实例间均匀分配请求（轮询/最少活跃/哈希）。 熔断（Circuit Breaker）：服务故障时快速失败，避免级联崩溃（如 Hystrix/Sentinel）。 超时控制：设置请求超时，防止资源阻塞。 重试机制：对瞬态错误自动重试（如 502/503 错误）。 通俗易懂理解：若科室号已挂满，前台便停止该科室挂号服务直至该科室有空闲。\n5. 协议转换 适配不同协议：将 HTTP/HTTPS 转为 WebSocket/gRPC 或其他协议。 请求/响应处理： 映射请求路径（如 /api/v1/ → 简化为 /）。 数据格式转换（如 XML → JSON）。 请求头修改（如添加 tracing ID）。 通俗易懂理解：不同科室之间通过前台通信，前台负责将A科室的请求转换后发送到B科室。\n6. 日志监控 日志记录：统一记录请求/响应日志（含时间、路径、状态码）。 监控指标：收集延迟、QPS、错误率等数据（ Prometheus 格式）。 链路追踪：注入 trace ID（如 Zipkin/Jaeger）。 通俗易懂理解：前台可以监视各个科室发生的事件并记录。\n微服务架构未来发展 目前市面上各个大厂（如阿里、字节、腾讯等）都在使用微服务架构，即使是一些老项目也在用微服务重构。\n企业 核心场景 微服务价值体现 阿里云 双11大促（百万级TPS） 弹性扩容（秒级扩容1000+服务器）、故障隔离（订单宕机不影响商品浏览） 字节跳动 全球化短视频（抖音/TikTok） 跨地域部署（不同区域独立服务实例）、多语言能力（Go/Java/Rust按需求选择） 腾讯 微信生态（10亿用户+） 服务自治（支付/消息/登录独立容灾）、灰度发布（A/B测试新功能） 美团 O2O全链路（交易到配送） 复杂业务解耦（骑手调度/优惠券/商户服务独立迭代） 同时各个大厂也自研了微服务网关\n企业 网关方案 技术栈 应用场景 阿里云 MSE微服务引擎 自研+Envoy+Dubbo 企业级多云混合云 字节跳动 自研高性能网关 Go+Rust+DPDK 全球化高并发 腾讯 API网关（TSF集成） Spring Cloud+Envoy 金融级高可用 百度 智能网关Baidu Cloud Mesh（BCM） Istio+Kubernetes AI+大数据服务网关 微服务网关与云结合\n\n未来已来：当网关能力完全云服务化后，企业将不再关心“网关在哪里”，只需定义“我要什么功能”——这正是云计算终极目标的体现。建议企业优先采用云厂商全托管网关服务，将精力聚焦在业务创新而非基础设施运维。\n综上可见，未来微服务将是软件行业发展趋势，微服务网关与云的结合将会产生新一轮的变革。\n","date":"2025-10-01T00:00:00Z","image":"https://leonincs.github.io/p/go%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%BD%91%E5%85%B31-%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5%E4%BB%8B%E7%BB%8D/face_hu_782ac07a16f74397.png","permalink":"https://leonincs.github.io/p/go%E5%BE%AE%E6%9C%8D%E5%8A%A1%E7%BD%91%E5%85%B31-%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5%E4%BB%8B%E7%BB%8D/","title":"Go微服务网关(1): 基本概念介绍"},{"content":"Hugo + GitHub快速搭建个人博客 视频教程\n引言 一直想拥有一个属于自己的个人博客，分享技术心得或生活点滴？也许你曾被 WordPress 的笨重、服务器的费用或是某些平台有限的自定义功能劝退。\n如果我告诉你，有一种方法可以让你免费、极速地搭建一个完全由你掌控的现代化博客，你是否会心动？\n本文将带你走进 Hugo + GitHub Pages 的世界。我们将利用 Hugo 的闪电般的速度和 GitHub 的免费托管服务，从零开始，一步步打造一个优雅、高效且无需任何花费的个人网站。准备好，让我们开始构建吧！\nHugo：Go语言编写的超高速静态网站生成器。\nGit：一个免费、开源的分布式版本控制系统。\nGithub：全球最大的基于Git的代码托管与协作平台。\n环境准备 安装Hugo Hugo安装\n安装Git Git安装\n注册一个GitHub账号 GitHub注册\n本文章仅提供在windows上安装教程\n安装 Git\n访问 git-scm.com/downloads 下载 Windows 版的 Git。 运行安装程序，一路点击 \u0026ldquo;Next\u0026rdquo; 使用默认选项完成安装即可。 安装 Hugo Extended\n点击“开始”菜单，输入 PowerShell，选择 “Windows PowerShell” (不要选“以管理员身份运行”)。\n在打开的 PowerShell 窗口中，依次执行以下三行命令（复制一行，按 Enter，再复制下一行\n1 2 3 4 5 6 7 # 1. 允许当前用户执行远程脚本 (只需执行一次) Set-ExecutionPolicy RemoteSigned -Scope CurrentUser # (如果询问，输入 Y 并回车) # 2. 安装 Scoop 包管理器 irm get.scoop.sh | iex # 3. 使用 Scoop 安装 Hugo Extended 版本 (功能最全) scoop install hugo-extended 打开终端验证安装是否成功\n按下win+R，输入cmd，回车进入终端，输入以下命令\n1 2 git --version hugo version 如果能看到版本号，说明安装成功。\n创建本地博客项目 打开终端，cd 到你希望存放博客项目的文件夹。\n运行以下命令来创建一个新的 Hugo 站点。我们将博客项目命名为 my-blog。\n1 hugo new site my-blog 进入刚刚创建的目录并初始化Git仓库 1 2 cd my-blog git init 添加主题 选择主题 Hugo官方主题库\n由于各个主题文件目录不同，配置方法也有所差异，但大体相同，可借助AI帮助配置，本文仅展示stack主题的配置\n选择一个点击进去，点击demo即可看到主题的效果,点击download即可跳转到github仓库\n点击tag\n下载最新版本\n下载源码\n解压缩到themes文件夹，并将名字中的版本号去掉，效果如下\n配置主题 在 my-blog 目录下，执行以下命令（仅针对本项目，功能是提取作者的演示案例作为初始项目，并清除作者的演示案例）\n1 2 3 4 5 6 7 8 9 10 11 :: 强制删除当前目录的 content 文件夹（若存在） rd /s /q content 2\u0026gt;nul :: 将主题示例的 content 复制到当前目录 xcopy \u0026#34;themes\\hugo-theme-stack\\exampleSite\\content\u0026#34; \u0026#34;content\u0026#34; /E /I /H /Y :: 删除 exampleSite 文件夹 rd /s /q \u0026#34;themes\\hugo-theme-stack\\exampleSite\u0026#34; 2\u0026gt;nulontinue :: 删除 rich-content 文件夹 rd /s /q \u0026#34;content/post/rich-content\u0026#34; 删除hugo.toml，并创建hugo.yaml (进入相应文件夹删除创建即可)\n将下面模板代码完整复制到config.yaml文件中，根据注释修改你的信息\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 # 站点基础配置 baseurl: https://example.com/ # 网站根URL languageCode: en-us # 默认语言代码 theme: hugo-theme-stack # 使用的主题名称 title: Example Site # 网站标题 copyright: Example Person # 版权信息 # 国际化配置 # 支持的语言: ar, bn, ca, de, el, en, es, fr, hu, id, it, ja, ko, nl, pt-br, th, uk, zh-cn, zh-hk, zh-tw DefaultContentLanguage: en # 默认内容语言 # 如果默认语言是中文/日文/韩文，需设置为true # 这将使.Summary和.WordCount对CJK语言正常工作 hasCJKLanguage: false # 多语言详细配置 languages: en: languageName: English # 语言显示名称 title: Example Site # 该语言下的网站标题 weight: 1 # 权重（排序用） params: sidebar: subtitle: Example description # 侧边栏副标题 zh-cn: languageName: 中文 title: 演示站点 weight: 2 params: sidebar: subtitle: 演示说明 ar: languageName: عربي languagedirection: rtl # 文本方向（阿拉伯语从右到左） title: موقع تجريبي weight: 3 params: sidebar: subtitle: وصف تجريبي # 第三方服务配置 services: # Disqus评论系统（使用前需修改为你的shortname） disqus: shortname: \u0026#34;hugo-theme-stack\u0026#34; # Google Analytics跟踪ID googleAnalytics: id: # 填入你的GA跟踪ID # 分页设置 pagination: pagerSize: 3 # 分页器显示页码数量 # 永久链接格式 permalinks: post: /p/:slug/ # 文章URL格式 page: /:slug/ # 页面URL格式 # 主题参数配置 params: mainSections: - post # 主要内容区域 featuredImageField: image # 特色图片字段名 rssFullContent: true # RSS是否包含全文 favicon: # 网站图标路径，如：/favicon.ico # 页脚设置 footer: since: 2020 # 起始年份 customText: # 自定义文本 # 日期格式 dateFormat: published: Jan 02, 2006 # 发布日期格式 lastUpdated: Jan 02, 2006 15:04 MST # 最后更新日期格式 # 侧边栏设置 sidebar: emoji: 🍥 # 表情图标 subtitle: Lorem ipsum dolor sit amet, consectetur adipiscing elit. # 副标题 avatar: enabled: true # 是否显示头像 local: true # 是否使用本地图片 src: img/avatar.png # 头像路径 # 文章设置 article: math: false # 是否支持数学公式 toc: true # 是否显示目录 readingTime: true # 是否显示阅读时间 license: enabled: true # 是否显示版权信息 default: Licensed under CC BY-NC-SA 4.0 # 默认许可证 # 评论系统配置 comments: enabled: true # 启用评论 provider: disqus # 评论提供商 # 各评论系统的具体配置 disqusjs: {...} # DisqusJS配置 utterances: {...} # Utterances配置（GitHub-based） beaudar: {...} # Beaudar配置 remark42: {...} # Remark42配置 vssue: {...} # Vssue配置 waline: {...} # Waline配置 twikoo: {...} # Twikoo配置 cactus: {...} # Cactus Chat配置 giscus: {...} # Giscus配置（GitHub Discussions） gitalk: {...} # Gitalk配置 cusdis: {...} # Cusdis配置 # 小工具配置 widgets: homepage: # 首页小工具 - type: search # 搜索框 - type: archives # 文章归档 params: limit: 5 # 显示数量 - type: categories # 分类 params: limit: 10 - type: tag-cloud # 标签云 params: limit: 10 page: - type: toc # 页面目录 # OpenGraph设置（社交媒体分享） opengraph: twitter: site: # Twitter用户名 card: summary_large_image # 卡片类型：summary或summary_large_image # 默认图片设置 defaultImage: opengraph: enabled: false # 是否启用 local: false # 是否使用本地图片 src: # 图片路径 # 颜色方案 colorScheme: toggle: true # 是否显示切换按钮 default: auto # 默认模式：auto, light, dark # 图片处理 imageProcessing: cover: enabled: true # 处理封面图片 content: enabled: true # 处理内容图片 # 自定义菜单配置 menu: main: [] # 主导航菜单 social: # 社交链接菜单 - identifier: github name: GitHub url: https://github.com/CaiJimmy/hugo-theme-stack params: icon: brand-github # 图标名称 # 相关文章设置 related: includeNewer: true # 是否包含较新文章 threshold: 60 # 相关度阈值 toLower: false # 是否忽略大小写 indices: - name: tags # 使用标签作为关联依据 weight: 100 # 权重 - name: categories # 使用分类作为关联依据 weight: 200 # Markdown渲染设置 markup: goldmark: extensions: passthrough: enable: true # 启用原始HTML通过 delimiters: block: # 块级分隔符 - - \\[ - \\] - - $$ - $$ inline: # 行内分隔符 - - \\( - \\) renderer: unsafe: true # 允许不安全HTML（直接渲染HTML内容） tableOfContents: endLevel: 4 # 目录结束级别 ordered: true # 是否有序 startLevel: 2 # 目录开始级别 highlight: noClasses: false # 是否使用内联样式 codeFences: true # 启用代码围栏 guessSyntax: true # 自动猜测语法 lineNoStart: 1 # 行号起始值 lineNos: true # 显示行号 lineNumbersInTable: true # 在表格中显示行号 tabWidth: 4 # 制表符宽度 重点： 务必修改 baseURL、title、avatar.src 和 social 部分的内容为你自己的信息。\n本地预览 在发布到互联网前，先在本地预览效果。\n在 my-blog 目录的 PowerShell 中执行：\n1 hugo server -D 终端会提示一个地址，通常是 http://localhost:1313/。在浏览器中打开它，你应该能看到你的博客了。\n托管代码到 GitHub 并设置自动化部署 创建并设置源代码仓库 登录 GitHub，创建一个新仓库用于存放博客的源代码。 Repository name: \u0026lt;你的用户名\u0026gt;.github.io (这是获取顶级访问域名的关键，例如用户名 john 则仓库名为 john.github.io)。 将仓库 visibility 设为 Public (私有仓库需要付费账户才能使用 Pages 功能)。 不要勾选 \u0026ldquo;Initialize this repository with a README\u0026rdquo;。 创建完成后，复制仓库的 HTTPS 地址（格式为 https://github.com/\u0026lt;你的用户名\u0026gt;/\u0026lt;你的用户名\u0026gt;.github.io.git）。 推送本地代码到仓库 在本地终端 (如 PowerShell) 中，进入你的 Hugo 站点根目录 (例如 my-blog)，执行以下命令：\n1 2 3 4 5 6 7 8 # 关联远程仓库（地址替换为刚复制的地址） git remote add origin https://github.com/\u0026lt;你的用户名\u0026gt;/\u0026lt;你的用户名\u0026gt;.github.io.git # 添加所有文件到暂存区并提交 git add . git commit -m \u0026#34;Initial commit with Hugo site and Stack theme\u0026#34; # 将本地 main 分支推送到 GitHub 并设为上游分支 git branch -M main git push -u origin main 配置自动化部署 (GitHub Actions) 设置Github Pages\n点击Settings\n点击左侧Pages\n将Buile and depolyment由Deploy from branch 改为 GitHub Actions\n在本地站点根目录下，创建目录结构：.github/workflows/，并在 workflows 目录中新建文件 deploy.yml。\n用文本编辑器打开 deploy.yml，粘贴以下工作流配置（该配置使用官方推荐 Actions，支持自动构建、发布）：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 name: Deploy Hugo Site to Pages on: push: branches: [\u0026#34;main\u0026#34;] # 代码推送至 main 分支时触发 workflow_dispatch: # 支持手动触发 permissions: contents: read pages: write id-token: write concurrency: group: \u0026#34;pages\u0026#34; cancel-in-progress: false jobs: build: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 with: submodules: recursive # 自动拉取主题子模块 fetch-depth: 0 - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#39;latest\u0026#39; # extended: true # 若主题需扩展版，取消注释 - name: Build with Hugo run: hugo --minify --gc # 生成优化后的静态文件 - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: ./public # 上传生成的站点文件 deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 保存文件后，在终端中执行以下命令提交并推送工作流配置： 1 2 3 git add .github/workflows/deploy.yml git commit -m \u0026#34;chore: Add GitHub Actions workflow for deployment\u0026#34; git push 查看部署状态和访问博客 推送完成后，在浏览器中访问你的 GitHub 仓库：https://github.com/\u0026lt;你的用户名\u0026gt;/\u0026lt;你的用户名\u0026gt;.github.io。 点击顶部 Actions 标签页，查看名为 \u0026ldquo;Deploy Hugo Site to Pages\u0026rdquo; 的工作流运行状态。等待运行完成（出现绿色对勾 ✅）。 部署成功后，进入 Settings → Pages，页面顶部会显示你的博客访问地址：https://\u0026lt;你的用户名\u0026gt;.github.io。 点击该链接即可访问已自动发布的博客。 你的日常写作流程 (Windows) 从此以后，你更新博客的流程将非常简单：\n新建文章：\n1 2 3 4 5 #两种方式 #第一种：命令创建 hugo new content post/文章名/index.md #第二种：手动添加 将写好的markdown文档添加到content/post里面 写作与预览：\n用 VS Code 编辑刚生成的 content/post/我的新文章.md 文件。 在终端运行 hugo server，然后在浏览器打开 http://localhost:1313 实时预览。 发布上线：\n写完并预览满意后，只需执行： 1 2 3 git add . git commit -m \u0026#34;Publish: 我的新文章\u0026#34; git push 推送后，GitHub Actions 会自动开始构建和部署。几分钟后，新文章就会出现在你的网站上。\n完成！ 你已经拥有了一个完全自动化、部署在 GitHub Pages 上的现代化博客系统。\n重点 GitHub仓库一定要命名为\u0026lt;用户名\u0026gt;.github.io,仓库的Buile and depolyment一定要设为GitHub Actions 若手动添加markdown文档，则需要手写front matter，如果是纯文章可以任意命名，否则必须命名为index.md，只有这样才可以包含图片 图片要和相应的文档放在同一文件夹，采用! [Alt] (文件地址)调用 文件地址要采用相对路径 若采取相同主题则可一步一步按照上述流程进行操作，若采取不同主题该文档仅供参考 ","date":"2025-09-30T00:00:00Z","image":"https://leonincs.github.io/p/hugo--github%E5%BF%AB%E9%80%9F%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/face_hu_7df7d756ea0053a9.jpg","permalink":"https://leonincs.github.io/p/hugo--github%E5%BF%AB%E9%80%9F%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/","title":"Hugo + GitHub快速搭建个人博客"}]