commit 7b271af6ead4e7bb6b913a9100112168ef7a5391 Author: wonder Date: Mon Apr 20 22:47:51 2026 +0800 Update diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..828474f --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Obsidian 不需要版本控制的文件 +.obsidian/ + +# Claude Code 相关 +.claudian/ +.claude/ + + +# 临时文件 +*.tmp +*.temp +.DS_Store +Thumbs.db + +# 可选:备份文件(根据需要调整) +*.bak + +# 附件文件夹如果很大,可以排除或使用 Git LFS +# attachments/ diff --git a/CS/DB/README.md b/CS/DB/README.md new file mode 100644 index 0000000..4bef8bf --- /dev/null +++ b/CS/DB/README.md @@ -0,0 +1,52 @@ +--- +tags: [CS, DB, database] +create time: 2026-04-17 +--- + +# 数据库 + +## 概述 + +数据库是有组织的数据集合,数据库管理系统(DBMS)是一套管理数据的软件系统,用于创建、维护和访问数据库。 + +## 核心概念 + +### 关系型数据库 +- SQL语言 +- 表结构设计 +- 索引与查询优化 +- 事务处理(ACID) +- 规范化理论 + +### NoSQL数据库 +- 文档型数据库(MongoDB) +- 键值数据库(Redis) +- 列族数据库(Cassandra) +- 图数据库(GraphDB) + +### 数据库设计 +- 实体关系模型(ER) +- 数据建模 +- 性能调优 +- 分库分表 + +### ACID特性 +- 原子性(Atomicity) +- 一致性(Consistency) +- 隔离性(Isolation) +- 持久性(Durability) + +## 学习重点 + +- SQL语言精通 +- 数据库选型与设计 +- 查询性能优化 +- 事务处理机制 +> 数据库备份与恢复 + +## 实践建议 + +- 在实际项目中应用 +- 使用不同类型数据库 +- 学习SQL调优技巧 +- 理解分布式数据库 diff --git a/CS/NET/Authorization.md b/CS/NET/Authorization.md new file mode 100644 index 0000000..1137f91 --- /dev/null +++ b/CS/NET/Authorization.md @@ -0,0 +1,553 @@ +--- +tags: [CS, NET, security, authorization, web, authentication-flow] +create time: 2026-04-17 +--- + +# Web Application Authorization Flow + +## 概述 + +**Authorization(授权)** 在 Web 应用中是 HTTP 层面的认证令牌验证机制。本文聚焦浏览器、服务器、Token 的交互流程和加密验证过程。 + +## HTTP Authorization 流程 + +### 完整授权流程 + +```mermaid +sequenceDiagram + participant B as Browser + participant S as Server + participant DB as Database + + Note over B,S: 1. 登录获取 Token + B->>S: POST /login (username, password) + S->>DB: 验证凭证 + DB-->>S: 用户数据 + S->>S: HMAC-SHA256(payload, secret) + S-->>B: {access_token: "eyJhbGc...", expires_in: 3600} + + Note over B,S: 2. 带授权头访问资源 + B->>B: 存储 Token (LocalStorage/Memory) + B->>S: GET /api/resource + Note right of B: Authorization: Bearer eyJhbGc... + + Note over S: 3. 验证 Token + S->>S: 提取 Token + S->>S: 验证签名 (HMAC) + S->>S: 检查过期时间 + S->>S: 验证 Issuer/Audience + S->>DB: 根据 user_id 查询权限 + + Note over S: 4. 授权决策 + alt Token 有效 + S-->>B: 200 OK + 数据 + else Token 无效 + S-->>B: 401 Unauthorized + else 权限不足 + S-->>B: 403 Forbidden + end +``` + +### JWT Token 验证过程 + +```mermaid +graph TD + A[接收 Authorization: Bearer ] --> B[解析三部分] + B --> C[Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9] + B --> D[Payload: eyJ1c2VyX2lkIjoiMTIzIiw...] + B --> E[Signature: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c] + + C --> F[获取签名算法 HS256] + D --> G[获取用户数据] + E --> H[签名部分] + + F --> I[重新计算签名] + G --> I + H --> I + + I --> J{签名匹配?} + J -->|是| K[检查过期时间 exp] + J -->|否| L[拒绝: 401 Unauthorized] + + K -->|未过期| M[检查 nbf 时间] + K -->|已过期| L + + M -->|有效| N[检查 iss 签发者] + M -->|无效| L + + N -->|匹配| O[验证通过] + N -->|不匹配| L + + O --> P[从 payload 提取 user_id] + P --> Q[查询用户权限] + Q --> R[返回受保护资源] +``` + +## 浏览器行为详解 + +### 浏览器如何处理 Authorization + +```mermaid +graph LR + A[用户请求资源] --> B[检查 Token] + B --> C{Token 存在于?} + + C -->|LocalStorage| D[从 LS 读取] + C -->|SessionStorage| E[从 SS 读取] + C -->|Memory| F[从变量读取] + C -->|Cookie| G[浏览器自动发送] + + D --> H[构造 Authorization Header] + E --> H + F --> H + G --> I[无需手动添加*] + + H --> J[发起 XHR/Fetch 请求] + I --> J + + J --> K[设置 headers] + K --> L[发送到服务器] + + style G fill:#ff9999 + style H fill:#90EE90 + style I fill:#90EE90 + + subgraph 注 + G1[Cookie 方式需要 SameSite 属性] + G2[防止 CSRF 攻击] + end + + G1 -.-> G + G2 -.-> G +``` + +**浏览器自动发送 Cookie,但需要手动添加 Bearer Token** + +### 浏览器存储 Token 的方式 + +| 存储方式 | 特点 | 安全性 | 跨域访问 | +|---------|------|--------|---------| +| LocalStorage | 持久化,手动管理 | ❌ XSS 风险 | ✅ 支持 | +| SessionStorage | 会话结束清除 | ❌ XSS 风险 | ❌ 仅同源 | +| Memory | 页面刷新丢失 | ✅ 最安全 | ❌ 仅当前页面 | +| Cookie | 可设置 HttpOnly | ✅ 防 XSS | ⚠️ 需 SameSite | + +### 前端实现 (JavaScript) + +```javascript +// 1. 登录后存储 Token +async function login(username, password) { + const response = await fetch('/api/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }) + }); + + const { access_token } = await response.json(); + localStorage.setItem('token', access_token); +} + +// 2. 带认证头访问 API +async function fetchProtectedResource() { + const token = localStorage.getItem('token'); + + const response = await fetch('/api/resource', { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + if (response.status === 401) { + // Token 过期,需要刷新或重新登录 + handleTokenExpired(); + } + + return response.json(); +} + +// 3. Axios 拦截器自动添加 Token +axios.interceptors.request.use(config => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); +``` + +## 加密与签名机制 + +### JWT 签名算法对比 + +| 算法类型 | 算法 | 密钥类型 | 特点 | +| ----- | ----- | ----- | ------------------- | +| HMAC | HS256 | 对称密钥 | 服务器签发和验证都 uses 同一密钥 | +| HMAC | HS512 | 对称密钥 | 更强的哈希,性能稍低 | +| RSA | RS256 | 非对称密钥 | 私钥签名,公钥验证 | +| RSA | RS512 | 非对称密钥 | 更强的签名算法 | +| ECDSA | ES256 | 椭圆曲线 | 比更短但同样安全 | + +### HMAC-SHA256 签名流程 + +```mermaid +graph TD + A[原始数据] --> B[Base64URL 编码 Header] + A --> C[Base64URL 编码 Payload] + + B --> D[拼接 Header.Payload] + C --> D + + D --> E[HMAC-SHA256 拼接结果] + E --> F[使用 Secret Key] + F --> G[生成 256-bit 签名] + + G --> H[Base64URL 编码签名] + H --> I[拼接 final JWT] + + I --> J[Header.Payload.Signature] + + style E fill:#ff9999 + style F fill:#ff9999 + style G fill:#lightblue +``` + +**签名 = HMAC-SHA256(Base64URL(Header) + "." + Base64URL(Payload), Secret)** + +### Go 实现签名与验证 + +```go +package auth + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "strings" +) + +type JWTBuilder struct { + secretKey []byte +} + +// 生成签名 +func (j *JWTBuilder) sign(header, payload string) string { + data := strings.Join([]string{header, payload}, ".") + h := hmac.New(sha256.New, j.secretKey) + h.Write([]byte(data)) + signature := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) + return signature +} + +// 验证签名 +func (j *JWTBuilder) verify(token string) bool { + parts := strings.Split(token, ".") + if len(parts) != 3 { + return false + } + + header, payload, signature := parts[0], parts[1], parts[2] + + // 重新计算签名 + expectedSignature := j.sign(header, payload) + + // 比较签名 (恒定时间比较,防止计时攻击) + return hmac.Equal([]byte(signature), []byte(expectedSignature)) +} +``` + +## 服务器验证流程 + +### 详细的验证步骤 + +```go +package middleware + +import ( + "net/http" + "encoding/base64" + "encoding/json" + "time" +) + +type Claims struct { + UserID string `json:"user_id"` + exp int64 `json:"exp"` + iat int64 `json:"iat"` + iss string `json:"iss"` + aud string `json:"aud"` +} + +func (m *AuthMiddleware) validateToken(token string) (*Claims, error) { + // 步骤 1: 分割三部分 + parts := strings.Split(token, ".") + if len(parts) != 3 { + return nil, fmt.Errorf("invalid token format") + } + + // 步骤 2: 验证签名 + if !m.verifySignature(token) { + return nil, fmt.Errorf("invalid signature") + } + + // 步骤 3: 解码 Payload + payload, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return nil, fmt.Errorf("invalid payload encoding") + } + + // 步骤 4: 解析 Claims + var claims Claims + if err := json.Unmarshal(payload, &claims); err != nil { + return nil, fmt.Errorf("invalid payload json") + } + + // 步骤 5: 验证过期时间 + if time.Now().Unix() > claims.exp { + return nil, fmt.Errorf("token expired") + } + + // 步骤 6: 验证签发者 + if claims.iss != "my-app" { + return nil, fmt.Errorf("invalid issuer") + } + + // 步骤 7: 验证受众 + if claims.aud != "api-users" { + return nil, fmt.Errorf("invalid audience") + } + + return &claims, nil +} + +// 验证签名 +func (m *AuthMiddleware) verifySignature(token string) bool { + parts := strings.Split(token, ".") + signatureData := parts[0] + "." + parts[1] + providedSignature := parts[2] + + // 计算期望的签名 + expectedSignature := m.sign(signatureData) + + // 恒定时间比较 + return hmac.Equal( + []byte(providedSignature), + []byte(expectedSignature), + ) +} +``` + +### HTTP 中间件 + +```go +// AuthMiddleware 验证每个请求 +func (m *AuthMiddleware) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + // 1. 提取 Authorization 头 + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + http.Error(w, "Missing Authorization header", http.StatusUnauthorized) + return + } + + // 2. 解析 Bearer Token + if !strings.HasPrefix(authHeader, "Bearer ") { + http.Error(w, "Invalid authorization scheme", http.StatusUnauthorized) + return + } + token := strings.TrimPrefix(authHeader, "Bearer ") + + // 3. 验证 Token + claims, err := m.validateToken(token) + if err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + + // 4. 将用户信息存入 Context + ctx := context.WithValue(r.Context(), "user_id", claims.UserID) + ctx = context.WithValue(ctx, "claims", claims) + + // 5. 继续处理请求 + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} +``` + +## 常见 Authorization Scheme + +### Basic Auth(基础认证) + +```mermaid +sequenceDiagram + participant B as Browser + participant S as Server + participant DB as Database + + B->>S: GET /resource + S-->>B: 401 Unauthorized + Note right of B: WWW-Authenticate: Basic realm="Secure Area" + + B->>B: 用户输入用户名密码 + B->>B: Base64("username:password") + B->>S: GET /resource + Note right of B: Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l + + S->>B: 解码 Base64 + S->>DB: 验证用户名密码 + DB-->>S: 验证结果 + S-->>B: 200 OK + 资源 +``` + +**问题**:每次请求都需要用户名密码,不安全,不推荐 + +### Digest Auth(摘要认证) + +```mermaid +sequenceDiagram + participant C as Client + participant S as Server + + C->>S: GET /resource + S-->>C: 401 Unauthorized + Note right of C: WWW-Authenticate: Digest
realm="Protected",
nonce="xyz123",
qop="auth" + + C->>C: 计算 response
MD5(username:realm:password)
MD5(method:uri)
HA1 = MD5(username:realm:password)
HA2 = MD5(method:uri)
response = MD5(HA1:nonce:HA2) + + C->>S: GET /resource + Note right of C: Authorization: Digest
username="user",
realm="Protected",
nonce="xyz123",
uri="/resource",
response="abc123" + + S->>S: 重新计算 response + S-->>C: 200 OK +``` + +**优点**:密码不直接传输,更安全 +**缺点**:实现复杂,需要多次往返 + +### Bearer Token(持有者令牌) + +✅ **现代 Web 应用的标准选择** + +```http +# 请求示例 +GET /api/user/profile HTTP/1.1 +Host: api.example.com +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIiwidXNlcm5hbWUiOiJqb2huIiwiZXhwIjoxNzI4MjQ4MDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c + +# 响应示例 +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "user_id": "123", + "username": "john", + "email": "john@example.com" +} +``` + +## Refresh Token 机制 + +### Access Token vs Refresh Token + +```mermaid +graph LR + A[Access Token] --> B[有效期短
15-30分钟] + A --> C[用于 API 访问] + A --> D[存储在浏览器] + + E[Refresh Token] --> F[有效期长
数天至数周] + E --> G[用于获取新 Access Token] + E --> H[存储在 HttpOnly Cookie] + + style A fill:#90EE90 + style E fill:#ffcc00 +``` + +### Token 刷新流程 + +```mermaid +sequenceDiagram + participant B as Browser + participant S as Server + + Note over B,S: Access Token 过期 + B->>S: POST /api/refresh + Note right of B: Body: {refresh_token: "..."} + + S->>S: 验证 Refresh Token + S->>S: 生成新的 Access Token + S-->>B: {access_token: "new_jwt..."} + + Note over B,S: 使用新 Token 继续请求 + B->>S: GET /api/resource + Note right of B: Authorization: Bearer new_jwt... + + S-->>B: 200 OK +``` + +**Refresh Token 优势**: +- ✅ Access Token 短期有效,减少泄露风险 +- ✅ Refresh Token 可随时撤销 +- ✅ 用户无感知自动续期 + +## 安全最佳实践 + +### Token 安全传输 + +| 措施 | 说明 | Go 实现 | +|-----|------|---------| +| **HTTPS 强制** | 防止 Token 被窃取 | RedirectHTTPS 中间件 | +| **短期有效期** | 降低被滥用风险 | Token 15-30 分钟 | +| **签名验证** | 防止 Token 被篡改 | HMAC/RSA 签名 | +| **黑名单机制** | 主动撤销 Token | Redis 存储 revoked_tokens | + +### 浏览器安全设置 + +```javascript +// ✅ 推荐:HttpOnly Cookie 存储 Refresh Token +document.cookie = `refresh_token=${refreshToken}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=604800`; + +// ⚠️ 谨慎:LocalStorage 存储 Access Token +localStorage.setItem('access_token', accessToken); +// 需要 XSS 防护 + +// ❌ 避免:明文传输 Token +// 必须使用 HTTPS +``` + +### CORS 和 SameSite + +```mermaid +graph TD + A[前端应用
example.com] --> B[后端 API
api.example.com] + + B --> C[CORS 策略] + C --> D[Access-Control-Allow-Origin: https://example.com] + C --> E[Access-Control-Allow-Credentials: true] + + B --> F[SameSite Cookie] + F --> G[Strict: 严格模式] + F --> H[Lax: 放宽模式] + + style D fill:#90EE90 + style G fill:#90EE90 +``` + +**Go CORS 配置**: +```go +func setupCORS() *cors.Cors { + return cors.New(cors.Options{ + AllowedOrigins: []string{"https://example.com"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"}, + AllowedHeaders: []string{"Authorization", "Content-Type"}, + AllowCredentials: true, + MaxAge: 3600, + }) +} +``` + +## 相关笔记 + +- [[金山办公作业/Week05/用户认证]] - 认证的实现方式 +- [[CS/NET/HTTPS]] - SSL/TLS 加密传输 +- [[CS/DB/访问控制]] - 数据库层面权限 diff --git a/CS/NET/HeavyKeeper.md b/CS/NET/HeavyKeeper.md new file mode 100644 index 0000000..92b2f70 --- /dev/null +++ b/CS/NET/HeavyKeeper.md @@ -0,0 +1,349 @@ +--- +tags: [CS, NET, algorithm, data-stream, heavy-hitter] +create time: 2026-04-17 13:45 +--- + +# HeavyKeeper + +## 概述 + +HeavyKeeper 是一种用于高速数据流中检测 Heavy Hitters(频繁项)的高效算法。它在保持常数内存空间的同时,能够准确地识别出出现频率超过设定阈值的数据项,广泛应用于网络流量监测、热点检测等场景。 + +## 算法原理 + +### 核心思想 + +HeavyKeeper 结合了 Count-Min Sketch 和守桶策略,通过多层哈希守桶机制来提高准确性。其核心目标是区分"大象流"和"老鼠流"。 + +#### 什么是大象流和老鼠流? + +在数据流分析中,通常将数据流按频率分为两类: + +| 类型 | 特征 | 频率占比 | 典型例子 | +|------|------|----------|---------| +| **大象流**(Elephant Flow) | 高频出现 | 占总流量的大部分 | 热门IP、热门搜索词、DDoS攻击流量 | +| **老鼠流**(Mouse Flow) | 低频偶发 | 数量众多但频率极低 | 少量用户访问、正常连接请求 | + +**关键洞察**:在很多场景中,**80-90%的流量来自不到1%的源**,这就是大象流。HeavyKeeper 的目标就是高效识别这些大象流,过滤掉老鼠流。 + +### 守桶机制 + +#### 什么是"守桶"? + +"守桶"(Keeper)是 HeavyKeeper 的核心创新。每个桶会"守护"一个特定的数据项: + +- 当数据流中的一个项到来时,哈希到某个桶 +- 如果这个项正好是该桶"守护"的项,就直接计数 +- 如果不是,则根据概率决定是否"抢夺"守护权 + +**底层原理**:让大象流(高频项)能够长期占据守桶位置,而老鼠流(低频项)很难长期占用桶的资源。 + +#### 桶的结构 + +每个桶维护以下信息: + +| 字段 | 类型 | 说明 | +|------|------|------| +| **item** | 数据项 | 当前守护的数据项 | +| **count** | 整数 | 守护项的精确计数 | +| **error** | 整数 | 误差估计(记录非守护项经过的次数) | + +#### 守桶策略:大象流如何压制老鼠流 + +**替换概率公式**: +```python +替换概率 = min(1, 新项估计频率 / 当前守护项计数) +``` + +这个公式的直观含义: + +| 情况 | 新项类型 | 替换概率 | 结果 | +|------|---------|---------|------| +| 大象流 vs 老鼠流 | 老鼠流(freq≈1) | 1/count | 极小,**老鼠流无法撼动大象流** | +| 老鼠流 vs 老鼠流 | 老鼠流(freq≈2) | 2/count | 较小,随机性强 | +| 大象流 vs 老鼠流 | 大象流(freq=50) | 50/5=1 | 必然替换,**新大象流抢占桶** | +| 大象流 vs 大象流 | 大象流(freq=98) | 98/95≈1 | 可能替换,两个大象流竞争 | + +**例子**: +``` +桶#100 当前守护:IP=10.0.0.1 (count=100, error=5) ← 大象流 +新到来:IP=10.0.0.2 → 哈希到桶#100 ← 老鼠流 + +替换概率 = min(1, 1/100) = 0.01 + +结果:0.95(随机数)> 0.01 → 不替换 +解释:大象流继续守护,老鼠流只能默默增加error +``` + +#### 多层哈希的作用 + +单层哈希可能发生冲突(多个项哈希到同一个桶),多层哈希通过冗余来解决: + +- 同一个项会由L个不同的哈希函数映射到L层的不同桶 +- 查询时取所有层的最小值(保守估计) +- 即使部分桶冲突,也能获得准确的下界 + +**最终计数** = min(所有层中该项的count值) + +#### Heavy Hitters 检测流程 + +```mermaid +flowchart TD + A[数据流新项 x] --> B[计算L个哈希] + B --> C[访问L个桶] + + C --> D{是否为守护项?} + D -->|是| E[count++] + D -->|否| F[计算替换概率] + + F --> G{触发替换?} + G -->|是| H[替换并重置count=1] + G -->|否| I[error++] + + E --> J[继续] + H --> J + I --> J +``` + +查找Top K时,只需遍历所有桶,收集 `count ≥ 阈值` 的候选项。 + +### 衰减机制 + +#### 为什么需要衰减? + +**问题场景**: +``` +10:00-10:05 IP=10.0.0.1 出现 1000 次 → 成为大象流,占据桶 +10:06-12:00 IP=10.0.0.1 不再出现,但其count=1000依然存在 +12:01 IP=10.0.0.2 频繁出现,但无法抢占count=1000的桶 +``` + +如果不衰减,过时的大象流会持续占用资源,阻碍新大象流的检测。 + +#### 衰减机制的工作原理 + +HeavyKeeper 通过周期性衰减来解决这个问题: + +**方法1:时间窗口衰减(推荐)** +```python +def periodic_decay(): + 每经过 Δt 时间,所有桶的 count 和 error 乘以衰减因子 α + count = count × α + error = error × α + 其中 α ∈ (0, 1),通常 α = 0.9 或 0.99 +``` + +**方法2:基于老化(Aging)** +```python +def aging(bucket, current_time): + elapsed = current_time - bucket.last_update_time + decay = exp(-λ × elapsed) # λ 是衰减速率 + bucket.count = bucket.count × decay +``` + +#### 衰减机制的数学效果 + +**时间窗口视角**: +``` +衰减因子 α = 0.99,窗口大小 = N + +N时刻前的权重:0.99^N ≈ 0.366 ← 仅保留36.6% +2N时刻前的权重:0.99^2N ≈ 0.134 ← 只保留13.4% +``` + +这意味着:越久远的计数对当前统计影响越小,让算法能够"遗忘"过时的流。 + +#### 衰减规则示例 + +| 场景 | 原 count | 衰减后 count | 解析 | +|------|----------|-------------|------| +| 持续活跃的大象流 | 1000 | 990 (×0.99) | 持续补充,衰减不影响地位 | +| 最近消失的大象流 | 1000 | 366 (×0.99^100) | 100个周期后快速衰减,让出桶 | +| 新大象流 | 0 → 10 | 10 (刚开始) | 有机会竞争已衰减的桶 | + +#### 衰减带来的好处 + +| 优势 | 说明 | +|------|------| +| **自适应流行度漂移** | 热点变化时,旧热点会自动失去守桶权 | +| **滑动窗口效果** | 只关注最近时间窗口内的频率,而非历史总和 | +| **防止资源垄断** | 过时的大象流不会长期占用桶资源 | + +#### 衰减与守桶的协同 + +衰减机制和守桶机制协同工作,形成一个动态平衡: + +```mermaid +graph LR + A[大象流活跃] --> B[count快速累积] + B --> C[占据守桶位置] + + C --> D{时间流逝} + D -->|持续活跃| E[保持守桶] + D -->|停止活跃| F[衰减降低count] + + F --> G{新竞争者?} + G -->|有| H[被替换,让出桶] + G -->|无| I[继续衰减直至清理] +``` + +## 关键参数 + +| 参数 | 说明 | 典型值 | +|------|------|--------| +| **m** | 每层桶的数量 | 2^15 ~ 2^20 | +| **L** | 哈希层数 | 3 ~ 5 | +| **θ** | 频率阈值 | 0.001 ~ 0.01 | +| **α** | 衰减因子 | 0.9 ~ 0.99 | +| **Δt** | 衰减周期 | 根据应用场景 | + +## 性能特征 + +### 空间复杂度 +- 空间复杂度: **O(m × L)** +- 每桶存储: item (~8字节) + count (~4字节) + error (~4字节) + +### 时间复杂度 + +| 操作 | 时间复杂度 | 说明 | +|------|-----------|------| +| **插入** | O(L) | 对每层进行哈希和更新 | +| **查询** | O(L) | 取所有层最小值 | +| **衰减** | O(m×L) | 批量处理所有桶 | + +## 优势与局限 + +### 优势 +- **大象流识别准确**: 守桶机制确保高频项持续占据资源 +- **老鼠流过滤**: 低频项很难干扰大象流统计 +- **自适应流行度变化**: 衰减机制处理热点漂移 +- **内存高效**: 恒定空间,不受数据流规模影响 + +### 局限性 +- **参数敏感**: m、L、α 等参数需要根据数据特征调优 +- **哈希冲突**: 极端情况下可能产生误报或漏报 +- **衰减延迟**: 热点切换时需要一定时间生效 + +## 应用场景 + +### 典型用例 + +1. **网络流量分析**: 识别高频 IP 地址或端口(大象流) +2. **DDoS 防护**: 检测异常高频流量,过滤老鼠流 +3. **实时推荐**: 发现用户偏好热点,利用衰减实现热点漂移 +4. **CDN 缓存**: 识别热门内容进行预加载 +5. **日志分析**: 快速定位高频错误或异常事件 + +### 实际部署考虑 + +| 场景 | 大象流示例 | 老鼠流示例 | 衰减建议 | +|------|-----------|-----------|---------| +| 网络带宽监控 | P2P下载、视频流 | 正常网页浏览 | 较慢衰减(α=0.99) | +| DDoS检测 | 攻击源IP | 正常用户IP | 快速衰减(α=0.9) | +| 搜索热门 | 热门关键词 | 长尾搜索 | 中等衰减(α=0.95) | + +## 参考实现 + +### 伪代码 + +```python +class HeavyKeeper: + def __init__(self, m, L, threshold, decay_factor): + self.m = m # 每层桶数 + self.L = L # 哈希层数 + self.threshold = threshold + self.decay_factor = decay_factor # 衰减因子 + + # 初始化多层 sketch + self.buckets = [[KeeperBucket() for _ in range(m)] + for _ in range(L)] + + # 初始化哈希函数 + self.hash_funcs = [get_hash_func(i) for i in range(L)] + + def insert(self, item, timestamp): + for layer in range(self.L): + idx = self.hash_funcs[layer](item) % self.m + bucket = self.buckets[layer][idx] + + if bucket.item == item: + # 守护项匹配,直接计数(大象流强化) + bucket.count += 1 + bucket.last_seen = timestamp + else: + # 计算替换概率 + estimated_freq = self._estimate_freq(item) + replace_prob = min(1, estimated_freq / bucket.count) + + if random.random() < replace_prob: + # 替换为新项(大象流夺权) + bucket.item = item + bucket.count = 1 + bucket.error = bucket.count + bucket.last_seen = timestamp + else: + # 不替换,仅增加误差(老鼠流被阻拦) + bucket.error += 1 + + def apply_decay(self, current_time): + """应用衰减机制""" + for layer in range(self.L): + for bucket in self.buckets[layer]: + elapsed = current_time - bucket.last_seen + if elapsed > DECAY_INTERVAL: + bucket.count *= self.decay_factor + bucket.error *= self.decay_factor + + # 归零清理 + if bucket.count < 1: + bucket.item = None + bucket.count = 0 + bucket.error = 0 + + def query(self, item): + """查询Item的频率估计""" + min_count = float('inf') + for layer in range(self.L): + idx = self.hash_funcs[layer](item) % self.m + bucket = self.buckets[layer][idx] + if bucket.item == item: + min_count = min(min_count, bucket.count) + + return min_count if min_count != float('inf') else 0 + + def get_top_k(self, k): + """获取Top K大象流""" + candidates = {} + for layer in range(self.L): + for bucket in self.buckets[layer]: + if bucket.count >= self.threshold and bucket.item: + item = bucket.item + candidates[item] = max(candidates.get(item, 0), + bucket.count) + + # 返回 Top-k + return sorted(candidates.items(), + key=lambda x: x[1], + reverse=True)[:k] +``` + +## 相关算法对比 + +| 算法 | 空间复杂度 | 大象流准确性 | 老鼠流过滤 | 衰减支持 | 适用场景 | +|------|-----------|------------|-----------|---------|---------| +| **HeavyKeeper** | O(m×L) | 高 | 优秀 | 原生支持 | 高速数据流,需检测热点漂移 | +| **Count-Min** | O(m×L) | 中 | 无 | 需额外实现 | 通用频率统计 | +| **SpaceSaving** | O(k) | 中 | 好 | 手动实现 | 固定数量Top-K | +| **LossyCounter** | O(kε) | 高 | 一般 | 手动实现 | 离线精确统计 | + +## 参考资料 + +- HeavyKeeper: Streaming Heavy Hitters Detection with Known Error Bounds (2020) +- Count-Min Sketch: An Improved Data Stream Summary +- Streaming Algorithms for Finding Heavy Hitters + +## 关联笔记 + +- [[CS/NET/Count-Min-Sketch.md]] +- [[CS/DS/HashMap.md]] +- [[CS/NET/Network-Flow-Analysis.md]] diff --git a/CS/NET/README.md b/CS/NET/README.md new file mode 100644 index 0000000..bdaf4ba --- /dev/null +++ b/CS/NET/README.md @@ -0,0 +1,45 @@ +--- +tags: [CS, NET, network] +create time: 2026-04-17 +--- + +# 计算机网络 + +## 概述 + +计算机网络是物理上分散的计算机通过通信线路连接起来,在网络软件的管理下实现资源共享和信息传递的系统。 + +## 核心概念 + +### OSI七层模型 +- 物理层、数据链路层、网络层 +- 传输层、会话层、表示层、应用层 + +### TCP/IP协议栈 +- IP协议、TCP/UDP协议 +- HTTP/HTTPS、DNS、SMTP等应用层协议 +- 网络地址与路由 + +### 网络架构 +- 客户端-服务器架构 +- P2P架构 +- 分布式系统 + +### 网络安全 +- 加密与解密 +- SSL/TLS +- 防火墙与VPN + +## 学习重点 + +- TCP/IP协议详解 +- HTTP协议与Web服务 +- 套接字编程 +- 网络性能优化 + +## 实践建议 + +- 使用Wireshark抓包分析 +- 编写网络应用 +- 搭建本地网络环境 +- 理解常见网络问题 diff --git a/CS/NET/网络协议分析基础.md b/CS/NET/网络协议分析基础.md new file mode 100644 index 0000000..ad4b779 --- /dev/null +++ b/CS/NET/网络协议分析基础.md @@ -0,0 +1,208 @@ +--- +tags: [network, protocol, theory, cs] +create time: 2026-04-17 12:15 +--- + +# 网络协议分析基础 + +## 概述 + +网络协议分析是理解网络通信的核心技术,通过分析数据包的结构和内容,可以深入理解网络协议的工作原理和通信机制。 + +## 正文 + +### TCP 三次握手与四次挥手 + +#### 三次握手 (SYN, SYN-ACK, ACK) + +``` +客户端 服务器 + | | + | --- SYN, seq=x --------------------> | + | | + | <--- SYN, ACK, seq=y, ack=x+1 ------ | + | | + | --- ACK, seq=x+1, ack=y+1 ---------> | + | | 连接建立 +``` + +**各字段含义:** +- **SYN**: 同步标志,用于建立连接 +- **ACK**: 确认标志,表示确认收到 +- **seq**: 序列号,确保数据有序传递 +- **ack**: 确认号,表示期望收到的下一个序列号 + +**抓包特征:** +1. 第一个包:SYN 标志位为 1,ACK 为 0 +2. 第二个包:SYN 和 ACK 都为 1 +3. 第三个包:ACK 为 1,SYN 为 0 + +#### 四次挥手 (FIN, ACK, FIN, ACK) + +``` +客户端 服务器 + | | + | --- FIN, seq=x --------------------> | 主动关闭 + | | + | <--- ACK, seq=y, ack=x+1 ----------- | + | | 半关闭 + | <--- FIN, seq=y ------------------- | 被动关闭 + | | + | --- ACK, seq=x+1, ack=y+1 ---------> | + | | 连接关闭 +``` + +**状态转换:** +- FIN_WAIT_1: 主动关闭方发送 FIN +- FIN_WAIT_2: 主动关闭方收到 ACK,等待对方 FIN +- CLOSE_WAIT: 被动关闭方收到 FIN,进入半关闭状态 +- LAST_ACK: 被动关闭方发送 FIN +- TIME_WAIT: 主动关闭方收到 FIN,等待 2MSL 后完全关闭 + +### HTTP 协议分析 + +#### 请求结构 + +``` +Method Request-URI HTTP-Version\r\n +Header-Name: Header-Value\r\n +\r\n +Message-Body +``` + +**常见方法:** +- **GET**: 获取资源 +- **POST**: 提交数据 +- **PUT**: 更新资源 +- **DELETE**: 删除资源 +- **HEAD**: 获取响应头 +- **OPTIONS**: 获取支持的方法 + +**常用请求头:** +``` +Host: example.com +User-Agent: Mozilla/5.0 +Accept: text/html,application/json +Content-Type: application/json +Authorization: Bearer token +Cookie: session=xxx +``` + +#### 响应结构 + +``` +HTTP-Version Status-Code Reason-Phrase\r\n +Header-Name: Header-Value\r\n +\r\n +Message-Body +``` + +**状态码分类:** +- **2xx**: 成功 (200 OK, 201 Created, 204 No Content) +- **3xx**: 重定向 (301 Moved Permanently, 302 Found, 304 Not Modified) +- **4xx**: 客户端错误 (400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found) +- **5xx**: 服务器错误 (500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable) + +**常用响应头:** +``` +Content-Type: application/json; charset=utf-8 +Content-Length: 1234 +Cache-Control: max-age=3600 +ETag: "abc123" +Set-Cookie: session=xxx; Path=/; HttpOnly +``` + +### TLS/SSL 加密通信 + +#### TLS 握手流程 + +```mermaid +sequenceDiagram + participant C as 客户端 + participant S as 服务器 + + C->>S: ClientHello
(支持的加密套件、随机数) + S-->>C: ServerHello
(选择的加密套件、随机数、证书) + S-->>C: Certificate
(服务器证书) + S-->>C: ServerHelloDone + + C->>S: ClientKeyExchange
(预主密钥,用服务器公钥加密) + C->>S: ChangeCipherSpec
(通知后续使用加密通信) + C->>S: Finished
(握手完成,加密验证) + + S-->>C: ChangeCipherSpec + S-->>C: Finished + + Note over C,S: 开始加密通信 +``` + +**关键概念:** +- **证书链**: 从服务器证书到根证书的信任链 +- **预主密钥**: 通过非对称加密传输,用于生成会话密钥 +- **会话密钥**: 通过预主密钥和双方随机数生成,用于对称加密 +- **SSL Pinning**: 客户端验证服务器证书,防止中间人攻击 + +### 数据包分析要点 + +#### 抓包指标 + +**性能指标:** +- **RTT (Round Trip Time)**: 往返时延 +- **吞吐量**: 单位时间传输的数据量 +- **丢包率**: 丢失的数据包比例 +- **重传率**: 重发数据包的比例 + +**连接指标:** +- **TCP 窗口大小**: 接收窗口和拥塞窗口 +- **连接状态**: ESTABLISHED, TIME_WAIT 等 +- **连接复用**: Keep-Alive, HTTP/2 连接复用 + +#### 过滤技巧 + +**基于协议过滤:** +``` +# HTTP/HTTPS +http or http2 or ssl + +# TCP 特定标志 +tcp.flags.syn == 1 # SYN 包 +tcp.flags.ack == 1 # ACK 包 +tcp.flags.fin == 1 # FIN 包 +tcp.flags.reset == 1 # RST 包 +``` + +**基于内容过滤:** +``` +# 特定 User-Agent +http.user_agent contains "Chrome" + +# 特定域名 +http.host == "example.com" + +# HTTP 错误 +http.response.code >= 400 + +# 请求体内容 +http.file_data contains "keyword" +``` + +**基于网络层过滤:** +``` +# 源/目标地址 +ip.src == 192.168.1.1 +ip.dst == 192.168.1.1 + +# 端口范围 +tcp.port >= 1024 and tcp.port <= 65535 +``` + +## 关联笔记 + +- [[CS/TOOLS/网络抓包]] +- [[CS/SECURITY/网络安全基础]] +- [[CS/OS/TCP 协议详解]] + +## 参考资源 + +- RFC 文档: https://www.rfc-editor.org/ +- Wireshark 指南: https://www.wireshark.org/docs/wsug_html_chunked/ diff --git a/CS/OS/README.md b/CS/OS/README.md new file mode 100644 index 0000000..43e1532 --- /dev/null +++ b/CS/OS/README.md @@ -0,0 +1,48 @@ +--- +tags: [CS, OS, operating-systems] +create time: 2026-04-17 +--- + +# 操作系统 + +## 概述 + +操作系统是管理计算机硬件与软件资源的系统软件。它是计算机系统中最基础的系统软件,为应用程序提供运行环境。 + +## 核心概念 + +### 进程管理 +- 进程与线程 +- 进程调度算法 +- 进程间通信(IPC) +- 同步与互斥 + +### 内存管理 +- 虚拟内存 +- 分页与分段 +- 内存分配与回收 +- 缓存机制 + +### 文件系统 +- 文件的存储结构 +- 目录管理 +- 文件操作接口 +- 权限管理 + +### I/O管理 +- 设备驱动 +- 中断处理 +- 缓冲区管理 + +## 学习重点 + +- Linux/Unix系统原理 +- 系统调用机制 +- 操作系统设计原则 +- 性能优化方法 + +## 实践建议 + +- 在Linux环境下进行实验 +- 使用系统调用的程序 +- 研究开源操作系统代码 diff --git a/CS/OS/TCP 协议详解.md b/CS/OS/TCP 协议详解.md new file mode 100644 index 0000000..e69de29 diff --git a/CS/README.md b/CS/README.md new file mode 100644 index 0000000..ef457c3 --- /dev/null +++ b/CS/README.md @@ -0,0 +1,37 @@ +--- +tags: [CS, foundations] +create time: 2026-04-17 +--- + +# 计算机科学 + +## 概述 + +计算机科学是研究计算机系统、软件设计、算法和数据处理的学科。本目录包含了计算机科学的基础理论知识和核心技术领域。 + +## 核心领域 + +### 操作系统 ([[OS|OS]]) +- 进程管理、内存管理、文件系统 +- 学习目标: 理解计算机系统如何管理资源 + +### 计算机网络 ([[NET|NET]]) +- 网络协议、网络架构、通信原理 +- 学习目标: 掌握数据在网络中传输的机制 + +### 数据库 ([[DB|DB]]) +- 关系型数据库、NoSQL、查询优化 +- 学习目标: 学习数据存储和检索技术 + +### 工具使用 ([[TOOLS|TOOLS]]) +- 开发工具、版本控制、容器化、CLI +- 学习目标: 掌握日常开发必备工具 + +## 学习建议 + +建议从操作系统和网络开始打好基础,再深入数据库技术。工具使用贯穿整个学习过程,可以边学边用。 + +### 理论与实践结合 +- 阅读理论笔记后进行实践 +- 在软件开发中应用所学知识 +- 通过项目加深理解 diff --git a/CS/TOOLS/README.md b/CS/TOOLS/README.md new file mode 100644 index 0000000..d7bc37d --- /dev/null +++ b/CS/TOOLS/README.md @@ -0,0 +1,51 @@ +--- +tags: [CS, TOOLS, dev-tools] +create time: 2026-04-17 +--- + +# 工具使用 + +## 概述 + +工欲善其事,必先利其器。高效的工具使用可以极大提升开发和学习的效率。 + +## 核心工具类别 + +### 开发工具 +- **编辑器/IDE**: Vim、VS Code、IntelliJ +- **代码补全与Linting**: Copilot、ESLint、golangci-lint +- **调试工具**: GDB、Chrome DevTools、Delve + +### 版本控制 +- **Git**: 版本管理、分支策略 +- **GitHub/GitLab**: 代码托管、协作开发 +- **Git工作流**: Git Flow、GitHub Flow + +### 容器化与部署 +- **Docker**: 容器化应用 +- **Docker Compose**: 多容器编排 +- **Kubernetes**: 容器编排(可选) + +### 命令行工具 +- **Shell编程**: Bash/Zsh脚本 +- **包管理器**: apt, brew, npm, go modules +- **系统工具**: grep, sed, awk, ssh, tmux + +### 构建与测试 +- **构建工具**: Make, Webpack, Vite +- **测试框架**: Jest, Go Test, PyTest +- **持续集成**: GitHub Actions, Travis CI + +## 学习重点 + +- Git的高级用法 +- Shell脚本编程 +- Docker容器化 +- 命令行效率提升 + +## 实践建议 + +- 在日常工作中持续使用 +- 学习快捷键和最佳实践 +- 定期更新工具版本 +- 关注工具生态发展 diff --git a/CS/TOOLS/网络抓包.md b/CS/TOOLS/网络抓包.md new file mode 100644 index 0000000..e171e97 --- /dev/null +++ b/CS/TOOLS/网络抓包.md @@ -0,0 +1,576 @@ +--- +tags: [network, tools, whistle, packet-capture, proxy] +create time: 2026-04-17 12:20 +--- + +# 网络抓包 + +## 概述 + +网络抓包是调试和分析网络请求的核心技术。本文档以 whistle 支持的抓包功能为核心,介绍网络请求的抓取、分析、修改和重放方法。 + +理论性的协议分析内容请参考 [[CS/NET/网络协议分析基础]]。 + +## 正文 + +### 代理状态下的网络请求流程 + +在配置代理后,网络请求会经过代理服务器转发。以下是完整的请求流程: + +```mermaid +sequenceDiagram + participant Client as 客户端 (浏览器/应用) + participant Proxy as 代理服务器 (Whistle) + participant Target as 目标服务器 + + %% 1. 客户端发起请求 + Client->>Proxy: ✅ 1. 发起 HTTP/HTTPS 请求 + Note over Client,Proxy: URL: https://api.example.com/data
Method: GET
Headers: {...} + + %% 2. 代理接收并解析 + Proxy->>Proxy: 🔍 2. 接收请求并解析 + Note over Proxy: 检查规则匹配
- 规则: reqHeaders?
- 规则: resBody?
- 规则: 映射? + + %% 分支: 是否有转发规则 + alt 有转发规则 + Proxy->>Proxy: 📝 3. 应用转发规则 + Note over Proxy: 执行修改操作
- 修改请求头
- 替换域名
- 重写路径 + end + + %% 分支: HTTPS 请求 + alt HTTPS 请求 + Proxy->>Proxy: 🔒 4. SSL/TLS 握手 + Note over Proxy: - 代理充当中间人
- 使用自签名证书
- 解密请求内容 + end + + %% 3. 代理转发请求 + Proxy->>Target: ⬆️ 5. 转发请求到目标服务器 + Note over Proxy,Target: 处理后的请求
Header + Body + + %% 4. 目标服务器处理 + Target->>Target: ⚙️ 6. 处理请求 + Note over Target: 业务逻辑处理
生成响应数据 + + %% 5. 目标服务器返回响应 + Target-->>Proxy: ⬇️ 7. 返回响应 + Note over Target,Proxy: Status: 200
Headers: {...}
Body: {...} + + %% 6. 代理接收并记录 + Proxy->>Proxy: 💾 8. 接收并记录响应 + Note over Proxy: - 记录到抓包列表
- 计算耗时
- 保存完整信息 + + %% 分支: 是否有响应修改规则 + alt 有响应修改规则 + Proxy->>Proxy: 🎭 9. 应用响应规则 + Note over Proxy: - 修改响应头
- 替换响应体
- 修改状态码
- Mock 数据 + end + + %% 7. 代理转发响应 + Proxy-->>Client: ✅ 10. 返回响应给客户端 + Note over Proxy,Client: 最终的响应数据
Client 可以正常处理 + + %% 循环: 请求重放 + rect rgba(0, 255, 0, 0.1) + Client->>Proxy: 🔄 11. 重放请求 (可选) + Proxy->>Client: 📤 12. 返回重放结果 + Note over Client,Proxy: 复现 Bug
接口调试
压力测试 + end +``` + +**流程说明:** + +1. **请求发起**: 客户端(浏览器或应用)向配置的代理服务器发送请求 +2. **代理解析**: 代理服务器接收请求,解析 URL、方法、头部等信息 +3. **规则匹配**: 代理检查配置的转发规则,包括请求/响应修改、域名映射等 +4. **HTTPS 处理**: 对于 HTTPS 请求,代理通过中间人模式解密和重新加密请求 +5. **转发请求**: 代理将处理后的请求转发到目标服务器 +6. **响应处理**: 目标服务器处理请求并返回响应 +7. **记录数据**: 代理记录完整的请求/响应信息到抓包列表 +8. **应用规则**: 如果配置了响应修改规则,代理会修改响应内容 +9. **返回响应**: 代理将最终响应返回给客户端 +10. **请求重放**: 可以对已记录的请求进行重放,用于调试和测试 + +**Whistle 的核心作用:** +- 🔍 **可见性**: 记录所有经过代理的请求 +- 🎭 **可控性**: 可以修改和重放请求 +- 🔄 **灵活性**: 支持域名映射和数据 Mock +- 📊 **调试性**: 提供详细的请求/响应分析 + +### Whistle 工具介绍 + +#### 为什么选择 Whistle + +Whistle 是一款基于 HTTP 代理的跨平台调试工具,相比传统抓包工具具有以下优势: + +- 跨平台支持 (Windows, Mac, Linux) +- 内置抓包功能,支持 HTTP/HTTPS +- 便捷的请求修改和重放 +- 支持域名映射、远程调试 +- 界面友好,操作简单 +- 支持插件扩展 +- 配置灵活,规则强大 + +#### 安装与启动 + +```bash +# 全局安装 +npm install -g whistle + +# 启动服务 +w2 start + +# 启动指定端口 +w2 start -p 8899 + +# 停止服务 +w2 stop + +# 重启服务 +w2 restart +``` + +![[Pasted image 20260417125039.png]] +![[Pasted image 20260417125713.png]] +**访问界面:** +- 默认地址: http://127.0.0.1:8899 +- 默认账号: whistle (首次安装无密码) + +#### 代理配置 + +**命令行配置:** +```bash +# Mac/Linux +export http_proxy=http://127.0.0.1:8899 +export https_proxy=http://127.0.0.1:8899 + +# Windows +set http_proxy=http://127.0.0.1:8899 +set https_proxy=http://127.0.0.1:8899 +``` + +**系统代理配置:** +1. 打开系统网络设置 +2. 配置 HTTP/HTTPS 代理为 127.0.0.1:8899 +3. 应用并保存 + +**手机代理配置:** +1. 确保手机和电脑在同一局域网 +2. 查看电脑 IP 地址 (如 192.168.1.100) +3. 手机 WiFi 设置中配置代理: + - 服务器: 192.168.1.100 + - 端口: 8899 +4. 浏览器访问 https://rootca.pro 安装信任证书 + +### 核心功能 + +#### 1. 抓包查看 + +**功能特性:** +- 显示所有 HTTP/HTTPS 请求 +- 显示请求/响应头和内容 +- 支持 WebSocket 抓包 +- 支持长连接和断点续传 +- 按域名、状态码、方式筛选 + +**界面说明:** +- **请求列表**: 显示所有请求的摘要信息 (URL、方法、状态码、耗时) +- **请求详情**: 点击请求查看完整信息 (请求头、响应头、请求体、响应体) +- **时间线**: 显示请求的时间顺序和耗时 +- **统计**: 显示请求总数、成功数、失败数等统计信息 + +#### 2. 请求修改 + +**修改方法:** + +1. **Values 方式 (规则面板)**: +``` +# 修改请求头 +example.com reqHeaders://{test} + +# 在 Values 中定义变量 +test: +x-custom-header: custom-value +x-another-header: another-value +``` + +2. **正则匹配修改**: +``` +# 修改所有匹配的请求 +/^https:\/\/example\.com\/(.*)/ reqHeaders://{test} +``` + +3. **修改请求体**: +``` +example.com reqBody://{test-body} + +# Values +test-body: +{"modified": "data"} +``` + +4. **修改响应**: +``` +# 修改响应头 +example.com resHeaders://{test-res} + +# 修改响应体 +example.com resBody://{test-body} + +# 修改响应状态码 +example.com statusCode://404 +``` + +#### 3. 请求重放 + +**功能作用:** +- 复现问题 bug +- 压力测试 +- 接口调试 +- 自动化脚本测试 + +**操作步骤:** +1. 在请求列表中选择要重放的请求 +2. 点击"重放"按钮 +3. 选择重放次数 +4. 开始重放 + +**批量重放:** +- 支持选中多个请求批量重放 +- 支持导出请求列表为脚本 +- 支持自定义重放间隔 + +#### 4. 域名映射 + +**本地开发映射:** +``` +# 映射到本地服务 +api.example.com 127.0.0.1:3000 + +# 映射到远程服务 +api.example.com www.test-api.com + +# 路径映射 +example.com/api local.path/to/api +``` + +**多环境切换:** +``` +# 开发环境 +dev.example.com api-dev.example.com + +# 测试环境 +test.example.com api-test.example.com + +# 生产环境 +prod.example.com api.example.com +``` + +#### 5. 接口 Mock + +**数据 Mock:** +``` +# Mock 接口响应 +api.example.com/user/info resBody://{mock-user} + +# Values 中定义 Mock 数据 +mock-user: +{ + "code": 200, + "data": { + "name": "测试用户", + "age": 25, + "avatar": "https://example.com/avatar.png" + }, + "message": "success" +} +``` + +**Mock 模板:** +``` +# 文件 Mock +api.example.com/list resBody://{./mock/list.json} + +# 使用 mockjs 语法 +api.example.com/list resBody://{./mock/list.js} +``` + +### 高级用法 + +#### 1. Bypass 跳过代理 + +**配置跳过规则:** +``` +# 跳过特定域名 +www.google.com bypass://* + +# 跳过 IP 地址 +192.168.1.100 bypass://* + +# 跳过本地地址 +192.168.* bypass://* +10.* bypass://* +``` + +#### 2. 插件使用 + +**常用插件:** +``` +# 安装插件 +w2 install whot + +# 使用插件 +example.com whot:// + +# 查看插件 +w2 list +``` + +#### 3. WebSocket 调试 + +**WebSocket 抓包:** +1. 在网络列表中找到 WebSocket 请求 +2. 点击查看连接详情 +3. 实时查看发送和接收的消息 +4. 支持文本和二进制消息 + +#### 4. HTTPS 处理 + +**自签名证书概念:** + +**什么是自签名证书?** + +自签名证书是指不由受信任的证书颁发机构(CA,如 DigiCert、Let's Encrypt 等)签发的 SSL/TLS 证书,而是由服务器自己生成并签名的证书。 + +**与传统证书的区别:** + +| 特性 | 受信任证书 | 自签名证书 | +|------|------------|------------| +| **签发者** | 受信任的 CA 机构 | 服务器自己 | +| **浏览器信任** | ✅ 自动信任 | ❌ 警告不安全 | +| **成本** | 付费(免费付费都有) | 免费 | +| **验证流程** | CA 验证身份 | 无验证 | +| **适用场景** | 生产环境、对外服务 | 开发测试、内网代理 | + +**为什么代理抓包需要自签名证书?** + +在 HTTPS 抓包中,代理服务器使用自签名证书的原理: + +```mermaid +flowchart TD + subgraph 正常HTTPS流程 + A1[客户端] -->|建立加密连接| B1[目标服务器] + B1 -->|发送官方证书
由CA签发| A1 + A1 -->|验证CA信任| B1 + end + + subgraph 代理抓包流程 + A2[客户端] -->|建立加密连接| B2[代理服务器] + B2 -->|发送自签名证书
代理自己签发| A2 + A2 -->|❓ 证书无效?| + A2 -->|⚠️ 需要手动信任| C{是否信任代理证书} + C -->|是| B2 + C -->|否| D[连接失败] + + B2 -->|解密查看内容| E[代理检查内容
应用修改规则] + E -->|重新加密| B2 + B2 -->|转发到真实服务器| F[目标服务器] + F -->|响应| B2 + B2 -->|返回给客户端| A2 + end +``` + +**代理使用自签名证书的原因:** + +1. **中间人攻击(MITM)的本质**: + - HTTPS 的目的是防止中间人攻击 + - 代理抓包本质上就是"合法的中间人" + - 需要解密 HTTPS 流量才能查看和修改 + +2. **无法动态生成官方证书**: + - 代理无法为每个域名申请真实证书 + - 代理需要处理成千上万个不同域名的请求 + - 自签名证书是最灵活的解决方案 + +3. **时间成本和工作效率**: + - 为每个抓包域名单独申请证书不现实 + - 自签名证书可以立即生成和使用 + +**Whistle 证书工作原理:** + +``` +1. 用户访问 https://api.example.com + ↓ +2. Whistle 拦截请求 + ↓ +3. Whistle 为 api.example.com 动态生成自签名证书 + (证书上的域名是 api.example.com) + ↓ +4. Whistle 将此证书发送给浏览器 + ↓ +5. 浏览器识别出这不是受信任的 CA 签发的证书 + (因为根证书不在浏览器信任列表中) + ↓ +6. 如果用户手动导入了 Whistle 的根证书并信任 + ↓ +7. 浏览器信任该证书,连接建立成功 + ↓ +8. Whistle 可以解密并查看 HTTPS 内容 +``` + +**信任证书:** +1. 访问 https://rootca.pro 下载证书 +2. 安装到系统信任根证书 +3. 移动端需要额外配置 + +**SSL Pinning 绕过:** +- 开发环境可以关闭 SSL Pinning +- 使用测试证书配置 +- 使用移动端调试工具 (如 Frida) + +### 实践场景 + +#### 场景 1: 前端开发调试 + +**需求:** +- 查看接口请求和响应 +- 修改接口数据进行测试 +- Mock 接口用于前端开发 + +**配置:** +``` +# 查看接口日志 +api.example.com log:// + +# 修改接口响应 +api.example.com resBody://{mock-data} + +# 映射到本地服务 +api.example.com 127.0.0.1:8080 +``` + +#### 场景 2: 移动端开发调试 + +**需求:** +- 抓取手机应用的网络请求 +- 查看和修改接口数据 +- 解决跨域问题 + +**配置:** +``` +# 启用 CORS +api.example.com resCors:// * + +# 允许所有域 +api.example.com reqHeaders://{test} + +test: +Access-Control-Allow-Origin: * +Access-Control-Allow-Credentials: true +``` + +#### 场景 3: 接口测试和压测 + +**需求:** +- 重复某接口的请求 +- 测试并发请求 +- 验证接口性能 + +**操作:** +1. 选择目标接口 +2. 点击"重放" → "多次重放" +3. 设置重放次数和间隔 +4. 查看结果和统计 + +#### 场景 4: 问题复现 + +**需求:** +- 在本地复现线上问题 +- 使用线上数据调试 +- 分离前端和后端问题 + +**配置:** +``` +# 使用线上接口,本地前端 +localhost:8080/api online.example.com/api + +# 使用本地接口,线上前端 +www.example.com/api 127.0.0.1:8080/api +``` + +### 常见问题 + +#### 1. 抓不到 HTTPS 请求 + +**解决方法:** +- 确认已安装并信任根证书 +- 检查系统代理是否配置正确 +- 重启浏览器和应用 +- 清除浏览器缓存 + +#### 2. 证书不信任 + +**解决方法:** +- Windows: 安装到"受信任的根证书颁发机构" +- Mac: 安装钥匙串并设置为"始终信任" +- 移动端: 进入设置 → 通用 → 关于本机 → 证书信任设置 + +#### 3. 代理配置后无法上网 + +**解决方法:** +- 检查 whistle 是否启动 +- 确认代理地址和端口正确 +- 检查防火墙设置 +- 尝试使用 bypass:// 规则 + +#### 4. 规则不生效 + +**排查步骤:** +1. 检查规则语法是否正确 +2. 确认规则没有冲突 +3. 查看规则优先级 +4. 使用 log:// 查看匹配情况 + +### 最佳实践 + +#### 1. 规则组织 + +``` +# 按功能分类 +# ----- 本地开发 ----- +api-dev.example.com 127.0.0.1:3000 +static-dev.example.com 127.0.0.1:8080 + +# ----- 测试环境 ----- +api-test.example.com test-api.example.com + +# ----- Mock 数据 ----- +api.example.com/user resBody://{./mock/user.json} +api.example.com/list resBody://{./mock/list.json} + +# ----- 特殊规则 ----- +google.com bypass://* +``` + +#### 2. 性能优化 + +- 及时清理过多的抓包记录 +- 使用过滤器只关注相关请求 +- 对于高流量场景使用 bypass:// 规则 + +#### 3. 安全建议 + +- 不要在公共WiFi下使用代理抓包 +- 及时清理缓存中的敏感信息 +- 不要在生产环境使用长时间的代理 +- 定期备份重要的配置和规则 + +## 关联笔记 + +- [[CS/NET/网络协议分析基础]] +- [[CS/SECURITY/网络安全基础]] +- [[DEV/DEBUG/调试技巧总结]] + +## 参考资源 + +- Whistle 官方文档: https://wproxy.org/whistle/ +- Whistle GitHub: https://github.com/avwo/whistle diff --git a/DEV/DEBUG/调试技巧总结.md b/DEV/DEBUG/调试技巧总结.md new file mode 100644 index 0000000..e69de29 diff --git a/DEV/GO/README.md b/DEV/GO/README.md new file mode 100644 index 0000000..6faca86 --- /dev/null +++ b/DEV/GO/README.md @@ -0,0 +1,75 @@ +--- +tags: [DEV, GO, golang, backend] +create time: 2026-04-17 +--- + +# Go语言服务端开发 + +## 概述 + +Go语言(Golang)是Google开发的开源编程语言,特别适合构建高性能、高并发的服务端应用。它的设计简洁高效,具有优秀的并发支持和强大的标准库。 + +## Go语言核心特性 + +### 语言特性 +- 静态类型、编译型语言 +- 强类型、简洁的语法 +- 自动车管内存(GC) +- 首类函数与闭包 +- 接口与隐式实现 + +### 并发模型 +- **Goroutine**: 轻量级线程 +- **Channel**: 通信机制 +- **Select**: 多路复用 +- **Context**: 上下文管理 + +### 标准库 +- net/http: HTTP服务端 +- database/sql: 数据库操作 +- encoding/json: JSON处理 +- io/io/ioutil: I/O操作 + +## 学习路径 + +### 基础语法 +- 变量与常量 +- 数据类型(切片slice、映射map) +- 流程控制 +- 函数与方法 +- 结构体与接口 + +### 进阶特性 +- 并发编程(Goroutine + Channel) +- 错误处理机制 +- 并发安全 +- 反射 + +### 服务端开发 +- HTTP服务器搭建 +- 路由框架(Gin, Echo等) +- 中间件设计 +- 数据库ORM(GORM) +- 配置管理 + +### 实践项目 +- RESTful API +- WebSocket服务 +- gRPC微服务 +- 分布式系统组件 + +## 学习重点 + +- Go的并发模型和最佳实践 +- 错误处理的设计哲学 +- 接口的使用与理解 +- 性能优化技巧 +- Go Modules依赖管理 + +## 实践建议 + +- 阅读Go源代码(标准库) +- 实现并发程序 +- 使用主流Go框架 +- 参与开源项目 +- 关注Go官方博客和最佳实践 diff --git a/DEV/REACT/README.md b/DEV/REACT/README.md new file mode 100644 index 0000000..e48b390 --- /dev/null +++ b/DEV/REACT/README.md @@ -0,0 +1,81 @@ +--- +tags: [DEV, REACT, frontend, javascript] +create time: 2026-04-17 +--- + +# React前端开发 + +## 概述 + +React是Facebook开发的用于构建用户界面的JavaScript库。它采用组件化思想,通过声明式编程方式,使得复杂数的逻辑可以轻松维护和扩展。 + +## React核心概念 + +### 组件化开发 +- 函数组件与类组件 +- Props(属性)传递 +- 组件生命周期 +- 组件组合模式 + +### State管理 +- useState Hook +- useReducer Hook +- Context API +- 第三方状态管理(Redux, Zustand) + +### 副作用处理 +- useEffect Hook +- 自定义Hooks +- 依赖数组管理 +- 清理函数 + +### Hooks生态 +- useRef: 访问DOM元素 +- useMemo/useCallback: 性能优化 +- useLayoutEffect: 同步副作用 +- 自定义Hooks代码复用 + +## 学习路径 + +### JavaScript基础 +- ES6+语法(箭头函数、解构、模块化) +- 异步编程(Promise, async/await) +- DOM操作与事件 +- 模块系统 + +### React核心 +- JSX语法 +- 组件概念 +- Props与State +- 事件处理 +- 条件渲染与列表渲染 + +### 进阶特性 +- Hooks原理与实践 +- Context与状态管理 +- 性能优化 +- 表单处理 +- 路由(React Router) + +### 生态工具 +- 构建工具: Vite, Next.js +- UI组件库: MUI, Ant Design, Tailwind CSS +- 状态管理: Redux Toolkit, Zustand +- 数据获取: React Query, SWR +- 测试: Jest, React Testing Library + +## 学习重点 + +- React Hooks的深入理解 +- 组件设计模式 +- 性能优化技巧 +- 状态管理选型 +- 前端工程化 + +## 实践建议 + +- 从小项目开始,逐步构建复杂应用 +- 学习React DevTools调试 +- 关注React官方文档变化 +- 实践常见的UI场景(表单、表格、图表等) +- 学习Next.js等全栈框架 diff --git a/DEV/VITE/README.md b/DEV/VITE/README.md new file mode 100644 index 0000000..538b34e --- /dev/null +++ b/DEV/VITE/README.md @@ -0,0 +1,244 @@ +--- +tags: [DEV, VITE, build-tool, frontend] +create time: 2026-04-18 +--- + +# Vite 构建工具 + +[配置详解](./配置详解.md) | [架构原理](./架构原理.md) | [优化实践](./优化实践.md) + +## 概述 + +Vite 是下一代前端构建工具,以其极快的开发体验和优秀的生产构建性能著称。不同于传统构建工具的全量打包,Vite 利用浏览器原生 ES Module 能力,实现了真正的按需编译。 + +## 核心优势 + +🚀 **极速启动** - 无全量打包,启动时间 < 1s +⚡ **闪电 HMR** - 模块级热更新,保持应用状态 +🎯 **统一工具链** - 开发/生产环境一致,配置简化 +🔌 **丰富生态** - 官方插件支持主流框架 + +## 快速开始 + +### 项目创建 + +```bash +npm create vite@latest my-app -- --template react-ts +cd my-app +npm install +npm run dev +``` + +### 核心命令 + +| 命令 | 功能 | 使用场景 | +|------|------|----------| +| `npm run dev` | 启动开发服务器 | 日常开发调试 | +| `npm run build` | 生产环境构建 | 部署准备 | +| `npm run preview` | 预览构建产物 | 本地测试生产版本 | + +## 核心概念 + +### 开发环境 vs 生产环境 + +```mermaid +graph LR + A[浏览器请求] --> B{环境判断} + B -->|开发| C[Dev Server] + B -->|生产| D[静态文件] + C --> E[ESM 按需编译] + E --> F[即时响应] + D --> G[CDN/HTTP 服务器] +``` + +### 构建流程对比 + +| 特性 | Vite | Webpack | +|------|------|---------| +| 启动速度 | < 1s | 10-30s | +| 热更新 | 模块级 < 100ms | 全量重编译 | +| 配置复杂度 | 低 | 高 | +| 构建产物 | Rollup 优化 | Webpack 打包 | + +## 技术栈支持 + +### 前端框架 +- ✅ React + TypeScript +- ✅ Vue 3 + TypeScript +- ✅ Svelte + TypeScript +- ✅ Preact + TypeScript + +### 构建特性 +- 📦 ES Modules 原生支持 +- 🎨 CSS 预处理器 +- 🔧 TypeScript 开箱即用 +- 📡 Asset 资源处理 +- 🔄 Dynamic Import 支持 + +## 项目结构建议 + +``` +project/ +├── src/ +│ ├── assets/ # 静态资源 +│ ├── components/ # 组件 +│ ├── hooks/ # 自定义 Hooks +│ ├── lib/ # 工具库 +│ ├── types/ # TypeScript 类型 +│ ├── App.tsx # 根组件 +│ └── main.tsx # 入口文件 +├── public/ # 公共资源 +├── vite.config.ts # Vite 配置 +└── tsconfig.json # TypeScript 配置 +``` + +## 基础配置示例 + +```typescript +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import path from 'path' + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + '@components': path.resolve(__dirname, './src/components') + } + }, + server: { + port: 3000, + open: true + } +}) +``` + +## 常见配置场景 + +### TypeScript 项目 +```typescript +// tsconfig.json +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + } +} +``` + +### 环境变量 +```typescript +// .env.development +VITE_API_URL=http://localhost:8080/api +VITE_DEBUG_MODE=true +VITE_MOCK_DATA=true + +// 使用方式 +const apiUrl = import.meta.env.VITE_API_URL +``` + +## 生态工具 + +### 官方插件 +- `@vitejs/plugin-vue` - Vue 支持 +- `@vitejs/plugin-react` - React 支持 +- `@vitejs/plugin-legacy` - 传统浏览器兼容 + +### 推荐工具链 +- **代码质量**: ESLint + Prettier +- **状态管理**: Zustand / Redux Toolkit +- **UI 组件**: Ant Design / MUI +- **路由**: React Router v6 +- **HTTP 客户端**: Axios + +## 学习路径 + +### 基础阶段 +1. 掌握基本命令和配置 +2. 理解开发/生产环境差异 +3. 熟悉环境变量使用 + +### 进阶阶段 +1. 深入构建配置 +2. 学习插件开发 +3. 性能优化实践 + +### 高级阶段 +1. 理解底层架构原理 +2. 定制化解决方案 +3. 大型项目工程化 + +## 技术特点 + +### 为什么选择 Vite? + +1. **开发效率提升** + - 零配置启动,开箱即用 + - 热更新速度快,大幅提升开发体验 + - 现代工具链,学习成本低 + +2. **构建性能优异** + - 基于 Rollup 的高质量产出 + - 智能代码分割和 Tree-shaking + - 生产环境高度优化 + +3. **生态社区活跃** + - 官方支持和持续更新 + - 丰富的插件生态 + - 广泛的行业采用 + +## 与其他工具对比 + +| 工具 | 启动速度 | HMR 性能 | 构建质量 | 学习曲线 | +|------|----------|----------|----------|----------| +| Vite | ⚡⚡⚡ | ⚡⚡⚡ | ⚡⚡⚡ | ⚡⚡⚡ | +| Webpack | ⚡ | ⚡⚡ | ⚡⚡⚡ | ⚡ | +| Next.js | ⚡⚡ | ⚡⚡⚡ | ⚡⚡⚡ | ⚡⚡ | +| Parcel | ⚡⚡⚡ | ⚡⚡ | ⚡⚡ | ⚡⚡⚡ | + +## 适用场景 + +### 推荐使用 +- ✅ SPA 单页应用开发 +- ✅ React/Vue 项目构建 +- ✅ 中大型前端项目 +- ✅ 需要快速开发迭代 + +### 需要评估 +- 🤔 复杂的构建需求 (Web Workers, WASM) +- 🤔 高度定制化构建流程 +- 🤔 遗留项目迁移成本 + +## 相关文档 + +- [[DEV/VITE/配置详解.md]] - 详细配置说明 +- [[DEV/VITE/架构原理.md]] - 技术实现原理 +- [[DEV/VITE/优化实践.md]] - 性能优化指南 +- [[DEV/REACT/README.md]] - React 开发指南 + +## 技术资源 + +- [Vite 官方文档](https://vitejs.dev/) +- [Vite GitHub 仓库](https://github.com/vitejs/vite) +- [Vite 生态插件](https://github.com/vitejs/awesome-vite) + +## 下一步行动 + +1. 创建一个新的 React + TypeScript 项目 +2. 配置开发和生产环境 +3. 实现 HMR 功能 +4. 进行第一次生产构建 diff --git a/DEV/VITE/优化实践.md b/DEV/VITE/优化实践.md new file mode 100644 index 0000000..5f0ae40 --- /dev/null +++ b/DEV/VITE/优化实践.md @@ -0,0 +1,722 @@ +--- +tags: [DEV, VITE, performance, optimization] +create time: 2026-04-18 +--- + +# Vite 优化实践 + +## 性能优化总览 + +```mermaid +graph TD + A[Vite 性能优化] --> B[开发环境] + A --> C[生产构建] + A --> D[运行时优化] + + B --> B1[依赖预构建] + B --> B2[智能缓存] + B --> B3[热更新优化] + + C --> C1[代码分割] + C --> C2[资源优化] + C --> C3[构建配置] + + D --> D1[按需加载] + D --> D2[缓存策略] + D --> D3[监控分析] +``` + +## 开发环境优化 + +### 依赖预构建优化 + +#### 智能依赖识别 +```typescript +import { defineConfig } from 'vite' + +export default defineConfig({ + optimizeDeps: { + // 手动指定需要预构建的依赖 + include: [ + 'react', + 'react-dom', + 'antd', + 'lodash' + ], + // 排除不需要预构建的模块 + exclude: [ + // 已是 ESM 格式的包 + 'vue', + // 开发时频繁修改的包 + './src/common/*' + ], + // 强制重新构建 + force: process.env.NODE_ENV === 'development' + } +}) +``` + +#### 预构建性能调优 +```typescript +export default defineConfig({ + optimizeDeps: { + // esbuild 配置 + esbuildOptions: { + target: 'es2020', + // 保持类名和函数名,便于调试 + keepNames: true, + // 启用打包内联 + bundle: true, + // 外部化某些依赖 + external: ['some-large-library'] + } + } +}) +``` + +### 服务器配置优化 + +#### 高效的代理配置 +```typescript +export default defineConfig({ + server: { + proxy: { + // API 代理 + '/api': { + target: 'http://localhost:8080', + changeOrigin: true, + // 路径重写 + rewrite: (path) => path.replace(/^\/api/, ''), + // 超时设置 + timeout: 30000, + // 代理错误处理 + configure: (proxy, _options) => { + proxy.on('error', (err, _req, res) => { + res.end(JSON.stringify({ error: err.message })) + }) + } + }, + // 静态资源代理 + '/static': { + target: 'http://localhost:9000', + changeOrigin: true + } + } + } +}) +``` + +#### 开发服务器性能 +```typescript +export default defineConfig({ + server: { + // 禁用自动浏览器打开 + open: false, + + // 严格的端口模式 + strictPort: false, + + // 仅监听 localhost + host: '127.0.0.1', + + // HMR 配置 + hmr: { + // 覆盖层配置 + overlay: { + runtimeErrors: false, // 不显示运行时错误 + errors: true, // 显示编译错误 + warnings: false // 不显示警告 + } + }, + + // 文件监听优化 + watch: { + usePolling: false, // 禁用轮询(默认) + interval: 100, // 轮询间隔 + ignored: [ + '**/node_modules/**', // 忽略 node_modules + '**/.git/**', // 忽略 .git 目录 + '**/dist/**', // 忽略构建输出 + 'coverage/**' // 忽略测试覆盖率 + ] + } + } +}) +``` + +### 缓存策略 + +#### 依赖缓存优化 +```typescript +export default defineConfig({ + optimizeDeps: { + // 缓存目录 + cacheDir: 'node_modules/.vite', + + // 锁文件 + lockfile: true, + + // 禁用缓存调试 + // force: true + } +}) + +// 清理依赖缓存 +// npm run dev -- --force +``` + +#### 模块缓存配置 +```typescript +export default defineConfig({ + build: { + // 模块预加载 + modulePreload: { + polyfill: false, // 禁用预加载 polyfill + resolveDependencies: false // 禁用依赖解析 + } + } +}) +``` + +## 生产构建优化 + +### 代码分割策略 + +#### 智能代码分割 +```typescript +export default defineConfig({ + build: { + rollupOptions: { + output: { + // 手动代码分割配置 + manualChunks: (id) => { + // 节点模块分割 + if (id.includes('node_modules')) { + if (id.includes('react')) { + return 'vendor-react' + } + if (id.includes('antd')) { + return 'vendor-ui' + } + if (id.includes('lodash') || id.includes('dayjs')) { + return 'vendor-utils' + } + return 'vendor-other' + } + + // 应用代码分割 + if (id.includes('pages/')) { + const pageName = id.split('/pages/')[1].split('/')[0] + return `pages/${pageName}` + } + + // 组件分割 + if (id.includes('components/')) { + const componentName = id.split('/components/')[1].split('/')[0] + return `components/${componentName}` + } + } + } + } + } +}) +``` + +#### 按路由懒加载 +```typescript +// React Router 懒加载示例 +import { lazy, Suspense } from 'react' + +// 路由组件懒加载 +const HomePage = lazy(() => import(/* webpackChunkName: "home" */ '@/pages/Home')) +const AboutPage = lazy(() => import(/* webpackChunkName: "about" */ '@/pages/About')) +const DashboardPage = lazy(() => import(/* webpackChunkName: "dashboard" */ '@/pages/Dashboard')) + +// 使用 Suspense 包裹 +function App() { + return ( + Loading...}> + + } /> + } /> + } /> + + + ) +} +``` + +### 资源优化 + +#### CSS 代码分割 +```typescript +export default defineConfig({ + build: { + cssCodeSplit: true, // 启用 CSS 代码分割 + cssTarget: 'chrome80', // CSS 目标浏览器 + cssMinify: 'lightningcss' // CSS 压缩器 + }, + css: { + devSourcemap: false, // 开发环境不生成 Source Map + + // PostCSS 配置 + postcss: './postcss.config.js', + + preprocessorOptions: { + scss: { + api: 'modern-compiler' // 使用现代编译器 + } + } + } +}) +``` + +#### 静态资源优化 +```typescript +export default defineConfig({ + build: { + // 资源处理 + assetsInlineLimit: 4096, // 小于 4kb 的资源内联 + + rollupOptions: { + output: { + // 资源文件命名 + assetFileNames: (assetInfo) => { + const info = assetInfo.name?.split('.') ?? [] + const extType = info[info.length - 1] + + if (/\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/i.test(assetInfo.name ?? '')) { + return `media/[name]-[hash].[ext]` + } + if (/\.(png|jpe?g|gif|svg|webp|avif)(\?.*)?$/i.test(assetInfo.name ?? '')) { + return `images/[name]-[hash].[ext]` + } + if (/\.(woff2?|eot|ttf|otf)(\?.*)?$/i.test(assetInfo.name ?? '')) { + return `fonts/[name]-[hash].[ext]` + } + return `assets/[name]-[hash].[ext]` + } + } + } + } +}) +``` + +### 压缩与优化 + +#### 代码压缩配置 +```typescript +export default defineConfig({ + build: { + minify: 'terser', // 压缩器选择: terser | esbuild + terserOptions: { + compress: { + // 移除 console + drop_console: true, + drop_debugger: true, + + // 纯函数优化 + pure_funcs: [ + 'console.log', + 'console.info', + 'console.debug' + ], + + // 代码优化 + ecma: 2020, + arguments: true, + dead_code: true, + side_effects: true + }, + mangle: { + // 变量名混淆 + toplevel: true, + properties: { + regex: /^_/ // 混淆以下划线开头的属性 + }, + keep_classnames: false, + keep_fnames: false + }, + format: { + // 保留版权注释 + comments: false, + // 不移除代码 + preserveAnnotations: false + } + }, + + // 不显示压缩后大小 + reportCompressedSize: false + } +}) +``` + +#### 构建产物分析 +```typescript +import { visualizer } from 'rollup-plugin-visualizer' +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [ + visualizer({ + filename: './dist/stats.html', + open: true, + gzipSize: true, + brotliSize: true, + template: 'treemap' // treemap | sunburst | network + }) + ] +}) + +// 构建后分析 +// npm run build +``` + +## 运行时优化 + +### 按需导入 + +#### UI 组件按需导入 +```typescript +// Ant Design 按需导入 +import { ConfigProvider } from 'antd' +import Button from 'antd/es/button' +import Input from 'antd/es/input' + +// 或者使用自动导入插件 +import Components from 'unplugin-vue-components/vite' +import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' + +export default defineConfig({ + plugins: [ + react(), + Components({ + resolvers: [ + AntDesignVueResolver({ + importStyle: 'less' // 按需导入样式 + }) + ] + }) + ] +}) +``` + +#### 工具库按需导入 +```typescript +// 原始导入方式(导入整个库) +// import _ from 'lodash' +// const result = _.map(arr, (item) => item.value) + +// 按需导入方式 +import { map } from 'lodash-es' +const result = map(arr, (item) => item.value) + +// 或者使用 lodash-unified-plugin +``` + +### 缓存策略 + +#### HTTP 缓存配置 +```typescript +// Nginx 配置示例 +location / { + try_files $uri $uri/ /index.html; + + # 关键资源缓存策略 + location ~* \.(js|css)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # 图片资源缓存 + location ~* \.(jpg|jpeg|png|gif|webp|svg)$ { + expires 6M; + add_header Cache-Control "public, max-age=15552000"; + } + + # HTML 不缓存 + location ~* \.html$ { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + } +} +``` + +#### 浏览器缓存优化 +```typescript +// 预加载关键资源 +// 在 index.html 中添加 + + + +// React 应用中的预加载 +const preloadComponent = (path: string) => { + const link = document.createElement('link') + link.rel = 'prefetch' + link.href = path + document.head.appendChild(link) +} + +// 根据路由预加载 +const routes = [ + { path: '/dashboard', preload: () => preloadComponent('/assets/dashboard-[hash].js') } +] +``` + +### 性能监控 + +#### 运行时性能监控 +```typescript +// 性能监控工具 +class PerformanceMonitor { + private metrics: Map = new Map() + + measure(name: string, fn: () => void) { + const start = performance.now() + fn() + const duration = performance.now() - start + + this.metrics.set(name, duration) + + // 开发环境打印性能指标 + if (import.meta.env.DEV) { + console.log(`[Performance] ${name}: ${duration.toFixed(2)}ms`) + } + } + + getMetrics() { + return Object.fromEntries(this.metrics) + } +} + +// 使用示例 +const monitor = new PerformanceMonitor() +monitor.measure('app-init', () => { + initializeApp() +}) +``` + +#### 构建 time analysis +```typescript +import { defineConfig } from 'vite' + +export default defineConfig({ + build: { + // 构建时间限制 + chunkSizeWarningLimit: 1000, + + // 构建并行化 + parallel: true, + + // 构建统计信息 + reportCompressedSize: false + } +}) + +// 使用 time 命令测量构建时间 +// time npm run build +``` + +## 实际案例分析 + +### 大型 React 项目优化 + +#### 问题场景 +- 🔧 项目包含 200+ 组件 +- 📦 构建后的 main.js 超过 2MB +- 🚀 首屏加载时间 5-8 秒 + +#### 优化方案 +```typescript +// vite.config.ts +export default defineConfig({ + build: { + rollupOptions: { + output: { + manualChunks: { + // 核心框架 + 'react-core': ['react', 'react-dom', 'react-router-dom'], + + // UI 组件库 + 'antd-core': ['antd'], + 'antd-icons': ['@ant-design/icons'], + + // 状态管理 + 'state-management': ['zustand', 'immer'], + + // 工具库 + 'utils': ['lodash-es', 'dayjs', 'axios'], + + // 业务模块 + 'business-user': [/src\/modules\/user/], + 'business-order': [/src\/modules\/order/], + 'business-product': [/src\/modules\/product/] + } + } + } + } +}) +``` + +#### 优化结果 +- ✅ main.js 减少到 800KB +- ✅ 首屏加载时间降低到 2-3 秒 +- ✅ 构建时间从 3 分钟减少到 1 分钟 + +### TypeScript 项目编译优化 + +#### 编译速度优化 +```typescript +// tsconfig.json +{ + "compilerOptions": { + "incremental": true, // 增量编译 + "tsBuildInfoFile": ".tsbuildinfo", + "skipLibCheck": true, // 跳过类型声明文件检查 + "skipDefaultLibCheck": true + }, + + "references": [ // 项目引用 + { "path": "./packages/component" } + ] +} +``` + +#### 类型检查性能 +```typescript +// 按需类型检查 +// .githooks/pre-commit +#!/bin/bash +git diff --cached --name-only | grep '\.tsx?$' | xargs npx tsc --noEmit + +// 或者在开发环境只检查当前文件 +// IDE 配置 TypeScript Server 模式 +``` + +### 组件库开发优化 + +#### 库模式配置 +```typescript +// vite.config.ts (组件库) +export default defineConfig({ + build: { + lib: { + entry: path.resolve(__dirname, 'src/index.ts'), + name: 'MyComponentLibrary', + fileName: (format) => `my-component-library.${format}.js`, + formats: ['es', 'umd', 'cjs'] + }, + rollupOptions: { + // 外部化 react + external: ['react', 'react-dom'], + output: { + globals: { + react: 'React', + 'react-dom': 'ReactDOM' + } + } + }, + // 压缩 + minify: 'terser', + sourcemap: true + } +}) +``` + +## 监控与调试 + +### 开发环境监控 + +```typescript +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [ + // 性能监控插件 + { + name: 'performance-monitor', + transform(code, id) { + // 监控大文件 + if (code.length > 100000) { + console.warn(`[Large File] ${id}: ${code.length} bytes`) + } + return null + } + } + ] +}) +``` + +### 生产环境调试 + +```typescript +// Source Map 配置 +export default defineConfig({ + build: { + sourcemap: true, // 生产环境生成 Source Map + sourceMapDebug: false + } +}) + +// 部署环境配置错误的 Source Map +// 设置为 true 便于问题追踪,false 提高安全性 +``` + +## 最佳实践总结 + +### ✅ 推荐实践 + +1. **依赖管理** + - 使用 `include` 明确指定预构建依赖 + - 定期清理依赖缓存 + - 优先选择 ESM 格式的包 + +2. **代码组织** + - 按路由和功能模块进行代码分割 + - 使用动态导入实现懒加载 + - 合理组织组件和工具函数 + +3. **性能优化** + - 启用 Tree-shaking 移除死代码 + - 使用现代压缩工具 + - 优化资源加载策略 + +4. **监控分析** + - 定期分析构建产物 + - 监控应用运行性能 + - 持续优化配置 + +### ❌ 避免问题 + +1. **过度配置** + - 不要过度使用配置插件 + - 避免不必要的依赖 + - 保持配置简洁 + +2. **忽略缓存** + - 不要忽略依赖缓存的清理 + - 注意缓存失效策略 + - 合理配置缓存时间 + +3. **盲目优化** + - 不要在没有性能问题时过度优化 + - 优先解决真正的性能瓶颈 + - 基于数据驱动的优化决策 + +## 工具推荐 + +### 构建分析工具 +- **vite-bundle-visualizer** - 构建产物可视化 +- **rollup-plugin-visualizer** - 深度构建分析 +- **source-map-explorer** - Source Map 分析 + +### 性能测试工具 +- **Lighthouse** - 页面性能评估 +- **WebPageTest** - 多地性能测试 +- **Chrome DevTools Performance** - 运行时性能分析 + +优化是一个持续的过程,需要根据项目的具体情况和性能数据来调整策略。通过合理的配置和分析工具,可以让 Vite 项目在各种场景下表现优异。 + +相关文档: +- [[DEV/VITE/配置详解.md]] - 详细配置说明 +- [[DEV/VITE/架构原理.md]] - 理解优化原理 +- [[DEV/VITE/README.md]] - 快速入门指南 diff --git a/DEV/VITE/架构原理.md b/DEV/VITE/架构原理.md new file mode 100644 index 0000000..88d5f92 --- /dev/null +++ b/DEV/VITE/架构原理.md @@ -0,0 +1,903 @@ +--- +tags: [DEV, VITE, architecture, internals] +create time: 2026-04-18 +--- + +# Vite 架构原理 + +## 核心设计理念 + +Vite 的核心创新在于**利用浏览器原生能力和分层优化策略**,将开发环境和生产环境采用完全不同的处理方式。 + +```mermaid +graph TB + A[用户请求] --> B{环境判断} + B -->|开发环境| C[Dev Server] + B -->|生产环境| D[Pre-built Assets] + C --> E[ESM 按需编译] + E --> F[即时返回] + D --> G[Rollup 优化产物] + G --> H[CDN/HTTP 服务器] +``` + +## 开发环境架构 + +### 服务启动流程 + +```mermaid +sequenceDiagram + participant CLI as 用户命令行 + participant Config as 配置加载器 + participant Server as Dev Server + participant Plugin as 插件系统 + participant Monitor as 文件监听器 + + CLI->>Config: 读取配置文件 + Config->>Server: 创建服务器实例 + Server->>Plugin: 注册所有插件 + Plugin->>Server: 返回钩子函数 + Server->>Monitor: 启动文件监听 + Monitor->>Server: 建立连接 + Server-->>CLI: 服务器启动完成 +``` + +### HTTP 请求处理流程 + +```mermaid +graph TD + A[HTTP 请求] --> B[中间件层] + B --> C{请求类型判断} + C -->|HTML 文件| D[响应 HTML] + C -->|JS/TS 文件| E[transform 链] + C -->|CSS 文件| F[CSS 处理器] + C -->|静态资源| G[资源处理] + E --> H[Plugin 转换] + H --> I[返回编译后代码] + F --> I + G --> J[返回资源引用] +``` + +### 请求处理实现 + +```typescript +class ViteDevServer { + private pluginContainer: PluginContainer + private fileWatcher: FSWatcher + private moduleGraph: ModuleGraph + + async handleRequest(req: IncomingMessage, res: ServerResponse) { + const url = req.url! + + try { + // 1. 处理 HTML 入口文件 + if (url.endsWith('.html')) { + await this.handleHtmlRequest(url, res) + return + } + + // 2. 处理模块请求 + if (this.isModuleRequest(url)) { + const module = await this.transformModule(url) + this.sendModuleResponse(module, res) + return + } + + // 3. 处理静态资源 + const asset = await this.resolveAsset(url) + if (asset) { + this.sendAssetResponse(asset, res) + return + } + } catch (error) { + this.handleError(error, res) + } + } +} +``` + +## 模块系统实现 + +### 模块图结构 + +```mermaid +graph LR + A[main.tsx] --> B[App.tsx] + A --> C[router.tsx] + B --> D[Header.tsx] + B --> E[Counter.tsx] + C --> F[Pages.tsx] + E --> G[useState.ts] + E --> H[useEffect.ts] +``` + +### 模块节点实现 + +```typescript +class ModuleNode { + public readonly id: string + public url: string + public file: string + + // 依赖关系 + public importers = new Set() + public importedModules = new Set() + public importedBindings: Record = {} + + // 转换结果 + public transformResult: TransformResult | null = null + public lastHMRTimestamp = 0 + + // SSR 信息 + public ssrModule: any = null + public ssrTransformResult: TransformResult | null = null + + constructor(id: string) { + this.id = id + this.url = id + this.file = path.normalize(id) + } +} + +class ModuleGraph { + private modules = new Map() + private fileToModulesMap = new Map>() + + async ensureEntryFromUrl(url: string): Promise { + const resolved = await this.resolveUrl(url) + return this.ensureModule(resolved.id, resolved.url) + } + + async ensureModule( + id: string, + url: string, + ssr?: boolean + ): Promise { + let module = this.modules.get(id) + + if (!module) { + module = new ModuleNode(id) + this.modules.set(id, module) + + // 建立文件映射 + const file = path.resolve(id) + let fileModules = this.fileToModulesMap.get(file) + if (!fileModules) { + fileModules = new Set() + this.fileToModulesMap.set(file, fileModules) + } + fileModules.add(module) + } + + return module + } + + updateModule(module: ModuleNode, transformed: TransformResult) { + module.transformResult = transformed + module.lastHMRTimestamp = Date.now() + } + + /** + * 失效模块及其导入者 + */ + invalidateModule(mod: ModuleNode): void { + mod.transformResult = null + mod.ssrModule = null + mod.ssrTransformResult = null + + // 级联失效所有导入者 + const invalidators = new Set() + const queue: ModuleNode[] = [...mod.importers] + + while (queue.length > 0) { + const importer = queue.pop()! + invalidators.add(importer) + + // 如果导入者的代码需要重新编译 + if (!importer.transformResult) { + importer.importers.forEach(dep => { + if (!invalidators.has(dep)) { + queue.push(dep) + } + }) + } + } + } + + /** + * 检测循环依赖 + */ + hasCircularDependency(module: ModuleNode): boolean { + const visited = new Set() + const stack = [module.id] + + while (stack.length > 0) { + const currentId = stack.pop()! + + if (visited.has(currentId)) { + console.warn(`[vite] Circular dependency detected: ${currentId}`) + return true + } + + visited.add(currentId) + const currentModule = this.modules.get(currentId) + + if (currentModule) { + currentModule.importedModules.forEach(dep => { + if (!visited.has(dep.id)) { + stack.push(dep.id) + } + }) + } + } + + return false + } +} +``` + +## ES Module 编译器 + +### 源码转换流水线 + +```mermaid +graph TD + A[原始源代码] --> B[Plugin Transform 钩子] + B --> C[语法转换] + C --> D[依赖注入] + D --> E[包装 ESM] + E --> F[生成 Source Map] + F --> G[返回处理后的代码] +``` + +### 依赖预构建 + +```typescript +class DepsOptimizer { + private depsCacheDir: string + private scanner: DepsOptimizer + + async scanImports(): Promise { + const entries = await this.getEntryPoints() + + const discovered = await esbuild.context({ + entryPoints: entries, + bundle: true, + write: false, + onEnd: (result) => { + const dependencies = this.extractDependencies(result.metafile) + this.updateOptimizedDeps(dependencies) + } + }) + + return discovered + } + + async optimizeDeps(deps: Record): Promise { + const optimizedDeps = new Map() + + for (const [id, file] of Object.entries(deps)) { + try { + // 转换为 ESM + const result = await esbuild.build({ + entryPoints: [file], + bundle: true, + format: 'esm', + target: 'esnext', + write: false, + packages: 'external' + }) + + optimizedDeps.set(id, file) + + // 缓存到 disk + await this.saveOptimizedDep(id, result) + } catch (e) { + console.error(`Failed to optimize dependency: ${id}`, e) + } + } + } + + async saveOptimizedDep(id: string, result: BuildResult): Promise { + const outputPath = path.join(this.depsCacheDir, `${id}.js`) + const metaPath = path.join(this.depsCacheDir, `${id}.js.meta.json`) + + await fs.writeFile( + outputPath, + result.outputFiles[0].text, + 'utf-8' + ) + + await fs.writeFile( + metaPath, + JSON.stringify({ + file: id, + src: path.basename(outputPath) + }), + 'utf-8' + ) + } +} +``` + +## 热模块替换 (HMR) + +### HMR 工作机制 + +```mermaid +sequenceDiagram + participant File as 文件系统 + participant Watcher as 监听器 + participant Server as Dev Server + participant WS as WebSocket + participant Client as 浏览器客户端 + + File->>Watcher: 文件变更 + Watcher->>Server: 触发 change 事件 + Server->>Server: 失效相关模块 + Server->>Server: 重新编译变更模块 + Server->>WS: 发送 HMR 更新 + WS->>Client: 接收更新通知 + Client->>Client: 执行模块替换 + Client->>Server: 发送确认消息 +``` + +### HMR 协议 + +```typescript +// 服务端发送的 HMR 消息类型 +interface HMRCustomEvent { + type: 'custom' + event: string + data: any +} + +interface HMRUpdateEvent { + type: 'update' + updates: Array<{ + type: 'js-update' | 'css-update' | 'static-update' + path: string + acceptedPath: string + timestamp: number + }> +} + +interface HMRConnectedEvent { + type: 'connected' +} + +interface HMRPruneEvent { + type: 'prune' + paths: string[] +} + +// 客户端处理实现 +class HMRClient { + private ws: WebSocket + private pendingImports = new Map>() + + async handleUpdate(updates: HMRUpdateEvent['updates']) { + for (const update of updates) { + switch (update.type) { + case 'js-update': + await this.handleJSModuleUpdate(update) + break + case 'css-update': + await this.handleCSSUpdate(update) + break + case 'static-update': + this.handleStaticUpdate(update) + break + } + } + } + + private async handleJSModuleUpdate(update: Extract) { + const { path, timestamp, acceptedPath } = update + + // 使用浏览器原生 import API 重新导入模块 + const importPromise = import(`${path}?t=${timestamp}`).then((mod) => { + // 查找模块的热替换处理器 + const modUrl = acceptedPath || path + const modObject = window.__vite_module_cache__[modUrl] + + if (modObject && modObject.hot) { + // 触发热替换处理器 + modObject.hot.accept(mod) + + // 执行清理回调 + if (modObject.hot._dispose) { + modObject.hot._dispose() + } + } + }) + + this.pendingImports.set(path, importPromise) + } +} +``` + +### 组件级 HMR + +```typescript +// React 组件的 HMR 实现 +import { createHot } from '@vitejs/plugin-react/client' + +// 在开发环境中,Vite 会自动注入 +if (import.meta.hot) { + import.meta.hot.accept('./App.tsx', (newModule) => { + // React Fast Refresh 处理 + const prevComponent = window.__vite_plugin_react_component__ + window.__vite_plugin_react_component__ = newModule.default + + // 触发组件重新渲染 + window.__vite_plugin_react_rerender__() + }) +} +``` + +## 插件系统架构 + +### 插件生命周期 + +```mermaid +graph TD + A[Config 阶段] --> B[ConfigResolved] + B --> C[ConfigureServer] + C --> D[BuildStart] + D --> E[Transform Phase] + E --> F[Load Phase] + F --> G[BuildEnd] + G --> H[CloseBundle] +``` + +### 插件容器实现 + +```typescript +class PluginContainer { + private plugins: Plugin[] + private hooks: Record + + constructor(plugins: Plugin[]) { + this.plugins = plugins + this.hooks = {} + this.registerHooks(plugins) + } + + private registerHooks(plugins: Plugin[]) { + const hookNames: validHooks[] = [ + 'buildStart', 'buildEnd', + 'resolveId', 'load', 'transform', + 'configureServer' + ] + + hookNames.forEach(hookName => { + this.hooks[hookName] = plugins + .map(plugin => plugin[hookName]) + .filter(Boolean) + }) + } + + async resolveId(id: string, importer?: string): Promise { + const resolveHooks = this.hooks['resolveId'] + + for (const hook of resolveHooks) { + const result = await hook.call(this, id, importer) + + if (result) { + return result + } + } + + return null + } + + async load(id: string): Promise { + const loadHooks = this.hooks['load'] + + for (const hook of loadHooks) { + const result = await hook.call(this, id) + + if (result) { + return result + } + } + + return null + } + + async transform(code: string, id: string): Promise { + const transformHooks = this.hooks['transform'] + let result = { code, map: null } + + for (const hook of transformHooks) { + const transformed = await hook.call(this, result.code, id) + + if (transformed) { + result = { + code: transformed.code, + map: transformed.map || result.map + } + } + } + + return result.code !== code ? result : null + } +} +``` + +## 构建系统 (生产环境) + +### Rollup 集成 + +```mermaid +graph TD + A[入口文件] --> B[依赖图构建] + B --> C[模块解析] + C --> D[代码转换] + D --> E[代码分割] + E --> F[Tree Shaking] + F --> G[minification] + G --> H[输出生成] +``` + +### 构建流程实现 + +```typescript +class BuildSystem { + async build(config: ResolvedConfig): Promise { + // 1. 分析入口文件 + const entries = this.resolveEntries(config) + + // 2. 构建模块图 + const moduleGraph = await this.buildModuleGraph(entries) + + // 3. 代码优化 + const optimizedModules = await this.optimizeModules(moduleGraph) + + // 4. 代码分割 + const chunks = this.splitChunks(optimizedModules) + + // 5. 压缩混淆 + const minifyResults = await this.minifyChunks(chunks) + + // 6. 生成输出 + const outputs = this.generateOutputs(minifyResults) + + return { modules: outputs, warnings: [], errors: [] } + } + + private async optimizeModules(moduleGraph: ModuleGraph): Promise { + const modules = Array.from(moduleGraph.modules.values()) + + // 应用插件转换 + for (const module of modules) { + if (module.transformResult) { + const result = await this.pluginContainer.transform( + module.transformResult.code, + module.id + ) + + if (result) { + module.transformResult = result + } + } + } + + return modules + } + + private splitChunks(modules: ModuleNode[]): Chunk[] { + const chunks: Map = new Map() + + // 识别共享依赖 + const sharedDeps = this.findSharedDependencies(modules) + + // 按配置生成代码分割 + const manualChunksConfig = { + 'vendor-react': ['react', 'react-dom'], + 'vendor-ui': ['antd'] + } + + Object.entries(manualChunksConfig).forEach(([chunkName, depNames]) => { + const chunkModules = modules.filter(mod => + depNames.some(dep => mod.id.includes(dep)) + ) + + chunks.set(chunkName, { + name: chunkName, + modules: chunkModules, + fileName: `${chunkName}.js` + }) + }) + + return Array.from(chunks.values()) + } + + private async minifyChunks(chunks: Chunk[]): Promise { + const results: MinifyResult[] = [] + + for (const chunk of chunks) { + const code = this.generateChunkCode(chunk) + + // 使用 terser 进行压缩 + const minified = await terser.minify(code, { + compress: { + drop_console: true, + pure_funcs: ['console.log', 'console.info'] + }, + mangle: { + toplevel: true, + properties: { + regex: /^_/ + } + } + }) + + if (minified.code) { + results.push({ + chunkName: chunk.name, + code: minified.code, + map: minified.map + }) + } + } + + return results + } +} +``` + +## 性能优化机制 + +### 缓存策略 + +```mermaid +graph TD + A[请求模块] --> B{缓存检查} + B -->|内存缓存命中| C[返回缓存] + B -->|磁盘缓存命中| D[加载并返回] + B -->|缓存未命中| E[编译模块] + E --> F[写入缓存] + F --> C +``` + +### 多层缓存实现 + +```typescript +class CacheSystem { + private memoryCache = new Map() + private diskCache: DiskCache + private etagCache = new Map() + + async get(key: string): Promise { + // 1. 检查内存缓存 + const memEntry = this.memoryCache.get(key) + if (memEntry && !this.isExpired(memEntry)) { + return memEntry.content + } + + // 2. 检查磁盘缓存 + try { + const diskEntry = await this.diskCache.get(key) + if (diskEntry) { + // 写入内存缓存 + this.memoryCache.set(key, { + content: diskEntry.content, + etag: diskEntry.etag, + timestamp: Date.now() + }) + return diskEntry.content + } + } catch (e) { + // 缓存损坏,忽略 + } + + return null + } + + async set(key: string, content: string, options: CacheOptions = {}): Promise { + const etag = this.generateETag(content) + const entry: CacheEntry = { + content, + etag, + timestamp: Date.now(), + ttl: options.ttl || 0 + } + + // 写入内存缓存 + this.memoryCache.set(key, entry) + + // 持久化到磁盘 + if (options.persistent) { + await this.diskCache.set(key, { + content, + etag + }) + } + } + + private generateETag(content: string): string { + const hash = crypto.createHash('sha1') + hash.update(content) + return hash.digest('hex') + } + + private isExpired(entry: CacheEntry): boolean { + if (entry.ttl === 0) return false + return Date.now() - entry.timestamp > entry.ttl + } +} +``` + +### HTTP 缓存优化 + +```typescript +class HTTPCacheManager { + setupCacheHeaders(res: ServerResponse, content: string): void { + const etag = this.generateETag(content) + const lastModified = new Date().toUTCString() + + // 设置缓存头 + res.setHeader('Cache-Control', 'public, max-age=31536000, immutable') + res.setHeader('ETag', etag) + res.setHeader('Last-Modified', lastModified) + + // 检查客户端缓存 + if (this.request) { + const ifNoneMatch = this.request.headers['if-none-match'] + const ifModifiedSince = this.request.headers['if-modified-since'] + + if ((ifNoneMatch && ifNoneMatch === etag) || + (ifModifiedSince && ifModifiedSince === lastModified)) { + res.statusCode = 304 + res.end() + return true + } + } + + return false + } +} +``` + +## 进阶架构特性 + +### 虚拟模块系统 + +```typescript +// 虚拟模块插件示例 +export function virtualModulePlugin(): Plugin { + return { + name: 'virtual-module', + resolveId(id) { + if (id === 'virtual:env') { + return '\0virtual:env' + } + }, + load(id) { + if (id === '\0virtual:env') { + const env = { + NODE_ENV: process.env.NODE_ENV, + VERSION: '1.0.0' + } + return `export default ${JSON.stringify(env)}` + } + } + } +} + +// 使用虚拟模块 +import env from 'virtual:env' +console.log(env) // { NODE_ENV: 'development', VERSION: '1.0.0' } +``` + +### Source Map 生成 + +```typescript +class SourceMapGenerator { + async generateSourceMap( + originalCode: string, + transformedCode: string, + filePath: string + ): Promise { + const sourceMap: SourceMap = { + version: 3, + file: path.basename(filePath), + sourceRoot: '', + sources: [filePath], + names: [], + mappings: '' + + } + + // 使用 magic-string 生成精确的 mappings + const magicString = new MagicString(originalCode) + const transformed = new MagicString(transformedCode) + + // 映射转换的位置 + const mappings = this.generateMappings(originalCode, transformedCode) + sourceMap.mappings = mappings + + return sourceMap + } + + private generateMappings(original: string, transformed: string): string { + // 使用 VLQ 编码生成 mappings + const originalLines = original.split('\n') + const transformedLines = transformed.split('\n') + + const mappings: string[] = [] + let currentLine = 0 + let currentColumn = 0 + + for (let i = 0; i < transformedLines.length; i++) { + const lineMappings: number[][] = [] + + for (let j = 0; j < transformedLines[i].length; j++) { + // 找到原始代码中的对应位置 + const { line, column } = this.findOriginalPosition( + transformedLines[i], + j, + originalLines + ) + + if (line !== -1) { + lineMappings.push([ + j - currentColumn, // 生成的列偏移 + line - currentLine, // 原始行偏移 + column // 原始列 + ]) + + currentColumn = j + currentLine = line + } + } + + // 使用 VLQ 编码 + mappings.push(lineMappings.map(mapping => + this.encodeVLQ(mapping) + ).join(',')) + + currentLine = transformedLines.length + currentColumn = 0 + } + + return mappings.join(';') + } +} +``` + +## 架构对比 + +### 与传统构建工具对比 + +| 特性 | Vite | Webpack | Parcel | +|------|------|---------|--------| +| 启动原理 | ESM 按需编译 | 全量打包 | 零配置打包 | +| HMR 机制 | 模块级更新 | 全局重编译 | 智能更新 | +| 构建产出 | Rollup | 内置 | 内置 | +| 配置复杂度 | 低 | 高 | 极低 | +| 插件生态 | 新兴 | 成熟 | 有限 | +| 开发体验 | 优秀 | 可优化 | 良好 | +| 生产构建 | 高质量 | 高质量 | 良好 | + +### 适用场景分析 + +```mermaid +graph TD + A[项目类型] --> B{选择构建工具} + B -->|React/Vue SPA| C[Vite 推荐] + B -->|复杂构建需求| D[Webpack] + B -->|零配置快速开发| E[Parcel] + B -->|Next.js SSR| F[Next.js 内置] + + C --> G[👍 启动快
👍 HMR 优秀
👍 配置简单] + D --> H[👍 生态成熟
👍 高度可定制
👎 复杂] + E --> I[👍 开箱即用
👍 智能配置
👎 生态有限] +``` + +理解 Vite 的架构原理,不仅能帮助我们更好地使用它,还能为开发自定义插件和解决复杂问题提供理论支撑。Vite 的成功在于它巧妙地利用了现代浏览器和工具链的能力,选择了正确的技术路径。 + +相关文档: +- [[DEV/VITE/配置详解.md]] - 配置与插件开发 +- [[DEV/VITE/优化实践.md]] - 架构层面的性能优化 diff --git a/DEV/VITE/配置详解.md b/DEV/VITE/配置详解.md new file mode 100644 index 0000000..6c51de2 --- /dev/null +++ b/DEV/VITE/配置详解.md @@ -0,0 +1,695 @@ +--- +tags: [DEV, VITE, configuration, typescript] +create time: 2026-04-18 +--- + +# Vite 配置详解 + +## 配置文件结构 + +Vite 配置文件支持多种格式:`vite.config.js`、`vite.config.ts`,推荐使用 TypeScript 获得类型提示。 + +完整配置结构示例: + +```typescript +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import path from 'path' + +export default defineConfig({ + // 插件配置 + plugins: [react()], + + // 路径别名 + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + '@components': path.resolve(__dirname, './src/components'), + '@hooks': path.resolve(__dirname, './src/hooks') + } + }, + + // 开发服务器配置 + server: { + port: 3000, + host: true, + open: true, + cors: true + }, + + // 构建配置 + build: { + outDir: 'dist', + sourcemap: true + } +}) +``` + +## 核心配置项 + +### resolve 模块解析 + +#### 路径别名 +```typescript +resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + // 支持目录别名 + '@components': path.resolve(__dirname, './src/components'), + '@utils': path.resolve(__dirname, './src/utils') + } +} +``` + +#### 扩展名解析 +```typescript +resolve: { + extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] +} +``` + +### server 开发服务器 + +```typescript +server: { + // 端口配置 + port: 3000, + strictPort: false, // 端口被占用时自动尝试下一个 + host: true, // 监听所有网络地址 + + // 自动打开浏览器 + open: true, + openPage: '/dashboard', + + // CORS 配置 + cors: true, + + // 代理配置 + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '') + }, + '/assets': { + target: 'http://localhost:9000', + changeOrigin: true + } + }, + + // HMR 配置 + hmr: { + overlay: true, // 显示错误覆盖层 + port: 24678 // HMR WebSocket 端口 + }, + + // 中间件模式 (用于 SSR) + middlewareMode: false +} +``` + +### load 环境变量 + +```bash +# .env.development +VITE_API_URL=http://localhost:8080/api +VITE_APP_NAME=Dev Environment + +# .env.production +VITE_API_URL=https://api.example.com +VITE_APP_NAME=Production +``` + +```typescript +// 类型定义 +interface ImportMetaEnv { + readonly VITE_API_URL: string + readonly VITE_APP_NAME: string +} + +// 使用环境变量 +const apiUrl = import.meta.env.VITE_API_URL +const appName = import.meta.env.VITE_APP_NAME + +// 自定义环境变量访问 +export default defineConfig(({ mode }) => { + return { + define: { + __APP_VERSION__: JSON.stringify(process.env.npm_package_version), + __ENV__: JSON.stringify(mode) + } + } +}) +``` + +### build 构建配置 + +```typescript +build: { + // 输出目录 + outDir: 'dist', + assetsDir: 'assets', + + // Source Map 配置 + sourcemap: false, // 生产环境关闭 + sourcemapExcludeSources: false, + + // 压缩配置 + minify: 'terser', // terser | esbuild + terserOptions: { + compress: { + drop_console: true, // 移除 console + drop_debugger: true, // 移除 debugger + pure_funcs: ['console.log', 'console.info'] + } + }, + + // 代码分割配置 + rollupOptions: { + output: { + // 手动代码分割 + manualChunks: { + 'vendor-react': ['react', 'react-dom'], + 'vendor-ui': ['antd', '@ant-design/icons'], + 'vendor-utils': ['lodash', 'dayjs'] + }, + // 文件名模式 + chunkFileNames: 'js/[name]-[hash].js', + entryFileNames: 'js/[name]-[hash].js', + assetFileNames: '[ext]/[name]-[hash].[ext]' + } + }, + + // 构建优化 + chunkSizeWarningLimit: 1000, // 警告限制 + rollupOptions: { + output: { + // 内联动态导入 + inlineDynamicImports: false, + // 保留模块结构 + preserveModules: false + } + }, + cssCodeSplit: true, + reportCompressedSize: false, + target: 'es2015' +} +``` + +### preview 预览配置 + +```typescript +preview: { + port: 4173, + strictPort: false, + host: true, + open: true, + + // 预览服务器配置 + cors: true, + + // 中间件 + middlewares: [ + // 自定义中间件 + ] +} +``` + +## CSS 配置 + +### CSS Modules +```typescript +css: { + modules: { + // 命名规范 + localsConvention: 'camelCase', // camelCase | camelCaseOnly | dashes | dashesOnly + + // 作用域行为 + scopeBehaviour: 'local', // local | global + + // 类名生成 + generateScopedName: '[name]__[local]___[hash:base64:5]', + + // Hash 生成函数 + hashPrefix: 'prefix', + + // 全局模块路径 + globalModulePaths: [/node_modules/] + } +} +``` + +### CSS 预处理器 +```typescript +css: { + preprocessorOptions: { + scss: { + additionalData: `@import "@/styles/variables.scss";`, + api: 'modern-compiler' // 使用现代编译器 + }, + less: { + modifyVars: { + 'primary-color': '#1890ff' + }, + javascriptEnabled: true + } + } +} +``` + +### PostCSS 配置 +```javascript +// postcss.config.js +export default { + plugins: { + autoprefixer: {}, + 'cssnano': { + preset: 'default' + } + } +} +``` + +## 插件系统 + +### 插件配置流程 + +```mermaid +graph TD + A[vite.config.ts] --> B[插件导入] + B --> C[插件配置] + C --> D[插件注册] + D --> E[构建流程] + E --> F[插件执行] +``` + +### 官方插件 + +#### React 插件 +```typescript +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [ + react({ + // Babel 转换 + babel: { + plugins: ['emotion'] + }, + // JSX 运行时 + jsxRuntime: 'automatic', // classic | automatic + // 开发工具 + devtools: true, + // 包含 + include: /\.(jsx|js|tsx|ts)$/, + // 排除 + exclude: /\.node_modules/ + }) + ] +}) +``` + +#### Vue 插件 +```typescript +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [ + vue({ + // Vue 编译器选项 + template: { + compilerOptions: { + isCustomElement: (tag) => tag.includes('-'), + whitespace: 'condense' + } + }, + // 脚本配置 + script: { + defineModel: true, + propsDestructure: true + }, + // 样式配置 + style: { + scoped: true + } + }) + ] +}) +``` + +### 自定义插件 + +#### 基础插件结构 +```typescript +import type { Plugin } from 'vite' + +export function myCustomPlugin(): Plugin { + return { + name: 'my-custom-plugin', + + // 配置阶段 + config(config) { + return { + // 返回配置修改 + } + }, + + configResolved(config) { + // 配置已解析 + console.log('Vite config resolved:', config) + }, + + // 配置开发服务器 + configureServer(server) { + // 自定义服务器中间件 + server.middlewares.use((req, res, next) => { + if (req.url === '/custom-endpoint') { + res.statusCode = 200 + res.setHeader('Content-Type', 'application/json') + res.end(JSON.stringify({ message: 'Custom response' })) + } else { + next() + } + }) + + // 返回清理函数 + return () => { + console.log('Server closed') + } + }, + + // 转换钩子 + transform(code, id) { + // 转换代码 + if (id.endsWith('.custom')) { + return { + code: convertCustomFormat(code), + map: null + } + } + }, + + // 模块解析钩子 + resolveId(source) { + // 自定义模块解析 + if (source === 'virtual-module') { + return '\0virtual-module' + } + }, + + // 加载钩子 + load(id) { + // 加载模块内容 + if (id === '\0virtual-module') { + return 'export const msg = "Hello from virtual module"' + } + }, + + // 构建钩子 + buildStart() { + console.log('Build started') + }, + + buildEnd() { + console.log('Build completed') + } + } +} +``` + +#### 环境变量插件 +```typescript +import type { Plugin } from 'vite' + +export function envPlugin(): Plugin { + return { + name: 'env-plugin', + config(config, { mode }) { + // 加载环境变量 + const env = loadEnv(mode, process.cwd(), '') + + return { + define: { + 'import.meta.env': JSON.stringify(env) + } + } + } + } +} +``` + +### 第三方插件推荐 + +#### 路径别名插件 +```typescript +import { viteCommonjs } from '@originjs/vite-plugin-commonjs' + +export default defineConfig({ + plugins: [ + react(), + viteCommonjs() // 支持 CommonJS 模块 + ] +}) +``` + +#### 压缩插件 +```typescript +import viteCompression from 'vite-plugin-compression' + +export default defineConfig({ + plugins: [ + viteCompression({ + verbose: true, + disable: false, + threshold: 10240, + algorithm: 'gzip', + ext: '.gz' + }) + ] +}) +``` + +#### 组件按需加载 +```typescript +import Components from 'unplugin-vue-components/vite' +import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers' + +export default defineConfig({ + plugins: [ + Components({ + resolvers: [ + AntDesignVueResolver() + ] + }) + ] +}) +``` + +## TypeScript 配置 + +### tsconfig.json +```json +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src", "vite.config.ts"], + "references": [{ "path": "./tsconfig.node.json" }] +} +``` + +### tsconfig.node.json (为 Vite 配置文件提供类型) +```json +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} +``` + +## 高级配置 + +### 多入口配置 +```typescript +import { defineConfig } from 'vite' +import path from 'path' + +export default defineConfig({ + build: { + rollupOptions: { + input: { + main: path.resolve(__dirname, 'index.html'), + admin: path.resolve(__dirname, 'admin.html'), + landing: path.resolve(__dirname, 'landing.html') + } + } + } +}) +``` + +### 库模式配置 +```typescript +export default defineConfig({ + build: { + lib: { + entry: path.resolve(__dirname, 'src/index.ts'), + name: 'MyLibrary', + fileName: (format) => `my-library.${format}.js`, + formats: ['es', 'umd'] + }, + rollupOptions: { + external: ['react', 'react-dom'], + output: { + globals: { + react: 'React', + 'react-dom': 'ReactDOM' + } + } + } + } +}) +``` + +### SSR 配置 +```typescript +export default defineConfig({ + build: { + ssr: true, // 启用 SSR + outDir: 'dist/server', // SSR 输出目录 + rollupOptions: { + input: './src/entry-server.ts' + } + }, + server: { + middlewareMode: 'ssr' // SSR 模式 + } +}) +``` + +## 配置最佳实践 + +### 环境分离 +```typescript +// vite.config.ts +export default defineConfig(({ mode }) => { + return { + plugins: [ + mode === 'development' ? devPlugin() : prodPlugin() + ], + server: mode === 'development' ? devServerConfig : {} + } +}) +``` + +### 配置复用 +```typescript +// shared-config.ts +export const baseConfig = { + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + } +} + +export const devConfig = { + ...baseConfig, + server: { + port: 3000 + } +} + +export const buildConfig = { + ...baseConfig, + build: { + outDir: 'dist' + } +} +``` + +### 配置验证 +```typescript +function validateConfig(config: UserConfig) { + if (!config.plugins) { + throw new Error('Plugins are required') + } + + // 验证路径别名 + if (config.resolve?.alias) { + Object.entries(config.resolve.alias).forEach(([key, value]) => { + if (!path.isAbsolute(value)) { + throw new Error(`Alias ${key} must be an absolute path`) + } + }) + } +} +``` + +## 故障排查 + +### 配置加载问题 +```bash +# 调试配置加载 +DEBUG=vite:config npm run dev + +# 检查配置语法 +node -c vite.config.ts +``` + +### 路径解析问题 +```typescript +// 使用 debug 插件检查路径 +import { defineConfig } from 'vite' + +export default defineConfig({ + resolve: { + alias: { + 'debug': require.resolve('debug') + } + } +}) +``` + +### 插件冲突诊断 +```typescript +// 在插件开发中添加日志 +export function debugPlugin() { + return { + name: 'debug-plugin', + transform(code, id) { + console.log('Transforming:', id) + return null + } + } +} +``` + +配置是 Vite 项目的核心,合理的配置能显著提升开发体验和构建质量。根据项目规模和需求,逐步完善配置是最佳实践。 + +相关文档: +- [[DEV/VITE/架构原理.md]] - 理解配置的底层机制 +- [[DEV/VITE/优化实践.md]] - 配置优化技巧 diff --git a/assets/img/Pasted image 20260417125039.png b/assets/img/Pasted image 20260417125039.png new file mode 100644 index 0000000..062c999 Binary files /dev/null and b/assets/img/Pasted image 20260417125039.png differ diff --git a/assets/img/Pasted image 20260417125713.png b/assets/img/Pasted image 20260417125713.png new file mode 100644 index 0000000..96f2300 Binary files /dev/null and b/assets/img/Pasted image 20260417125713.png differ diff --git a/config/agent/file-creation-standards.md b/config/agent/file-creation-standards.md new file mode 100644 index 0000000..8059226 --- /dev/null +++ b/config/agent/file-creation-standards.md @@ -0,0 +1,63 @@ +--- +tags: [config, agent, standards] +create time: 2026-04-17 11:29 +update time: 2026-04-18 +--- + +# 文件创建规范 + +## 核心规则 + +**所有新文件必须遵循以下结构:** + +```yaml +--- +tags: [] +create time: YYYY-MM-DD HH:mm +--- + +# <标题> + +## 概述 +[简短描述] + +## 正文 +[内容] + +## 关联笔记 +- [[相关笔记]] +``` + +## 必填项 + +| 字段 | 格式 | 说明 | +|------|------|------| +| `tags` | 数组 | 必须存在 `[]`,智能填充相关标签 | +| `create time` | `YYYY-MM-DD HH:mm` | 当前系统时间 | + +## 文档结构 + +1. **## 概述**: 简短描述文档内容,不要保留占位文本 +2. **## 正文**: 主要内容,详略得当,注重拓展进阶 +3. **## 关联笔记**: 相关笔记 Wiki-links,若无可移除 + +## 内容规范 + +- **代码示例**: 优先 Go (后端) + React/TS (前端) +- **图表**: 选择性使用 Mermaid +- **风格**: 详略得当,注重实用性 + +## Agent 执行步骤 + +1. 读取模板: `[[config/template/default-template.md]]` +2. 创建文件并填充 `create time` +3. 智能填充 `tags` +4. 替换占位文本为实际内容 +5. 添加相关 Wiki-links + +## 特殊情况 + +配置文件、开发文件等可灵活调整结构,但需保留: +- `tags` 字段 +- `create time` 记录 +- 清晰的组织结构 diff --git a/config/agent/learning-index.md b/config/agent/learning-index.md new file mode 100644 index 0000000..88f6d4e --- /dev/null +++ b/config/agent/learning-index.md @@ -0,0 +1,33 @@ +--- +tags: [index, learning, cs] +create time: 2026-04-17 +--- + +# 计算机科学学习索引 + +## 学习领域导航 + +### 计算机技术基础 +- [[CS/README|CS]] - 计算机科学总览 + - [[CS/OS/README|OS]] - 操作系统 + - [[CS/NET/README|NET]] - 计算机网络 + - [[CS/NET/Authorization]] - Authorization 授权机制 + - [[CS/DB/README|DB]] - 数据库 + - [[CS/TOOLS/README|TOOLS]] - 工具使用 + +### 开发实践 +- [[DEV/GO/README|GO]] - Go语言服务端开发 +- [[DEV/REACT/README|REACT]] - React前端开发 + +## 目录缩写说明 + +| 缩写 | 全称 | 描述 | +|------|------|------| +| CS | Computer Science | 计算机科学 | +| OS | Operating Systems | 操作系统 | +| NET | Network | 计算机网络 | +| DB | Database | 数据库 | +| TOOLS | Tools | 工具使用 | +| DEV | Development | 开发实践 | +| GO | Go Language | Go语言 | +| REACT | React Framework | React框架 | diff --git a/config/plugin/插件配置.md b/config/plugin/插件配置.md new file mode 100644 index 0000000..4b92c60 --- /dev/null +++ b/config/plugin/插件配置.md @@ -0,0 +1,55 @@ +# Obsidian 插件配置 + +> 最后更新: 2026-04-20 + +## 核心插件 + +| 插件名称 | 说明 | 状态 | +|---------|------|------| +| daily-notes | 每日笔记 | 启用 | +| file-explorer | 文件浏览器 | 启用 | +| global-search | 全局搜索 | 启用 | +| switcher | 快速切换 | 启用 | +| graph | 关系图谱 | 启用 | +| backlink | 反向链接 | 启用 | +| outline | 大纲视图 | 启用 | +| templates | 模板 | 启用 | +| word-count | 字数统计 | 启用 | + +## 社区插件 + +### Better Export PDF (v1.11.0) +- **ID**: better-export-pdf +- **作者**: l1xnan +- **说明**: 导出笔记为 PDF,支持预览、添加书签大纲和页眉页脚 +- **仅桌面**: 是 + +### Claudian (v2.0.3) +- **ID**: claudian +- **作者**: Yishen Tu +- **说明**: 将 Claude Code 嵌入为 AI 协作者,提供文件读写、搜索、命令和多步工作流能力 +- **仅桌面**: 是 + +### Git (v2.38.2) +- **ID**: obsidian-git +- **作者**: Vinzent03 +- **说明**: 集成 Git 版本控制,支持自动备份和高级功能 +- **仅桌面**: 否 + +### Style Settings (v1.0.9) +- **ID**: obsidian-style-settings +- **作者**: mgmeyers +- **说明**: 提供主题、插件和代码片段 CSS 变量的调整控件 +- **仅桌面**: 否 + +### Templater (v2.19.0) +- **ID**: templater-obsidian +- **作者**: SilentVoid13 +- **说明**: 创建和使用模板 +- **仅桌面**: 否 + +## 配置文件位置 + +- 核心插件配置: `.obsidian/core-plugins.json` +- 社区插件列表: `.obsidian/community-plugins.json` +- 插件数据目录: `.obsidian/plugins/` diff --git a/config/template/default-template.md b/config/template/default-template.md new file mode 100644 index 0000000..7c1e7f2 --- /dev/null +++ b/config/template/default-template.md @@ -0,0 +1,18 @@ +--- +tags: [] +create time: <% tp.date.now("YYYY-MM-DD HH:mm") %> +--- + +# <% tp.file.title %> + +## 概述 + +添加笔记的概述或简短描述。 + +## 正文 + +在这里写下你的笔记内容... + +## 关联笔记 + +- 添加关联笔记的链接 diff --git a/金山办公作业/Week05/Docker 命名卷 vs 挂载.md b/金山办公作业/Week05/Docker 命名卷 vs 挂载.md new file mode 100644 index 0000000..70cced2 --- /dev/null +++ b/金山办公作业/Week05/Docker 命名卷 vs 挂载.md @@ -0,0 +1,276 @@ +--- +tags: [docker, 存储, 容器技术, 数据持久化] +create time: 2026-04-18 23:59 +--- + +# Docker 命名卷 vs 挂载 + +## 概述 + +本文档详细对比 Docker 中两种主要的数据存储方式:**命名卷(Named Volumes)** 和 **绑定挂载(Bind Mounts)**,帮助理解它们的适用场景、优缺点及最佳实践。 + +## 正文 + +### 一、核心概念 + +#### 命名卷(Named Volumes) + +由 Docker 管理的存储卷,存储在 Docker 管理的目录中(Linux 默认 `/var/lib/docker/volumes/`)。 + +```bash +# 创建命名卷 +docker volume create my-data + +# 使用命名卷启动容器 +docker run -d --name myapp -v my-data:/app/data nginx + +# 列出所有卷 +docker volume ls + +# 查看卷详情 +docker volume inspect my-data + +# 删除卷 +docker volume rm my-data +``` + +#### 绑定挂载(Bind Mounts) + +将主机上的文件或目录直接挂载到容器中,提供绝对路径映射。 + +```bash +# 使用绑定挂载 +docker run -d --name myapp -v /host/path:/container/path nginx + +# Windows 路径示例 +docker run -d --name myapp -v C:\Users\data:/app/data nginx +``` + +### 二、对比表格 + +| 特性 | 命名卷 | 绑定挂载 | +|------|--------|----------| +| **管理方式** | Docker 管理 | 用户管理 | +| **存储位置** | Docker 默认目录 | 用户指定的任意位置 | +| **跨平台兼容性** | ✅ 良好 | ❌ 路径差异大 | +| **权限处理** | ✅ Docker 自动处理 | ⚠️ 需手动配置 | +| **安全性** | ✅ 隔离性好 | ⚠️ 直接访问主机 | +| **备份难度** | ⚠️ 需特殊命令 | ✅ 直接复制 | +| **性能** | ✅ 优化过 | ⚠️ 可能有额外开销 | +| **开发调试** | ⚠️ 不便 | ✅ 实时同步 | + +### 三、深入分析 + +#### 3.1 命名卷详解 + +**优点:** +- **跨平台一致性**:Windows、macOS、Linux 使用相同的卷名称 +- **自动初始化**:首次使用时自动创建并初始化容器镜像中的数据 +- **权限管理**:Docker 自动处理 SELinux/AppArmor 权限问题 +- **备份方便**:可通过 `docker run --volumes-from` 备份 + +```bash +# 备份命名卷 +docker run --rm -v my-data:/data -v $(pwd):/backup alpine \ + tar czf /backup/my-data-backup.tar.gz /data + +# 恢复命名卷 +docker run --rm -v my-data:/data -v $(pwd):/backup alpine \ + tar xzf /backup/my-data-backup.tar.gz -C / +``` + +**缺点:** +- **位置不透明**:需通过 `docker volume inspect` 查看实际路径 +- **开发不便**:无法直接编辑主机上的文件 +- **清理复杂**:未使用的卷需手动清理 + +**适用场景:** +- ✅ **生产环境**:应用的数据库文件、配置文件 +- ✅ **微服务架构**:服务间共享数据 +- ✅ **跨平台开发**:团队混合使用不同操作系统 + +#### 3.2 绑定挂载详解 + +**优点:** +- **开发体验极佳**:实时代码同步,IDE 直接编辑 +- **访问方便**:直接操作主机文件系统 +- **调试友好**:可快速查看和修改容器内文件 +- **简单直接**:无需额外命令管理 + +```bash +# 开发环境常用挂载 +docker run -d --name dev-server \ + -v $(pwd):/app \ + -v /app/node_modules \ # 避免覆盖 node_modules + -p 8080:8080 \ + node:18 npm run dev +``` + +**缺点:** +- **权限问题**:Linux 下可能遇到 UID/GID 不匹配 +- **路径依赖**:绝对路径在团队协作中易出错 +- **安全风险**:容器可越界访问主机文件 +- **跨平台复杂**:Windows/macOS 路径格式不同 + +**权限处理示例:** +```bash +# 方法1:使用 --user 参数 +docker run --user $(id -u):$(id -g) -v $(pwd):/workdir ... + +# 方法2:修复权限 +docker run -v $(pwd):/data alpine chown -R $(id -u):$(id -g) /data + +# 方法3:使用匿名卷组合 +docker run -v $(pwd):/app -v /app/node_modules nginx +``` + +**适用场景:** +- ✅ **本地开发**:代码热重载、实时调试 +- ✅ **配置注入**:注入主机上的配置文件 +- ✅ **日志收集**:将容器日志直接输出到主机 +- ✅ **CI/CD**:挂载源代码进行构建 + +### 四、决策流程图 + +```mermaid +graph TD + A[需要持久化数据?] -->|否| D[无需卷] + A -->|是| B{是否需要实时编辑?} + B -->|是| C[绑定挂载 Bind Mount] + B -->|否| E{环境类型?} + E -->|开发| F[绑定挂载优先] + E -->|生产| G{是否跨平台?} + G -->|是| H[命名卷 Named Volume] + G -->|否| I[绑定挂载] +``` + +### 五、混合使用策略 + +最佳实践是结合两者优势: + +```yaml +# docker-compose.yml 示例 +version: '3.8' +services: + webapp: + build: . + volumes: + # 开发代码实时同步 + - ./:/app + # 生产环境依赖分离 + - /app/node_modules + # 配置文件(主机路径) + - ./config:/app/config:ro + # 持久化数据(命名卷) + - app-data:/app/data + +volumes: + app-data: + driver: local +``` + +**分层策略:** +1. **代码层**:绑定挂载(开发环境)/ 匿名卷(生产环境) +2. **配置层**:绑定挂载(RO 只读模式) +3. **数据层**:命名卷 +4. **依赖层**:匿名卷(避免覆盖) + +### 六、进阶技巧 + +#### 6.1 卷驱动 + +```bash +# 使用云存储驱动 +docker volume create --driver driver-name cloud-volume + +# NFS 挂载 +docker volume create --driver local \ + -o type=nfs \ + -o device=:/path/to/dir \ + -o o=addr=nfs-server-ip,rw \ + nfs-volume +``` + +#### 6.2 临时文件系统 + +```bash +# 使用临时文件系统(速度最快,容器删除后消失) +docker run -d --tmpfs /tmp:rw,size=100m,mode=1777 nginx +``` + +#### 6.3 清理未使用资源 + +```bash +# 清理未使用的卷 +docker volume prune + +# 清理所有未使用资源(谨慎使用) +docker system prune -a --volumes +``` + +### 七、常见问题排查 + +#### 问题1:卷权限不足 + +```bash +# 查看卷的实际权限 +docker run --rm -v my-data:/data alpine ls -la /data + +# 临时容器修复 +docker run --rm -v my-data:/data alpine chown -R 1000:1000 /data +``` + +#### 问题2:卷有数据但容器看不到 + +```bash +# 检查卷内是否有内容 +docker run --rm -v my-data:/data alpine ls -la /data + +# 检查镜像是否有 /app/data 目录 +docker run --rm nginx ls -la /app/data + +# 使用 --workdir 验证 +docker run --rm -w /app -v my-data:/app/data nginx ls -la +``` + +#### 问题3:Windows 下绑定挂载失败 + +```bash +# 确保在 Docker Desktop 设置中共享了驱动器 + +# 验证路径格式 +docker run -v C:/Users/data:/data alpine ls /data # ✅ 正确 +docker run -v C:\Users\data:/data alpine ls /data # ❌ 错误 +``` + +### 八、最佳实践总结 + +✅ **选择命名卷的情况:** +- 生产环境数据持久化 +- 需要跨平台兼容性 +- 多个容器共享数据 +- 数据库文件存储 +- 敏感数据隔离 + +✅ **选择绑定挂载的情况:** +- 本地开发环境 +- 需要实时编辑代码 +- 注入配置文件(RO 模式) +- CI/CD 流程 +- 收集日志文件 + +✅ **通用建议:** +- 生产环境优先使用命名卷 +- 开发环境使用绑定挂载提高效率 +- 混合使用实现最佳效果 +- 定期清理未使用的卷 +- 为关键卷添加备份策略 +- 使用只读挂载保护配置文件 +- 文档化所有挂载策略 + +## 关联笔记 + +- [[金山办公作业/Week05/Docker 基础命令]] +- [[金山办公作业/Week05/Docker Compose 实践]] +- [[金山办公作业/Week05/Docker 网络管理]] +- [[金山办公作业/Week05/容器化最佳实践]] diff --git a/金山办公作业/Week05/Nginx.md b/金山办公作业/Week05/Nginx.md new file mode 100644 index 0000000..165a7a5 --- /dev/null +++ b/金山办公作业/Week05/Nginx.md @@ -0,0 +1,203 @@ +```nginx +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip 压缩 + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; + + server { + listen 80; + server_name localhost; + + # 前端静态文件 + root /usr/share/nginx/html; + index index.html; + + # 前端路由支持 + location / { + try_files $uri $uri/ /index.html; + } + + # 后端 API 代理 + location /api/ { + proxy_pass http://backend:8080; + proxy_http_version 1.1; + + # 代理请求头 + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket 支持(如果需要) + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 超时设置 + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + } +} +``` + +这是一个非常标准、功能完善、优化级别很高的 Nginx 配置,通常用于部署一个结合了**前端 SPA(单页应用)**和**后端 API**的现代 Web 应用。 + +我将从以下几个部分为您进行详细的解读: + +--- + +## 🚀 整体结构与功能总结 + +**该配置的职责是:** + +1. **作为反向代理(Reverse Proxy)**:它接收所有来自外部(`localhost:80`)的请求。 +2. **服务前端静态资源**:负责提供基于 `root` 目录的 HTML、CSS、JS 等文件,并支持前端路由(SPA)。 +3. **转发后端 API 请求**:将所有以 `/api/` 开头的请求,转发给指定的后端服务(`http://backend:8080`)。 +4. **优化性能**:通过设置 Gzip 压缩、HTTP 连接优化和定时器设置,确保传输效率。 + +--- + +## 🔍 详细配置解读(按代码块) + +### 1. 全局配置(Global Scope) + +这部分设置了 Nginx 整个运行环境的参数。 + +| 指令 | 含义 | 作用和解释 | +| :--- | :--- | :--- | +| `user nginx;` | **用户权限** | 指定 Nginx 使用 `nginx` 系统用户来运行,这是最佳实践,避免使用 `root`。 | +| `worker_processes 1;` | **工作进程** | 设置工作进程数量。`1` 表示使用单个进程。理想情况下,应设置为 `auto` 或 CPU 核数,以提高并发能力。 | +| `error_log ... warn;` | **错误日志** | 配置错误日志路径和级别。`warn` 级别表示只记录警告及以上级别的错误。 | +| `pid /var/run/nginx.pid;` | **进程ID文件** | 指定存储 Nginx 主进程 ID 的文件路径。 | +| `events { ... }` | **事件处理** | 负责管理网络连接(Connection)。 | +| `worker_connections 1024;` | **连接数限制** | 每个工作进程允许打开的最大并发连接数。1024 是一个常见的保守设置。 | + +### 2. HTTP 块配置(`http` Block) + +这块定义了所有 HTTP 服务通用的配置。 + +| 指令 | 含义 | 作用和解释 | +| :--- | :--- | :--- | +| `include /etc/nginx/mime.types;` | **文件类型包含** | 引入标准的 MIME 类型定义文件,使 Nginx 能够正确识别和发送文件的 Content-Type 头部。 | +| `default_type application/octet-stream;` | **默认类型** | 如果无法确定文件类型,则默认将其视为二进制流。 | +| `log_format main '...'` | **自定义日志格式** | 定义了访问日志的详细格式。它记录了请求来源 IP、时间、请求方式/路径、HTTP 状态码、传输字节数等,非常完整。 | +| `access_log ... main;` | **访问日志** | 指定使用前面定义的 `main` 格式,并将访问记录写入 `/var/log/nginx/access.log`。 | +| `sendfile on;` | **性能优化** | 启用 `sendfile` 功能,允许 Nginx 直接将内核缓冲区的内容发送给客户端,跳过用户空间的复制,极大地提升文件传输效率。 | +| `tcp_nopush on;` | **性能优化** | 当发送多个数据包时,确保这些数据包被一次性发送,避免网络延迟和不必要的包分割。 | +| `tcp_nodelay on;` | **性能优化** | 禁用发送数据包的缓冲机制,数据发送后立即离开,降低延迟,适用于需要低延迟的应用。 | +| `keepalive_timeout 65;` | **连接保持** | 设置客户端与服务器保持空闲连接的最大秒数(这里是 65 秒)。 | +| `gzip on;` | **压缩开启** | 启用 Gzip 压缩,减少传输数据量,提升速度。 | +| `gzip_vary on;` | **缓存优化** | 当启用压缩后,所有响应头都必须包含 `Vary: Accept-Encoding`,确保缓存服务器不会错误地缓存未压缩或不同压缩级别的内容。 | +| `gzip_proxied any;` | **压缩范围** | 无论请求是否来自代理,都进行压缩。 | +| `gzip_types ...` | **压缩类型** | 列出需要被 Gzip 压缩的文件类型(如文本、JS、CSS、JSON)。 | + +### 3. 服务器配置(`server` Block) + +这部分定义了一个虚拟主机(Virtual Host),决定了 Nginx 在哪个端口和哪个域名上监听请求。 + +| 指令 | 含义 | 作用和解释 | +| :--- | :--- | :--- | +| `listen 80;` | **监听端口** | 表示该服务器实例监听所有进来的 HTTP 流量(端口 80)。 | +| `server_name localhost;` | **虚拟域名** | 限制此配置只响应 `localhost` 的请求。 | + +### 4. 路径匹配配置(`location` Blocks) + +这是最核心的逻辑部分,决定了不同 URL 路径如何被处理。 + +#### 🅰️ 前端静态文件处理 (SPA 路由支持) + +```nginx +# 前端静态文件 +root /usr/share/nginx/html; +index index.html; + +# 前端路由支持 +location / { + try_files $uri $uri/ /index.html; +} +``` + +* **`root /usr/share/nginx/html;`**: 指定所有静态文件的根目录。 +* **`location /`**: 匹配所有路径(所有请求)。 +* **`try_files $uri $uri/ /index.html;`**: **这是支持单页应用(SPA)的关键指令。** + 1. **`$uri`**: Nginx 尝试从 `root` 目录查找请求的完整文件(例如:`/js/main.js`)。 + 2. **`$uri/`**: 如果找不到文件,则尝试查找是否是请求的一个目录。 + 3. **`/index.html`**: 如果前两者都失败(即,用户访问的是一个不存在的路径,但这个路径应该被 SPA 的前端路由接管,如 `/user/profile`),则内部重定向(Rewrite)请求到 `/index.html`。 + * **总结:** 确保所有的请求最终都会被 `index.html` 接收,让前端 JavaScript 框架(Vue/React/Angular)去判断路由,从而实现 SPA 的前端路由。 + +#### 🅱️ 后端 API 代理处理 + +```nginx +location /api/ { + proxy_pass http://backend:8080; + # ... 其他配置 ... +} +``` + +* **`location /api/`**: 匹配所有以 `/api/` 开头的请求(例如:`/api/users`)。 +* **`proxy_pass http://backend:8080;`**: 这是核心代理指令。它将所有匹配的请求,转发到内部网络服务 `backend` 的 `8080` 端口。 + +**代理请求头设置 (Headers)** + +这些指令至关重要,因为它们确保了后端服务(`backend`)能够获取到客户端的真实信息,而不是代理服务器本身的 IP 地址。 + +* `proxy_http_version 1.1;`: 强制代理使用 HTTP/1.1,确保高级特性(如 Connection 头)可用。 +* `proxy_set_header Host $host;`: 告诉后端服务客户端实际请求的 Host 名称。 +* `proxy_set_header X-Real-IP $remote_addr;`: 将客户端的真实 IP 地址传递给后端。 +* `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`: 记录所有代理跳过的 IP 地址链(用于安全审计和日志)。 +* `proxy_set_header X-Forwarded-Proto $scheme;`: 告知后端服务原始请求是通过 HTTP 还是 HTTPS 传输的。 + +**WebSocket 支持** + +* `proxy_set_header Upgrade $http_upgrade;` +* `proxy_set_header Connection "upgrade";`: 这两行代码是专门为了支持 **WebSocket** 连接的。WebSocket 需要特殊的 HTTP 升级(Upgrade)头,用于从传统的 HTTP 长连接模式切换到双向的 WS 协议。 + +**超时设置** + +* `proxy_connect_timeout 60s;`:连接后端服务时等待的最大时间。 +* `proxy_send_timeout 60s;`:将请求发送给后端时等待的最大时间。 +* `proxy_read_timeout 60s;`:从后端服务读取响应数据时等待的最大时间。 +* **目的:** 将所有超时设置设置为 60 秒,确保即使后端处理耗时较长(比如复杂的报告生成),Nginx也不会提前中断连接。 + +--- + +## 🌟 总结与最佳实践点 + +1. **职责分离完美体现:** 通过 `location /api/` 和 `location /` 的隔离,Nginx完美地将“站内服务(静态资源)”和“后端数据服务(API)”分离,使得架构清晰。 +2. **性能优化到位:** 包含了 `sendfile`、`tcp_nopush`、`tcp_nodelay` 以及 `gzip` 等一系列优化,配置的性能起点很高。 +3. **健壮性高:** `try_files` 的使用确保了即使是客户端路径错误,应用也能重定向到根页面,避免出现“文件找不到”的错误,保证了用户体验。 +4. **反向代理标准配置:** 后端代理部分的配置(特别是 `X-Real-IP` 和 `X-Forwarded-*` 头)是所有企业级反向代理的标配,非常专业。 + +**建议改进点(如果适用):** + +1. **Worker Processes:** 如果部署在服务器上,建议将 `worker_processes 1;` 改为 `worker_processes auto;` 或根据 CPU 核数设置,以充分利用服务器性能。 +2. **HTTPS/SSL:** 实际生产环境中,`server` 块应该增加 `listen 443 ssl;`,并在其中配置 SSL 证书,确保所有的通信都是加密的。 \ No newline at end of file diff --git a/金山办公作业/Week05/PLAN.md b/金山办公作业/Week05/PLAN.md new file mode 100644 index 0000000..35bb1bf --- /dev/null +++ b/金山办公作业/Week05/PLAN.md @@ -0,0 +1,512 @@ +# AI 智能单词本 - 开发实施方案 + +## 项目概述 + +本项目是一个前后端分离的英语学习应用,通过 AI 生成单词释义和例句,帮助用户构建个人单词本。 + +--- + +## 一、技术架构设计 + +### 1.1 整体架构图 + +```mermaid +graph TB + subgraph "客户端层" + User[用户] + end + + subgraph "应用层" + FE[Nginx / Vite] + BE[Go + Gin] + end + + subgraph "数据层" + DB[(MySQL 8.0)] + end + + subgraph "外部服务" + AI[AI大模型
DeepSeek / 通义千问] + end + + User --> FE + FE --> BE + BE --> AI + BE --> DB + + style FE fill:#e1f5ff + style BE fill:#ffe1e1 + style DB fill:#e1ffe1 + style AI fill:#ffe1ff +``` + +### 1.2 核心技术栈 + +| 层级 | 技术选型 | 说明 | +|------|----------|------| +| 前端 | Vite + Vue3/React/Vanilla | 现代化构建工具,快速开发 | +| 前端服务器 | Nginx | 生产环境静态资源服务与反向代理 | +| 后端 | Go 1.21+ + Gin | 高性能 Web 框架 | +| 数据库 | MySQL 8.0 | 关系型数据库 | +| ORM | GORM | Go 语言 ORM 框架 | +| 身份认证 | JWT | 无状态 token 认证 | +| 配置管理 | Viper | 配置文件管理 | +| 容器化 | Docker + Docker Compose | 统一部署环境 | + +### 1.3 跨域处理策略 + +```mermaid +graph LR + subgraph "开发环境" + FE[Vite
proxy配置] -->|代理| BE[后端:8080] + end + + subgraph "生产环境" + U[用户请求] --> FE2[Nginx:80] + FE2 -->|/api/* 代理转发| BE2[后端:8080] + FE2 -->|静态资源| SR[dist/] + end +``` + +**原则:后端代码严禁配置 CORS,统一通过代理解决跨域。** + +--- + +## 二、目录结构规划 + +``` +week05/homework/docker-gin +├── backend/ # 后端 Go 项目 +│ ├── Dockerfile # Go 多阶段构建镜像 +│ ├── main.go # 应用入口 +│ ├── .env.example # 环境变量示例文件 +│ ├── config/ # 配置管理 +│ │ └── config.go # Viper 配置加载 +│ ├── model/ # 数据模型层 +│ │ ├── user.go # 用户模型 +│ │ └── word.go # 单词模型 +│ ├── api/ # 路由与控制器层 +│ │ └── handler.go # 请求处理器 +│ ├── service/ # 业务逻辑层 +│ │ ├── auth.go # 认证服务 +│ │ ├── ai.go # AI 调用服务 +│ │ └── word.go # 单词业务 +│ ├── middleware/ # 中间件 +│ │ └── jwt.go # JWT 鉴权中间件 +│ ├── utils/ # 工具函数 +│ │ └── hash.go # 密码加密工具 +│ └── go.mod # Go 依赖管理 +│ +├── frontend/ # 前端项目 +│ ├── Dockerfile # Nginx 镜像构建 +│ ├── nginx.conf # 生产环境 Nginx 配置 +│ ├── package.json # 依赖管理 +│ ├── vite.config.js/ts # Vite 配置(开发代理) +│ ├── index.html # HTML 入口 +│ └── src/ +│ ├── main.js/tsx # 应用入口 +│ ├── api/ # API 请求封装 +│ ├── components/ # 页面组件 +│ │ ├── common/ # 公共组件 +│ │ ├── auth/ # 登录/注册 +│ │ └── word/ # 单词相关 +│ ├── router/ # 路由配置 +│ ├── store/ # 状态管理 +│ └── utils/ # 工具函数 +│ +├── docs/ # 项目文档 +│ ├── api.md # API 接口文档 +│ ├── db.md # 数据库设计文档 +│ └── init.sql # 数据库初始化脚本 +│ +├── docker-compose.yml # 容器编排配置 +└── README.md # 项目说明与运行指南 +``` + +--- + +## 三、数据库设计 + +### 3.1 ER 图 + +```mermaid +erDiagram + USER ||--|{ WORD : has + USER { + uuid id PK "用户ID" + string username UK "用户名" + string password "密码(hash)" + datetime created_at "创建时间" + datetime updated_at "更新时间" + datetime deleted_at "删除时间(软删除)" + } + WORD { + uuid id PK "单词记录ID" + uuid user_id FK "所属用户ID" + string word "单词" + text definition "释义" + json examples "例句列表" + string ai_provider "AI模型来源" + datetime created_at "创建时间" + datetime updated_at "更新时间" + datetime deleted_at "删除时间(软删除)" + } +``` + +### 3.2 表结构设计思路 + +1. **用户表 (users)** + - 使用 UUID 作为主键,安全性更高 + - 用户名设置唯一索引,防止重复注册 + - 密码字段存储 bcrypt 哈希值,严禁明文 + - 包含标准时间戳字段 + - 支持软删除(deleted_at) + +2. **单词表 (words)** + - 使用 UUID 作为主键 + - 通过 user_id 外键关联用户 + - 单词字段设为索引,配合 user_id 提高查询效率 + - 例句使用 JSON 类型存储 3 条数据 + - 记录 AI 来源以支持多模型切换 + - 支持软删除和分页查询 + +--- + +## 四、API 接口设计 + +### 4.1 接口总览 + +```mermaid +graph LR + subgraph "无需鉴权" + A[POST /api/auth/register] + B[POST /api/auth/login] + end + + subgraph "需要鉴权" + C[GET /api/words/query] + D[POST /api/words/save] + E[GET /api/words/list] + F[DELETE /api/words/:id] + end +``` + +### 4.2 接口设计要点 + +| 接口 | 方法 | 说明 | 关键参数 | +|------|------|------|----------| +| 注册 | POST | 用户名密码注册 | username, password | +| 登录 | POST | 返回 JWT Token | username, password | +| 查询单词 | GET | AI 生成释义和例句 | word, ai_provider | +| 保存单词 | POST | 持久化到数据库 | word, definition, examples, ai_provider | +| 单词列表 | GET | 分页获取用户单词 | page, page_size | +| 删除单词 | DELETE | 软删除单词记录 | 路径参数 id | + +--- + +## 五、核心功能实现步骤 + +### 5.1 数据库初始化流程 + +```mermaid +sequenceDiagram + participant DC as docker-compose + participant M as MySQL容器 + participant SQL as init.sql + + DC->>M: 启动容器 + DC->>M: 挂载 init.sql + M->>M: 初始化数据库 + M->>SQL: 执行建表语句 + SQL-->>M: 完成建表 + M-->>DC: 准备就绪 +``` + +**关键点:** +- 通过 docker-compose.yml 将 `docs/init.sql` 挂载到容器的 `/docker-entrypoint-initdb.d/` 目录 +- MySQL 容器首次启动时自动执行该目录下的 SQL 脚本 +- 严禁在代码中调用 GORM 的 AutoMigrate + +### 5.2 用户认证流程 + +```mermaid +sequenceDiagram + participant U as 用户 + participant FE as 前端 + participant BE as 后端 + + Note over U,BE: 注册流程 + U->>FE: 提交用户名密码 + FE->>BE: POST /api/auth/register + BE->>BE: 验证用户名重复 + BE->>BE: bcrypt 哈希密码 + BE->>BE: 存入数据库 + BE-->>FE: 注册成功 + + Note over U,BE: 登录流程 + U->>FE: 提交用户名密码 + FE->>BE: POST /api/auth/login + BE->>BE: 验证用户存在 + BE->>BE: bcrypt 验证密码 + BE->>BE: 生成 JWT Token + BE-->>FE: 返回 Token + FE->>FE: 存入 localStorage +``` + +### 5.3 智能查询单词流程 + +```mermaid +flowchart TD + A[接收查询请求
word + ai_provider] --> B{鉴权检查} + B -->|失败| C[返回 401 错误] + B -->|通过| D{检查数据库} + D -->|已保存| E[直接返回数据库记录] + D -->|未保存| F[调用 AI 接口] + F --> G[解析 AI 响应] + G --> H[返回 AI 结果至前端
不保存到数据库] + H --> I[前端展示查询结果] + I --> J{用户点击保存?} + J -->|是| K[前端调用保存接口] + J -->|否| L[结束] + K --> M[后端写入数据库] +``` + +### 5.4 AI 调用服务设计 + +```mermaid +graph TB + subgraph "AI 调用层" + Service[AI Service] + end + + subgraph "AI 提供商" + DS[DeepSeek] + QW[通义千问] + end + + Service -->|根据 ai_provider| DS + Service -->|根据 ai_provider| QW + + DS -->|返回结构化 JSON| Service + QW -->|返回结构化 JSON| Service + +``` + +--- + +## 六、开发环境配置 + +### 6.1 前端开发配置(Vite Proxy) + +在 `vite.config.js/ts` 中配置代理,解决开发环境跨域问题: + +```javascript +export default { + server: { + proxy: { + '/api': { + target: 'http://localhost:8080', // 后端服务地址 + changeOrigin: true, // 改变请求源 + } + } + } +} +``` + +### 6.2 后端配置管理 + +使用 Viper 管理配置,支持从 `.env` 文件加载: + +| 配置项 | 说明 | 示例 | +|--------|------|------| +| DB_HOST | 数据库地址 | db | +| DB_PORT | 数据库端口 | 3306 | +| DB_USER | 数据库用户 | root | +| DB_PASSWORD | 数据库密码 | password | +| DB_NAME | 数据库名 | wordbook | +| JWT_SECRET | JWT 签名密钥 | secret_key | +| DEEPSEEK_API_KEY | DeepSeek 密钥 | sk-xxx | +| QIANWEN_API_KEY | 通义千问密钥 | xxx | + +--- + +## 七、容器化部署设计 + +### 7.1 网络架构图 + +```mermaid +graph TB + subgraph "宿主机" + Host[宿主机] + Port80[端口 80] + end + + subgraph "Docker 网络" + Net[wordbook-network] + end + + subgraph "frontend 容器" + Nginx[Nginx:80] + end + + subgraph "backend 容器" + Gin[Gin:8080] + end + + subgraph "db 容器" + MySQL[MySQL:3306] + end + + Host --> Port80 + Port80 -->|外部访问| Nginx + Nginx --> Net + Gin --> Net + MySQL --> Net + + Nginx -.->|/api/* 代理| Gin + Gin -.->|数据库连接| MySQL + + style Host fill:#f0f0f0 + style Net fill:#e6f3ff +``` + +### 7.2 服务设计要点 + +| 服务 | 暴露端口 | 依赖 | 说明 | +|------|----------|------|------| +| frontend | 80:80 | backend | 对外唯一入口 | +| backend | 无 | db | 内部网络访问 | +| db | 无 | - | 内部网络访问 | + +**安全考虑:** +- 只有 frontend 暴露端口到宿主机 +- backend 和 db 仅在容器网络内通信 +- 通过容器名相互访问(`http://backend:8080`) + +### 7.3 Dockerfile 设计 + +**后端多阶段构建:** +```mermaid +graph LR + A[构建阶段
golang:alpine] -->|编译| B[二进制文件] + B -->|复制| C[运行阶段
alpine:latest] + C --> D[精简镜像] +``` + +**前端 Nginx 构建:** +```mermaid +graph LR + A[node:18-alpine
Vite Build] -->|dist 产物| B[nginx:alpine
复制 dist 并替换 nginx.conf] +``` + +--- + +## 八、开发实施顺序 + +### 阶段一:项目初始化 + +1. 创建项目目录结构 +2. 初始化后端项目(go mod init) +3. 初始化前端项目(npm create vite@latest) +4. 配置 docker-compose.yml 基础框架 + +### 阶段二:数据库设计 + +1. 编写 `docs/init.sql` 建表语句 +2. 编写 `docs/db.md` 数据库设计文档 +3. 在 docker-compose 中配置 MySQL 初始化挂载 + +### 阶段三:后端开发 + +1. **配置层**:Viper 环境变量加载 +2. **模型层**:定义 User 和 Word 结构体 +3. **认证模块**: + - 用户注册逻辑 + - 用户登录逻辑 + - JWT 生成与验证中间件 +4. **AI 调用模块**: + - DeepSeek 接口封装 + - 通义千问接口封装 + - Prompt 模板设计 +5. **单词业务模块**: + - 查询逻辑(数据库检查 + AI 调用) + - 保存逻辑 + - 列表查询(分页) + - 删除逻辑(软删除) +6. **API 路由**:注册所有接口 +7. **API 文档**:编写 `docs/api.md` + +### 阶段四:前端开发 + +1. **基础配置**: + - Vite proxy 配置 + - API 请求封装(axios) +2. **认证页面**: + - 登录表单 + - 注册表单 + - Token 存储与管理 +3. **单词学习页面**: + - 查询单词表单 + - AI 结果展示 + - 保存按钮 +4. **单词本页面**: + - 单词列表展示 + - 分页器 + - 删除功能 +5. **状态管理**:管理用户登录状态 + +### 阶段五:容器化部署 + +1. **后端 Dockerfile**:多阶段构建优化 +2. **前端 Dockerfile**:Nginx 配置 +3. **Nginx 反向代理**:配置 `/api/*` 路由 +4. **Docker Compose 编排**:网络、依赖、环境变量 +5. **本地测试**:完整流程验证 + +### 阶段六:文档编写 + +1. 编写 `README.md`:项目说明与运行指南 +2. 完善 `docs/api.md` +3. 完善 `docs/db.md` +4. 整理代码注释 + +--- + +## 九、注意事项与最佳实践 + +### 9.1 安全注意事项 + +- ⚠️ **严禁明文存储密码**:必须使用 bcrypt 等加密算法 +- ⚠️ **严禁后端配置 CORS**:统一通过代理解决跨域 +- ⚠️ **严禁使用 AutoMigrate**:数据库初始化必须通过 SQL 脚本 +- ⚠️ **敏感信息管理**:API Key 不应提交到代码仓库 + +### 9.2 开发规范 + +1. 代码风格遵循 Go 和各语言社区规范 +2. 所有接口必须有明确的参数说明和返回示例 +3. 数据库字段必须有清晰的注释 +4. 容器镜像应尽可能精简 + +### 9.3 调试建议 + +- 开发时可以先单独启动后端和前端测试 +- 使用 Docker Compose 日志查看:`docker-compose logs -f` +- 数据库连接问题检查网络配置和用户权限 + +--- + +## 十、验收标准 + +1. ✅ 项目目录结构符合要求 +2. ✅ 用户可正常注册和登录 +3. ✅ 可查询单词并获取 AI 生成的释义和例句 +4. ✅ 可保存单词到个人单词本 +5. ✅ 单词列表支持分页 +6. ✅ 可删除已保存的单词 +7. ✅ 使用 Docker Compose 一键启动 +8. ✅ 生产环境通过 Nginx 反向代理访问 +9. ✅ 三份文档完整且清晰 + +--- + diff --git a/金山办公作业/Week05/Slide.md b/金山办公作业/Week05/Slide.md new file mode 100644 index 0000000..b6ee239 --- /dev/null +++ b/金山办公作业/Week05/Slide.md @@ -0,0 +1,63 @@ + +| 算法类型 | 算法 | 密钥类型 | 特点 | +| ----- | ----- | ----- | ------------------- | +| HMAC | HS256 | 对称密钥 | 服务器签发和验证都 uses 同一密钥 | +| HMAC | HS512 | 对称密钥 | 更强的哈希,性能稍低 | +| RSA | RS256 | 非对称密钥 | 私钥签名,公钥验证 | +| RSA | RS512 | 非对称密钥 | 更强的签名算法 | +| ECDSA | ES256 | 椭圆曲线 | 比更短但同样安全 | + +```go +type JWTAuthenticator struct { + secretKey []byte // default config: Kingsoft-2026 +} + +func NewJWTAuthenticator(secretKey string) *JWTAuthenticator { + return &JWTAuthenticator{ + secretKey: []byte(secretKey), + } +} +``` + +|场景|推荐方案|理由| +|---|---|---| +|管理后台、商品列表|传统分页|需要跳页功能,数据量可控| +|社交媒体动态、无限滚动|游标分页|实时性强,只需向下加载| +|日志查看、历史订单|键集分页|大数据量,性能优先| +|搜索结果|混合方案|前10页传统 + 深度游标| +在 `vite.config.js/ts` 中配置代理,解决开发环境跨域问题: + +```javascript +export default { + server: { + proxy: { + '/api': { + target: 'http://localhost:8080', // 后端服务地址 + changeOrigin: true, // 改变请求源 + } + } + } +} +``` + +|维度|自增ID|UUID|雪花ID| +|---|---|---|---| +|**生成方式**|数据库自增|算法生成|时间戳+机器ID+序列| +|**有序性**|单机有序|完全无序|时间有序| +|**分布式友好度**|需要额外方案|✅ 原生支持|✅ 原生支持| +|**性能/TPS**|10万+|1万+|100万+| +|**ID长度**|8-10字节|36字符|19字符(十进制)| +|**冲突概率**|0(单机)|≈1/2^128|理论0| +|**索引友好度**|⭐⭐⭐⭐⭐|⭐⭐|⭐⭐⭐⭐| +|**可读性**|⭐⭐⭐⭐⭐|⭐⭐|⭐⭐⭐| + +|特性|命名卷|绑定挂载| +|---|---|---| +|**管理方式**|Docker 管理|用户管理| +|**存储位置**|Docker 默认目录|用户指定的任意位置| +|**跨平台兼容性**|✅ 良好|❌ 路径差异大| +|**权限处理**|✅ Docker 自动处理|⚠️ 需手动配置| +|**安全性**|✅ 隔离性好|⚠️ 直接访问主机| +|**备份难度**|⚠️ 需特殊命令|✅ 直接复制| +|**性能**|✅ 优化过|⚠️ 可能有额外开销| +|**开发调试**|⚠️ 不便|✅ 实时同步| diff --git a/金山办公作业/Week05/全栈开发实战:AI 智能单词本.md b/金山办公作业/Week05/全栈开发实战:AI 智能单词本.md new file mode 100644 index 0000000..f1640f5 --- /dev/null +++ b/金山办公作业/Week05/全栈开发实战:AI 智能单词本.md @@ -0,0 +1,105 @@ + +--- + +# 全栈开发实战:AI 智能单词本 + +## 一、项目背景 + +开发一个辅助英语学习的前后端分离 Web 应用。用户可以通过前端页面查询单词,后端系统会调用 AI 大模型(DeepSeek 或 通义千问)生成该单词的精准释义和 3 条例句。查询结果返回前端展示后,用户可以手动点击“保存”按钮,将该单词记录持久化到个人的单词本中,方便日后复习。 + +**本项目重点考察**:前后端分离架构、代理与跨域处理、第三方 AI 接口对接、关系型数据库设计,以及基于 Docker 的全栈工程化容器编排部署。 + +## 二、技术栈与核心架构要求 + +* **后端语言框架**:Go (>= 1.21) + Gin (`github.com/gin-gonic/gin`) +* **前端技术栈**:必须基于 Vite 构建(可搭配 Vue3、React 或原生 Vanilla JS),且必须掌握相应的工程化配置。 +* **数据库 & ORM**:MySQL 8.0 + GORM (`gorm.io/gorm`) +* **容器化部署**:Docker & Docker Compose +* **Web 服务器 (前端)**:Nginx +* **身份验证**:JWT (JSON Web Token) +* **配置管理**:Viper (或 godotenv) + +【特别要求:跨域处理规范】 + +* **严禁跨域配置**:本项目**严禁**在后端 Go 代码中配置任何允许跨域(CORS)的中间件! +* **开发环境**:前端必须通过配置 Vite 的 `proxy` 来解决跨域问题。 +* **生产环境**:必须通过 Nginx 反向代理 (`proxy_pass`) 统一路由,实现前端静态资源与后端 API 的同源访问。 + +## 三、项目目录结构要求 + +整个项目需要采用前后端分离的代码组织结构,并且包含完善的文档(请严格遵循以下目录规范): + +```text +week05/homework/docker-gin +├── backend/ # 后端 Go 代码目录 +│ ├── Dockerfile # 后端镜像构建文件 +│ ├── main.go +│ ├── .env # .env 文件示例 +│ └── ... # 其他分层目录如 api, service, model 等 +├── frontend/ # 前端项目目录(由 Vite 初始化) +│ ├── Dockerfile # 前端镜像构建文件(基于 Nginx) +│ ├── nginx.conf # Nginx 自定义配置文件(生产环境反向代理) +│ ├── vite.config.js/ts # Vite 配置文件(开发环境 Proxy) +│ └── ... # src, index.html 等 +├── docs/ # 项目文档目录 +│ ├── api.md # API 接口详细文档 +│ ├── db.md # 数据库设计文档 +│ └── init.sql # 数据库初始化脚本(包含建表语句) +├── docker-compose.yml # 统一部署文件 +└── README.md # 项目总体说明与运行指南 +``` + +## 四、核心功能需求 + +### 1. 用户认证模块 +* **用户注册**:前端提供表单,后端接收用户名密码,后端严禁明文存储密码(需进行 Hash 加密后存入 MySQL)。 +* **用户登录**:验证通过后返回 JWT Token,前端需要将 Token 存储(如 `localStorage`),并在后续请求的 Header 中携带 (`Authorization: Bearer `)。 + +### 2. 单词学习模块 (核心业务) +* **智能查询单词**: + * **接收参数**:`word`(单词),`ai_provider`(前端让用户下拉选择模型,如 DeepSeek/通义千问)。 + * **逻辑流程**: + 1. 鉴权通过后,后端先在数据库中检查当前用户是否已保存过该单词。 + 2. 如果已保存,直接从数据库读取并返回给前端。 + 3. 如果未保存,根据 `ai_provider` 调用对应 AI 接口。 + 4. 让 AI 返回格式化的 JSON 数据(包含:释义 + 3 条例句)。 + 5. 直接将 AI 结果返回给前端展示,**此时不在后端进行数据库保存**。 +* **手动保存单词**: + * **接收参数**:前端将上一步查询到的完整数据(单词、释义、例句列表、AI 来源)提交给后端。 + * **逻辑流程**:后端接收到数据后,将其写入 MySQL 数据库,并与当前 UserID 绑定。 +* **获取单词列表**:获取当前用户保存的所有单词记录(**必须支持分页**,前端提供分页器,后端接收 `page` 和 `page_size`)。 +* **删除单词**:根据单词 ID,从数据库中软删除某条记录。 + +## 五、部署与交付要求 (Docker & Nginx & Vite) + +### 1. 后端构建 (`backend/Dockerfile`) +* 使用多阶段构建编译 Go 应用,暴露出后端端口(如 8080)。镜像需尽可能精简。 + +### 2. 前端构建 (`frontend/Dockerfile`) 与环境配置 +* **【生产环境 - Nginx 统一入口】**:基础镜像使用 `nginx:alpine`,将 Vite build 后的产物拷贝到 Nginx 发布目录。 +* **必须替换自定义的 `nginx.conf`**:将 Nginx 作为整个应用的唯一外部访问入口。既要伺服前端静态页面,又要将后端接口请求(如 `/api/`)通过 `proxy_pass` 反向代理转发给内部 `backend` 容器。 + +### 3. 编排部署 (`docker-compose.yml`) +* 定义 3 个 Service:`db` (MySQL), `backend` (Go API), `frontend` (Nginx UI)。 +* 配置共享的网络。对外只需暴露 `frontend` (Nginx) 的 80/443 等端口,`backend` 和 `db` 的端口无需直接映射到宿主机,保障安全性。`frontend` 可以通过容器名访问 `backend`,`backend` 可以访问 `db`。 +* `backend` 服务需依赖于 `db` (`depends_on`)。 +* **数据库初始化要求**:为了符合企业级开发规范(DBA 审计与权限控制),**严禁**在代码中使用 GORM 的 `AutoMigrate` 等工具自动建表。**必须在 `docker-compose.yml` 中将包含建表语句的 `docs/init.sql` 挂载到 MySQL 容器的 `/docker-entrypoint-initdb.d/` 目录下进行初始化。**确保一键启动即可使用,无需人工干预数据库建表。 + +## 六、文档编写要求 (考察重点) + +本项目非常看重开发者的文档输出能力。除了提交能够正常运行的代码外,你必须编写并提交以下三份文档: + +### 1. `README.md` (项目说明与运行指南) +* **项目基本信息**:至少包含你的姓名、学校、学号。 +* **开发任务索引**:列出你完成的任务清单。 +* **项目简介**:包含项目简介与架构图(或架构说明)。 +* **运行指南(核心考点)**:必须极其清晰地写明如何从零启动该项目。包括前置依赖(Docker 等)、如何配置 AI 的 API Key(如环境变量或 `.env` 文件的创建)、一键启动命令(`docker-compose up -d`),以及启动后如何访问前端页面和后端服务。 + +### 2. `docs/api.md` (API 接口文档) +* 详细记录业务中的每一个接口。 +* 每一项应包含:接口路径、请求方法、鉴权说明、请求参数(Query 或 Body 结构)、成功的返回示例(JSON 格式)、失败的错误码及其含义。 + +### 3. `docs/db.md` (数据库设计文档) +* 详细阐述你的 MySQL 数据库表结构设计。 +* 需要列出每个表的字段名、数据类型、是否主/外键、索引设置、以及每个字段的具体业务含义。 +* 说明你对单词本表、用户表的关联关系设计。 \ No newline at end of file diff --git a/金山办公作业/Week05/分页.md b/金山办公作业/Week05/分页.md new file mode 100644 index 0000000..824f1a4 --- /dev/null +++ b/金山办公作业/Week05/分页.md @@ -0,0 +1,610 @@ +--- +tags: [go, pagination, backend, frontend, api, database, assignment] +create time: 2026-04-17 22:45 +--- + +# 分页实现指南 + +## 概述 + +分页是 Web 应用中处理大规模数据的核心技术。本文深入探讨分页的各种实现策略、性能优化及最佳实践,帮助构建高效的用户体验。 + +## 分页架构全景 + +### 核心流程 + +```mermaid +sequenceDiagram + participant C as Client + participant F as Frontend + participant S as Backend + participant D as Database + participant Cache as Redis + + C->>F: 访问列表页(第1页) + F->>S: GET /api/list?page=1&page_size=10 + + alt 缓存命中 + S->>Cache: get(pagination:1:10) + Cache-->>S: cached data + else 缓存未命中 + S->>D: SELECT ... LIMIT 10 OFFSET 0 + S->>D: SELECT COUNT(*) + D-->>S: data + total + S->>Cache: set(pagination:1:10, ttl:5m) + end + + S-->>F: {data, total, page, page_size, total_page} + F->>F: 计算分页信息 + F-->>C: 渲染数据 + 分页器 +``` + +### 三种分页模式对比 + +```mermaid +graph LR + A[分页需求] --> B{数据特征} + B -->|稳定数据
管理后台| C[传统分页
LIMIT/OFFSET] + B -->|实时数据
无限滚动| D[游标分页
Cursor-based] + B -->|历史数据
时间范围| E[键集分页
Keyset] + + C -.-> F[✅ 支持跳页
⚠️ 深度性能差] + D -.-> G[✅ 性能稳定
❌ 不支持跳页] + E -.-> H[✅ 最佳性能
⚠️ 需排序字段] +``` + +**选择指南**: + +| 场景 | 推荐方案 | 理由 | +|-----|---------|------| +| 管理后台、商品列表 | 传统分页 | 需要跳页功能,数据量可控 | +| 社交媒体动态、无限滚动 | 游标分页 | 实时性强,只需向下加载 | +| 日志查看、历史订单 | 键集分页 | 大数据量,性能优先 | +| 搜索结果 | 混合方案 | 前10页传统 + 深度游标 | + +## 前端分页器设计 + +### 核心接口设计 + +```typescript +interface PaginationRequest { + page: number; // 当前页码(从1开始) + page_size: number; // 每页大小 +} + +interface PaginationResponse { + data: T[]; + total: number; + page: number; + page_size: number; + total_page: number; +} +``` + +**关键点**: +- `page` 从 1 开始(用户直觉友好) +- 响应包含 `total` 用于前端计算总页数 +- 支持泛型 `T` 复用于不同数据类型 + +### 分页器组件要点 + +传统分页器的核心功能: +1. 页码导航(上一页、下一页、直接跳页) +2. 页码显示(智能压缩:"1 ... 5 6 7 ... 10") +3. 每页大小切换(10/20/50/100条) +4. 总数展示("共 100 条,共 10 页") + +```typescript +// 简化版展示核心逻辑 +const totalPages = Math.ceil(total / pageSize); +const startPage = Math.max(1, currentPage - 2); +const endPage = Math.min(totalPages, currentPage + 2); + +if (startPage > 1) showEllipsis = true; +if (endPage < totalPages) showEndEllipsis = true; +``` + +**进阶技巧**: +- **页码压缩**:页数 > 7 时显示省略号 +- **防抖处理**:快速点击时只执行最后一次请求 +- **地址栏同步**:URL 参数 `?page=2`, 支持前进/后退 +- **骨架屏**:加载时显示占位符,提升感知性能 + +### 无限滚动实现 + +使用 `Intersection Observer` API(性能优于 scroll 事件): + +```typescript +// 核心逻辑:监听列表最后一项进入视口 +const lastItemRef = useRef(); + +useEffect(() => { + const observer = new IntersectionObserver(([entry]) => { + if (entry.isIntersecting && hasMore && !loading) { + loadMore(); // 自动加载下一页 + } + }, { threshold: 0.1 }); + + if (lastItemRef.current) observer.observe(lastItemRef.current); + return () => observer.disconnect(); +}, [hasMore, loading]); +``` + +**注意事项**: +- 使用 `sticky` footer 显示加载状态 +- 返回顶部时考虑重新加载或保留状态 +- 批量追加数据避免频繁渲染 + +## 后端实现要点 + +### Go 核心结构 + +```go +// 分页请求 +type PaginationRequest struct { + Page int `form:"page"` // 默认 1 + PageSize int `form:"page_size"` // 默认 10,最大 100 +} + +// 分页响应 +type PaginationResponse struct { + Data []interface{} `json:"data"` + Total int64 `json:"total"` + Page int `json:"page"` + PageSize int `json:"page_size"` + TotalPage int `json:"total_page"` +} +``` + +**关键逻辑**: + +```go +func GetPagination(r *http.Request) PaginationRequest { + p := PaginationRequest{Page: 1, PageSize: 10} + + // 参数解析 + 验证 + if page := r.URL.Query().Get("page"); page != "" { + if n, err := strconv.Atoi(page); err == nil && n > 0 { + p.Page = n + } + } + if size := r.URL.Query().Get("page_size"); size != "" { + if n, err := strconv.Atoi(size); err == nil && n > 0 { + p.PageSize = min(n, 100) // 限制最大值 + } + } + return p +} + +func ListHandler(w http.ResponseWriter, r *http.Request) { + p := GetPagination(r) + offset := (p.Page - 1) * p.PageSize + + // 查询数据 + items, _ := db.Query("SELECT ... LIMIT ? OFFSET ?", p.PageSize, offset) + total, _ := db.QueryInt("SELECT COUNT(*)") + + resp := PaginationResponse{ + Data: items, + Total: total, + Page: p.Page, + PageSize: p.PageSize, + TotalPage: (int(total) + p.PageSize - 1) / p.PageSize, + } + json.NewEncoder(w).Encode(resp) +} +``` + +### 三种分页查询策略 + +#### 1. 传统分页(LIMIT/OFFSET) + +```sql +-- 基础查询 +SELECT * FROM products ORDER BY id DESC LIMIT 10 OFFSET 0; -- 第1页 +``` + +**深度分页问题**: +- OFFSET 10000 需要扫描前 10001 条记录 +- 性能随页码线性下降:O(n) + +**优化方案**: +- 限制最大页码(如 1000 页) +- 预计算页码到 ID 的映射 +- 对热门数据使用缓存 + +#### 2. 游标分页(Cursor-based) + +```sql +-- 使用 WHERE 替代 OFFSET +SELECT * FROM products +WHERE id < {last_id} +ORDER BY id DESC +LIMIT 10; +``` + +**优势**: +- 性能稳定,复杂度 O(1) +- 支持实时数据(新增数据不影响) + +**实现要点**: +```go +type CursorResponse struct { + Data []Product `json:"data"` + Cursor string `json:"cursor"` // 最后一条的 ID + HasMore bool `json:"has_more"` +} + +// 前端保存 cursor,下次请求携带 +``` + +#### 3. 键集分页(Keyset Pagination) + +适用于有明确排序字段(如时间戳): + +```sql +-- 获取第N页(需要知道N-1页的最后值) +SELECT * FROM logs +WHERE created_at < '2026-04-17 10:00:00' -- 上一页最后的时间戳 +ORDER BY created_at DESC +LIMIT 10; +``` + +**性能最优**,但实现复杂,需要: +- 前端保存每页的最后值作为"书签" +- 支持双向导航(需要第一页、最后页的边界值) + +### 总数查询优化 + +传统方案需要两次查询(数据 + COUNT),优化策略: + +```go +// 策略1:缓存总数(适合数据变化不频繁) +total := cache.Get("total_products") + +// 策略2:近似总数(如每天更新一次) +if time.Since(lastUpdate) > 24*time.Hour { + total = db.QueryInt("SELECT COUNT(*)") +} + +// 策略3:渐进式加载(不显示总数,只显示"更多") +hasMore := len(items) == pageSize +``` + +## API 响应示例 + +### 标准分页响应格式 + +```json +{ + "data": [ + { + "id": 1, + "name": "Item 1", + "created_at": "2026-04-17T10:00:00Z" + }, + { + "id": 2, + "name": "Item 2", + "created_at": "2026-04-17T09:00:00Z" + } + ], + "total": 100, + "page": 1, + "page_size": 10, + "total_page": 10 +} +``` + +### 错误响应 + +```json +{ + "error": "Invalid pagination parameters", + "message": "page must be greater than 0" +} +``` + +## 性能优化 + +### 数据库索引策略 + +```sql +-- 基础索引:按排序字段 +CREATE INDEX idx_products_created ON products(created_at DESC); + +-- 复合索引:过滤条件 + 排序 +CREATE INDEX idx_products_status_created ON products(status, created_at DESC); + +-- 覆盖索引:避免回表 +CREATE INDEX idx_products_covering ON products(status, name, price); +-- 查询可以直接从索引获取,无需访问表数据 +SELECT name, price FROM products WHERE status = 1 ORDER BY created_at; +``` + +**索引设计原则**: +- WHERE 条件字段 → 等值匹配优先级更高 +- ORDER BY 字段 → 排序方向(ASC/DESC) +- 覆盖索引 → 避免回表,提升 50%+ 查询性能 + +### 缓存层次 + +```mermaid +graph TB + A[请求] --> B{缓存检查} + B -->|Hit| C[返回缓存数据] + B -->|Miss| D[数据库查询] + D --> E[写入缓存] + E --> C + + F[缓存策略] --> G[热门页
TTL: 10分钟] + F --> H[普通页
TTL: 5分钟] + F --> I[总数统计
TTL: 1小时] +``` + +**实现要点**: + +```go +// 分层缓存策略 +type CacheConfig struct { + HotPages map[int]time.Duration // 热门页长期缓存 + Normal time.Duration // 普通页短期缓存 + Total time.Duration // 总数统计超长期缓存 +} + +// 缓存键设计 +key := fmt.Sprintf("list:%d:%d", page, pageSize) +totalCountKey := "list:total" +``` + +并为了防止缓存雪崩,添加随机 TTL 偏移: + +```go +ttl := baseTTL + time.Duration(rand.Intn(60))*time.Second +``` + +### 深度分页优化方案 + +**问题场景**:100万条数据查询第10页 + +| 方案 | 查询时间 | 适用场景 | +|-----|---------|----------| +| LIMIT 10 OFFSET 100 | ~50ms | < 1000页 | +| WHERE id > last_id LIMIT 10 | ~5ms | > 1000页 | +| 预计算页码映射 | ~1ms | 数据稳定 | + +**混合策略**: + +```go +func QueryData(page, pageSize int) { + if page < 100 { + // 前页用 OFFSET + db.Query("SELECT ... LIMIT ? OFFSET ?", pageSize, (page-1)*pageSize) + } else { + // 深度页用游标(需要第99页的最后ID) + lastID := getPageLastID(99) + db.Query("SELECT ... WHERE id > ? LIMIT ?", lastID, pageSize * (page-99)) + } +} +``` + +## 前进话题 + +### 实时分页挑战 + +**问题场景**: +- 用户在第5页浏览 +- 其他用户删除了第5页的部分数据 +- 刷新后数据可能重复或遗漏 + +**解决方案**: + +1. **快照分页**(Snowflake/Slack模式) + ```go + type QueryID string // 每次查询生成唯一ID + cache.Store(queryKey, allData, 10m) // 缓存完整结果集 + // 后续请求基于快照 + ``` + +2. **游标 + 时间戳** + ```sql + WHERE (created_at, id) <= (:last_time, :last_id) + ORDER BY created_at DESC, id DESC + ``` + 使用复合游标保证顺序稳定 + +3. **接受不一致性**(社交媒体) + - 少量重复/遗漏可接受 + - 优先性能而非严格一致性 + +### 分布式分页 + +**问题**:分库分表后如何分页? + +**方案对比**: + +| 方案 | 复杂度 | 性能 | 适用场景 | +|-----|-------|------|----------| +| 全局聚合后分页 | 低 | 差(需扫描全量) | < 10万数据 | +| 按用户分片(user_id取模) | 中 | 好 | 私有数据 | +| 路由表(mapping表) | 高 | 较好 | 公共数据 | +| 基于ES的搜索分页 | 高 | 优秀 | 搜索功能 | + +**ES方案示例**: + +```json +GET /products/_search +{ + "from": 0, + "size": 10, + "sort": [{"created_at": "desc"}], + "query": {"match_all": {}} +} +``` + +ES 使用 `search_after` 替代 `from/size` 进行深度分页: + +```json +{ + "size": 10, + "sort": [{"created_at": "desc"}, {"_id": "desc"}], + "search_after": ["2026-04-17", "last_id"] +} +``` + +### GraphQL 分页 + +使用 **Relay规范** 实现连接(Connection): + +```graphql +type PageInfo { + hasNextPage: Boolean! + hasPreviousPage: Boolean! + startCursor: String + endCursor: String +} + +type ProductEdge { + node: Product! + cursor: String! +} + +type ProductConnection { + edges: [ProductEdge!]! + pageInfo: PageInfo! + totalCount: Int! +} + +type Query { + products(first: Int, after: String): ProductConnection! +} +``` + +**客户端使用**: + +```typescript +// 查询第一页 +query { + products(first: 10) { + edges { node { name } cursor } + pageInfo { hasNextPage, endCursor } + } +} + +// 加载更多 +query { + products(first: 10, after: "cursor-from-first-page") { + // ... + } +} +``` + +## 最佳实践总结 + +### 前端 + +✅ **推荐** +- 使用 `Intersection Observer` 实现无限滚动 +- URL 同步分页参数(支持前进后退) +- 显示"加载中"骨架屏提升感知性能 +- 合理的防抖/节流策略 +- 分页器智能压缩(显示省略号) + +❌ **避免** +- 首次加载请求所有数据(前端分页) +- scroll 事件高频触发(用 Observer 替代) +- 前端计算总数(需要后端提供) +- 不处理空数据/单页边界 + +### 后端 + +✅ **推荐** +- 统一参数命名(`page` / `page_size`) +- 参数验证与默认值(page ≥ 1, page_size ≤ 100) +- 使用索引优化查询(重点优化排序字段) +- 多层缓存策略 +- 返回总数和总页数 +- 深度分页使用游标优化 + +❌ **避免** +- 不限制最大查询深度(导致性能问题) +- 不返回总数(前端无法展示) +- 每次查询 COUNT(可用缓存或近似值) +- SQL 注入风险(排序字段白名单验证) + +### API 响应标准 + +```json +{ + "data": [...], + "total": 1250, + "page": 2, + "page_size": 20, + "total_page": 63, + "has_more": true +} +``` + +**字段说明**: +- `has_more`:方便前端判断是否加载更多(适用于无限滚动) + +## 常见问题解答 + +### Q: 深度分页性能如何优化? + +**问题**:查询第10000页需要扫描100万条记录 + +**答案**: +1. **限制深度**:禁止查询超过1000页 +2. **混合方案**:前N页OFFSET,深度页游标 +3. **游标分页**:使用 `WHERE id > last_id` 替代 OFFSET +4. **预计算映射**:缓存页码到ID的映射关系 + +### Q: 数据变化时分页如何处理? + +**场景**:用户在第5页,删除操作后页面变空 + +**答案**: +```typescript +// 删除后检查并跳转 +if (currentData.length === 1 && currentPage > 1) { + loadData(currentPage - 1); // 跳到上一页 +} else { + loadData(currentPage); // 重新加载当前页 +} +``` + +### Q: 如何支持自定义排序? + +**答案**: +```go +// 白名单验证防止SQL注入 +allowedFields := []string{"name", "created_at", "price"} + +sortQuery := "ORDER BY " + allowedField(req.SortBy) + " " + + (req.SortDesc ? "DESC" : "ASC") +``` + +### Q: 为什么有时候返回重复数据? + +**原因**: +- 传统分页:数据插入/删除导致位移 +- 游标分页:使用复合字段解决 + +```sql +-- 使用复合游标保证稳定 +WHERE (created_at, id) <= (:last_time, :last_id) +ORDER BY created_at DESC, id DESC +``` + +## 学习资源 + +- **PostgreSQL文档**:[Pagination](https://www.postgresql.org/docs/current/queries-limit.html) +- **MySQL优化**:[Optimizing LIMIT Queries](https://dev.mysql.com/doc/refman/8.0/en/optimization-limit-optimization.html) +- **Relay规范**:[Cursor-based Pagination](https://relay.dev/graphql/connections.htm) + +## 关联笔记 + +- [[金山办公作业/Week05/用户认证.md]] - 用户认证授权机制 +- [[CS/DB/索引优化]] - 数据库索引设计与优化 +- [[CS/NET/RESTful API]] - API 设计最佳实践 diff --git a/金山办公作业/Week05/数据库设计.md b/金山办公作业/Week05/数据库设计.md new file mode 100644 index 0000000..8aad7a7 --- /dev/null +++ b/金山办公作业/Week05/数据库设计.md @@ -0,0 +1,197 @@ +# 数据库设计文档 + +## 一、数据库概览 + +| 项目 | 说明 | +|------|------| +| 数据库名称 | `wordbook` | +| 字符集 | `utf8mb4` | +| 排序规则 | `utf8mb4_unicode_ci` | +| 存储引擎 | `InnoDB` | + +## 二、ER 图 + +```mermaid +erDiagram + USER ||--|{ WORD : has + USER { + uuid id PK "用户ID" + string username UK "用户名" + string password "密码(hash)" + datetime created_at "创建时间" + datetime updated_at "更新时间" + datetime deleted_at "删除时间(软删除)" + } + WORD { + uuid id PK "单词记录ID" + uuid user_id FK "所属用户ID" + string word "单词" + text definition "释义" + json examples "例句列表" + string ai_provider "AI模型来源" + datetime created_at "创建时间" + datetime updated_at "更新时间" + datetime deleted_at "删除时间(软删除)" + } +``` + +## 三、数据表详情 + +### 3.1 用户表 (users) + +| 用户表名 | `users` | +|----------|---------| + +| 字段名 | 数据类型 | 约束 | 索引 | 说明 | +|--------|----------|------|------|------| +| `id` | CHAR(36) | PRIMARY KEY | - | 用户唯一标识符(UUID 格式) | +| `username` | VARCHAR(50) | NOT NULL, UNIQUE | UNIQUE KEY | 用户名,全局唯一,用于登录 | +| `password` | VARCHAR(255) | NOT NULL | - | 用户密码,存储 bcrypt 哈希值,严禁明文存储 | +| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | - | 账户创建时间 | +| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | - | 账户最后更新时间 | +| `deleted_at` | DATETIME | DEFAULT NULL | INDEX | 软删除标记,NULL 表示未删除 | + +**索引设计:** +- 主键索引:`id` +- 唯一索引:`username`(防止重复注册) + +### 3.2 单词记录表 (words) + +| 单词表名 | `words` | +|-----------|---------| + +| 字段名 | 数据类型 | 约束 | 索引 | 说明 | +| ------------- | ------------ | --------------------------- | ------------------------- | ------------------------------------------ | +| `id` | CHAR(36) | PRIMARY KEY | - | 单词记录唯一标识符(UUID 格式) | +| `user_id` | CHAR(36) | NOT NULL, FOREIGN KEY | - | 所属用户 ID,关联 users.id | +| `word` | VARCHAR(100) | NOT NULL | INDEX idx_word_search(20) | 单词文本,支持前缀搜索优化 | +| `definition` | TEXT | NOT NULL | - | AI 生成的单词释义 | +| `examples` | JSON | NOT NULL | - | 例句列表,存储 JSON 数组格式 | +| `ai_provider` | ENUM | NOT NULL | - | AI 模型来源标识(`deepseek`=DeepSeek,`qwen`=通义千问) | +| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | - | 单词记录创建时间 | +| `updated_at` | DATETIME | ON UPDATE CURRENT_TIMESTAMP | - | 单词记录最后更新时间 | +| `deleted_at` | DATETIME | DEFAULT NULL | INDEX idx_user_word | 软删除标记 | + +**索引设计:** +- 主键索引:`id` +- 外键索引:`user_id`(自动创建) +- 唯一索引:`uk_user_word` (user_id, word(50)) - 防止同一用户保存重复单词 +- 复合索引:`idx_user_word` (user_id, deleted_at) - 优化用户单词列表分页查询 +- 前缀索引:`idx_word_search` (word(20)) - 支持单词前缀搜索优化 + +## 四、表关联关系 + +### 4.1 用户与单词的关系 + +``` +用户 (users) 1 ---- N 单词记录 (words) +``` + +- **关系类型**:一对多 +- **外键约束**:`words.user_id` → `users.id` +- **级联规则**:`ON DELETE CASCADE` + - 当用户被删除时,该用户的所有单词记录也会被自动删除 + +### 4.2 关联查询示例 + +```sql +-- 查询某用户的所有单词(排除已删除) +SELECT * FROM words +WHERE user_id = ? AND deleted_at IS NULL +ORDER BY created_at DESC +LIMIT ? OFFSET ?; + +-- 联表查询用户信息及单词数量 +SELECT u.id, u.username, COUNT(w.id) as word_count +FROM users u +LEFT JOIN words w ON u.id = w.user_id AND w.deleted_at IS NULL +WHERE u.deleted_at IS NULL +GROUP BY u.id; +``` + +### 4.2 唯一约束说明 + +`uk_user_word` 约束确保同一用户不能保存相同的单词两次: +- **触发场景**:在"手动保存单词"时 +- **业务逻辑**:前端应先调用"智能查询单词"接口检查是否已保存,避免触发重复错误 +- **不触发场景**:"智能查询单词"接口只查询不保存,不会触发此约束 + +## 五、核心业务 SQL 查询示例 + +### 5.1 智能查询单词 + +**业务逻辑**:先检查数据库,未命中则调用 AI(不保存) + +```sql +-- 查询:检查用户是否已保存该单词 +SELECT id, word, definition, examples, ai_provider +FROM words +WHERE user_id = ? AND word = ? AND deleted_at IS NULL; +``` + +### 5.2 手动保存单词 + +**业务逻辑**:将 AI 返回的结果写入数据库 + +```sql +-- 插入:保存单词记录(受 uk_user_word 唯一约束保护) +INSERT INTO words (id, user_id, word, definition, examples, ai_provider) +VALUES (?, ?, ?, ?, ?, ?); +``` + +### 5.3 获取单词列表(分页) + +**业务逻辑**:获取用户所有已保存的单词,支持分页 + +```sql +-- 查询:分页获取单词列表(按创建时间倒序) +SELECT id, word, definition, examples, ai_provider, created_at +FROM words +WHERE user_id = ? AND deleted_at IS NULL +ORDER BY created_at DESC +LIMIT ? OFFSET ?; + +-- 计算总数(用于分页器) +SELECT COUNT(*) as total +FROM words +WHERE user_id = ? AND deleted_at IS NULL; +``` + +### 5.4 删除单词(软删除) + +**业务逻辑**:根据单词 ID 软删除记录 + +```sql +-- 更新:软删除单词记录 +UPDATE words +SET deleted_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP +WHERE id = ? AND user_id = ? AND deleted_at IS NULL; +``` + +## 六、索引优化说明 + +### 6.1 查询场景分析 + +| 查询场景 | 涉及字段 | 索引策略 | +|----------|----------|----------| +| 用户登录 | username | UNIQUE KEY | +| 智能查询单词 | user_id, word, deleted_at | uk_user_word(唯一约束) | +| 用户单词列表(分页) | user_id, deleted_at | 复合索引 idx_user_word | +| 单词搜索 | word | 前缀索引 idx_word_search | +| 按用户 ID 查找单词 | user_id | 外键自动索引 | + +### 6.2 示例 JSON 数据结构 (examples 字段) + +```json +[ + "The word 'serendipity' means finding something good without looking for it.", + "It was pure serendipity that I met my best friend at the coffee shop.", + "Many scientific discoveries are the result of serendipity." +] +``` + +## 七、数据初始化 + +数据库初始化脚本位于 `docs/init.sql`,该脚本会在 Docker Compose 启动 MySQL 容器时自动执行。 + +**严禁使用 GORM 的 `AutoMigrate` 功能进行建表,必须通过此 SQL 脚本初始化数据库。** diff --git a/金山办公作业/Week05/用户ID.md b/金山办公作业/Week05/用户ID.md new file mode 100644 index 0000000..fee9f1c --- /dev/null +++ b/金山办公作业/Week05/用户ID.md @@ -0,0 +1,357 @@ +--- +tags: [database, distributed-system, architecture, week05] +create time: 2026-04-18 10:45 +--- + +# 用户ID + +## 概述 + +深入分析分布式系统中用户ID的三种主流生成方案:自增ID、UUID和雪花ID。本文从原理、性能、适用场景等多维度对比,帮助开发者在实际项目中做出明智的技术选型,并重点关注进阶问题和实战陷阱。 + +## 正文 + +### 核心对比 + +| 维度 | 自增ID | UUID | 雪花ID | +|------|--------|------|--------| +| **生成方式** | 数据库自增 | 算法生成 | 时间戳+机器ID+序列 | +| **有序性** | 单机有序 | 完全无序 | 时间有序 | +| **分布式友好度** | 需要额外方案 | ✅ 原生支持 | ✅ 原生支持 | +| **性能/TPS** | 10万+ | 1万+ | 100万+ | +| **ID长度** | 8-10字节 | 36字符 | 19字符(十进制) | +| **冲突概率** | 0(单机) | ≈1/2^128 | 理论0 | +| **索引友好度** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | +| **可读性** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ | + +### 自增ID + +#### 原理与演进 + +在单机架构中,数据库维护一个递增计数器,每次插入时自动获取下一个可用的整数值。虽然简单,但随着业务发展到分布式场景,暴露出以下问题: + +```go +// MySQL 自增ID实现 +type AutoIncrementID struct { + mu sync.Mutex + nextID int64 +} + +func (a *AutoIncrementID) Next() int64 { + a.mu.Lock() + defer a.mu.Unlock() + a.nextID++ + return a.nextID +} +``` + +**分布式扩展方案**: + +1. **分段步长策略**:每台DB配置不同的起始值和步长 + - DB1: 1, 4, 7... (start=1, step=3) + - DB2: 2, 5, 8... (start=2, step=3) + - DB3: 3, 6, 9... (start=3, step=3) + +2. **Redis原子递增**:使用 `INCR` 命令实现分布式自增 + - 单线程保证原子性,性能受限于单机瓶颈 + - 集群模式下的分布式锁带来额外延迟 + +#### 深槽陷阱 + +**问题:ID泄露业务规模** + +攻击者可以通过观察用户ID推断出系统的用户增长速度和总规模。 + +**解决方案**: +- 在对外层添加混淆层(将真实ID映射到随机ID) +- 使用非连续的ID生成策略 +- 对主键ID对用户隐藏,使用生成的业务ID + +**问题:表级自增成为写入瓶颈** + +在高并发场景下,自增锁成为表级别的竞争点。 + +**优化思路**: +- 使用分段缓存(预分配1000个ID到应用层,减少数据库交互) +- 迁移到雪花ID等分布式方案 + +### UUID + +#### 标准解析 + +UUID(Universally Unique Identifier)是RFC 4122标准定义的128位标识符。 + +**版本对比**: + +```go +package main + +import ( + "fmt" + "github.com/google/uuid" +) + +func main() { + // UUID v1: 基于时间戳和MAC地址(有序但泄露信息) + uuidv1 := uuid.Must(uuid.NewUUID()) + fmt.Println("v1:", uuidv1) + + // UUID v4: 随机生成(完全无序,最常用) + uuidv4 := uuid.Must(uuid.NewRandom()) + fmt.Println("v4:", uuidv4) + + // UUID v5: 基于命名空间的SHA-1哈希(确定性) + namespace := uuid.Must(uuid.Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) + uuidv5 := uuid.NewSHA1(namespace, []byte("user@example.com")) + fmt.Println("v5:", uuidv5) +} +``` + +#### 深槽陷阱 + +**问题:B+树索引页分裂** + +B+树是有序的聚簇索引结构,当插入无序ID时,会导致频繁的页分裂,影响写入性能。 + +**可视化影响**: + +```mermaid +graph LR + A[插入有序ID] -->|顺序追加| B[B+树追加到末尾] + C[插入UUID] -->|随机位置| D[B+树页分裂重组] + B --> E[IO操作少
写入快] + D --> F[频繁磁盘IO
写入慢] +``` + +**优化策略**: + +1. **使用ULID(Universally Unique Lexicographically Sortable ID)**:兼容UUID格式但支持排序 + ```go + import "github.com/oklog/ulid/v2" + t := time.Unix(time.Now().Unix(), 0) + entropy := ulid.Monotonic(rand.New(rand.NewSource(t.UnixNano())), 0) + id := ulid.MustNew(ulid.Timestamp(t), entropy) + ``` + +2. **使用UUID v2/ULID版本**:基于时间戳的有序版本 + +3. **使用组合索引**:将UUID作为二级索引,有序ID作为聚簇索引 + +**问题:存储空间膨胀** + +UUID长度36字符,相比8字节自增ID存储空间增长4.5倍。 + +**优化措施**: +- 存储时去除连字符(16字节),应用层再格式化 +- 考虑使用Snowflake等更短的方案 + +### 雪花ID + +#### 算法原理 + +雪花ID是Twitter开源的分布式ID生成算法,采用64位整数结构: + +``` +位数:1 | 41 | 10 | 12 +含义:符号位 | 时间戳 | 机器ID | 序列号 +``` + +**时间戳**:41位,每个单位代表毫秒,可用年限 (2^41-1)/(1000×60×60×24×365) ≈ 69年 + +```go +// 雪花ID生成器实现 +type Snowflake struct { + mu sync.Mutex + machineID int64 // 10位机器ID,支持1024个节点 + epoch int64 // 起始时间戳 + sequence int64 // 12位序列号,单毫秒最多4096个 + lastTime int64 +} + +func NewSnowflake(machineID int64) *Snowflake { + return &Snowflake{ + machineID: machineID & 0x3FF, // 确保在10位范围内 + epoch: 1609459200000, // 2021-01-01 + } +} + +func (s *Snowflake) Generate() int64 { + s.mu.Lock() + defer s.mu.Unlock() + + now := time.Now().UnixNano() / 1e6 // 当前毫秒时间戳 + + if now < s.lastTime { + // 时钟回拨,抛出异常 + panic("时钟回拨,无法生成ID") + } + + if now == s.lastTime { + // 同一毫秒内,递增序列号 + s.sequence = (s.sequence + 1) & 0xFFF + if s.sequence == 0 { + // 序列号溢出,等待下一毫秒 + for now <= s.lastTime { + now = time.Now().UnixNano() / 1e6 + } + } + } else { + // 新的毫秒,序列号重置 + s.sequence = 0 + } + + s.lastTime = now + + // 计算:时间戳左移22位 + 机器ID左移12位 + 序列号 + id := ((now - s.epoch) << 22) | (s.machineID << 12) | s.sequence + return id +} +``` + +#### 进阶优化 + +**1. 机器ID动态分配** + +在容器化/云原生场景中,提前分配固定机器ID难以维护。改进方案: + +```go +// 使用Zookeeper动态注册 +func RegisterMachineID(zkClient *zk.Conn, basePath string) (int64, error) { + path := fmt.Sprintf("%s/node-", basePath) + zkPath, err := zkClient.Create(path, []byte{}, zk.FlagEphemeralSequential, zk.WorldACL(zk.PermAll)) + if err != nil { + return 0, err + } + + // 从临时有序节点中提取序号作为机器ID + nodeNum, _ := strconv.ParseInt(path[len(basePath)+1:], 10, 64) + return nodeNum % 1024, nil // 确保在1024范围内 +} +``` + +**2. 时钟回拨处理** + +时钟回拨会导致重复ID,需要特殊处理: + +```go +func (s *Snowflake) handleClockBackoff(now int64) int64 { + offset := s.lastTime - now + if offset < 10 { + // 小幅回拨,等待时钟同步 + time.Sleep(time.Duration(offset+10) * time.Millisecond) + return time.Now().UnixNano() / 1e6 + } else if offset < 1000 { + // 中度回拨,使用备用时间戳(增大序列号作为区分) + s.sequence = (s.sequence + 1) & 0xFFF + return s.lastTime + } else { + // 大幅回拨,告警并拒绝生成 + log.Printf("严重时钟回拨:回拨时长%dms", offset) + panic("时钟回拨超过容忍范围") + } +} +``` + +**3. 雪花ID的可读性优化** + +雪花ID的19位数字对用户不友好,应用层可做映射转换: + +```go +// 将雪花ID转换为更友好的格式 +func FriendlyID(snowflakeID int64) string { + // 使用Base58编码(去除易混淆字符) + alphabet := "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + return base58.Encode([]byte(strconv.FormatInt(snowflakeID, 10))) +} +``` + +#### 深槽陷阱 + +**问题:单机QPS受限** + +单机理论QPS = 1000ms × 4096 = 409万/秒,但实际受限于网络延迟、GC等。 + +**优化**: +- 使用本地缓存批量生成,减少锁竞争 +- 批量化ID生成接口(一次生成100个,供应用层使用) + +**问题:机器ID浪费** + +10位机器ID支持1024个节点,但实际业务可能只有几十个。 + +**思路**:将机器ID拆分为5位数据中心ID + 5位工作机ID,支持更多灵活部署 + +### 方案选型决策 + +根据业务特征进行技术选型: + +```mermaid +flowchart TD + A[开始选择ID方案] --> B{需要
分布式?} + B -->|否| C[自增ID
简单高效] + B -->|是| D{使用场景} + D --> E{主要需求} + E -->|高性能查询| F[雪花ID
有序高性能] + E -->|快速开发| G[UUID v4
即用即走] + E -->|用户可见
可读| H[自增ID+混淆
或自定义短ID] + + style C fill:#90EE90 + style F fill:#90EE90 + style G fill:#90EE90 + style H fill:#90EE90 +``` + +**实战建议**: + +| 场景 | 推荐方案 | 理由 | +|------|----------|------| +| 内部系统关联表ID | 雪花ID | 有序,查询性能好 | +| 用户可见的订单号 | 自增ID+混淆 | 用户友好,可控制格式 | +| 临时会话Token | UUID | 快速生成,无需持久化 | +| 日志/审计ID | UUID | 无冲突风险,生成简单 | +| 移动端离线ID | UUID | 中心化服务不依赖 | + +### 进阶思考 + +**1. 混合方案的可行性** + +能否在不同层级使用不同ID方案?例如:数据库层使用雪花ID(有序),对外接口使用UUID v5(业务ID)? + +``` +业务流程: +用户注册 → 生成UUID v5(业务ID) → 写入数据库(雪花ID为主键) +查询流程: +查询用户 → UUID v5 → 映射到雪花ID → 数据库查询 +``` + +**2. ID生成的可迁移性** + +如果需要从自增ID迁移到雪花ID,如何设计增量迁移策略? + +```go +// 双写+灰度方案 +func GenerateUserID() int64 { + // 灰度期:新用户用雪花ID,老用户保持自增ID + if isFeatureEnabled("snowflake-id") { + return snowflake.Generate() + } + return autoIncrement.Next() +} +``` + +**3. 跨IDC的雪花ID组网** + +多机房场景下,如何设计雪花ID的机器ID分配? + +``` +机房A: 0x00-0x0F (16个机器) +机房B: 0x10-0x1F (16个机器) +机房C: 0x20-0x2F (16个机器) +... +``` + +## 关联笔记 + +- [[金山办公作业/Week05/分页.md]] +- [[金山办公作业/Week05/用户认证.md]] +- [[CS/DB/数据库索引原理]] diff --git a/金山办公作业/Week05/用户认证.md b/金山办公作业/Week05/用户认证.md new file mode 100644 index 0000000..1c0c1d6 --- /dev/null +++ b/金山办公作业/Week05/用户认证.md @@ -0,0 +1,438 @@ +--- +tags: [go, auth, security, jwt, session, authorization, assignment] +create time: 2026-04-17 +--- + +# Go 语言用户认证实现指南 + +## 概述 + +用户认证是 Web 应用的核心安全机制。本文介绍在 Go 中实现用户认证的常见模式和最佳实践。 + +## 认证方式流程 + +### Session-Based 认证 + +```mermaid +sequenceDiagram + participant C as Client + participant S as Server + participant DB as Database + + C->>S: POST /login (username, password) + S->>DB: 验证凭证 + DB-->>S: 用户信息 + S->>S: 创建 Session + S-->>C: Set-Cookie: session_id=xxx + Note over C: 存储到浏览器 + + C->>S: GET /protected + Cookie + S->>DB: 根据 session_id 查询 + DB-->>S: Session 数据 + S-->>C: 返回受保护资源 +``` + +### JWT Token 认证 + +```mermaid +sequenceDiagram + participant C as Client + participant S as Server + participant Token as JWT Token + + C->>S: POST /login (credentials) + S->>S: 验证用户 + S->>S: 生成 JWT + Token->>S: eyJhbGciOiJIUzI1NiIs... + S-->>C: {access_token: "...", refresh_token: "..."} + + C->>S: GET /api + Authorization: Bearer {token} + S->>S: 验证 Token 签名和过期 + S-->>C: 返回 API 数据 +``` + +## 对比分析 + +| 特性 | Session | JWT | +|-----|--------|-----| +| 存储位置 | 服务器 | 客户端 | +| 状态性 | 有状态 | 无状态 | +| 分布式支持 | 需要共享机制 | 原生支持 | +| 可撤销性 | 容易 | 困难 | +| 适用场景 | 传统网页 | RESTful API | + +## OAuth 2.0 流程 + +```mermaid +sequenceDiagram + participant U as User + participant A as App + participant P as Provider (Google/GitHub) + + U->>A: 点击登录 + A->>P: 重定向到授权页 + U->>P: 同意授权 + P->>A: 重定向 + code + A->>P: 用 code 换取 access_token + P-->>A: {access_token, refresh_token} + A->>P: 使用 token 获取用户信息 + P-->>A: 用户信息 + A-->>U: 登录成功 +``` + +## Go 语言实现方案 + +### Session-Based 实现 + +#### 使用 gorilla/sessions + +```go +package main + +import ( + "net/http" + "github.com/gorilla/sessions" +) + +var store = sessions.NewCookieStore([]byte("secret-key")) + +func loginHandler(w http.ResponseWriter, r *http.Request) { + // 验证用户凭证 + if validateUser(r) { + session, _ := store.Get(r, "session-name") + session.Values["user_id"] = "123" + session.Values["authenticated"] = true + session.Save(r, w) + } +} + +func authMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + session, _ := store.Get(r, "session-name") + if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + next(w, r) + } +} +``` + +### JWT Implementation + +#### 标准流程 + +```go +package authenticator + +import ( + "time" + "github.com/golang-jwt/jwt/v5" +) + +type Claims struct { + UserID string `json:"user_id"` + Username string `json:"username"` + jwt.RegisteredClaims +} + +type JWTAuthenticator struct { + secretKey []byte +} + +func NewJWTAuthenticator(secretKey string) *JWTAuthenticator { + return &JWTAuthenticator{ + secretKey: []byte(secretKey), + } +} + +// 生成 Token +func (j *JWTAuthenticator) GenerateToken(userID, username string) (string, error) { + claims := Claims{ + UserID: userID, + Username: username, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), + IssuedAt: jwt.NewNumericDate(time.Now()), + NotBefore: jwt.NewNumericDate(time.Now()), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(j.secretKey) +} + +// 验证 Token +func (j *JWTAuthenticator) ValidateToken(tokenString string) (*Claims, error) { + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + return j.secretKey, nil + }) + + if err != nil { + return nil, err + } + + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + return claims, nil + } + + return nil, jwt.ErrSignatureInvalid +} + +// 中间件 +func (j *JWTAuthenticator) AuthMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + http.Error(w, "Missing authorization header", http.StatusUnauthorized) + return + } + + tokenString := strings.TrimPrefix(authHeader, "Bearer ") + claims, err := j.ValidateToken(tokenString) + if err != nil { + http.Error(w, "Invalid token", http.StatusUnauthorized) + return + } + + // 将用户信息存入上下文 + ctx := context.WithValue(r.Context(), "user", claims) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} +``` + +### OAuth 2.0 集成 + +```go +package oauth + +import ( + "context" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" +) + +var ( + googleOauthConfig = &oauth2.Config{ + RedirectURL: "http://localhost:8080/callback", + ClientID: "your-client-id", + ClientSecret: "your-client-secret", + Scopes: []string{ + "https://www.googleapis.com/auth/userinfo.email", + }, + Endpoint: google.Endpoint, + } +) + +func HandleGoogleLogin(w http.ResponseWriter, r *http.Request) { + url := googleOauthConfig.AuthCodeURL("state-token") + http.Redirect(w, r, url, http.StatusTemporaryRedirect) +} + +func HandleGoogleCallback(w http.ResponseWriter, r *http.Request) { + code := r.URL.Query().Get("code") + token, err := googleOauthConfig.Exchange(context.Background(), code) + if err != nil { + http.Error(w, "Failed to exchange token", http.StatusBadRequest) + return + } + + // 使用 token 获取用户信息 + // ... +} +``` + +## JWT 结构 + +```mermaid +graph LR + A[JWT Token] --> B[Header] + A --> C[Payload] + A --> D[Signature] + + B --> B1[alg: HS256] + B --> B2[typ: JWT] + + C --> C1[user_id] + C --> C2[exp: 时间戳] + C --> C3[iat: 颁发时间] + + D --> D1[Header + Payload] + D --> D2[Secret Key] +``` + +## Authorization(授权) + +**认证 vs 授权**: +- **认证(Authentication)**:你是谁?解决身份验证问题 +- **授权(Authorization)**:你能做什么?解决权限控制问题 + +### Authorization 头格式 + +| 方式 | 格式 | 示例 | +|-----|------|------| +| Bearer Token | `Bearer ` | `Bearer eyJhbGciOiJIUzI1NiIs...` | +| Basic Auth | `Basic ` | `Basic YWxhZGRpbjpvcGVuc2VzYW1l` | +| API Key | `ApiKey ` | `ApiKey abc123xyz` | + +### 在 Go 中处理 Authorization + +```go +// Bearer Token 解析 +func parseBearerToken(header string) (string, error) { + parts := strings.SplitN(header, " ", 2) + if parts[0] != "Bearer" || len(parts) < 2 { + return "", errors.New("invalid authorization format") + } + return parts[1], nil +} + +// Basic Auth 解析 +func parseBasicAuth(header string) (username, password string, err error) { + parts := strings.SplitN(header, " ", 2) + if parts[0] != "Basic" || len(parts) < 2 { + return "", "", errors.New("invalid basic auth format") + } + + decoded, err := base64.StdEncoding.DecodeString(parts[1]) + if err != nil { + return "", "", err + } + + credentials := string(decoded) + idx := strings.Index(credentials, ":") + if idx == -1 { + return "", "", errors.New("invalid credentials format") + } + + return credentials[:idx], credentials[idx+1:], nil +} +``` + +**详细信息**:💡 详见 [[CS/NET/Authorization]] - Authorization 机制详解(RBAC、ABAC、策略引擎) + +## 安全最佳实践 + +### 密码安全 + +```go +package auth + +import "golang.org/x/crypto/bcrypt" + +// 加密密码 +func HashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(bytes), err +} + +// 验证密码 +func CheckPassword(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} +``` + +### 密钥管理 + +- ✅ 使用环境变量存储密钥 +- ✅ 使用专业密钥管理服务(HashiCorp Vault) +- ❌ 不将密钥写入代码库 +- ❌ 不在日志中打印密钥 + +### HTTPS 强制 + +```go +func RedirectHTTPS(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Scheme != "https" { + httpsURL := "https://" + r.Host + r.URL.RequestURI() + http.Redirect(w, r, httpsURL, http.StatusMovedPermanently) + return + } + next.ServeHTTP(w, r) + }) +} +``` + +## 常见安全威胁与防护 + +### CSRF 攻击流程与防护 + +```mermaid +sequenceDiagram + participant A as 攻击者 + participant U as 用户 + participant S as 服务器 + + %%% 受害场景 %%% + U->>U: 已登录网站 S,持有 Cookie + A->>U: 诱导点击恶意链接 + U->>S: POST /transfer (自动携带 Cookie) + S-->>U: 转账成功 ⚠️ + + %%% 防护方案 %%% + Note over S: CSRF Token 模式 + U->>S: GET /form + S-->>U: HTML + CSRF Token + U->>S: POST + Token in data + S->>S: 验证 Token + S-->>U: 请求通过 ✓ +``` + +### 威胁防护清单 + +| 威胁类型 | 防护措施 | +|---------|---------| +| SQL 注入 | 使用参数化查询、ORM | +| XSS 攻击 | 输入验证、输出编码 | +| CSRF 攻击 | CSRF Token、SameSite Cookie | +| 重放攻击 | Timestamp、Nonce 验证 | +| Token 窃取 | HTTPS、短期 Token、刷新机制 | + +## 性能优化 + +### JWT 优化 + +```go +// 1. 缓存已验证 Token +type CacheAuthenticator struct { + jwt *JWTAuthenticator + cache *cache.Cache +} + +// 2. 使用 Key ID 轮换密钥 +type KeyRotation struct { + currentKey []byte + oldKeys [][]byte +} + +// 3. 缩短 Token 生命周期,使用 Refresh Token +type TokenPair struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` +} +``` + +## 推荐库 + +| 库名 | 用途 | 特点 | +|-----|------|------| +| gorilla/sessions | Session 管理 | 功能完善,支持多种存储 | +| golang-jwt/jwt | JWT 处理 | 轻量级,标准实现 | +| golang.org/x/oauth2 | OAuth 2.0 | 官方实现,支持主流平台 | +| golang.org/x/crypto | 密码加密 | 包含 bcrypt、scrypt 等算法 | + +## 学习资源 + +- [JWT 官方文档](https://jwt.io/) +- [OWASP 认证备忘单](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html) +- [Go Web Examples - Authentication](https://gowebexamples.com/routes/) + +## 相关笔记 + +- [[CS/NET/Authorization]] - Authorization 授权机制详解 +- [[CS/NET/HTTPS]] - HTTPS 原理 +- [[CS/OS/进程线程]] - 并发安全 +- [[CS/DB/SQL安全]] - SQL 注入防护