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