ethan-skill 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +83 -24
  2. package/dist/skills/15-git-workflow.d.ts +3 -0
  3. package/dist/skills/15-git-workflow.d.ts.map +1 -0
  4. package/dist/skills/15-git-workflow.js +288 -0
  5. package/dist/skills/15-git-workflow.js.map +1 -0
  6. package/dist/skills/16-unit-testing.d.ts +3 -0
  7. package/dist/skills/16-unit-testing.d.ts.map +1 -0
  8. package/dist/skills/16-unit-testing.js +298 -0
  9. package/dist/skills/16-unit-testing.js.map +1 -0
  10. package/dist/skills/17-system-design.d.ts +3 -0
  11. package/dist/skills/17-system-design.d.ts.map +1 -0
  12. package/dist/skills/17-system-design.js +294 -0
  13. package/dist/skills/17-system-design.js.map +1 -0
  14. package/dist/skills/18-database-optimize.d.ts +3 -0
  15. package/dist/skills/18-database-optimize.d.ts.map +1 -0
  16. package/dist/skills/18-database-optimize.js +294 -0
  17. package/dist/skills/18-database-optimize.js.map +1 -0
  18. package/dist/skills/19-docker.d.ts +3 -0
  19. package/dist/skills/19-docker.d.ts.map +1 -0
  20. package/dist/skills/19-docker.js +360 -0
  21. package/dist/skills/19-docker.js.map +1 -0
  22. package/dist/skills/20-cicd.d.ts +3 -0
  23. package/dist/skills/20-cicd.d.ts.map +1 -0
  24. package/dist/skills/20-cicd.js +364 -0
  25. package/dist/skills/20-cicd.js.map +1 -0
  26. package/dist/skills/21-performance.d.ts +3 -0
  27. package/dist/skills/21-performance.d.ts.map +1 -0
  28. package/dist/skills/21-performance.js +139 -0
  29. package/dist/skills/21-performance.js.map +1 -0
  30. package/dist/skills/22-refactoring.d.ts +3 -0
  31. package/dist/skills/22-refactoring.d.ts.map +1 -0
  32. package/dist/skills/22-refactoring.js +235 -0
  33. package/dist/skills/22-refactoring.js.map +1 -0
  34. package/dist/skills/23-observability.d.ts +3 -0
  35. package/dist/skills/23-observability.d.ts.map +1 -0
  36. package/dist/skills/23-observability.js +266 -0
  37. package/dist/skills/23-observability.js.map +1 -0
  38. package/dist/skills/24-design-patterns.d.ts +3 -0
  39. package/dist/skills/24-design-patterns.d.ts.map +1 -0
  40. package/dist/skills/24-design-patterns.js +258 -0
  41. package/dist/skills/24-design-patterns.js.map +1 -0
  42. package/dist/skills/index.d.ts +10 -0
  43. package/dist/skills/index.d.ts.map +1 -1
  44. package/dist/skills/index.js +41 -1
  45. package/dist/skills/index.js.map +1 -1
  46. package/dist/skills/skills.test.js +3 -3
  47. package/dist/skills/skills.test.js.map +1 -1
  48. package/dist/templates/templates.test.js +2 -3
  49. package/dist/templates/templates.test.js.map +1 -1
  50. package/package.json +1 -1
  51. package/rules/claude-code/CLAUDE.md +2410 -3
  52. package/rules/cline/.clinerules +2262 -2
  53. package/rules/codebuddy/CODEBUDDY.md +2361 -2
  54. package/rules/continue/.continuerules +2262 -2
  55. package/rules/copilot/copilot-instructions.md +2331 -2
  56. package/rules/cursor/.cursorrules +2399 -2
  57. package/rules/cursor/smart-flow.mdc +2399 -2
  58. package/rules/jetbrains/smart-flow.md +2331 -2
  59. package/rules/lingma/smart-flow.md +2352 -3
  60. package/rules/windsurf/.windsurf/rules/smart-flow.md +2332 -3
  61. package/rules/zed/smart-flow.rules +2251 -1
@@ -1,4 +1,4 @@
1
- Ethan v1.6.0 | 2026-03-31T07:56:06.363Z
1
+ Ethan v1.8.0 | 2026-03-31T16:55:43.257Z
2
2
 
3
3
  Workflow automation assistant. IMPORTANT: When user message matches a trigger keyword below, immediately activate that skill and execute ALL steps in order. Do not skip steps. Output per skill template.
4
4
 
@@ -1174,6 +1174,2256 @@ Workflow automation assistant. IMPORTANT: When user message matches a trigger ke
1174
1174
 
1175
1175
  Output: Markdown PRD 文档,含背景目标、用户故事、功能详情(含验收标准 AC)、非功能性需求、UI/UX 说明和埋点方案
1176
1176
 
