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