1177
+ ------------------------------------------------------------
1178
+
1179
+ 15. Git 工作流 [Git 工作流/git workflow/git 规范]
1180
+ 规范 Git 分支策略、提交规范、合并流程,建立团队一致的版本控制工作流
1181
+
1182
+ 1. 评估项目特征,选择分支策略
1183
+ 根据团队规模和发布节奏选择合适的分支策略:
1184
+
1185
+ **GitFlow 适用场景**
1186
+ - 有明确版本号的产品(如 App、SDK、开源库)
1187
+ - 需要维护多个线上版本
1188
+ - 发布周期较长(周/月级别)
1189
+
1190
+ ```
1191
+ main ──●────────────────────●── (生产稳定)
1192
+ hotfix/1.0.1 └──●──┘ (紧急修复)
1193
+ release/1.1 └──●──┘ (预发布验证)
1194
+ develop ──●──────●──────●──────●── (集成分支)
1195
+ feature/login └──●──┘ (功能开发)
1196
+ ```
1197
+
1198
+ **Trunk-Based Development 适用场景**
1199
+ - 持续部署(CD)体系成熟
1200
+ - 有完善的 Feature Flag 机制
1201
+ - 团队规模适中(≤50 人),发布频率高(日/周)
1202
+
1203
+ ```
1204
+ main ──●──●──●──●──●── (直接推送或短命分支 <2天)
1205
+ feat └──●──┘ (短命功能分支,快速合并)
1206
+ ```
1207
+
1208
+ **决策矩阵**
1209
+
1210
+ | 维度 | GitFlow | Trunk-Based |
1211
+ |------|---------|-------------|
1212
+ | 发布频率 | 低(周/月) | 高(日/周) |
1213
+ | 团队规模 | 大 | 中小 |
1214
+ | 多版本维护 | 支持 | 不擅长 |
1215
+ | CI/CD 成熟度 | 低要求 | 高要求 |
1216
+
1217
+ 2. 制定提交信息规范(Conventional Commits)
1218
+ 采用 Conventional Commits 规范,格式:`<type>(<scope>): <subject>`
1219
+
1220
+ **类型(type)定义**
1221
+
1222
+ | type | 用途 | 版本影响 |
1223
+ |------|------|---------|
1224
+ | `feat` | 新功能 | MINOR |
1225
+ | `fix` | Bug 修复 | PATCH |
1226
+ | `perf` | 性能优化 | PATCH |
1227
+ | `refactor` | 重构(无功能变化) | — |
1228
+ | `docs` | 文档变更 | — |
1229
+ | `test` | 测试相关 | — |
1230
+ | `chore` | 构建/依赖/工具 | — |
1231
+ | `ci` | CI 配置变更 | — |
1232
+ | `BREAKING CHANGE` | 破坏性变更(Footer) | MAJOR |
1233
+
1234
+ **示例**
1235
+ ```bash
1236
+ # 好的提交信息
1237
+ feat(auth): add OAuth2 login with Google provider
1238
+ fix(cart): prevent duplicate item addition on rapid click
1239
+ perf(query): add composite index on (user_id, created_at)
1240
+ refactor(api): extract pagination helper to shared utils
1241
+ docs(readme): update installation steps for Node 20
1242
+
1243
+ # 破坏性变更写法
1244
+ feat(api)!: rename /users endpoint to /accounts
1245
+
1246
+ BREAKING CHANGE: /users endpoint removed, use /accounts instead
1247
+ ```
1248
+
1249
+ **工具链配置**
1250
+ ```bash
1251
+ # 安装 commitlint
1252
+ npm install -D @commitlint/cli @commitlint/config-conventional
1253
+ echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
1254
+
1255
+ # 配合 husky 在 commit-msg 钩子校验
1256
+ npx husky add .husky/commit-msg 'npx --no -- commitlint --edit $1'
1257
+ ```
1258
+
1259
+ 3. Rebase vs Merge 决策与实践
1260
+ **核心原则:黄金法则 — 不要 rebase 已推送的公共分支**
1261
+
1262
+ **何时用 Merge**
1263
+ - 合并长期分支(feature → develop)
1264
+ - 需要保留完整历史记录(审计场景)
1265
+ - 多人协作的共享分支
1266
+
1267
+ ```bash
1268
+ # 保留合并记录(推荐用于 PR/MR 合并)
1269
+ git merge --no-ff feature/login
1270
+
1271
+ # 快进合并(适合独立小修改)
1272
+ git merge --ff-only hotfix/typo
1273
+ ```
1274
+
1275
+ **何时用 Rebase**
1276
+ - 更新本地功能分支,与主干保持同步
1277
+ - 整理本地提交历史,推送 PR 前清理
1278
+
1279
+ ```bash
1280
+ # 将功能分支变基到最新 main
1281
+ git checkout feature/login
1282
+ git rebase origin/main
1283
+
1284
+ # 交互式 rebase:合并/重排/修改最近 3 个提交
1285
+ git rebase -i HEAD~3
1286
+ # 选项: pick / squash(s) / fixup(f) / reword(r) / drop(d)
1287
+ ```
1288
+
1289
+ **Squash Merge**(GitHub/GitLab PR 推荐)
1290
+ ```bash
1291
+ # 将功能分支所有提交合并为一个干净提交
1292
+ git merge --squash feature/login
1293
+ git commit -m "feat(auth): add login page with form validation"
1294
+ ```
1295
+
1296
+ **推荐工作流**
1297
+ 1. 本地开发:随意提交,保持节奏
1298
+ 2. 推送 PR 前:`git rebase -i origin/main` 整理提交
1299
+ 3. PR 合并:使用 Squash Merge 保持主干干净
1300
+
1301
+ 4. 冲突解决流程
1302
+ **结构化冲突解决步骤**
1303
+
1304
+ ```bash
1305
+ # Step 1: 理解冲突来源
1306
+ git log --oneline --graph --all # 查看分支关系
1307
+ git diff HEAD origin/main # 对比差异
1308
+
1309
+ # Step 2: 标记冲突文件分析
1310
+ git status # 查看所有冲突文件
1311
+ # conflict markers: <<<<<<< HEAD ... ======= ... >>>>>>> branch
1312
+
1313
+ # Step 3: 使用工具辅助解决
1314
+ git mergetool # 调用配置的 merge tool(VSCode / IntelliJ)
1315
+
1316
+ # 配置 VSCode 为默认 merge tool
1317
+ git config --global merge.tool vscode
1318
+ git config --global mergetool.vscode.cmd 'code --wait $MERGED'
1319
+ ```
1320
+
1321
+ **三路合并理解(Three-way merge)**
1322
+ ```
1323
+ BASE(公共祖先):const timeout = 5000;
1324
+ OURS(当前分支):const timeout = 10000; // 改为10s
1325
+ THEIRS(被合并):const TIMEOUT = 5000; // 改为大写常量名
1326
+ RESULT(手动): const TIMEOUT = 10000; // 两个改动都要
1327
+ ```
1328
+
1329
+ **预防冲突的最佳实践**
1330
+ - 功能分支生命周期控制在 1-3 天内
1331
+ - 每日同步主干:`git pull --rebase origin main`
1332
+ - 大文件/自动生成文件加入 `.gitattributes` 配置合并策略
1333
+ ```gitattributes
1334
+ # 始终使用 ours 策略合并 lock 文件(减少冲突)
1335
+ package-lock.json merge=ours
1336
+ yarn.lock merge=ours
1337
+ ```
1338
+
1339
+ 5. Pull Request / Code Review 流程规范
1340
+ **PR 模板设计**
1341
+ ```markdown
1342
+ ## 变更说明
1343
+ [简洁描述本次变更做了什么、为什么]
1344
+
1345
+ ## 变更类型
1346
+ - [ ] 新功能 (feat)
1347
+ - [ ] Bug 修复 (fix)
1348
+ - [ ] 重构 (refactor)
1349
+ - [ ] 性能优化 (perf)
1350
+
1351
+ ## 测试验证
1352
+ - [ ] 单元测试通过
1353
+ - [ ] 手动测试场景: [描述]
1354
+ - [ ] 截图/录屏(UI 变更必填)
1355
+
1356
+ ## 影响范围
1357
+ [描述可能影响的模块或依赖方]
1358
+
1359
+ ## Checklist
1360
+ - [ ] 代码自查完毕
1361
+ - [ ] 无调试代码 (console.log/debugger)
1362
+ - [ ] 文档已更新(如需要)
1363
+ ```
1364
+
1365
+ **PR 规模控制**
1366
+ - 理想 PR 大小:< 400 行(不含测试)
1367
+ - 超过 800 行:强制拆分为多个 PR
1368
+ - 可用 `git diff --stat origin/main` 提前检查
1369
+
1370
+ **分支保护规则(GitHub/GitLab 配置)**
1371
+ ```
1372
+ main 分支保护:
1373
+ ✅ Require pull request reviews (min: 1)
1374
+ ✅ Require status checks to pass (CI/lint/test)
1375
+ ✅ Require branches to be up to date
1376
+ ✅ Restrict push access (仅管理员)
1377
+ ✅ Require signed commits(高安全场景)
1378
+ ```
1379
+
1380
+ 6. 输出工作流规范文档
1381
+ 整理为团队可直接使用的规范文档,格式如下:
1382
+
1383
+ ```markdown
1384
+ ## Git 工作流规范
1385
+
1386
+ ### 分支命名
1387
+ - feature/<ticket-id>-short-description (如: feature/PROJ-123-user-login)
1388
+ - fix/<ticket-id>-short-description
1389
+ - hotfix/<version>-short-description (如: hotfix/1.2.1-payment-crash)
1390
+ - release/<version> (如: release/1.3.0)
1391
+
1392
+ ### 提交规范
1393
+ 格式: <type>(<scope>): <subject>
1394
+ 示例: feat(auth): add JWT refresh token support
1395
+
1396
+ ### 禁止行为
1397
+ ❌ 直接推送到 main/master
1398
+ ❌ force push 到共享分支
1399
+ ❌ rebase 已推送的公共分支
1400
+ ❌ 超过 1000 行的单次 PR(紧急 hotfix 除外)
1401
+
1402
+ ### 分支生命周期
1403
+ - feature 分支: ≤ 5 个工作日
1404
+ - release 分支: ≤ 2 周
1405
+ - hotfix 分支: ≤ 24 小时
1406
+ ```
1407
+
1408
+ Output: Markdown 工作流规范文档,含分支策略选型建议、提交规范示例、rebase/merge 决策指南、冲突解决 SOP 和 PR 规范模板
1409
+
1410
+ ------------------------------------------------------------
1411
+
1412
+ 16. 单元测试 [单元测试/unit test/写测试]
1413
+ 运用 AAA 模式和 TDD 工作流编写高质量单元测试,建立覆盖率目标和 Mock 策略
1414
+
1415
+ 1. 明确测试目标与范围
1416
+ 在编写测试前,先明确测什么:
1417
+
1418
+ **测试金字塔**
1419
+ ```
1420
+ ┌───────────┐
1421
+ │ E2E 测试 │ (少量,慢,高置信)
1422
+ ┌┴───────────┴┐
1423
+ │ 集成测试 │ (适量,中速)
1424
+ ┌┴─────────────┴┐
1425
+ │ 单元测试 │ (大量,快,低成本)
1426
+ └───────────────┘
1427
+ ```
1428
+
1429
+ **单元测试应该覆盖**
1430
+ - ✅ 纯函数的各种输入输出(含边界)
1431
+ - ✅ 类/模块的公共方法逻辑
1432
+ - ✅ 条件分支(if/switch/三元)
1433
+ - ✅ 错误处理路径(throw/catch)
1434
+ - ✅ 异步操作(Promise/async-await)
1435
+
1436
+ **不应该单元测试**
1437
+ - ❌ 简单的 getter/setter(无逻辑)
1438
+ - ❌ 第三方库内部实现
1439
+ - ❌ 框架本身(如 React 渲染机制)
1440
+ - ❌ 私有方法(通过公共方法间接测试)
1441
+
1442
+ 2. AAA 模式编写测试用例
1443
+ 每个测试用例遵循 **Arrange → Act → Assert** 三段式结构:
1444
+
1445
+ **基础示例(JavaScript/TypeScript with Vitest/Jest)**
1446
+ ```typescript
1447
+ describe('calculateDiscount', () => {
1448
+ it('should apply 20% discount for premium users', () => {
1449
+ // Arrange(准备:设置测试数据和依赖)
1450
+ const user = { type: 'premium', cart: [{ price: 100 }, { price: 50 }] };
1451
+ const expectedTotal = 120; // 150 * 0.8
1452
+
1453
+ // Act(执行:调用被测函数)
1454
+ const result = calculateDiscount(user);
1455
+
1456
+ // Assert(断言:验证结果)
1457
+ expect(result.total).toBe(expectedTotal);
1458
+ expect(result.discountRate).toBe(0.2);
1459
+ });
1460
+ });
1461
+ ```
1462
+
1463
+ **测试命名规范(Given-When-Then)**
1464
+ ```typescript
1465
+ // 格式: should <expected behavior> when <condition>
1466
+ it('should return null when user is not found')
1467
+ it('should throw AuthError when token is expired')
1468
+ it('should apply 20% discount when user has premium status')
1469
+
1470
+ // 或使用 Given-When-Then 风格
1471
+ it('given empty cart, when checkout, then throws EmptyCartError')
1472
+ ```
1473
+
1474
+ **边界条件测试清单**
1475
+ ```typescript
1476
+ describe('parseAge', () => {
1477
+ // 正常值
1478
+ it('should parse valid age 25')
1479
+ // 边界值
1480
+ it('should accept minimum age 0')
1481
+ it('should accept maximum age 150')
1482
+ // 非法值
1483
+ it('should throw when age is negative')
1484
+ it('should throw when age exceeds 150')
1485
+ // 类型边界
1486
+ it('should throw when age is not a number')
1487
+ it('should throw when age is null or undefined')
1488
+ it('should handle decimal by flooring to integer')
1489
+ });
1490
+ ```
1491
+
1492
+ 3. TDD 工作流(红-绿-重构)
1493
+ **TDD 循环步骤**
1494
+
1495
+ ```
1496
+ 🔴 Red → 写一个失败的测试(先设计接口)
1497
+ 🟢 Green → 写最少代码让测试通过(不过度设计)
1498
+ 🔵 Refactor → 在测试保护下重构代码
1499
+ ```
1500
+
1501
+ **实践示例:用 TDD 实现邮箱验证**
1502
+
1503
+ ```typescript
1504
+ // Step 1 🔴 先写测试(此时 validateEmail 还不存在)
1505
+ describe('validateEmail', () => {
1506
+ it('should return true for valid email', () => {
1507
+ expect(validateEmail('user@example.com')).toBe(true);
1508
+ });
1509
+ it('should return false for missing @', () => {
1510
+ expect(validateEmail('userexample.com')).toBe(false);
1511
+ });
1512
+ it('should return false for empty string', () => {
1513
+ expect(validateEmail('')).toBe(false);
1514
+ });
1515
+ });
1516
+
1517
+ // Step 2 🟢 写最简实现让测试通过
1518
+ export function validateEmail(email: string): boolean {
1519
+ return /^[^s@]+@[^s@]+.[^s@]+$/.test(email);
1520
+ }
1521
+
1522
+ // Step 3 🔵 重构:提取正则为常量,添加类型注释
1523
+ const EMAIL_REGEX = /^[^s@]+@[^s@]+.[^s@]+$/;
1524
+ export function validateEmail(email: string): boolean {
1525
+ if (!email) return false;
1526
+ return EMAIL_REGEX.test(email);
1527
+ }
1528
+ ```
1529
+
1530
+ **TDD 适用场景**
1531
+ - 明确需求的业务逻辑函数
1532
+ - 工具库/SDK 开发
1533
+ - Bug 修复(先写复现测试再修复)
1534
+
1535
+ **不强制 TDD 的场景**
1536
+ - 探索性开发阶段
1537
+ - UI 组件(先实现再补测试)
1538
+
1539
+ 4. Mock / Stub / Spy 策略
1540
+ **三种测试替身的区别**
1541
+
1542
+ | 类型 | 用途 | 验证方式 |
1543
+ |------|------|---------|
1544
+ | **Stub** | 替换外部依赖,控制返回值 | 只验证输出 |
1545
+ | **Mock** | 验证函数是否被正确调用 | 验证调用行为 |
1546
+ | **Spy** | 监听真实函数的调用情况 | 包装真实实现 |
1547
+
1548
+ **Vitest/Jest 实践**
1549
+ ```typescript
1550
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
1551
+
1552
+ // Stub: 控制外部 API 返回值
1553
+ vi.mock('../api/user', () => ({
1554
+ fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
1555
+ }));
1556
+
1557
+ // Mock: 验证函数被调用
1558
+ it('should call sendEmail when user registers', async () => {
1559
+ const sendEmail = vi.fn();
1560
+ await registerUser({ email: 'test@test.com' }, { sendEmail });
1561
+ expect(sendEmail).toHaveBeenCalledOnce();
1562
+ expect(sendEmail).toHaveBeenCalledWith('test@test.com', expect.objectContaining({ subject: 'Welcome' }));
1563
+ });
1564
+
1565
+ // Spy: 包装真实函数监听
1566
+ it('should log error when fetch fails', async () => {
1567
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
1568
+ vi.mocked(fetchUser).mockRejectedValue(new Error('Network Error'));
1569
+ await loadUserProfile(1);
1570
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Network Error'));
1571
+ consoleSpy.mockRestore();
1572
+ });
1573
+ ```
1574
+
1575
+ **Mock 黄金法则**
1576
+ - 只 Mock 跨边界的依赖(网络、数据库、文件系统、时间)
1577
+ - 不要 Mock 被测单元的内部实现
1578
+ - 每次测试后还原 Mock(使用 `beforeEach(() => vi.clearAllMocks())`)
1579
+
1580
+ 5. 覆盖率目标与质量保障
1581
+ **覆盖率类型与目标**
1582
+
1583
+ | 覆盖率类型 | 说明 | 建议目标 |
1584
+ |----------|------|---------|
1585
+ | 语句覆盖(Statements) | 执行的语句比例 | ≥ 80% |
1586
+ | 分支覆盖(Branches) | if/else 分支比例 | ≥ 75% |
1587
+ | 函数覆盖(Functions) | 调用的函数比例 | ≥ 80% |
1588
+ | 行覆盖(Lines) | 执行的代码行比例 | ≥ 80% |
1589
+
1590
+ **Vitest 覆盖率配置**
1591
+ ```typescript
1592
+ // vitest.config.ts
1593
+ export default defineConfig({
1594
+ test: {
1595
+ coverage: {
1596
+ provider: 'v8', // 或 'istanbul'
1597
+ reporter: ['text', 'html', 'lcov'],
1598
+ thresholds: {
1599
+ statements: 80,
1600
+ branches: 75,
1601
+ functions: 80,
1602
+ lines: 80,
1603
+ },
1604
+ exclude: [
1605
+ 'node_modules/',
1606
+ 'src/types/',
1607
+ '**/*.config.*',
1608
+ '**/*.d.ts',
1609
+ ],
1610
+ },
1611
+ },
1612
+ });
1613
+ ```
1614
+
1615
+ **覆盖率反模式(要避免)**
1616
+ ```typescript
1617
+ // ❌ 为了覆盖率写无意义断言
1618
+ it('does something', () => {
1619
+ expect(doSomething()).toBeDefined(); // 没有验证具体行为
1620
+ });
1621
+
1622
+ // ✅ 验证真实业务逻辑
1623
+ it('should return correct discounted price', () => {
1624
+ expect(calculatePrice(100, 0.1)).toBe(90);
1625
+ });
1626
+ ```
1627
+
1628
+ **CI 集成**
1629
+ ```yaml
1630
+ # .github/workflows/test.yml
1631
+ - name: Run tests with coverage
1632
+ run: npm run test -- --coverage
1633
+
1634
+ - name: Comment coverage on PR
1635
+ uses: MishaKav/jest-coverage-comment@main
1636
+ with:
1637
+ coverage-summary-path: ./coverage/coverage-summary.json
1638
+ ```
1639
+
1640
+ Output: Markdown 测试方案文档,含测试用例设计(AAA 格式)、Mock 策略说明、覆盖率目标和 CI 配置示例
1641
+
1642
+ ------------------------------------------------------------
1643
+
1644
+ 17. 系统设计 [系统设计/system design/架构设计]
1645
+ 从需求澄清到架构设计全流程,完成高并发分布式系统的方案设计与权衡分析
1646
+
1647
+ 1. 需求澄清与范围界定
1648
+ 在动手设计前,花 5 分钟澄清需求:
1649
+
1650
+ **功能需求(Functional Requirements)**
1651
+ - 系统的核心用例是什么?(写出 3-5 个最关键的)
1652
+ - 哪些功能在 scope 内,哪些明确 out of scope?
1653
+ - 用户角色有哪些?各自的主要操作是什么?
1654
+
1655
+ **非功能需求(Non-Functional Requirements)**
1656
+
1657
+ | 维度 | 问题 | 示例指标 |
1658
+ |------|------|---------|
1659
+ | 规模 | 用户量 / DAU / QPS 是多少? | 1亿用户,1000万 DAU |
1660
+ | 性能 | 读写延迟要求?P99 是多少? | P99 < 100ms |
1661
+ | 可用性 | 允许多少停机时间? | 99.9%(每年 8.7h) |
1662
+ | 一致性 | 强一致 or 最终一致? | 最终一致(可接受) |
1663
+ | 持久性 | 数据丢失容忍度? | RPO = 0(不允许丢失) |
1664
+
1665
+ **明确边界的示例问题**
1666
+ ```
1667
+ Q: 设计一个 Twitter
1668
+ A(先澄清):
1669
+ - 只需要发推/关注/Feed 功能吗?(排除私信、广告)
1670
+ - 用户规模:3亿用户,1亿 DAU?
1671
+ - 读写比例:推文读多写少,100:1?
1672
+ - 媒体文件:支持图片/视频吗?
1673
+ - 全球分发还是单地区?
1674
+ ```
1675
+
1676
+ 2. 容量估算(Back-of-Envelope)
1677
+ 快速估算系统规模,为架构决策提供数据依据:
1678
+
1679
+ **常用基准数字**
1680
+ ```
1681
+ 内存访问: ~100ns
1682
+ SSD 访问: ~100μs
1683
+ HDD 访问: ~10ms
1684
+ 网络往返(同数据中心):~0.5ms
1685
+ 网络往返(跨地区): ~100ms
1686
+
1687
+ 1 MB = 10^6 bytes
1688
+ 1 GB = 10^9 bytes
1689
+ 1 TB = 10^12 bytes
1690
+ ```
1691
+
1692
+ **估算示例:设计微博(Twitter-like)**
1693
+ ```
1694
+ 用户数据:
1695
+ - DAU: 1亿
1696
+ - 每用户每天发1条推文 → 写 QPS = 100M / 86400 ≈ 1160 QPS
1697
+ - 每用户每天读100条 → 读 QPS = 100 × 1160 = 116,000 QPS
1698
+
1699
+ 存储估算:
1700
+ - 单条推文: 140字 × 2字节(UTF-16) = 280字节 ≈ 300字节
1701
+ - 元数据(user_id, timestamp等): 100字节
1702
+ - 每条推文总计: ~400字节
1703
+ - 每日新增: 1.16K QPS × 400字节 × 86400 = ~40 GB/天
1704
+ - 5年存储: 40GB × 365 × 5 ≈ 73 TB
1705
+
1706
+ 带宽估算:
1707
+ - 写带宽: 1160 × 400字节 = ~450 KB/s
1708
+ - 读带宽: 116K × 400字节 = ~45 MB/s
1709
+ ```
1710
+
1711
+ **结论:** 读多写少(100:1),需要读缓存;存储量大需分库分表;单机无法支撑读 QPS 需多副本。
1712
+
1713
+ 3. 高层架构设计
1714
+ 从整体入手,画出系统的核心模块和数据流:
1715
+
1716
+ **通用分层架构**
1717
+ ```
1718
+ 客户端 (Web/Mobile/API Consumer)
1719
+
1720
+
1721
+ DNS + CDN (静态资源 / 地理路由)
1722
+
1723
+
1724
+ Load Balancer (L4/L7, 负载均衡 + SSL 终止)
1725
+ ┌────┴────┐
1726
+ ▼ ▼
1727
+ API Srv API Srv (无状态,水平扩展)
1728
+
1729
+ ├──→ Cache (Redis: 热数据)
1730
+ ├──→ Message Queue (Kafka: 异步解耦)
1731
+ ├──→ Primary DB (写操作)
1732
+ └──→ Read Replica (读操作)
1733
+
1734
+
1735
+ Object Storage (S3: 文件/媒体)
1736
+ Search Engine (Elasticsearch)
1737
+ ```
1738
+
1739
+ **架构选型决策点**
1740
+
1741
+ | 场景 | 选型建议 |
1742
+ |------|---------|
1743
+ | 读多写少 | 读写分离 + 缓存层 |
1744
+ | 高写入吞吐 | 异步消息队列削峰 |
1745
+ | 数据量超百亿行 | 分库分表 / NoSQL |
1746
+ | 强一致性 | 单主 / Paxos / Raft |
1747
+ | 最终一致性 | 多主 / CRDT |
1748
+ | 低延迟全球访问 | CDN + 多地域部署 |
1749
+ | 复杂查询 | 专用搜索引擎 |
1750
+
1751
+ **微服务 vs 单体 决策**
1752
+ - 团队 < 10人,初创期:单体优先(避免过度工程)
1753
+ - 明确的服务边界、独立扩展需求:拆分微服务
1754
+ - 拆分原则:按业务边界(DDD 限界上下文),而非技术层
1755
+
1756
+ 4. 核心组件深度设计
1757
+ 针对最关键的 2-3 个组件进行深入设计:
1758
+
1759
+ **数据库 Schema 设计**
1760
+ ```sql
1761
+ -- 示例:推文表设计
1762
+ CREATE TABLE tweets (
1763
+ id BIGINT PRIMARY KEY, -- Snowflake ID(分布式唯一ID)
1764
+ user_id BIGINT NOT NULL,
1765
+ content VARCHAR(280) NOT NULL,
1766
+ created_at TIMESTAMP DEFAULT NOW(),
1767
+ like_count INT DEFAULT 0,
1768
+ retweet_count INT DEFAULT 0,
1769
+ INDEX idx_user_created (user_id, created_at DESC) -- 用户时间线查询
1770
+ );
1771
+
1772
+ -- Fan-out 策略:预写 vs 拉取
1773
+ -- 方案A: Push(写扩散): 发推时写入所有粉丝的 Feed 表
1774
+ -- 方案B: Pull(读扩散): 读取时聚合关注者的推文
1775
+ -- 混合方案: 普通用户 Push,大V(粉丝>100万)Pull
1776
+ ```
1777
+
1778
+ **缓存策略**
1779
+ ```
1780
+ Cache-Aside(旁路缓存)- 最通用
1781
+ 读: 查缓存 → miss → 查DB → 写缓存 → 返回
1782
+ 写: 更新DB → 删除缓存(避免双写不一致)
1783
+
1784
+ Write-Through(写穿)- 一致性高
1785
+ 写: 同时写DB和缓存
1786
+
1787
+ Write-Behind(写回)- 高性能
1788
+ 写: 先写缓存,异步批量写DB(风险:缓存宕机丢数据)
1789
+
1790
+ 缓存 Key 设计示例:
1791
+ user:{userId}:profile → 用户资料
1792
+ user:{userId}:feed:page:{n} → 用户 Feed 分页
1793
+ tweet:{tweetId} → 单条推文
1794
+ ```
1795
+
1796
+ **API 接口设计**
1797
+ ```
1798
+ POST /tweets 发布推文
1799
+ GET /users/{id}/feed 获取 Feed (cursor分页)
1800
+ POST /tweets/{id}/like 点赞
1801
+ GET /tweets/{id} 获取单条推文
1802
+
1803
+ 分页策略: cursor-based > offset-based(大数据量场景)
1804
+ cursor: base64(created_at + tweet_id)
1805
+ ```
1806
+
1807
+ 5. 可扩展性与可用性权衡
1808
+ **CAP 定理实践**
1809
+ ```
1810
+ C(一致性)+ A(可用性)+ P(分区容错)三选二
1811
+ 网络分区不可避免 → 通常是 CP 或 AP 的选择
1812
+
1813
+ CP 系统: ZooKeeper, HBase(金融交易、库存扣减)
1814
+ AP 系统: Cassandra, DynamoDB(社交Feed、购物车)
1815
+ ```
1816
+
1817
+ **水平扩展策略**
1818
+
1819
+ | 层次 | 策略 |
1820
+ |------|------|
1821
+ | 无状态应用层 | 直接水平扩展 + 负载均衡 |
1822
+ | 有状态缓存 | 一致性哈希分片(Redis Cluster) |
1823
+ | 数据库水平 | 分库分表(按 user_id % N) |
1824
+ | 数据库垂直 | 主从复制,读写分离 |
1825
+
1826
+ **单点故障(SPOF)消除清单**
1827
+ - [ ] Load Balancer 双活/主备
1828
+ - [ ] 数据库主从 + 自动故障转移(MHA/Orchestrator)
1829
+ - [ ] 缓存集群(Redis Sentinel / Cluster)
1830
+ - [ ] 消息队列多副本(Kafka Replication Factor ≥ 3)
1831
+ - [ ] 跨可用区部署(Multi-AZ)
1832
+
1833
+ **限流与熔断**
1834
+ ```
1835
+ 限流: Token Bucket(突发流量友好)
1836
+ Sliding Window(精准限流)
1837
+ 分级限流: 用户级 → 接口级 → 全局
1838
+
1839
+ 熔断: Closed → Open(失败率>50%)→ Half-Open(探测恢复)
1840
+ 工具: Resilience4j(Java)/ hystrix-go / Polly(.NET)
1841
+ ```
1842
+
1843
+ 6. 输出系统设计文档
1844
+ 整理为结构化设计文档:
1845
+
1846
+ ```markdown
1847
+ ## 系统设计方案:[系统名称]
1848
+
1849
+ ### 1. 需求概述
1850
+ **功能需求**(核心功能列表)
1851
+ **非功能需求**(QPS / 延迟 / 可用性 / 存储)
1852
+
1853
+ ### 2. 容量估算
1854
+ | 指标 | 估算值 |
1855
+ |------|-------|
1856
+ | DAU | X 万 |
1857
+ | 写 QPS | X |
1858
+ | 读 QPS | X |
1859
+ | 存储(5年) | X TB |
1860
+
1861
+ ### 3. 系统架构图
1862
+ [ASCII 图或 Mermaid 图]
1863
+
1864
+ ### 4. 核心组件设计
1865
+ - **数据库 Schema**:[关键表设计]
1866
+ - **缓存策略**:[策略选择与理由]
1867
+ - **API 设计**:[关键接口]
1868
+
1869
+ ### 5. 扩展性方案
1870
+ - **瓶颈点**:[识别的瓶颈]
1871
+ - **解决方案**:[具体方案]
1872
+
1873
+ ### 6. 权衡与风险
1874
+ [已知权衡和设计风险]
1875
+ ```
1876
+
1877
+ Output: Markdown 系统设计文档,含需求澄清结果、容量估算数据、架构图、核心组件设计方案和扩展性权衡分析
1878
+
1879
+ ------------------------------------------------------------
1880
+
1881
+ 18. 数据库优化 [数据库优化/database optimize/慢查询]
1882
+ 系统诊断数据库性能问题,涵盖 Schema 审查、索引设计、慢查询分析和 N+1 修复
1883
+
1884
+ 1. Schema 设计审查
1885
+ 检查数据库表结构是否存在设计问题:
1886
+
1887
+ **规范化检查(防止冗余)**
1888
+ ```sql
1889
+ -- ❌ 反模式:在用户表存储地址字符串
1890
+ CREATE TABLE users (
1891
+ id INT PRIMARY KEY,
1892
+ name VARCHAR(100),
1893
+ address VARCHAR(500) -- 难以精准查询城市/省份
1894
+ );
1895
+
1896
+ -- ✅ 正确:拆分为 addresses 表
1897
+ CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(100));
1898
+ CREATE TABLE addresses (
1899
+ id INT PRIMARY KEY,
1900
+ user_id INT REFERENCES users(id),
1901
+ province VARCHAR(50),
1902
+ city VARCHAR(50),
1903
+ detail VARCHAR(200)
1904
+ );
1905
+ ```
1906
+
1907
+ **数据类型选择**
1908
+
1909
+ | 场景 | 推荐类型 | 避免 |
1910
+ |------|---------|------|
1911
+ | 主键 | BIGINT / UUID | INT(可能溢出) |
1912
+ | 状态枚举 | TINYINT / ENUM | VARCHAR |
1913
+ | 金额 | DECIMAL(10,2) | FLOAT(精度丢失)|
1914
+ | 时间 | TIMESTAMP / DATETIME | VARCHAR |
1915
+ | 短字符串(≤255) | VARCHAR(N) | TEXT |
1916
+ | 布尔值 | TINYINT(1) | VARCHAR('true') |
1917
+
1918
+ **常见 Schema 问题清单**
1919
+ - [ ] 是否有未使用的列?
1920
+ - [ ] VARCHAR 长度是否合理(不要都 VARCHAR(255))?
1921
+ - [ ] 外键是否有索引?
1922
+ - [ ] 是否有重复的字段(非规范化导致)?
1923
+ - [ ] 是否用了 TEXT/BLOB 存储应该单独存储的大文件?
1924
+
1925
+ 2. 索引设计策略
1926
+ **索引类型选择**
1927
+
1928
+ ```sql
1929
+ -- 单列索引:高选择性字段(如 email、手机号)
1930
+ CREATE INDEX idx_users_email ON users(email);
1931
+
1932
+ -- 联合索引:遵循最左前缀原则
1933
+ -- 适合查询: WHERE status = ? AND created_at > ?
1934
+ -- 适合查询: WHERE status = ?
1935
+ -- 不适合: WHERE created_at > ? (无法命中)
1936
+ CREATE INDEX idx_orders_status_created ON orders(status, created_at);
1937
+
1938
+ -- 覆盖索引:索引包含查询所有字段,避免回表
1939
+ -- 查询: SELECT user_id, status FROM orders WHERE order_no = ?
1940
+ CREATE INDEX idx_orders_covering ON orders(order_no, user_id, status);
1941
+
1942
+ -- 前缀索引:长字符串节省空间
1943
+ CREATE INDEX idx_url_prefix ON pages(url(50));
1944
+
1945
+ -- 函数索引(MySQL 8.0+):对表达式建索引
1946
+ CREATE INDEX idx_lower_email ON users((LOWER(email)));
1947
+ ```
1948
+
1949
+ **EXPLAIN 分析索引使用**
1950
+ ```sql
1951
+ EXPLAIN SELECT * FROM orders
1952
+ WHERE user_id = 1001 AND status = 'PAID'
1953
+ ORDER BY created_at DESC LIMIT 10;
1954
+
1955
+ -- 关注字段:
1956
+ -- type: ref > range > index > ALL(ALL 最差)
1957
+ -- key: 使用的索引名(NULL 表示未使用索引)
1958
+ -- rows: 预估扫描行数(越小越好)
1959
+ -- Extra: Using filesort / Using temporary(需优化的信号)
1960
+ ```
1961
+
1962
+ **索引原则**
1963
+ - 高频查询的 WHERE / JOIN / ORDER BY 字段建索引
1964
+ - 选择性低的字段慎建索引(如 status 只有3个值)
1965
+ - 避免在频繁更新的列上建过多索引(写性能代价)
1966
+ - 复合索引字段顺序:等值条件在前,范围条件在后
1967
+
1968
+ 3. 慢查询分析与优化
1969
+ **开启慢查询日志**
1970
+ ```sql
1971
+ -- MySQL 配置
1972
+ SET GLOBAL slow_query_log = 'ON';
1973
+ SET GLOBAL long_query_time = 1; -- 超过1秒记录
1974
+ SET GLOBAL log_queries_not_using_indexes = 'ON';
1975
+
1976
+ -- 查看慢查询日志文件位置
1977
+ SHOW VARIABLES LIKE 'slow_query_log_file';
1978
+
1979
+ -- 使用 pt-query-digest 分析日志
1980
+ pt-query-digest /var/log/mysql/slow.log | head -100
1981
+ ```
1982
+
1983
+ **常见慢查询模式与修复**
1984
+ ```sql
1985
+ -- ❌ 问题1: SELECT * 全列查询
1986
+ SELECT * FROM orders WHERE user_id = 1001;
1987
+ -- ✅ 修复: 只查需要的列
1988
+ SELECT id, order_no, status, total FROM orders WHERE user_id = 1001;
1989
+
1990
+ -- ❌ 问题2: 对索引列使用函数,导致索引失效
1991
+ SELECT * FROM orders WHERE DATE(created_at) = '2024-01-01';
1992
+ -- ✅ 修复: 使用范围查询
1993
+ SELECT * FROM orders
1994
+ WHERE created_at >= '2024-01-01' AND created_at < '2024-01-02';
1995
+
1996
+ -- ❌ 问题3: OR 导致索引失效(某些情况)
1997
+ SELECT * FROM users WHERE email = ? OR phone = ?;
1998
+ -- ✅ 修复: UNION ALL
1999
+ SELECT * FROM users WHERE email = ?
2000
+ UNION ALL
2001
+ SELECT * FROM users WHERE phone = ?;
2002
+
2003
+ -- ❌ 问题4: LIKE 前缀通配符
2004
+ SELECT * FROM products WHERE name LIKE '%iPhone%';
2005
+ -- ✅ 修复: 使用全文索引或 Elasticsearch
2006
+ SELECT * FROM products WHERE MATCH(name) AGAINST('iPhone' IN BOOLEAN MODE);
2007
+
2008
+ -- ❌ 问题5: 隐式类型转换
2009
+ SELECT * FROM users WHERE user_id = '1001'; -- user_id 是 INT
2010
+ -- ✅ 修复: 类型匹配
2011
+ SELECT * FROM users WHERE user_id = 1001;
2012
+ ```
2013
+
2014
+ 4. N+1 查询识别与修复
2015
+ **N+1 问题定义**:查询1次获取N条记录,再针对每条记录查询1次,共 N+1 次数据库访问。
2016
+
2017
+ **ORM 场景中的 N+1**
2018
+ ```typescript
2019
+ // ❌ TypeORM N+1 示例:查100个用户 → 执行101次SQL
2020
+ const users = await userRepository.find(); // Query 1: SELECT * FROM users
2021
+ for (const user of users) {
2022
+ const orders = await user.orders; // Query 2-101: 每个用户各查一次
2023
+ console.log(orders.length);
2024
+ }
2025
+
2026
+ // ✅ 修复:使用 eager loading(JOIN)
2027
+ const users = await userRepository.find({
2028
+ relations: ['orders'], // 一次 JOIN 查询搞定
2029
+ });
2030
+
2031
+ // ✅ 或使用 QueryBuilder(更精确控制)
2032
+ const users = await userRepository
2033
+ .createQueryBuilder('user')
2034
+ .leftJoinAndSelect('user.orders', 'order')
2035
+ .where('order.status = :status', { status: 'PAID' })
2036
+ .getMany();
2037
+ ```
2038
+
2039
+ **原生 SQL 批量查询模式**
2040
+ ```sql
2041
+ -- ❌ N+1: 循环查询
2042
+ -- for user_id in user_ids: SELECT * FROM orders WHERE user_id = ?
2043
+
2044
+ -- ✅ 批量查询 + 应用层 Map 聚合
2045
+ SELECT user_id, COUNT(*) as order_count, SUM(total) as total_amount
2046
+ FROM orders
2047
+ WHERE user_id IN (1,2,3,...,100) -- 一次查询
2048
+ GROUP BY user_id;
2049
+ -- 在应用层用 Map 按 user_id 聚合
2050
+ ```
2051
+
2052
+ **检测 N+1 工具**
2053
+ ```
2054
+ - Laravel Debugbar(PHP)
2055
+ - Django Debug Toolbar(Python)
2056
+ - Bullet gem(Rails)
2057
+ - TypeORM logging: { logging: true } 观察 SQL 数量
2058
+ - DataLoader(GraphQL 场景批量加载)
2059
+ ```
2060
+
2061
+ 5. 分区与分表策略
2062
+ **表分区(Partitioning)— 单机方案**
2063
+ ```sql
2064
+ -- 按时间范围分区(适合日志、订单历史)
2065
+ CREATE TABLE orders (
2066
+ id BIGINT,
2067
+ user_id INT,
2068
+ created_at DATETIME,
2069
+ total DECIMAL(10,2)
2070
+ ) PARTITION BY RANGE (YEAR(created_at)) (
2071
+ PARTITION p2022 VALUES LESS THAN (2023),
2072
+ PARTITION p2023 VALUES LESS THAN (2024),
2073
+ PARTITION p2024 VALUES LESS THAN (2025),
2074
+ PARTITION pmax VALUES LESS THAN MAXVALUE
2075
+ );
2076
+
2077
+ -- 分区裁剪:查询自动只扫描相关分区
2078
+ SELECT * FROM orders WHERE created_at >= '2024-01-01';
2079
+ -- 只扫描 p2024 分区,跳过历史分区
2080
+ ```
2081
+
2082
+ **分库分表策略(超千万行后考虑)**
2083
+
2084
+ | 方案 | 分片键选择 | 适用场景 |
2085
+ |------|----------|---------|
2086
+ | 水平分表(同库) | user_id % N | 单库容量瓶颈 |
2087
+ | 水平分库 | user_id % N | 读写 QPS 瓶颈 |
2088
+ | 按地区分库 | region | 合规/延迟要求 |
2089
+
2090
+ ```
2091
+ 分片键选择原则:
2092
+ - 选择查询中高频使用的字段(避免跨分片查询)
2093
+ - 选择数据分布均匀的字段(避免热点)
2094
+ - 一旦确定不能轻易更改
2095
+
2096
+ 常见工具:
2097
+ - ShardingSphere(Java)
2098
+ - Vitess(MySQL 集群,YouTube 方案)
2099
+ - Citus(PostgreSQL 分布式扩展)
2100
+ ```
2101
+
2102
+ **读写分离配置**
2103
+ ```
2104
+ 主库(Primary): 处理写操作 + 强一致读
2105
+ 从库(Replica): 处理读操作(注意主从延迟,通常 <1s)
2106
+
2107
+ 适用于读写比 > 4:1 的场景
2108
+ 注意: 写后立即读可能读到旧数据(主从同步延迟)
2109
+ 解决: 重要读操作路由到主库;或用 Redis 缓存最新写入
2110
+ ```
2111
+
2112
+ Output: Markdown 优化报告,含 Schema 问题列表、索引设计方案、慢查询 EXPLAIN 分析、N+1 修复代码示例和分区建议
2113
+
2114
+ ------------------------------------------------------------
2115
+
2116
+ 19. Docker 容器化 [Docker/docker/容器化]
2117
+ 编写生产级 Dockerfile,实现多阶段构建、镜像优化和 docker-compose 编排
2118
+
2119
+ 1. Dockerfile 基础最佳实践
2120
+ **基础规则清单**
2121
+
2122
+ ```dockerfile
2123
+ # ✅ 使用具体版本标签,避免 latest(不可复现)
2124
+ FROM node:20.11-alpine3.19
2125
+
2126
+ # ✅ 设置工作目录(避免在根目录操作)
2127
+ WORKDIR /app
2128
+
2129
+ # ✅ 先复制依赖文件,利用层缓存
2130
+ # 依赖文件不变时,npm install 层直接复用缓存
2131
+ COPY package*.json ./
2132
+ RUN npm ci --only=production
2133
+
2134
+ # ✅ 再复制源码(源码改变不影响依赖缓存)
2135
+ COPY . .
2136
+
2137
+ # ✅ 使用非 root 用户运行(安全最佳实践)
2138
+ RUN addgroup -S appgroup && adduser -S appuser -G appgroup
2139
+ USER appuser
2140
+
2141
+ # ✅ 仅暴露必要端口
2142
+ EXPOSE 3000
2143
+
2144
+ # ✅ 使用 ENTRYPOINT + CMD 组合(更灵活)
2145
+ ENTRYPOINT ["node"]
2146
+ CMD ["dist/index.js"]
2147
+ ```
2148
+
2149
+ **层缓存优化原则**
2150
+ ```
2151
+ 构建缓存命中规则:指令 + 参数 + 上下文文件 都相同才命中缓存
2152
+
2153
+ 优化策略:
2154
+ 1. 变化频率低的指令放前面(基础镜像、系统依赖)
2155
+ 2. 变化频率高的指令放后面(应用代码)
2156
+ 3. 合并 RUN 指令减少层数
2157
+
2158
+ # ❌ 多个 RUN 产生多个层
2159
+ RUN apt-get update
2160
+ RUN apt-get install -y curl
2161
+ RUN apt-get clean
2162
+
2163
+ # ✅ 合并为一个 RUN,减少层数 + 及时清理缓存
2164
+ RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
2165
+ ```
2166
+
2167
+ 2. 多阶段构建(Multi-Stage Build)
2168
+ 多阶段构建将构建环境与运行环境分离,显著减小生产镜像体积:
2169
+
2170
+ **Node.js 应用示例**
2171
+ ```dockerfile
2172
+ # ===== Stage 1: Build =====
2173
+ FROM node:20.11-alpine3.19 AS builder
2174
+ WORKDIR /app
2175
+
2176
+ # 安装所有依赖(含 devDependencies)
2177
+ COPY package*.json ./
2178
+ RUN npm ci
2179
+
2180
+ # 编译 TypeScript
2181
+ COPY . .
2182
+ RUN npm run build
2183
+
2184
+ # ===== Stage 2: Dependencies =====
2185
+ FROM node:20.11-alpine3.19 AS deps
2186
+ WORKDIR /app
2187
+ COPY package*.json ./
2188
+ # 只安装生产依赖
2189
+ RUN npm ci --only=production
2190
+
2191
+ # ===== Stage 3: Production =====
2192
+ FROM node:20.11-alpine3.19 AS production
2193
+ WORKDIR /app
2194
+
2195
+ # 只从前两个阶段复制必要文件
2196
+ COPY --from=deps /app/node_modules ./node_modules
2197
+ COPY --from=builder /app/dist ./dist
2198
+
2199
+ # 非 root 用户
2200
+ RUN addgroup -S app && adduser -S app -G app
2201
+ USER app
2202
+
2203
+ EXPOSE 3000
2204
+ HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:3000/health || exit 1
2205
+ CMD ["node", "dist/index.js"]
2206
+ ```
2207
+
2208
+ **效果对比**
2209
+ ```
2210
+ 单阶段构建(含 devDeps + 源码): ~800 MB
2211
+ 多阶段构建(只含运行时): ~120 MB
2212
+ 体积减少约 85%
2213
+ ```
2214
+
2215
+ **Go 应用(静态二进制最小镜像)**
2216
+ ```dockerfile
2217
+ FROM golang:1.22-alpine AS builder
2218
+ WORKDIR /app
2219
+ COPY go.mod go.sum ./
2220
+ RUN go mod download
2221
+ COPY . .
2222
+ RUN CGO_ENABLED=0 GOOS=linux go build -o server .
2223
+
2224
+ # 使用 scratch(空镜像)或 distroless
2225
+ FROM gcr.io/distroless/static-debian12
2226
+ COPY --from=builder /app/server /server
2227
+ EXPOSE 8080
2228
+ ENTRYPOINT ["/server"]
2229
+ # 最终镜像仅 ~10MB
2230
+ ```
2231
+
2232
+ 3. .dockerignore 与镜像安全
2233
+ **配置 .dockerignore**
2234
+ ```dockerignore
2235
+ # 排除不需要的文件,减小构建上下文
2236
+ node_modules
2237
+ npm-debug.log
2238
+ .git
2239
+ .gitignore
2240
+ .env
2241
+ .env.*
2242
+ *.md
2243
+ .DS_Store
2244
+ coverage/
2245
+ dist/
2246
+ .nyc_output
2247
+ __tests__
2248
+ *.test.ts
2249
+ Dockerfile*
2250
+ docker-compose*
2251
+ ```
2252
+
2253
+ **镜像安全扫描**
2254
+ ```bash
2255
+ # Trivy(推荐,免费开源)
2256
+ docker pull aquasec/trivy
2257
+ trivy image --severity HIGH,CRITICAL myapp:latest
2258
+
2259
+ # 输出示例:
2260
+ # CRITICAL: CVE-2024-xxxx in openssl 3.0.0 → 升级到 3.0.13
2261
+
2262
+ # 集成到 CI(GitHub Actions)
2263
+ - name: Scan Docker image
2264
+ uses: aquasecurity/trivy-action@master
2265
+ with:
2266
+ image-ref: 'myapp:${{ github.sha }}'
2267
+ severity: 'CRITICAL,HIGH'
2268
+ exit-code: '1' # 发现高危漏洞时 CI 失败
2269
+ ```
2270
+
2271
+ **容器运行时安全配置**
2272
+ ```bash
2273
+ # 禁止 root 运行(Dockerfile 中已设置 USER,运行时再确认)
2274
+ docker run --user 1001:1001 myapp:latest
2275
+
2276
+ # 只读文件系统(防止容器内写文件)
2277
+ docker run --read-only --tmpfs /tmp myapp:latest
2278
+
2279
+ # 限制资源
2280
+ docker run --memory="256m" --cpus="0.5" myapp:latest
2281
+
2282
+ # 丢弃不需要的 Linux Capabilities
2283
+ docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp:latest
2284
+
2285
+ # 禁止权限提升
2286
+ docker run --security-opt no-new-privileges myapp:latest
2287
+ ```
2288
+
2289
+ 4. Docker Compose 服务编排
2290
+ **生产级 docker-compose.yml 示例**
2291
+ ```yaml
2292
+ version: '3.9'
2293
+
2294
+ services:
2295
+ app:
2296
+ build:
2297
+ context: .
2298
+ dockerfile: Dockerfile
2299
+ target: production # 指定多阶段构建的目标阶段
2300
+ image: myapp:${APP_VERSION:-latest}
2301
+ restart: unless-stopped
2302
+ ports:
2303
+ - "3000:3000"
2304
+ environment:
2305
+ NODE_ENV: production
2306
+ DATABASE_URL: ${DATABASE_URL} # 从 .env 文件读取,不硬编码
2307
+ env_file:
2308
+ - .env.production
2309
+ depends_on:
2310
+ db:
2311
+ condition: service_healthy # 等待健康检查通过
2312
+ redis:
2313
+ condition: service_healthy
2314
+ healthcheck:
2315
+ test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
2316
+ interval: 30s
2317
+ timeout: 5s
2318
+ retries: 3
2319
+ start_period: 40s
2320
+ deploy:
2321
+ resources:
2322
+ limits:
2323
+ cpus: '1.0'
2324
+ memory: 512M
2325
+ networks:
2326
+ - app-network
2327
+
2328
+ db:
2329
+ image: postgres:16-alpine
2330
+ restart: unless-stopped
2331
+ environment:
2332
+ POSTGRES_DB: ${DB_NAME}
2333
+ POSTGRES_USER: ${DB_USER}
2334
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
2335
+ volumes:
2336
+ - postgres-data:/var/lib/postgresql/data
2337
+ - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
2338
+ healthcheck:
2339
+ test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
2340
+ interval: 10s
2341
+ timeout: 5s
2342
+ retries: 5
2343
+ networks:
2344
+ - app-network
2345
+
2346
+ redis:
2347
+ image: redis:7-alpine
2348
+ restart: unless-stopped
2349
+ command: redis-server --requirepass ${REDIS_PASSWORD}
2350
+ volumes:
2351
+ - redis-data:/data
2352
+ healthcheck:
2353
+ test: ["CMD", "redis-cli", "ping"]
2354
+ interval: 10s
2355
+ networks:
2356
+ - app-network
2357
+
2358
+ networks:
2359
+ app-network:
2360
+ driver: bridge
2361
+
2362
+ volumes:
2363
+ postgres-data:
2364
+ redis-data:
2365
+ ```
2366
+
2367
+ **常用 Compose 命令**
2368
+ ```bash
2369
+ docker compose up -d # 后台启动
2370
+ docker compose up -d --build # 重新构建并启动
2371
+ docker compose logs -f app # 实时查看日志
2372
+ docker compose exec app sh # 进入容器 shell
2373
+ docker compose ps # 查看服务状态
2374
+ docker compose down -v # 停止并删除 volume
2375
+ ```
2376
+
2377
+ 5. 镜像优化与发布
2378
+ **镜像大小优化总结**
2379
+
2380
+ | 优化手段 | 效果 |
2381
+ |---------|------|
2382
+ | 使用 Alpine 基础镜像 | node:20 → node:20-alpine,1.1GB → 150MB |
2383
+ | 多阶段构建 | 去除构建工具 & devDependencies |
2384
+ | .dockerignore | 减小构建上下文 |
2385
+ | 合并 RUN 清理缓存 | 减少层数和大小 |
2386
+ | distroless/scratch | Go/Rust 应用极小镜像 |
2387
+
2388
+ **镜像打标签规范**
2389
+ ```bash
2390
+ # 语义化版本 + git commit hash
2391
+ docker build -t myapp:1.2.3 -t myapp:1.2.3-abc1234 .
2392
+
2393
+ # CI 中自动打标签
2394
+ docker build -t myregistry/myapp:${VERSION} -t myregistry/myapp:latest --label "git.commit=${GIT_SHA}" --label "build.date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" .
2395
+ ```
2396
+
2397
+ **镜像推送到 Registry**
2398
+ ```bash
2399
+ # 登录到 GitHub Container Registry
2400
+ echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin
2401
+
2402
+ # 推送
2403
+ docker push ghcr.io/org/myapp:1.2.3
2404
+
2405
+ # 使用 Docker BuildKit(并行构建,更快)
2406
+ DOCKER_BUILDKIT=1 docker build .
2407
+
2408
+ # 多平台构建(兼容 ARM Mac 和 x86 服务器)
2409
+ docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest --push .
2410
+ ```
2411
+
2412
+ Output: Markdown 容器化方案,含优化后的 Dockerfile、.dockerignore、docker-compose.yml 配置和安全加固建议
2413
+
2414
+ ------------------------------------------------------------
2415
+
2416
+ 20. CI/CD 流水线 [CI/CD/cicd/流水线]
2417
+ 设计完整 CI/CD 流水线,涵盖流水线阶段设计、测试自动化、部署门控和回滚策略
2418
+
2419
+ 1. 流水线阶段设计
2420
+ **标准 CI/CD 流水线结构**
2421
+
2422
+ ```
2423
+ Push/PR → [CI 阶段] → [镜像构建] → [部署到 Staging] → [部署到 Production]
2424
+
2425
+ CI 阶段(每次 Push/PR 触发):
2426
+ ├── 代码检查: Lint + Type Check
2427
+ ├── 单元测试: Unit Tests + Coverage
2428
+ ├── 安全扫描: SAST + Dependency Audit
2429
+ └── 构建验证: Build Success Check
2430
+
2431
+ 镜像构建(CI 通过后):
2432
+ ├── Docker Build(多平台)
2433
+ ├── 镜像安全扫描(Trivy)
2434
+ └── 推送到 Registry(打 tag)
2435
+
2436
+ 部署流程:
2437
+ ├── Staging(自动,合并到 main 后)
2438
+ │ ├── 集成测试
2439
+ │ └── E2E 测试(冒烟)
2440
+ └── Production(需审批 or 手动触发)
2441
+ ├── 部署策略(蓝绿/金丝雀)
2442
+ └── 部署后验证(健康检查)
2443
+ ```
2444
+
2445
+ **快速反馈原则**
2446
+ - CI 总时长目标:< 10 分钟(开发者等待阈值)
2447
+ - 测试并行化:单元测试 → 集成测试 → E2E(分层执行)
2448
+ - Fail Fast:代码格式错误最先检查,最快发现
2449
+
2450
+ 2. GitHub Actions 流水线配置
2451
+ **完整 CI 工作流示例**
2452
+ ```yaml
2453
+ # .github/workflows/ci.yml
2454
+ name: CI
2455
+
2456
+ on:
2457
+ push:
2458
+ branches: [main, develop]
2459
+ pull_request:
2460
+ branches: [main]
2461
+
2462
+ env:
2463
+ NODE_VERSION: '20'
2464
+ REGISTRY: ghcr.io
2465
+ IMAGE_NAME: ${{ github.repository }}
2466
+
2467
+ jobs:
2468
+ # ─── 代码质量检查 ───────────────────────────────
2469
+ lint:
2470
+ name: Lint & Type Check
2471
+ runs-on: ubuntu-latest
2472
+ steps:
2473
+ - uses: actions/checkout@v4
2474
+ - uses: actions/setup-node@v4
2475
+ with:
2476
+ node-version: ${{ env.NODE_VERSION }}
2477
+ cache: 'npm'
2478
+ - run: npm ci
2479
+ - run: npm run lint
2480
+ - run: npm run typecheck
2481
+
2482
+ # ─── 测试 ────────────────────────────────────────
2483
+ test:
2484
+ name: Unit Tests
2485
+ runs-on: ubuntu-latest
2486
+ steps:
2487
+ - uses: actions/checkout@v4
2488
+ - uses: actions/setup-node@v4
2489
+ with:
2490
+ node-version: ${{ env.NODE_VERSION }}
2491
+ cache: 'npm'
2492
+ - run: npm ci
2493
+ - run: npm run test -- --coverage
2494
+ - name: Upload coverage to Codecov
2495
+ uses: codecov/codecov-action@v4
2496
+ with:
2497
+ token: ${{ secrets.CODECOV_TOKEN }}
2498
+
2499
+ # ─── 安全扫描 ───────────────────────────────────
2500
+ security:
2501
+ name: Security Audit
2502
+ runs-on: ubuntu-latest
2503
+ steps:
2504
+ - uses: actions/checkout@v4
2505
+ - run: npm audit --audit-level=high
2506
+ - uses: github/codeql-action/init@v3
2507
+ with:
2508
+ languages: javascript
2509
+ - uses: github/codeql-action/analyze@v3
2510
+
2511
+ # ─── 构建镜像 ───────────────────────────────────
2512
+ build:
2513
+ name: Build & Push Image
2514
+ needs: [lint, test, security]
2515
+ runs-on: ubuntu-latest
2516
+ if: github.ref == 'refs/heads/main'
2517
+ permissions:
2518
+ contents: read
2519
+ packages: write
2520
+ outputs:
2521
+ image-tag: ${{ steps.meta.outputs.tags }}
2522
+ steps:
2523
+ - uses: actions/checkout@v4
2524
+ - uses: docker/setup-buildx-action@v3
2525
+ - uses: docker/login-action@v3
2526
+ with:
2527
+ registry: ${{ env.REGISTRY }}
2528
+ username: ${{ github.actor }}
2529
+ password: ${{ secrets.GITHUB_TOKEN }}
2530
+ - uses: docker/metadata-action@v5
2531
+ id: meta
2532
+ with:
2533
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
2534
+ tags: |
2535
+ type=sha,prefix={{branch}}-
2536
+ type=semver,pattern={{version}}
2537
+ - uses: docker/build-push-action@v5
2538
+ with:
2539
+ push: true
2540
+ tags: ${{ steps.meta.outputs.tags }}
2541
+ cache-from: type=gha
2542
+ cache-to: type=gha,mode=max
2543
+ ```
2544
+
2545
+ 3. 构建速度优化
2546
+ **缓存策略**
2547
+ ```yaml
2548
+ # npm/yarn 依赖缓存
2549
+ - uses: actions/cache@v4
2550
+ with:
2551
+ path: ~/.npm
2552
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
2553
+ restore-keys: |
2554
+ ${{ runner.os }}-node-
2555
+
2556
+ # Docker layer 缓存(使用 GitHub Actions Cache)
2557
+ - uses: docker/build-push-action@v5
2558
+ with:
2559
+ cache-from: type=gha
2560
+ cache-to: type=gha,mode=max
2561
+ ```
2562
+
2563
+ **并行执行策略**
2564
+ ```yaml
2565
+ # 使用 matrix 并行运行测试
2566
+ jobs:
2567
+ test:
2568
+ strategy:
2569
+ matrix:
2570
+ shard: [1, 2, 3, 4] # 4个并行 runner
2571
+ steps:
2572
+ - run: npm test -- --shard=${{ matrix.shard }}/4
2573
+ ```
2574
+
2575
+ **跳过不必要的 CI**
2576
+ ```yaml
2577
+ # 路径过滤:文档变更不触发完整 CI
2578
+ on:
2579
+ push:
2580
+ paths-ignore:
2581
+ - 'docs/**'
2582
+ - '*.md'
2583
+ - '.github/ISSUE_TEMPLATE/**'
2584
+
2585
+ # 或者使用 paths 只触发相关路径
2586
+ on:
2587
+ push:
2588
+ paths:
2589
+ - 'src/**'
2590
+ - 'tests/**'
2591
+ - 'package*.json'
2592
+ ```
2593
+
2594
+ **Self-hosted Runner(节省 CI 费用)**
2595
+ ```
2596
+ 适用场景: 大型项目、私有依赖、特殊硬件需求
2597
+ 注意事项:
2598
+ - 安全隔离(不要在 public repo 使用 self-hosted runner)
2599
+ - 定期更新 runner 软件
2600
+ - 隔离不同项目的 runner(避免环境污染)
2601
+ ```
2602
+
2603
+ 4. 部署策略与门控
2604
+ **三种主要部署策略**
2605
+
2606
+ **蓝绿部署(Blue-Green)**
2607
+ ```
2608
+ 适用: 需要零停机、可快速回滚的场景
2609
+ 成本: 双倍资源(同时运行两套环境)
2610
+
2611
+ Blue(当前生产): v1.0 → 接收所有流量
2612
+ Green(新版本): v1.1 → 部署验证中
2613
+ 切换: 负载均衡器流量从 Blue → Green(瞬间完成)
2614
+ 回滚: 流量切回 Blue(秒级)
2615
+ ```
2616
+
2617
+ **金丝雀部署(Canary Release)**
2618
+ ```
2619
+ 适用: 高风险变更、需要渐进式验证
2620
+ 流程:
2621
+ 1%流量 → 新版本(观察5min)
2622
+ → 10%(观察15min)
2623
+ → 50%(观察30min)
2624
+ → 100%(全量)
2625
+
2626
+ Kubernetes 实现:
2627
+ kubectl scale deployment app-v2 --replicas=1 # 1/10 = 10%
2628
+ kubectl scale deployment app-v1 --replicas=9
2629
+ ```
2630
+
2631
+ **部署门控(Deployment Gates)配置**
2632
+ ```yaml
2633
+ # GitHub Environments 配置审批
2634
+ deploy-production:
2635
+ environment:
2636
+ name: production
2637
+ url: https://app.example.com
2638
+ # 需要人工审批
2639
+ steps:
2640
+ - name: Request approval
2641
+ uses: trstringer/manual-approval@v1
2642
+ with:
2643
+ approvers: team-lead,cto
2644
+ minimum-approvals: 1
2645
+
2646
+ # 自动门控:基于健康检查
2647
+ deploy-production:
2648
+ steps:
2649
+ - name: Deploy
2650
+ run: kubectl apply -f k8s/
2651
+ - name: Wait for rollout
2652
+ run: kubectl rollout status deployment/app --timeout=5m
2653
+ - name: Smoke test
2654
+ run: |
2655
+ sleep 10
2656
+ curl -f https://api.example.com/health || exit 1
2657
+ ```
2658
+
2659
+ 5. 回滚策略与监控告警
2660
+ **自动回滚触发条件**
2661
+ ```yaml
2662
+ # 部署后自动验证,失败则回滚
2663
+ steps:
2664
+ - name: Deploy to production
2665
+ id: deploy
2666
+ run: kubectl set image deployment/app app=${{ env.NEW_IMAGE }}
2667
+
2668
+ - name: Monitor deployment health
2669
+ run: |
2670
+ # 等待10分钟,监控错误率
2671
+ for i in {1..20}; do
2672
+ ERROR_RATE=$(curl -s https://metrics.example.com/api/error-rate)
2673
+ if (( $(echo "$ERROR_RATE > 5" | bc -l) )); then
2674
+ echo "Error rate $ERROR_RATE% exceeds threshold, rolling back!"
2675
+ kubectl rollout undo deployment/app
2676
+ exit 1
2677
+ fi
2678
+ sleep 30
2679
+ done
2680
+
2681
+ - name: Rollback on failure
2682
+ if: failure() && steps.deploy.outcome == 'success'
2683
+ run: kubectl rollout undo deployment/app
2684
+ ```
2685
+
2686
+ **Kubernetes 滚动更新配置**
2687
+ ```yaml
2688
+ # deployment.yaml
2689
+ spec:
2690
+ strategy:
2691
+ type: RollingUpdate
2692
+ rollingUpdate:
2693
+ maxSurge: 1 # 最多多启动1个 Pod
2694
+ maxUnavailable: 0 # 始终保持满负载(零停机)
2695
+ minReadySeconds: 30 # Pod 就绪后等待30s再继续
2696
+ ```
2697
+
2698
+ **部署通知**
2699
+ ```yaml
2700
+ # 部署成功/失败通知到 Slack
2701
+ - name: Notify deployment status
2702
+ uses: slackapi/slack-github-action@v1
2703
+ with:
2704
+ channel-id: 'deployments'
2705
+ slack-message: |
2706
+ ${{ job.status == 'success' && '✅' || '❌' }} Deployment to Production
2707
+ Version: ${{ github.sha }}
2708
+ Actor: ${{ github.actor }}
2709
+ Status: ${{ job.status }}
2710
+ env:
2711
+ SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
2712
+ ```
2713
+
2714
+ **关键 CI/CD 指标**
2715
+
2716
+ | 指标 | 目标 | 说明 |
2717
+ |------|------|------|
2718
+ | Lead Time | < 1天 | 代码到生产的时间 |
2719
+ | Deploy Frequency | 每日1次+ | 部署频率 |
2720
+ | MTTR | < 1小时 | 故障恢复时间 |
2721
+ | Change Failure Rate | < 15% | 部署导致故障比例 |
2722
+
2723
+ Output: Markdown CI/CD 方案文档,含流水线阶段图、GitHub Actions YAML 配置、部署策略对比和回滚方案
2724
+
2725
+ ------------------------------------------------------------
2726
+
2727
+ 21. 性能优化 [性能优化/performance/页面慢]
2728
+ 系统化分析和优化前后端性能瓶颈,涵盖分析工具使用、优化策略和量化指标
2729
+
2730
+ 1. 建立性能基线与目标
2731
+ 优化前先量化,避免盲目优化。
2732
+
2733
+ **前端核心指标(Core Web Vitals)**
2734
+ | 指标 | 含义 | 优秀 | 需改进 | 差 |
2735
+ |------|------|------|--------|-----|
2736
+ | LCP | 最大内容绘制 | ≤ 2.5s | ≤ 4s | > 4s |
2737
+ | INP | 交互响应延迟 | ≤ 200ms | ≤ 500ms | > 500ms |
2738
+ | CLS | 累积布局偏移 | ≤ 0.1 | ≤ 0.25 | > 0.25 |
2739
+ | TTFB | 首字节时间 | ≤ 800ms | ≤ 1.8s | > 1.8s |
2740
+
2741
+ **采集工具**
2742
+ ```bash
2743
+ npm install -g @lhci/cli
2744
+ lhci autorun --collect.url=https://yoursite.com
2745
+
2746
+ npx autocannon -c 100 -d 30 http://localhost:3000/api/users
2747
+ ```
2748
+
2749
+ 2. 前端性能优化
2750
+ **资源加载优化**
2751
+ ```html
2752
+ <link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
2753
+ <link rel="preconnect" href="https://api.example.com">
2754
+ <img src="hero.jpg" loading="eager" fetchpriority="high" />
2755
+ <img src="below-fold.jpg" loading="lazy" />
2756
+ ```
2757
+
2758
+ **代码拆分(React)**
2759
+ ```typescript
2760
+ const UserProfile = lazy(() => import('./pages/UserProfile'));
2761
+
2762
+ // 虚拟列表(大数据量)
2763
+ import { FixedSizeList } from 'react-window';
2764
+ <FixedSizeList height={600} itemCount={10000} itemSize={50}>
2765
+ {({ index, style }) => <div style={style}>Row {index}</div>}
2766
+ </FixedSizeList>
2767
+ ```
2768
+
2769
+ **打包体积优化**
2770
+ ```bash
2771
+ npx vite-bundle-visualizer
2772
+ # Tree-shaking: 按需引入
2773
+ import { debounce } from 'lodash-es'; // ✅ 非 import _ from 'lodash'
2774
+ ```
2775
+
2776
+ 3. 后端与数据库性能优化
2777
+ **数据库查询优化**
2778
+ ```sql
2779
+ EXPLAIN ANALYZE SELECT u.*, COUNT(o.id)
2780
+ FROM users u LEFT JOIN orders o ON u.id = o.user_id
2781
+ WHERE u.status = 'active' GROUP BY u.id;
2782
+
2783
+ -- 复合索引
2784
+ CREATE INDEX idx_user_status_created ON users(status, created_at);
2785
+ ```
2786
+
2787
+ **缓存策略(Redis)**
2788
+ ```typescript
2789
+ async function getUserProfile(userId: string) {
2790
+ const cacheKey = `user:profile:${userId}`;
2791
+ const cached = await redis.get(cacheKey);
2792
+ if (cached) return JSON.parse(cached);
2793
+ const user = await db.users.findUnique({ where: { id: userId } });
2794
+ const ttl = 300 + Math.floor(Math.random() * 60); // 随机TTL防雪崩
2795
+ await redis.setex(cacheKey, ttl, JSON.stringify(user));
2796
+ return user;
2797
+ }
2798
+ ```
2799
+
2800
+ **并行化异步操作**
2801
+ ```typescript
2802
+ // ✅ 并行(快)
2803
+ const [user, orders] = await Promise.all([getUser(id), getOrders(id)]);
2804
+ ```
2805
+
2806
+ 4. 性能优化 Checklist 与持续监控
2807
+ **优化优先级矩阵**
2808
+ | 优化项 | 影响 | 成本 | 优先级 |
2809
+ |--------|------|------|--------|
2810
+ | 图片压缩/WebP | 高 | 低 | 🔴 立即 |
2811
+ | 关键资源预加载 | 高 | 低 | 🔴 立即 |
2812
+ | 数据库慢查询修复 | 高 | 中 | 🔴 立即 |
2813
+ | 代码拆分/懒加载 | 高 | 中 | 🟡 近期 |
2814
+ | Redis 缓存层 | 高 | 高 | 🟡 规划 |
2815
+
2816
+ **Lighthouse CI 集成**
2817
+ ```yaml
2818
+ - name: Lighthouse CI
2819
+ uses: treosh/lighthouse-ci-action@v10
2820
+ with:
2821
+ urls: https://yoursite.com
2822
+ uploadArtifacts: true
2823
+ ```
2824
+
2825
+ **性能优化报告模板**
2826
+ ```
2827
+ 优化前:LCP 4.8s | FCP 3.2s | P99 API 1200ms
2828
+ 已实施:图片WebP → LCP -1.8s;加索引 → P99 -600ms
2829
+ 优化后:LCP 2.3s ✅ | FCP 1.4s ✅ | P99 380ms ✅
2830
+ ```
2831
+
2832
+ Output: Markdown 性能分析报告,含当前指标基线、瓶颈列表、优化方案和预期收益
2833
+
2834
+ ------------------------------------------------------------
2835
+
2836
+ 22. 代码重构 [代码重构/refactoring/refactor]
2837
+ 系统化识别代码坏味道,运用重构手法安全改善代码结构,不改变外部行为
2838
+
2839
+ 1. 识别代码坏味道(Bad Smells)
2840
+ 重构前先诊断,明确改善目标:
2841
+
2842
+ **最常见的 12 种坏味道**
2843
+
2844
+ | 坏味道 | 症状 | 危害 |
2845
+ |--------|------|------|
2846
+ | **重复代码** | 相同逻辑出现 ≥2 处 | 修改需同步多处,极易遗漏 |
2847
+ | **过长函数** | 函数 > 20 行 | 难以理解、测试、复用 |
2848
+ | **过大的类** | 类承担过多职责 | 违反 SRP,耦合严重 |
2849
+ | **过长参数列表** | 参数 > 4 个 | 调用复杂,难以记忆 |
2850
+ | **发散式变化** | 一个类因不同原因被修改 | 违反 SRP |
2851
+ | **散弹式修改** | 一个变化需改多处 | 高耦合,遗漏风险高 |
2852
+ | **依恋情结** | 方法频繁访问其他类数据 | 逻辑放错了地方 |
2853
+ | **数据泥团** | 多处总是成组出现的数据 | 缺少封装 |
2854
+ | **基本类型偏执** | 用原始类型代替小对象 | 缺少领域建模 |
2855
+ | **注释过多** | 用注释弥补代码的不清晰 | 注释是坏味道的遮羞布 |
2856
+ | **过深嵌套** | 条件/循环嵌套 > 3 层 | 圈复杂度高,难以追踪 |
2857
+ | **僵尸代码** | 死代码、被注释的代码块 | 干扰阅读,增加维护负担 |
2858
+
2859
+ ```bash
2860
+ # 快速扫描工具
2861
+ npx eslint src --rule '{"complexity": ["warn", 10]}' # 圈复杂度
2862
+ npx jscpd src --threshold 5 # 重复代码检测
2863
+ ethan scan --todo # TODO/FIXME 清单
2864
+ ```
2865
+
2866
+ 2. 核心重构手法
2867
+ **提炼函数(Extract Function)** — 最常用
2868
+
2869
+ ```typescript
2870
+ // Before: 过长函数,注释掩盖意图
2871
+ function processOrder(order: Order) {
2872
+ // 计算折扣
2873
+ let discount = 0;
2874
+ if (order.user.isPremium) discount = 0.1;
2875
+ if (order.total > 1000) discount += 0.05;
2876
+ const finalPrice = order.total * (1 - discount);
2877
+
2878
+ // 发送确认邮件
2879
+ const subject = `订单 ${order.id} 确认`;
2880
+ sendEmail(order.user.email, subject, finalPrice);
2881
+ }
2882
+
2883
+ // After: 每个函数做一件事
2884
+ function calculateDiscount(order: Order): number {
2885
+ let discount = 0;
2886
+ if (order.user.isPremium) discount = 0.1;
2887
+ if (order.total > 1000) discount += 0.05;
2888
+ return discount;
2889
+ }
2890
+
2891
+ function sendOrderConfirmation(order: Order, finalPrice: number): void {
2892
+ const subject = `订单 ${order.id} 确认`;
2893
+ sendEmail(order.user.email, subject, finalPrice);
2894
+ }
2895
+
2896
+ function processOrder(order: Order) {
2897
+ const discount = calculateDiscount(order);
2898
+ const finalPrice = order.total * (1 - discount);
2899
+ sendOrderConfirmation(order, finalPrice);
2900
+ }
2901
+ ```
2902
+
2903
+ **以多态取代条件(Replace Conditional with Polymorphism)**
2904
+
2905
+ ```typescript
2906
+ // Before: switch 散弹式修改
2907
+ function getShippingCost(order: Order): number {
2908
+ switch (order.type) {
2909
+ case 'standard': return order.weight * 10;
2910
+ case 'express': return order.weight * 20 + 50;
2911
+ case 'overnight': return order.weight * 30 + 100;
2912
+ }
2913
+ }
2914
+
2915
+ // After: 策略模式/多态
2916
+ abstract class ShippingStrategy {
2917
+ abstract calculate(order: Order): number;
2918
+ }
2919
+ class StandardShipping extends ShippingStrategy {
2920
+ calculate(order: Order) { return order.weight * 10; }
2921
+ }
2922
+ class ExpressShipping extends ShippingStrategy {
2923
+ calculate(order: Order) { return order.weight * 20 + 50; }
2924
+ }
2925
+ ```
2926
+
2927
+ **引入参数对象(Introduce Parameter Object)**
2928
+
2929
+ ```typescript
2930
+ // Before: 过长参数列表
2931
+ function createReport(startDate: Date, endDate: Date, userId: string, format: string) {}
2932
+
2933
+ // After: 封装为值对象
2934
+ interface ReportParams { dateRange: DateRange; userId: string; format: string; }
2935
+ function createReport(params: ReportParams) {}
2936
+ ```
2937
+
2938
+ **其他常用手法速查**
2939
+
2940
+ | 手法 | 适用场景 |
2941
+ |------|---------|
2942
+ | 提炼类(Extract Class) | 一个类承担过多职责 |
2943
+ | 移动函数(Move Function) | 方法与数据不在一处 |
2944
+ | 内联函数(Inline Function) | 函数体比名字更清晰 |
2945
+ | 分解条件(Decompose Conditional) | 复杂 if-else 逻辑 |
2946
+ | 卫语句(Guard Clauses) | 深层嵌套 → 提前返回 |
2947
+ | 以查询取代临时变量 | 中间临时变量过多 |
2948
+
2949
+ 3. 重构安全网:测试先行
2950
+ **重构铁律:没有测试,不要重构**
2951
+
2952
+ ```bash
2953
+ # Step 1: 确保现有测试覆盖率充足
2954
+ npm run test:coverage
2955
+ # 目标:被重构的模块覆盖率 > 80%
2956
+
2957
+ # Step 2: 若无测试,先补特征测试(Characterization Test)
2958
+ # 不是测试"应该如何",而是记录"当前如何"
2959
+ it('characterization: processOrder returns expected price', () => {
2960
+ const result = processOrder(mockOrder);
2961
+ expect(result).toMatchSnapshot(); // 先快照,重构后验证不变
2962
+ });
2963
+
2964
+ # Step 3: 小步前进 — 每次重构后立即运行测试
2965
+ npm test -- --watch
2966
+ ```
2967
+
2968
+ **重构工作流**
2969
+
2970
+ ```
2971
+ 识别目标 → 写/补测试 → 最小重构 → 运行测试 → 提交
2972
+ ↑____________________________|
2973
+ 循环,每次改动 < 30min
2974
+ ```
2975
+
2976
+ **IDE 辅助重构(减少手工失误)**
2977
+
2978
+ | 操作 | VS Code / WebStorm |
2979
+ |------|-------------------|
2980
+ | 提炼函数 | Ctrl+Shift+R → Extract Method |
2981
+ | 重命名 | F2 → 自动更新所有引用 |
2982
+ | 移动文件 | 拖拽 → 自动更新 import |
2983
+ | 提炼变量 | Ctrl+Shift+R → Extract Variable |
2984
+
2985
+ 4. 重构策略与输出
2986
+ **Boy Scout Rule(童子军规则)**
2987
+ > 让代码比你来时更干净一点,每次 PR 顺手重构接触到的代码。
2988
+
2989
+ **大规模重构策略:Strangler Fig Pattern(绞杀榕模式)**
2990
+
2991
+ ```
2992
+ 旧系统 ──[façade]──→ 新模块(逐步替换)
2993
+ |
2994
+ └──→ 旧模块(逐步废弃)
2995
+ ```
2996
+
2997
+ 1. 在旧代码外包一层 Façade/Adapter
2998
+ 2. 新功能全部写在新结构中
2999
+ 3. 旧调用方逐步迁移到新结构
3000
+ 4. 旧代码最终归零删除
3001
+
3002
+ **何时停止重构**
3003
+
3004
+ | 信号 | 建议 |
3005
+ |------|------|
3006
+ | 测试全绿,代码可读性提升 | 提交,结束本轮 |
3007
+ | 发现需要改外部接口 | 创建新 Issue,本次不做 |
3008
+ | 重构范围不断扩大 | 停止,重新评估范围 |
3009
+
3010
+ **重构输出清单**
3011
+ - [ ] 坏味道清单(标注优先级 P1/P2/P3)
3012
+ - [ ] 本次重构的 Diff 说明(what changed & why)
3013
+ - [ ] 测试覆盖率前后对比
3014
+ - [ ] 技术债记录到 Issue/Backlog
3015
+
3016
+ Output: Markdown 重构报告:坏味道清单 + 重构手法说明 + 测试覆盖率变化 + 技术债 Backlog
3017
+
3018
+ ------------------------------------------------------------
3019
+
3020
+ 23. 可观测性 [可观测性/observability/监控]
3021
+ 建立日志、指标、链路追踪三支柱体系,实现系统状态完全可观测,快速定位生产问题
3022
+
3023
+ 1. 三支柱体系设计
3024
+ **可观测性三支柱(Three Pillars of Observability)**
3025
+
3026
+ | 支柱 | 回答的问题 | 工具栈 |
3027
+ |------|-----------|--------|
3028
+ | **Logs(日志)** | 发生了什么? | Winston/Pino + ELK/Loki |
3029
+ | **Metrics(指标)** | 系统状况如何? | Prometheus + Grafana |
3030
+ | **Traces(链路)** | 请求经过了哪里? | OpenTelemetry + Jaeger/Tempo |
3031
+
3032
+ **选型建议**
3033
+
3034
+ ```
3035
+ 轻量级单体: Pino + Prometheus + Grafana
3036
+ 微服务标准: OpenTelemetry SDK → Collector → Jaeger + Prometheus + Loki
3037
+ 云原生托管: Datadog / New Relic / AWS CloudWatch (开箱即用)
3038
+ ```
3039
+
3040
+ **黄金信号(Golden Signals)— 4个必监控指标**
3041
+
3042
+ | 信号 | 说明 | 告警阈值示例 |
3043
+ |------|------|-------------|
3044
+ | **Latency(延迟)** | P50/P99/P999 响应时间 | P99 > 500ms |
3045
+ | **Traffic(流量)** | RPS / 并发连接数 | 环比突增 50% |
3046
+ | **Errors(错误率)** | 5xx / 业务错误比例 | > 0.1% |
3047
+ | **Saturation(饱和度)** | CPU/内存/队列深度 | CPU > 80% |
3048
+
3049
+ 2. 结构化日志规范
3050
+ **日志必须是结构化 JSON,不要用 console.log**
3051
+
3052
+ ```typescript
3053
+ // ❌ Bad: 非结构化,无法机器解析
3054
+ console.log(`用户 ${userId} 下单失败: ${error.message}`);
3055
+
3056
+ // ✅ Good: 结构化 JSON 日志(使用 Pino)
3057
+ import pino from 'pino';
3058
+ const logger = pino({ level: 'info' });
3059
+
3060
+ logger.error({
3061
+ event: 'order.create.failed',
3062
+ userId,
3063
+ orderId,
3064
+ errorCode: error.code,
3065
+ msg: error.message,
3066
+ durationMs: Date.now() - startTime,
3067
+ });
3068
+ ```
3069
+
3070
+ **日志级别规范**
3071
+
3072
+ | 级别 | 使用场景 | 生产建议 |
3073
+ |------|---------|---------|
3074
+ | ERROR | 需要立即处理的错误 | 触发告警 |
3075
+ | WARN | 不影响功能但需关注 | 记录 + 汇总 |
3076
+ | INFO | 关键业务事件(下单/登录) | 默认级别 |
3077
+ | DEBUG | 调试信息,技术细节 | 生产关闭 |
3078
+
3079
+ **必带字段(Mandatory Fields)**
3080
+
3081
+ ```typescript
3082
+ interface LogContext {
3083
+ traceId: string; // 链路追踪 ID
3084
+ spanId: string; // 当前 Span ID
3085
+ userId?: string; // 用户 ID(有则带)
3086
+ requestId: string; // 请求唯一 ID
3087
+ service: string; // 服务名
3088
+ version: string; // 服务版本
3089
+ env: string; // prod / staging
3090
+ }
3091
+ ```
3092
+
3093
+ **日志采样策略**
3094
+
3095
+ ```typescript
3096
+ // 高流量场景:ERROR 全量,INFO 10% 采样
3097
+ const shouldLog = (level: string) =>
3098
+ level === 'error' || Math.random() < 0.1;
3099
+ ```
3100
+
3101
+ 3. 指标采集与告警(Prometheus + Grafana)
3102
+ **RED 方法论(微服务推荐)**
3103
+ - **R**ate — 每秒请求数
3104
+ - **E**rrors — 错误率
3105
+ - **D**uration — 请求时延分布
3106
+
3107
+ ```typescript
3108
+ // Node.js 指标暴露(prom-client)
3109
+ import { Counter, Histogram, register } from 'prom-client';
3110
+
3111
+ const httpRequests = new Counter({
3112
+ name: 'http_requests_total',
3113
+ help: 'Total HTTP requests',
3114
+ labelNames: ['method', 'route', 'status'],
3115
+ });
3116
+
3117
+ const httpDuration = new Histogram({
3118
+ name: 'http_request_duration_seconds',
3119
+ help: 'HTTP request duration in seconds',
3120
+ labelNames: ['method', 'route'],
3121
+ buckets: [0.005, 0.01, 0.05, 0.1, 0.5, 1, 5],
3122
+ });
3123
+
3124
+ // Express 中间件
3125
+ app.use((req, res, next) => {
3126
+ const end = httpDuration.startTimer({ method: req.method, route: req.path });
3127
+ res.on('finish', () => {
3128
+ httpRequests.inc({ method: req.method, route: req.path, status: res.statusCode });
3129
+ end();
3130
+ });
3131
+ next();
3132
+ });
3133
+
3134
+ // 暴露 /metrics 端点
3135
+ app.get('/metrics', async (_, res) => {
3136
+ res.set('Content-Type', register.contentType);
3137
+ res.end(await register.metrics());
3138
+ });
3139
+ ```
3140
+
3141
+ **Grafana 告警规则示例(Alertmanager)**
3142
+
3143
+ ```yaml
3144
+ groups:
3145
+ - name: api-alerts
3146
+ rules:
3147
+ - alert: HighErrorRate
3148
+ expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.01
3149
+ for: 2m
3150
+ labels:
3151
+ severity: critical
3152
+ annotations:
3153
+ summary: "错误率超过 1%,当前: {{ $value | humanizePercentage }}"
3154
+
3155
+ - alert: SlowP99
3156
+ expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 1
3157
+ for: 5m
3158
+ labels:
3159
+ severity: warning
3160
+ annotations:
3161
+ summary: "P99 延迟超过 1s"
3162
+ ```
3163
+
3164
+ 4. 分布式链路追踪(OpenTelemetry)
3165
+ **OpenTelemetry 是行业标准 —— 一次接入,多后端支持**
3166
+
3167
+ ```typescript
3168
+ // 初始化 OTel(Node.js)
3169
+ import { NodeSDK } from '@opentelemetry/sdk-node';
3170
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
3171
+ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
3172
+
3173
+ const sdk = new NodeSDK({
3174
+ traceExporter: new OTLPTraceExporter({
3175
+ url: 'http://otel-collector:4318/v1/traces',
3176
+ }),
3177
+ instrumentations: [
3178
+ getNodeAutoInstrumentations(), // 自动追踪 HTTP/Express/DB
3179
+ ],
3180
+ serviceName: 'order-service',
3181
+ });
3182
+ sdk.start();
3183
+ ```
3184
+
3185
+ **手动创建 Span(业务关键路径)**
3186
+
3187
+ ```typescript
3188
+ import { trace } from '@opentelemetry/api';
3189
+ const tracer = trace.getTracer('order-service');
3190
+
3191
+ async function createOrder(data: OrderData) {
3192
+ return tracer.startActiveSpan('order.create', async (span) => {
3193
+ try {
3194
+ span.setAttributes({
3195
+ 'order.user_id': data.userId,
3196
+ 'order.item_count': data.items.length,
3197
+ 'order.total': data.total,
3198
+ });
3199
+
3200
+ const order = await db.orders.create(data);
3201
+ span.setStatus({ code: SpanStatusCode.OK });
3202
+ return order;
3203
+ } catch (err) {
3204
+ span.recordException(err as Error);
3205
+ span.setStatus({ code: SpanStatusCode.ERROR });
3206
+ throw err;
3207
+ } finally {
3208
+ span.end();
3209
+ }
3210
+ });
3211
+ }
3212
+ ```
3213
+
3214
+ **SLO 定义模板**
3215
+
3216
+ ```yaml
3217
+ SLO: API 可用性
3218
+ SLI: (成功请求数 / 总请求数) * 100%
3219
+ 目标: ≥ 99.9% (月度 = 允许 43.8 min 故障)
3220
+ 告警: 1h 内错误预算消耗 > 5% 时 PagerDuty 通知
3221
+ ```
3222
+
3223
+ Output: Markdown 可观测性方案:技术栈选型 + 日志/指标/链路配置代码 + 告警规则 + SLO 定义
3224
+
3225
+ ------------------------------------------------------------
3226
+
3227
+ 24. 设计模式 [设计模式/design pattern/design patterns]
3228
+ 识别适用场景,选择合适的 GoF 设计模式,提升代码可扩展性与可维护性
3229
+
3230
+ 1. 三大类模式全景
3231
+ **23 种 GoF 模式分类速查**
3232
+
3233
+ | 类型 | 模式 | 解决的核心问题 |
3234
+ |------|------|--------------|
3235
+ | **创建型** | Factory Method | 子类决定创建哪种对象 |
3236
+ | | Abstract Factory | 创建一族相关对象 |
3237
+ | | Builder | 分步骤构建复杂对象 |
3238
+ | | Singleton | 全局唯一实例 |
3239
+ | | Prototype | 克隆已有对象 |
3240
+ | **结构型** | Adapter | 接口转换,兼容不兼容的接口 |
3241
+ | | Decorator | 动态添加行为(不继承) |
3242
+ | | Facade | 简化复杂子系统的接口 |
3243
+ | | Proxy | 控制对象访问(缓存/权限/懒加载)|
3244
+ | | Composite | 树形结构,统一处理单个和组合 |
3245
+ | **行为型** | Observer | 一对多事件通知 |
3246
+ | | Strategy | 运行时切换算法 |
3247
+ | | Command | 将请求封装为对象(支持撤销)|
3248
+ | | Iterator | 统一遍历集合的方式 |
3249
+ | | State | 状态机,行为随状态变化 |
3250
+ | | Chain of Responsibility | 请求沿链传递,直到被处理 |
3251
+ | | Template Method | 算法骨架固定,子类填充步骤 |
3252
+
3253
+ **最常用的 5 个(优先掌握)**:Strategy, Observer, Factory, Decorator, Proxy
3254
+
3255
+ 2. 高频模式 TypeScript 实现
3256
+ **策略模式(Strategy)— 取代 if/switch 的最佳武器**
3257
+
3258
+ ```typescript
3259
+ // 场景:支付方式可扩展
3260
+ interface PaymentStrategy {
3261
+ pay(amount: number): Promise<void>;
3262
+ }
3263
+
3264
+ class WechatPay implements PaymentStrategy {
3265
+ async pay(amount: number) { /* 微信支付逻辑 */ }
3266
+ }
3267
+ class AlipayStrategy implements PaymentStrategy {
3268
+ async pay(amount: number) { /* 支付宝逻辑 */ }
3269
+ }
3270
+
3271
+ class PaymentService {
3272
+ constructor(private strategy: PaymentStrategy) {}
3273
+ async checkout(amount: number) {
3274
+ await this.strategy.pay(amount);
3275
+ }
3276
+ }
3277
+
3278
+ // 运行时切换,新增支付方式不改原有代码
3279
+ const service = new PaymentService(new WechatPay());
3280
+ ```
3281
+
3282
+ **观察者模式(Observer / EventEmitter)**
3283
+
3284
+ ```typescript
3285
+ // 场景:订单状态变更通知多个系统
3286
+ class OrderEventEmitter extends EventEmitter {
3287
+ emitOrderCreated(order: Order) {
3288
+ this.emit('order:created', order);
3289
+ }
3290
+ }
3291
+
3292
+ const emitter = new OrderEventEmitter();
3293
+ emitter.on('order:created', sendConfirmationEmail);
3294
+ emitter.on('order:created', updateInventory);
3295
+ emitter.on('order:created', triggerRecommendation);
3296
+ ```
3297
+
3298
+ **装饰器模式(Decorator)— 不改原类,添加横切关注点**
3299
+
3300
+ ```typescript
3301
+ // 场景:为任意服务添加缓存
3302
+ function withCache<T extends object>(service: T, ttlMs = 60_000): T {
3303
+ return new Proxy(service, {
3304
+ get(target, prop) {
3305
+ const original = (target as Record<string, unknown>)[prop as string];
3306
+ if (typeof original !== 'function') return original;
3307
+ const cache = new Map<string, { value: unknown; expiry: number }>();
3308
+ return async (...args: unknown[]) => {
3309
+ const key = JSON.stringify(args);
3310
+ const cached = cache.get(key);
3311
+ if (cached && Date.now() < cached.expiry) return cached.value;
3312
+ const value = await (original as Function).apply(target, args);
3313
+ cache.set(key, { value, expiry: Date.now() + ttlMs });
3314
+ return value;
3315
+ };
3316
+ },
3317
+ });
3318
+ }
3319
+
3320
+ const cachedUserService = withCache(userService, 30_000);
3321
+ ```
3322
+
3323
+ 3. 创建型模式实践
3324
+ **工厂模式(Factory)— 解耦对象创建与使用**
3325
+
3326
+ ```typescript
3327
+ // 场景:根据配置创建不同日志处理器
3328
+ interface Logger {
3329
+ log(message: string): void;
3330
+ }
3331
+
3332
+ class ConsoleLogger implements Logger {
3333
+ log(message: string) { console.log(message); }
3334
+ }
3335
+ class FileLogger implements Logger {
3336
+ log(message: string) { fs.appendFile('app.log', message); }
3337
+ }
3338
+
3339
+ // 工厂函数(简单场景推荐函数而非类)
3340
+ function createLogger(type: 'console' | 'file'): Logger {
3341
+ if (type === 'file') return new FileLogger();
3342
+ return new ConsoleLogger();
3343
+ }
3344
+ ```
3345
+
3346
+ **建造者模式(Builder)— 处理复杂对象构造**
3347
+
3348
+ ```typescript
3349
+ // 场景:SQL 查询构建,参数组合多变
3350
+ class QueryBuilder {
3351
+ private query = { table: '', conditions: [] as string[], limit: 100 };
3352
+
3353
+ from(table: string) { this.query.table = table; return this; }
3354
+ where(condition: string) { this.query.conditions.push(condition); return this; }
3355
+ limit(n: number) { this.query.limit = n; return this; }
3356
+ build() {
3357
+ const where = this.query.conditions.length
3358
+ ? `WHERE ${this.query.conditions.join(' AND ')}`
3359
+ : '';
3360
+ return `SELECT * FROM ${this.query.table} ${where} LIMIT ${this.query.limit}`;
3361
+ }
3362
+ }
3363
+
3364
+ // 链式调用,可读性极强
3365
+ const sql = new QueryBuilder()
3366
+ .from('orders')
3367
+ .where('status = "pending"')
3368
+ .where('created_at > NOW() - INTERVAL 7 DAY')
3369
+ .limit(50)
3370
+ .build();
3371
+ ```
3372
+
3373
+ **单例注意事项**
3374
+
3375
+ ```typescript
3376
+ // ⚠️ 单例慎用:全局状态 = 隐式耦合
3377
+ // 适用:DB连接池、全局配置、Logger
3378
+ // 不适用:有状态的业务逻辑
3379
+
3380
+ // Node.js 模块缓存天然单例
3381
+ export const db = createDbPool(); // 模块级单例,足够用
3382
+ ```
3383
+
3384
+ 4. 模式选型指南与反模式
3385
+ **场景 → 模式 速查表**
3386
+
3387
+ | 遇到这种问题 | 考虑使用 |
3388
+ |------------|---------|
3389
+ | if/switch 随需求不断增长 | Strategy / Command |
3390
+ | 一个变化需要修改多处 | Observer / Mediator |
3391
+ | 需要在运行时给对象加功能 | Decorator / Proxy |
3392
+ | 创建逻辑复杂,参数多 | Factory / Builder |
3393
+ | 需要统一处理树形结构 | Composite |
3394
+ | 需要兼容旧接口/第三方库 | Adapter / Facade |
3395
+ | 请求需要多步验证/处理 | Chain of Responsibility |
3396
+ | 对象行为随状态显著变化 | State |
3397
+
3398
+ **反模式:过度设计的信号**
3399
+
3400
+ ```
3401
+ ❌ 为了用设计模式而用设计模式
3402
+ ❌ 一个功能引入 3 层抽象(不超过 2 个接口)
3403
+ ❌ 到处都是 Manager / Handler / Processor 命名
3404
+ ❌ 接口只有一个实现类(YAGNI 原则)
3405
+
3406
+ ✅ 正确姿势:先写简单代码,当变化来临时再重构引入模式
3407
+ ```
3408
+
3409
+ **SOLID 原则与模式的关系**
3410
+
3411
+ | 原则 | 对应常用模式 |
3412
+ |------|------------|
3413
+ | S 单一职责 | Facade, Extract Class |
3414
+ | O 开闭原则 | Strategy, Decorator |
3415
+ | L 里氏替换 | Template Method |
3416
+ | I 接口隔离 | Adapter |
3417
+ | D 依赖倒置 | Factory, DI Container |
3418
+
3419
+ **输出清单**
3420
+ - [ ] 识别到的模式应用场景(带代码位置)
3421
+ - [ ] 选型理由(为什么选这个模式而不是另一个)
3422
+ - [ ] 实现示例(TypeScript,保持 < 50 行)
3423
+ - [ ] 过度设计风险说明
3424
+
3425
+ Output: Markdown 设计模式方案:场景分析 + 模式选型理由 + TypeScript 实现示例 + 反模式警示
3426
+
1177
3427
  ============================================================
1178
3428
 
1179
3429
  ## 自动工作流执行(Auto-Pilot 协议)