@chenguangyao/devflow-kit 0.1.43
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/CHANGELOG.md +232 -0
- package/LICENSE +21 -0
- package/README.md +539 -0
- package/bin/devflow.js +9 -0
- package/docs/RFC-001-devflow-kit.md +617 -0
- package/docs/RFC-002-workflow-kernel.md +134 -0
- package/docs/enterprise-integration-supplement.md +274 -0
- package/docs/internal-gitlab-setup.md +426 -0
- package/docs/marketplace-skills.md +231 -0
- package/docs/migration-from-arb.md +232 -0
- package/docs/tooling-overview.md +774 -0
- package/docs/workflow-orchestration.md +695 -0
- package/docs/workflow-ui-prototype.html +271 -0
- package/package.json +52 -0
- package/schemas/config.schema.json +51 -0
- package/schemas/delta.schema.json +22 -0
- package/schemas/state.schema.json +130 -0
- package/schemas/status-surface.schema.json +197 -0
- package/schemas/workflow-confirmation-surface.schema.json +70 -0
- package/schemas/workflow-picker.schema.json +94 -0
- package/scripts/postinstall.js +101 -0
- package/scripts/render-workflow-ui-prototype.js +271 -0
- package/skills/apply/SKILL.md +313 -0
- package/skills/apply/references/discipline-checklist.md +145 -0
- package/skills/apply/references/subagent-implementer-prompt.md +113 -0
- package/skills/apply/references/subagent-orchestration.md +150 -0
- package/skills/apply/references/subagent-reviewer-prompt.md +180 -0
- package/skills/apply/references/tdd-loop.md +287 -0
- package/skills/apply/references/when-plan-is-wrong.md +279 -0
- package/skills/apply/references/worktree-swarm.md +292 -0
- package/skills/archive/SKILL.md +229 -0
- package/skills/archive/references/conflict-resolution.md +336 -0
- package/skills/archive/references/knowledge-deposit.md +381 -0
- package/skills/archive/references/spec-merge.md +365 -0
- package/skills/brainstorm/SKILL.md +123 -0
- package/skills/brainstorm/references/proposal-template.md +244 -0
- package/skills/brainstorm/references/question-catalog.md +168 -0
- package/skills/brainstorm/references/session-template.md +184 -0
- package/skills/ci-fix/SKILL.md +63 -0
- package/skills/ci-fix/references/loop.md +25 -0
- package/skills/code-review/SKILL.md +279 -0
- package/skills/code-review/references/escalation-playbook.md +192 -0
- package/skills/code-review/references/language-cheatsheets/go.md +175 -0
- package/skills/code-review/references/language-cheatsheets/java-spring-mybatis.md +246 -0
- package/skills/code-review/references/language-cheatsheets/python.md +170 -0
- package/skills/code-review/references/language-cheatsheets/vue.md +199 -0
- package/skills/code-review/references/output-template.md +275 -0
- package/skills/code-review/references/review-checklist.md +251 -0
- package/skills/complexity-grading/SKILL.md +259 -0
- package/skills/deliver/SKILL.md +271 -0
- package/skills/deliver/references/delivery-modes.md +299 -0
- package/skills/deliver/references/notify.md +359 -0
- package/skills/deliver/references/pr-description.md +319 -0
- package/skills/dependency-upgrade/SKILL.md +57 -0
- package/skills/dependency-upgrade/references/risk-matrix.md +38 -0
- package/skills/df-orchestrator/SKILL.md +407 -0
- package/skills/df-orchestrator/references/complexity-grading.md +177 -0
- package/skills/df-orchestrator/references/escalation-matrix.md +191 -0
- package/skills/df-orchestrator/references/routing-rules.md +290 -0
- package/skills/df-orchestrator/references/workflow-state-machine.md +208 -0
- package/skills/frontend-quality/SKILL.md +61 -0
- package/skills/frontend-quality/references/checklist.md +35 -0
- package/skills/handoff-resume/SKILL.md +59 -0
- package/skills/handoff-resume/references/handoff-template.md +54 -0
- package/skills/plan/SKILL.md +166 -0
- package/skills/plan/references/task-breakdown.md +207 -0
- package/skills/plan/references/task-sequencing.md +143 -0
- package/skills/plan/references/task-template.md +248 -0
- package/skills/requirement-analysis/SKILL.md +499 -0
- package/skills/requirement-analysis/references/acceptance-criteria.md +183 -0
- package/skills/requirement-analysis/references/code-recon.md +151 -0
- package/skills/requirement-analysis/references/edge-case-catalog.md +164 -0
- package/skills/requirement-analysis/references/requirement-template.md +339 -0
- package/skills/requirement-analysis/references/scope-negotiation.md +162 -0
- package/skills/security-hardening/SKILL.md +60 -0
- package/skills/security-hardening/references/checklist.md +42 -0
- package/skills/tech-spec/SKILL.md +388 -0
- package/skills/tech-spec/references/api-contract-design.md +172 -0
- package/skills/tech-spec/references/decision-records.md +110 -0
- package/skills/tech-spec/references/design-template.md +301 -0
- package/skills/tech-spec/references/rollout-and-rollback.md +203 -0
- package/skills/tech-spec/references/spec-delta-conventions.md +250 -0
- package/skills/tech-spec/references/transaction-patterns.md +212 -0
- package/skills/test-spec/SKILL.md +219 -0
- package/skills/test-spec/references/coverage-strategy.md +218 -0
- package/skills/test-spec/references/edge-case-to-test.md +143 -0
- package/skills/test-spec/references/test-case-template.md +276 -0
- package/skills/verify/SKILL.md +232 -0
- package/skills/verify/references/nfr-verification.md +292 -0
- package/skills/verify/references/report-templates.md +510 -0
- package/skills/verify/references/self-test-guide.md +240 -0
- package/skills/verify/references/verify-rollback-map.md +247 -0
- package/src/cli/commands/_helpers.js +108 -0
- package/src/cli/commands/_submit.js +718 -0
- package/src/cli/commands/apply.js +198 -0
- package/src/cli/commands/archive.js +180 -0
- package/src/cli/commands/checkpoint.js +113 -0
- package/src/cli/commands/deliver.js +377 -0
- package/src/cli/commands/deploy.js +504 -0
- package/src/cli/commands/design.js +158 -0
- package/src/cli/commands/disable.js +21 -0
- package/src/cli/commands/doctor.js +178 -0
- package/src/cli/commands/enable.js +21 -0
- package/src/cli/commands/flow.js +645 -0
- package/src/cli/commands/help.js +93 -0
- package/src/cli/commands/ingest.js +602 -0
- package/src/cli/commands/init.js +341 -0
- package/src/cli/commands/knowledge.js +523 -0
- package/src/cli/commands/logs.js +43 -0
- package/src/cli/commands/new.js +202 -0
- package/src/cli/commands/plan.js +49 -0
- package/src/cli/commands/propose.js +27 -0
- package/src/cli/commands/provider.js +698 -0
- package/src/cli/commands/report.js +143 -0
- package/src/cli/commands/requirement.js +227 -0
- package/src/cli/commands/review.js +301 -0
- package/src/cli/commands/skills.js +457 -0
- package/src/cli/commands/status.js +925 -0
- package/src/cli/commands/switch.js +27 -0
- package/src/cli/commands/sync.js +47 -0
- package/src/cli/commands/test.js +366 -0
- package/src/cli/commands/uninstall.js +32 -0
- package/src/cli/commands/update.js +74 -0
- package/src/cli/commands/verify.js +354 -0
- package/src/cli/commands/worktree.js +78 -0
- package/src/cli/index.js +72 -0
- package/src/cli/parse-args.js +102 -0
- package/src/core/autodetect.js +271 -0
- package/src/core/change.js +208 -0
- package/src/core/checkpoint.js +217 -0
- package/src/core/config.js +60 -0
- package/src/core/delta.js +290 -0
- package/src/core/markers.js +59 -0
- package/src/core/paths.js +173 -0
- package/src/core/plan-tasks.js +36 -0
- package/src/core/project-routing.js +285 -0
- package/src/core/projects.js +200 -0
- package/src/core/state.js +200 -0
- package/src/core/workflow-check.js +177 -0
- package/src/core/workflow-init.js +34 -0
- package/src/core/workflow-picker.js +154 -0
- package/src/core/workflow-policy.js +119 -0
- package/src/core/workflow-suggest.js +181 -0
- package/src/core/workflow-verify.js +88 -0
- package/src/core/workflow.js +433 -0
- package/src/core/worktree.js +241 -0
- package/src/knowledge/categories.js +107 -0
- package/src/knowledge/classify.js +125 -0
- package/src/knowledge/deposit.js +414 -0
- package/src/knowledge/migrate.js +149 -0
- package/src/knowledge/mr.js +219 -0
- package/src/knowledge/query.js +131 -0
- package/src/knowledge/registry.js +151 -0
- package/src/knowledge/sync.js +179 -0
- package/src/providers/base.js +74 -0
- package/src/providers/drivers/api-yapi.js +78 -0
- package/src/providers/drivers/ci-jenkins.js +109 -0
- package/src/providers/drivers/intake-confluence.js +544 -0
- package/src/providers/drivers/kb-git.js +549 -0
- package/src/providers/drivers/kb-weknora.js +472 -0
- package/src/providers/drivers/notify-smtp.js +515 -0
- package/src/providers/drivers/observability-oss.js +43 -0
- package/src/providers/drivers/observability-sls.js +50 -0
- package/src/providers/lifecycle.js +135 -0
- package/src/providers/loader.js +132 -0
- package/src/providers/local.js +190 -0
- package/src/providers/userconfig.js +283 -0
- package/src/reports/aggregate.js +185 -0
- package/src/reports/coverage.js +163 -0
- package/src/reports/detect.js +143 -0
- package/src/reports/parse.js +236 -0
- package/src/templates/files/ci/github.yml +38 -0
- package/src/templates/files/ci/gitlab.yml +27 -0
- package/src/templates/files/design.md +63 -0
- package/src/templates/files/ide/devflow-workflow.md +58 -0
- package/src/templates/files/ide/project-overview-reference.md +1 -0
- package/src/templates/files/ide/project-overview.md +27 -0
- package/src/templates/files/knowledge-index.json +17 -0
- package/src/templates/files/knowledge.md +28 -0
- package/src/templates/files/meta.json +8 -0
- package/src/templates/files/plan.md +38 -0
- package/src/templates/files/proposal.md +33 -0
- package/src/templates/files/reports/contract-test.md +40 -0
- package/src/templates/files/reports/e2e-test.md +30 -0
- package/src/templates/files/reports/integration-test.md +36 -0
- package/src/templates/files/reports/joint-test.md +58 -0
- package/src/templates/files/reports/perf.md +24 -0
- package/src/templates/files/reports/regression.md +20 -0
- package/src/templates/files/reports/remote-test.md +55 -0
- package/src/templates/files/reports/self-test.md +43 -0
- package/src/templates/files/reports/smoke-test.md +22 -0
- package/src/templates/files/reports/unit-test.md +36 -0
- package/src/templates/files/requirement.md +51 -0
- package/src/templates/files/review.md +38 -0
- package/src/templates/files/tests.md +36 -0
- package/src/templates/files/verify.md +32 -0
- package/src/templates/index.js +21 -0
- package/src/utils/log.js +37 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Language Cheatsheet — Java (Spring Boot + MyBatis)
|
|
2
|
+
|
|
3
|
+
配套 `review-checklist.md` 使用。通用 checklist 告诉你"要找什么",本 cheatsheet 告诉你"在 Spring + MyBatis 项目里长什么样"。
|
|
4
|
+
|
|
5
|
+
> 如果项目用 Spring Data JPA 而不是 MyBatis,本文 §2 / §3 改读 `java-spring-jpa.md`(尚未提供;以 JPA 的 LazyInit / @Transactional 传播 / N+1 为主)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## §1 分层与命名(配合 review-checklist.md §5 Maintainability)
|
|
10
|
+
|
|
11
|
+
| 层级 | 命名约定 | 职责 | 反例 |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| Controller | `XxxController` | HTTP 入口,参数校验,DTO ↔ Request/Response | Controller 直接写 SQL,`@Transactional` 加在 Controller |
|
|
14
|
+
| Service | `XxxService` + `XxxServiceImpl` | 业务编排,事务边界 | Service 直接返回 DO(泄漏持久层类型) |
|
|
15
|
+
| Manager | `XxxManager` | 复杂领域逻辑 / 聚合多个 Service | Manager 与 Service 职责边界模糊,互相循环依赖 |
|
|
16
|
+
| Mapper / Dao | `XxxMapper` + `XxxMapper.xml` | 单表 CRUD,不含业务 | Mapper 里写 join 多表业务查询 |
|
|
17
|
+
| DO(Data Object) | `XxxDO` | 1:1 映射数据库表 | DO 被返回给外部接口 |
|
|
18
|
+
| BO / Domain | `XxxBO` / `Xxx`(领域对象) | 业务领域模型 | 无 BO,直接 DO 穿透到 Service |
|
|
19
|
+
| DTO / CO | `XxxDTO` / `XxxCO` | 进程间 / 层间传输 | 把 DO 当 DTO 用 |
|
|
20
|
+
| Convertor | `XxxConvertor` | DO ↔ BO ↔ DTO ↔ VO 的转换 | 在 Service 里手写转换散落各处 |
|
|
21
|
+
| VO | `XxxVO` / `XxxResponse` | 对外接口返回 | VO 暴露了内部字段(如 `password`, `deleted_at`) |
|
|
22
|
+
|
|
23
|
+
**判定**:
|
|
24
|
+
|
|
25
|
+
- Controller 直接调 Mapper(绕过 Service)→ **MUST**(事务/可测性丢失)
|
|
26
|
+
- DO 泄漏到接口返回 → **MUST**(字段变更会直接影响前端契约)
|
|
27
|
+
- 命名不符合上表约定 → **NIT**(3+ 处 **SHOULD**)
|
|
28
|
+
|
|
29
|
+
### 1.1 DDD 多模块验证(编译 / 测试)
|
|
30
|
+
|
|
31
|
+
DDD / Spring 多模块项目里,单独编译某个 leaf module 经常会误报:domain 依赖 app 的聚合装配、app 依赖 domain 最新源码、adapter 依赖 mapper/DTO,且 Lombok / MapStruct 等 annotation processor 必须由 Maven 正常驱动。
|
|
32
|
+
|
|
33
|
+
**先识别实际构建 JDK,而不是只看 `java -version`**:
|
|
34
|
+
|
|
35
|
+
| 信号 | 命令 / 文件 | 用途 |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| Maven 实际运行 JDK | `mvn -version` | 看本次 Maven runtime 的 Java version / Java home |
|
|
38
|
+
| Toolchain | `maven-toolchains-plugin`、`~/.m2/toolchains.xml` | 判断 compiler 是否被 toolchain 切到另一套 JDK |
|
|
39
|
+
| 项目声明版本 | `maven.compiler.release/source/target`、`<java.version>` | 和实际构建 JDK 对照,不单独当结论 |
|
|
40
|
+
| 本地版本文件 | `.java-version`、`.sdkmanrc`、`.tool-versions` | 辅助选择 shell / IDE JDK |
|
|
41
|
+
|
|
42
|
+
Lombok / annotation processor 报错时,先诊断 JDK / Maven / toolchain / Lombok 版本,不要先改业务代码。尤其是 `Unsupported class file major version`、`IllegalAccessError`、`NoSuchFieldError` 这类错误,通常是构建 JDK 与插件/依赖版本不匹配,不是 BO/DTO 缺 getter。
|
|
43
|
+
|
|
44
|
+
**默认验证单位:最小可编译 reactor slice**。
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# 编译受影响链路,不跑测试
|
|
48
|
+
mvn -pl <entry-module> -am -DskipTests compile
|
|
49
|
+
|
|
50
|
+
# 跑相关测试
|
|
51
|
+
mvn -pl <entry-module> -am -Dtest=<TestClass> test
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`<entry-module>` 优先选真实业务入口模块,而不是机械选"当前文件所在模块":
|
|
55
|
+
|
|
56
|
+
| 改动位置 | 推荐 entry module |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| domain 纯规则 / 值对象 | domain 可独立编译时选 domain;否则选使用它的 app |
|
|
59
|
+
| parser / assembler / DTO / BO 转换 | app / application 模块 |
|
|
60
|
+
| mapper / repository / infrastructure | app / boot 模块(带上 infra 依赖) |
|
|
61
|
+
| 公共 DTO / interface / client | 主要 consumer 模块;task 完成前再全量 compile |
|
|
62
|
+
|
|
63
|
+
**不要为了让裸 `javac` 通过而给 BO/DTO 补 getter/setter**:
|
|
64
|
+
|
|
65
|
+
- `javac --proc:none` 不跑 Lombok / MapStruct / QueryDSL 等 annotation processor,只能当临时语法探针。
|
|
66
|
+
- 最终验证必须以 Maven reactor / CI 等价命令为准。
|
|
67
|
+
- 如果裸 `javac` 报 getter/setter 不存在,但 Maven reactor 能过,不能因此修改生产模型。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## §2 MyBatis 映射全链路(arb 原版 §2.8 完整对齐,高优先级)
|
|
72
|
+
|
|
73
|
+
**历史踩坑**:新增一个字段需要改 7-8 处,漏任意一处字段在接口里就是 null,而且 happy path 单测都会过。
|
|
74
|
+
|
|
75
|
+
### 2.1 全链路清单(每新增字段必填)
|
|
76
|
+
|
|
77
|
+
| 层级 | 检查内容 | 典型路径 |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| DDL | `ALTER TABLE` 加列 + 正确类型 + COMMENT | `db/migration/V*.sql` |
|
|
80
|
+
| DO | 属性已声明 + 类型匹配 DDL | `*.dao.model.XxxDO` |
|
|
81
|
+
| Domain / BO | 字段已添加到领域对象 | `*.domain.model.Xxx` |
|
|
82
|
+
| Mapper XML - resultMap | `<result column="xxx" property="xxxName" />` | `mapper/XxxMapper.xml` |
|
|
83
|
+
| Mapper XML - Column_List | `<sql id="Base_Column_List">` 包含新列 | 同上 |
|
|
84
|
+
| Mapper XML - insert | `<insert>` 的 column 列表和 `#{xxx}` 都有 | 同上 |
|
|
85
|
+
| Mapper XML - update | `<update>` 按需包含新字段 | 同上 |
|
|
86
|
+
| Convertor | DO ↔ Domain / DTO / VO 转换已处理 | `*.convertor.XxxConvertor` |
|
|
87
|
+
| CO / VO | 对外返回对象已暴露(如需) | `*.controller.vo.XxxVO` |
|
|
88
|
+
| 前端接口文档 | 已同步(`specs/前端接口文档.md` 若存在) | 文档 |
|
|
89
|
+
|
|
90
|
+
**判定**:任一层缺失 → **MUST**。
|
|
91
|
+
|
|
92
|
+
### 2.2 MyBatis 常见陷阱
|
|
93
|
+
|
|
94
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
95
|
+
| --- | --- | --- | --- |
|
|
96
|
+
| `#{}` vs `${}` | `WHERE name = ${name}`(SQL 注入) | `#{name}` 参数化 | **MUST**(安全红线) |
|
|
97
|
+
| `resultMap` 字段遗漏 | DDL 有 10 列,resultMap 映了 8 列 | 每列都映 | **MUST**(查询返回 null) |
|
|
98
|
+
| 驼峰 / 下划线映射 | `mapUnderscoreToCamelCase` 未配 + resultMap 手写半数 | 全局开启 + 显式 resultMap 统一风格 | 返回字段 null → **MUST**;风格不统一 → **SHOULD** |
|
|
99
|
+
| `<where>` / `<if>` 遗漏 `AND` | 拼出 `WHERE AND status = 1` 语法错 | 用 `<where>` 自动 trim 首 `AND` | **MUST** |
|
|
100
|
+
| `<foreach>` 空集合 | 传入空 list → `WHERE id IN ()` 语法错 | Service 层空集合 early return | **MUST** |
|
|
101
|
+
| 懒加载 + 跨线程 | 用 `@Async` / `CompletableFuture` 读懒加载字段 NPE | 显式 fetch 或改 eager | **MUST** |
|
|
102
|
+
| SELECT * | `SELECT * FROM t` | 显式 `Base_Column_List` | **SHOULD**(字段漂移风险) |
|
|
103
|
+
| 无 `<!-- -->` COMMENT | 复杂 SQL 无注释 | 复杂 SQL 必须注释"为什么这么写" | **NIT** |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## §3 DDL-DO 一致性(arb 原版 §2.10 完整对齐)
|
|
108
|
+
|
|
109
|
+
**历史踩坑**:cpcn-bindcard 项目 DDL 定义 `sign_status` 枚举值为 1,2,3,5,但 DO 类注释用了 10,20,30,40 —— 存储错误且极难排查。
|
|
110
|
+
|
|
111
|
+
| 校验维度 | 检查 | 反例 | 判定 |
|
|
112
|
+
| --- | --- | --- | --- |
|
|
113
|
+
| 字段命名 | DDL 下划线 ↔ DO 驼峰 | DDL `user_name`,DO `username`(漏驼峰规则) | **MUST** |
|
|
114
|
+
| 字段类型 | DDL → Java 类型映射 | `BIGINT` → `Integer`(溢出风险) | **MUST** |
|
|
115
|
+
| 枚举值 | DDL COMMENT + DO 常量 / 枚举类 | DDL `1=A,2=B`,DO 注释 `10=A,20=B` | **MUST**(存储错乱) |
|
|
116
|
+
| 长度约束 | DDL VARCHAR(64),代码写 128 限制 | 代码校验与 DDL 不一致 | **MUST**(插入溢出) |
|
|
117
|
+
| NULL 约束 | DDL NOT NULL,DO 字段类型 `Integer`(可空) | `int`(基本类型不可为 null) | **SHOULD** |
|
|
118
|
+
| 字段完整性 | DDL 每列在 DO 都有 | 漏属性 | **MUST** |
|
|
119
|
+
| 索引 ↔ 查询 | 高频查询字段有索引 | 新增字段做查询条件无索引 | **SHOULD** |
|
|
120
|
+
|
|
121
|
+
### Java 类型映射速查
|
|
122
|
+
|
|
123
|
+
| DDL | Java | 备注 |
|
|
124
|
+
| --- | --- | --- |
|
|
125
|
+
| `TINYINT` / `SMALLINT` | `Integer` / `Short` | 不要用 `int` / `short` 基本类型 |
|
|
126
|
+
| `INT` | `Integer` | |
|
|
127
|
+
| `BIGINT` | `Long` | 严禁用 `Integer`(溢出) |
|
|
128
|
+
| `DECIMAL(p,s)` | `BigDecimal` | 严禁用 `Double` / `Float`(精度丢失) |
|
|
129
|
+
| `VARCHAR` / `TEXT` | `String` | |
|
|
130
|
+
| `DATETIME` / `TIMESTAMP` | `LocalDateTime` / `Instant` | 不要用 `java.util.Date`(时区/可变) |
|
|
131
|
+
| `DATE` | `LocalDate` | |
|
|
132
|
+
| `JSON` | `String` + 手动反序列化 / 自定义 TypeHandler | 不要直接 `Map<String, Object>` 暴露 |
|
|
133
|
+
| `TINYINT(1)` / `BIT` | `Boolean` | |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## §4 Spring 事务与并发
|
|
138
|
+
|
|
139
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
140
|
+
| --- | --- | --- | --- |
|
|
141
|
+
| `@Transactional` 位置 | 加在 private / self-invoke 方法(AOP 失效) | 加在 public + 通过 Bean 调用 / 自注入 | AOP 失效 → **MUST** |
|
|
142
|
+
| 事务边界范围 | 长事务包住 HTTP 外调 | 事务只包 DB 写操作,外调在事务外 / 本地消息表 | 长事务占连接 → **MUST** |
|
|
143
|
+
| 异常回滚规则 | 默认只对 RuntimeException 回滚,抛 `IOException` 不回滚 | `@Transactional(rollbackFor = Exception.class)` | 数据不一致 → **MUST** |
|
|
144
|
+
| 传播行为 | 嵌套事务用默认 `REQUIRED` 但期望独立事务 | 明确 `REQUIRES_NEW` / `NESTED` | 语义不匹配 → **MUST** |
|
|
145
|
+
| 并发共享状态 | `@Service` 单例 + 成员变量 `counter` | 用 `AtomicInteger` / `ConcurrentHashMap` / ThreadLocal | 竞态 → **MUST** |
|
|
146
|
+
| 分布式锁 | `synchronized`(单机锁,集群失效) | Redisson / SET NX + lua 释放 | 集群场景 **MUST** |
|
|
147
|
+
| 库存 / 余额扣减 | 先 SELECT 再 UPDATE | 原子 `UPDATE ... WHERE qty > 0` + 检查 `rows` | 资损 → **MUST** |
|
|
148
|
+
| 幂等 | 支付回调无幂等 key | `payNo` 唯一索引 + 查找已处理 | 重复扣款 → **MUST** |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## §5 空安全 / Optional / NPE
|
|
153
|
+
|
|
154
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
155
|
+
| --- | --- | --- | --- |
|
|
156
|
+
| 链式 get 无判空 | `user.getProfile().getAvatar().toLowerCase()` | `Optional.ofNullable(user).map(...)` / 每层 if | NPE 崩溃 → **MUST** |
|
|
157
|
+
| `Boolean` 拆箱 | `if (dto.getEnabled())`(`Boolean` null → NPE) | `Boolean.TRUE.equals(dto.getEnabled())` | **MUST** |
|
|
158
|
+
| `Integer` 拆箱 | `int x = dto.getCount()`(null → NPE) | 判空 / 用 Integer / `Optional.of` | **MUST** |
|
|
159
|
+
| 集合空 | `list.get(0)` 无 isEmpty | `if (CollectionUtils.isEmpty(list)) return` | **MUST** |
|
|
160
|
+
| Map get | `map.get(key).length()` | null 检查或 `getOrDefault` | **MUST** |
|
|
161
|
+
| Optional 滥用 | `Optional.of(nullable)`(内部 NPE) | `Optional.ofNullable(nullable)` | **MUST** |
|
|
162
|
+
| Optional 返回集合 | 返回 `Optional<List<T>>` | 返回空 List 而不是 Optional | **SHOULD** |
|
|
163
|
+
| `@NonNull` / `@Nullable` | 没有契约注解 | 参数/返回值用 JSR-305 注解或 lombok 的 `@NonNull` | **NIT**(大项目 **SHOULD**) |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## §6 日志与脱敏
|
|
168
|
+
|
|
169
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
170
|
+
| --- | --- | --- | --- |
|
|
171
|
+
| 敏感信息 | `log.info("user={}", user)`(打印手机号/身份证/银行卡) | 脱敏工具类 / 结构化 logger 自动脱敏 | **MUST**(合规红线) |
|
|
172
|
+
| 日志占位符 | `log.info("user " + user)` 字符串拼接 | `log.info("user {}", user)` SLF4J 占位符 | NIT(对性能)+ **SHOULD**(异常时看不到参数) |
|
|
173
|
+
| 异常吞掉 | `catch (Exception e) { log.error("err"); }`(无 stack) | `log.error("err", e)` 带堆栈 | **MUST** |
|
|
174
|
+
| 日志级别 | 循环里 `log.info`(生产泛滥) | `log.debug` + 采样 | **SHOULD** |
|
|
175
|
+
| MDC / TraceID | 无链路追踪 | `MDC.put("traceId", ...)` + filter 清理 | **SHOULD** |
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## §7 异常处理
|
|
180
|
+
|
|
181
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
182
|
+
| --- | --- | --- | --- |
|
|
183
|
+
| 吞异常 | `catch (Exception e) {}` | 至少 log.error + rethrow / wrap | **MUST** |
|
|
184
|
+
| 捕获过宽 | 一 catch 抓 `Exception` | 按具体类型分别处理 | **SHOULD** |
|
|
185
|
+
| 自定义业务异常 | 直接抛 `RuntimeException` | 自定义 `BizException(code, msg)` + 全局 handler | **SHOULD** |
|
|
186
|
+
| 全局异常 handler | 无,错误直接透传 | `@RestControllerAdvice` + `@ExceptionHandler` 标准化响应 | 接口无统一格式 **MUST** |
|
|
187
|
+
| 业务错误码 | 用 HTTP 状态码表达业务错误 | HTTP 200 + body 里 `{code, msg}` 业务码 | **SHOULD** |
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## §8 单元测试(JUnit 5 + Mockito)
|
|
192
|
+
|
|
193
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
194
|
+
| --- | --- | --- | --- |
|
|
195
|
+
| 断言有效 | `assertDoesNotThrow(...)` | `assertEquals(expected, actual)` + 验证副作用 | 断言无效 **SHOULD** |
|
|
196
|
+
| Mock 被测类 | Mock 了被测的 Service | 只 Mock 外部依赖(Mapper/FeignClient/RestTemplate) | **MUST** |
|
|
197
|
+
| `@SpyBean` 滥用 | 到处 Spy 破坏测试隔离 | 优先 Mock;必要时 Spy + `doReturn` | **SHOULD** |
|
|
198
|
+
| 测试方法命名 | `test1()` | `test_<method>_<scene>_<expect>` | **NIT** |
|
|
199
|
+
| `@ParameterizedTest` | 重复代码 ×N | 参数化 + `@CsvSource` / `@MethodSource` | 重复 **SHOULD** |
|
|
200
|
+
| `@Disabled` / `@Ignore` | 入库 | CI 前必须清理 或带 `reason` | **MUST** |
|
|
201
|
+
| 事务测试 | `@Transactional` 自动回滚掩盖问题 | `@Rollback(false)` + 手动清理 / `@Sql` 脚本 | **SHOULD** |
|
|
202
|
+
| 时间依赖 | `LocalDateTime.now()` 直接用 | 注入 `Clock` + `Clock.fixed(...)` 固定 | **SHOULD** |
|
|
203
|
+
| Mockito verify | `verify(mock).method()` 无具体参数 | `verify(mock).method(eq(x), argThat(...))` | **SHOULD** |
|
|
204
|
+
| 覆盖率 | JaCoCo < 60%(L2)或 < 75%(L3) | 达标 | 详见 review-checklist.md §6.3 |
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## §9 Spring Boot / MVC 特有
|
|
209
|
+
|
|
210
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
211
|
+
| --- | --- | --- | --- |
|
|
212
|
+
| `@RequestParam` 默认值 | `@RequestParam Integer page`(null 引发 NPE) | `@RequestParam(defaultValue = "1") Integer page` | **MUST** |
|
|
213
|
+
| `@Valid` / `@Validated` | 入参无校验 | DTO 上 JSR-303 注解 + `@Valid` | 入参无校验 **MUST** |
|
|
214
|
+
| `@RequestBody` 空 body | 没校验 body 存在 | `@NotNull` / 全局 handler 处理 HttpMessageNotReadable | **SHOULD** |
|
|
215
|
+
| `@Cacheable` key | 默认 key 含对象 `toString()`(不可控) | 显式 `key = "#userId"` SpEL | **SHOULD** |
|
|
216
|
+
| `@Scheduled` | 单机 cron(集群会重复执行) | 分布式锁 / ShedLock / xxl-job | 集群环境 **MUST** |
|
|
217
|
+
| HTTP 客户端超时 | 无 connect/read timeout | 显式 `RequestConfig` 配 timeout + 熔断 | 生产 **MUST** |
|
|
218
|
+
| Feign fallback | 无 fallback | `@FeignClient(fallback = ...)` + 限流 | 对外强依赖 **MUST** |
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## §10 Spring Security 常见坑(若项目用到)
|
|
223
|
+
|
|
224
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
225
|
+
| --- | --- | --- | --- |
|
|
226
|
+
| CSRF | 关了 CSRF 但是 Cookie session | 保留 CSRF token 或改 JWT + Header | **MUST** |
|
|
227
|
+
| 密码存储 | MD5 / SHA1 | BCrypt / Argon2 | **MUST** |
|
|
228
|
+
| CORS | `allowedOrigins("*")` + 带 credentials | 显式 origin 白名单 | **MUST** |
|
|
229
|
+
| JWT 签名 | HS256 弱 secret / 不验签 | HS256 强 secret(≥ 32B) 或 RS256 | **MUST** |
|
|
230
|
+
| 权限注解 | `@PreAuthorize("hasRole('ADMIN')")` 忘加 | 每个写接口必加 | 写接口无权限 **MUST** |
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 快速映射到 `review-checklist.md`
|
|
235
|
+
|
|
236
|
+
| Cheatsheet 章节 | 对应通用 checklist |
|
|
237
|
+
| --- | --- |
|
|
238
|
+
| §1 分层命名 | §5 Maintainability |
|
|
239
|
+
| §2 MyBatis 映射全链路 | §7.3 新增字段全链路 |
|
|
240
|
+
| §3 DDL-DO 一致性 | §7.2 迁移 + §1.3 接口契约 |
|
|
241
|
+
| §4 Spring 事务与并发 | §1.5 并发 + §1.6 事务幂等 |
|
|
242
|
+
| §5 空安全 | §3 Security(null 安全) |
|
|
243
|
+
| §6 日志与脱敏 | §3 Security + §7.1 可观测性 |
|
|
244
|
+
| §7 异常处理 | §5 Maintainability |
|
|
245
|
+
| §8 单元测试 | §6 Tests |
|
|
246
|
+
| §9-10 Spring / Security | §3 Security + §1.3 接口契约 |
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Language Cheatsheet — Python
|
|
2
|
+
|
|
3
|
+
配套 `review-checklist.md` 使用。通用 checklist 告诉你"要找什么",本 cheatsheet 告诉你"在 Python 项目里长什么样"。
|
|
4
|
+
|
|
5
|
+
适配栈:`FastAPI` / `Django` / `Flask`,`SQLAlchemy` / `Django ORM`,`asyncio` / `celery`,`pytest` + `mypy`。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## §1 Python 经典陷阱(所有项目都要查)
|
|
10
|
+
|
|
11
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| **可变默认参数** | `def f(x=[]):` 全局共享同一 list | `def f(x=None): x = x or []` | **MUST**(跨调用污染状态) |
|
|
14
|
+
| 闭包循环捕获 | `[lambda: i for i in range(3)]` 全返回 2 | `[lambda i=i: i for i in range(3)]` 默认参数捕获 | **MUST** |
|
|
15
|
+
| `is` vs `==` | `if x is 100`(小整数缓存外 False) | `==` 比较值,`is` 只用于 `None` / `True` / `False` | **MUST** |
|
|
16
|
+
| 深浅拷贝 | `copy.copy(nested)` 后改内层仍相互影响 | `copy.deepcopy` / dataclass + `replace` | 业务逻辑错 **MUST** |
|
|
17
|
+
| 字典迭代时修改 | `for k in d: del d[k]` → `RuntimeError` | `for k in list(d):` | **MUST** |
|
|
18
|
+
| 数字精度 | `0.1 + 0.2 != 0.3` | 金额用 `decimal.Decimal` | 涉及金额 **MUST** |
|
|
19
|
+
| bare except | `except:` 吞掉 `KeyboardInterrupt` / `SystemExit` | `except Exception:` 或更具体 | **MUST** |
|
|
20
|
+
| 真值测试 | `if value:` 对 0 / "" / [] 都 falsy(可能不是预期) | `if value is not None:` 明确意图 | 语义错 **MUST**;否则 **SHOULD** |
|
|
21
|
+
| 类变量 vs 实例变量 | class 体写 `items = []` 被所有实例共享 | `__init__` 里 `self.items = []` | 共享状态 **MUST** |
|
|
22
|
+
| `__eq__` 无 `__hash__` | 自定义 `__eq__` 不提供 `__hash__`(实例不可哈希) | 两个一起重写或 `@dataclass(frozen=True)` | **SHOULD** |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## §2 类型注解与静态检查
|
|
27
|
+
|
|
28
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
29
|
+
| --- | --- | --- | --- |
|
|
30
|
+
| 无类型注解 | 公共 API 全 `def f(x, y):` | 所有公共函数签名带类型 | 新代码无注解 **SHOULD**;对外 API 必须 **MUST** |
|
|
31
|
+
| `Any` 泛滥 | `def f(x: Any) -> Any:` 形同无注解 | 精确类型 + `TypeVar` / `Protocol` | **SHOULD** |
|
|
32
|
+
| Optional 隐式 | `def f(x: int = None)`(隐式 Optional,mypy 要求显式) | `def f(x: int | None = None):` | **SHOULD** |
|
|
33
|
+
| 运行时校验 | API 入参信任类型注解 | pydantic / marshmallow / dataclass validators | 对外 API **MUST** |
|
|
34
|
+
| 可变集合类型 | 返回 `list` / `dict` 让调用方改 | 返回 `Sequence` / `Mapping` / frozen | **NIT**(库代码 **SHOULD**) |
|
|
35
|
+
| mypy / pyright | CI 不跑 | CI `strict` 或阶段性严格 | **SHOULD** |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## §3 asyncio / 并发
|
|
40
|
+
|
|
41
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
42
|
+
| --- | --- | --- | --- |
|
|
43
|
+
| async 函数同步调用 | `async def f():` 然后 `f()`(返回 coroutine 不执行) | `await f()` 或 `asyncio.run(f())` | **MUST** |
|
|
44
|
+
| sync 阻塞进 async | `async def h(): time.sleep(10)` 阻塞整个 event loop | `await asyncio.sleep(10)` / `run_in_executor` | **MUST** |
|
|
45
|
+
| blocking IO 进 async | `async def h(): requests.get(...)` | `aiohttp` / `httpx.AsyncClient` | **MUST** |
|
|
46
|
+
| asyncio.create_task 无引用 | `asyncio.create_task(f())` 不保留引用,被 GC | 持有 task 或 `asyncio.gather` | **MUST**(任务突然消失) |
|
|
47
|
+
| gather exception | `asyncio.gather(*tasks)` 其中一个异常取消其他 | `return_exceptions=True` + 手工聚合 | 视意图 **SHOULD** |
|
|
48
|
+
| shared state | 多协程共享 dict 无锁 | `asyncio.Lock` / 单协程 owner | 竞态 **MUST** |
|
|
49
|
+
| mix asyncio + threading | 线程里直接 `asyncio.run`,跨线程 event loop 冲突 | 明确分工 / `run_coroutine_threadsafe` | **MUST** |
|
|
50
|
+
| GIL 与 CPU 密集 | 多 thread 跑 CPU 密集,GIL 限制 | 用 `multiprocessing` / C 扩展 | **SHOULD** |
|
|
51
|
+
| Celery beat 重复 | 多实例跑 beat | 单实例 / redbeat / 数据库锁 | **MUST** |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## §4 ORM(SQLAlchemy / Django ORM)
|
|
56
|
+
|
|
57
|
+
### 4.1 SQLAlchemy
|
|
58
|
+
|
|
59
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
60
|
+
| --- | --- | --- | --- |
|
|
61
|
+
| N+1 | `users = session.query(User).all()`;`for u in users: u.profile.name` | `joinedload(User.profile)` / `selectinload` | 热点 **MUST** |
|
|
62
|
+
| 懒加载出 session | `user = repo.get(1)`;session 关闭后访问 `user.orders` → DetachedInstanceError | eager load 或在 session 内完成访问 | **MUST** |
|
|
63
|
+
| Session 生命周期 | Flask 里全局 Session | `scoped_session` + teardown 或 FastAPI `Depends(get_db)` | **MUST**(连接泄漏) |
|
|
64
|
+
| `flush` vs `commit` | 只 flush 不 commit,事务没提交 | 明确 `session.commit()` / `session.rollback()` | **MUST** |
|
|
65
|
+
| 裸 SQL 拼接 | `session.execute(f"... WHERE id={id}")` | `text("... WHERE id = :id")` + 参数 | **MUST**(SQL 注入) |
|
|
66
|
+
| 批量 insert | 循环 add + commit 每条 | `bulk_insert_mappings` / `bulk_save_objects` | 大量数据 **SHOULD** |
|
|
67
|
+
| 模型变更未迁移 | 改 model 不跑 Alembic | Alembic `revision --autogenerate` | **MUST** |
|
|
68
|
+
|
|
69
|
+
### 4.2 Django ORM
|
|
70
|
+
|
|
71
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
72
|
+
| --- | --- | --- | --- |
|
|
73
|
+
| N+1 | `for u in User.objects.all(): u.profile.name` | `User.objects.select_related('profile')` / `prefetch_related` | **MUST** |
|
|
74
|
+
| `.get()` vs `.filter().first()` | `.get()` 无结果抛异常(忘处理 `DoesNotExist`) | 统一策略 + try/except 或 get_object_or_404 | **MUST** |
|
|
75
|
+
| `.save()` 并发 | 两个请求同时 `obj.counter += 1; obj.save()` | `F('counter') + 1` 原子 | **MUST**(lost update) |
|
|
76
|
+
| `.update()` 绕 signal | 用 `.update()` 批量改,`pre_save` / `post_save` 不触发 | 明确意图,或用 `for obj; obj.save()` | 业务依赖 signal **MUST** |
|
|
77
|
+
| 未设 `Meta.ordering` 分页 | 无序分页 → 重复/漏记录 | 显式 `order_by('pk')` 或 `Meta.ordering` | **MUST** |
|
|
78
|
+
| Migration 冲突 | 手改 migration 历史 | 每次合并前 `makemigrations --check` + `squashmigrations` | **MUST** |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## §5 异常处理
|
|
83
|
+
|
|
84
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
85
|
+
| --- | --- | --- | --- |
|
|
86
|
+
| bare except 吞错 | `except: pass` | `except SpecificError:` + log / re-raise | **MUST** |
|
|
87
|
+
| `except Exception` 过宽 | 一个 except 抓一切 | 按类型分层处理 | **SHOULD** |
|
|
88
|
+
| 丢堆栈 | `raise MyError(str(e))` | `raise MyError("...") from e` 保留 chain | **SHOULD** |
|
|
89
|
+
| 自定义业务异常 | 直接 `raise ValueError` | `class BizError(Exception): code, msg` | **SHOULD** |
|
|
90
|
+
| with 资源管理 | `f = open(...); ...; f.close()` | `with open(...) as f:` | 异常时泄漏 **MUST** |
|
|
91
|
+
| contextlib | 手动 try/finally 重复 | `@contextmanager` / `ExitStack` | **NIT** |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## §6 Web 框架特有
|
|
96
|
+
|
|
97
|
+
### 6.1 FastAPI
|
|
98
|
+
|
|
99
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
100
|
+
| --- | --- | --- | --- |
|
|
101
|
+
| pydantic 校验 | 手写 dict 校验 | `BaseModel` + Field 约束 | **SHOULD** |
|
|
102
|
+
| 依赖注入范围 | Depends 里起 DB session 不关闭 | `yield` + finally 关闭 / `contextmanager` | 连接泄漏 **MUST** |
|
|
103
|
+
| async endpoint 里 sync IO | `async def h(): db.query(...)`(同步 ORM) | 要么全 async(`databases` / SQLA 2.0 async),要么用 `def` endpoint | **MUST** |
|
|
104
|
+
| background task | 复杂业务丢进 `BackgroundTasks`(进程重启丢失) | Celery / Arq / 消息队列 | 关键业务 **MUST** |
|
|
105
|
+
| OpenAPI schema | `response_model` 未指定 | 显式 `response_model` 防止字段泄漏 | **MUST**(内部字段泄漏) |
|
|
106
|
+
|
|
107
|
+
### 6.2 Django / DRF
|
|
108
|
+
|
|
109
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
110
|
+
| --- | --- | --- | --- |
|
|
111
|
+
| Serializer 字段 | 用 `fields = '__all__'` | 显式字段列表 | **MUST**(字段泄漏) |
|
|
112
|
+
| 权限校验 | View 无 `permission_classes` | DRF permission + object-level check | **MUST** |
|
|
113
|
+
| CSRF | API 关了 CSRF 但用 session cookie | JWT / token + CSRF | **MUST** |
|
|
114
|
+
| 查询注入 | `Model.objects.filter(**request.GET)` | 白名单 filter | **MUST** |
|
|
115
|
+
| 反序列化 pickle | `pickle.loads(user_input)` | 只接受 JSON | **MUST** |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## §7 日志与可观测性
|
|
120
|
+
|
|
121
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
122
|
+
| --- | --- | --- | --- |
|
|
123
|
+
| print 代替 log | `print("...")` 生产日志 | `logging.getLogger(__name__)` | **SHOULD** |
|
|
124
|
+
| f-string 日志 | `log.info(f"user {user}")`(预格式化,级别过滤也会格式化) | `log.info("user %s", user)` | **NIT** |
|
|
125
|
+
| 敏感信息 | log 里含手机号/token | structlog + 脱敏 processor | **MUST**(合规) |
|
|
126
|
+
| 异常 log | `log.error(e)` 丢堆栈 | `log.exception("...")` / `log.error(..., exc_info=True)` | **MUST** |
|
|
127
|
+
| 结构化日志 | 纯文本 | JSON + traceId | **SHOULD** |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## §8 pytest / 单元测试
|
|
132
|
+
|
|
133
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
134
|
+
| --- | --- | --- | --- |
|
|
135
|
+
| fixture scope | `@fixture` 默认 function,但跨测试共享状态 | 明确 `scope="function"` / `"module"` / `"session"` | 状态污染 **MUST** |
|
|
136
|
+
| `mock.patch` 路径 | `@patch('module.Class')` 打错导入路径 | `@patch('consumer_module.ClassName')`(patch 调用方) | Mock 无效 **MUST** |
|
|
137
|
+
| 测试数据库 | 生产库跑测试 | `pytest-django` / transaction rollback / container DB | **MUST** |
|
|
138
|
+
| 参数化 | 复制粘贴 N 个 test | `@pytest.mark.parametrize(...)` | **SHOULD** |
|
|
139
|
+
| 断言强度 | `assert result`(真值) | `assert result == expected` 精确 | **SHOULD** |
|
|
140
|
+
| skip / xfail | `@pytest.mark.skip` 无 reason,留了半年 | 带 reason + 定期复查 | **SHOULD** |
|
|
141
|
+
| 时间依赖 | `datetime.now()` | `freezegun` / 注入 clock | **SHOULD** |
|
|
142
|
+
| env 依赖 | 测试依赖真 env var | `monkeypatch.setenv(...)` | **SHOULD** |
|
|
143
|
+
| 覆盖率 | 不测 | `pytest-cov` + CI 阈值 | L2 < 60% **SHOULD**;L3 < 75% **MUST** |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## §9 依赖与打包
|
|
148
|
+
|
|
149
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
150
|
+
| --- | --- | --- | --- |
|
|
151
|
+
| requirements 无锁 | `Flask` 不锁版本 | `pip-tools` / `poetry.lock` / `uv.lock` | **MUST**(构建不可重现) |
|
|
152
|
+
| 开发依赖混入 | 生产安装含 pytest | 分组 `requirements-dev.txt` / poetry group | **SHOULD** |
|
|
153
|
+
| 安全漏洞扫描 | 无 | `pip-audit` / `safety` / CI | 高危 CVE **MUST** |
|
|
154
|
+
| `__init__.py` 副作用 | 导入时连 DB / 读文件 | 显式 init 函数 | 测试难 **SHOULD** |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 快速映射到 `review-checklist.md`
|
|
159
|
+
|
|
160
|
+
| Cheatsheet 章节 | 对应通用 checklist |
|
|
161
|
+
| --- | --- |
|
|
162
|
+
| §1 经典陷阱 | §1.4 边界 + §5 Maintainability |
|
|
163
|
+
| §2 类型注解 | §5 Maintainability + §1.1 正确性 |
|
|
164
|
+
| §3 asyncio | §1.5 并发 + §4 Performance |
|
|
165
|
+
| §4 ORM | §1.6 事务幂等 + §7.3 全链路 + §4 Performance |
|
|
166
|
+
| §5 异常处理 | §5 Maintainability |
|
|
167
|
+
| §6 Web 框架 | §3 Security + §1.3 接口契约 |
|
|
168
|
+
| §7 日志 | §3 Security + §7.1 可观测性 |
|
|
169
|
+
| §8 pytest | §6 Tests |
|
|
170
|
+
| §9 依赖打包 | §3 Security + §7.2 迁移 |
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Language Cheatsheet — Vue (Vue 3 + TypeScript)
|
|
2
|
+
|
|
3
|
+
配套 `review-checklist.md` 使用。通用 checklist 告诉你"要找什么",本 cheatsheet 告诉你"在 Vue 3 + TS 项目里长什么样"。
|
|
4
|
+
|
|
5
|
+
适配栈:Vue 3 Composition API / `<script setup>` / Pinia / Vue Router 4 / Vite / Vitest + Vue Test Utils / axios 或 `@vueuse/core`。
|
|
6
|
+
|
|
7
|
+
Vue 2(Options API)的旧项目请在 §0 先看过渡建议。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## §0 Vue 2 / Vue 3 过渡(若混合项目)
|
|
12
|
+
|
|
13
|
+
| 检查点 | 判定 |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| 新代码仍写 Options API / mixins | **SHOULD**(mixins 来源不透明,优先 Composition API + composables) |
|
|
16
|
+
| 使用 `Vue.set` / `this.$set` | **SHOULD**(Vue 3 reactivity 已不需要) |
|
|
17
|
+
| 使用 filter 语法 `{{ x \| uppercase }}` | **MUST**(Vue 3 已移除) |
|
|
18
|
+
| 使用 `.sync` 修饰符 | **MUST**(改用 `v-model:xxx`) |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## §1 响应性陷阱(最高频错误源)
|
|
23
|
+
|
|
24
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
25
|
+
| --- | --- | --- | --- |
|
|
26
|
+
| **ref 漏 .value** | ```const count = ref(0); count++``` | ```count.value++``` | **MUST**(不生效且 TS 报错) |
|
|
27
|
+
| **template 里写 .value** | ```{{ count.value }}``` | ```{{ count }}```(模板自动解包) | **MUST** |
|
|
28
|
+
| **reactive 解构丢响应** | ```const { name } = reactive(user); name = 'x'``` 不触发更新 | ```const { name } = toRefs(user)``` | **MUST** |
|
|
29
|
+
| **reactive 整体替换** | ```state = reactive({...newState})```(引用被换,外部持有的旧引用失效) | 用 `Object.assign(state, newState)` 或 `ref({})` + `state.value = newState` | **MUST** |
|
|
30
|
+
| **props 直接改** | ```props.user.name = 'x'``` | emit 事件或 clone | **MUST** |
|
|
31
|
+
| **非响应源包 ref** | ```ref(new Date())``` 每次渲染重建 | `computed` 或 `shallowRef` | **SHOULD** |
|
|
32
|
+
| **ref 嵌套解包** | Vue 3.3- 模板里嵌套 ref 在数组 / Map 中不解包 | 顶层使用,内层避免 ref | **SHOULD** |
|
|
33
|
+
| **deep watch 性能** | ```watch(bigObject, ..., { deep: true })``` 大对象 | 精确 watch 具体 key / 用 getter | 大对象 **SHOULD** |
|
|
34
|
+
| **watchEffect 依赖不明** | 副作用依赖隐式,重构丢依赖 | 优先 `watch(source, cb)` 显式 | **SHOULD** |
|
|
35
|
+
| **computed 有副作用** | ```computed(() => { save(); return x })``` | computed 只算不做副作用;副作用用 watch | **MUST** |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## §2 Composition API / composables
|
|
40
|
+
|
|
41
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
42
|
+
| --- | --- | --- | --- |
|
|
43
|
+
| 生命周期漏清理 | `onMounted` 加了 `addEventListener` / `setInterval` / watcher,没 `onUnmounted` 清理 | 成对出现或用 `useEventListener` / `useIntervalFn` | **MUST**(内存泄漏) |
|
|
44
|
+
| composable 命名 | `function myState()` / `function State()` | `useXxx` 前缀 | **NIT** |
|
|
45
|
+
| composable 返回值 | 返回裸对象导致解构丢响应 | 返回 `{ state: ref, ... }` + 明确 readonly | **SHOULD** |
|
|
46
|
+
| composable 调用位置 | 在 if / 异步回调 / 事件 handler 里调 `useXxx` | 只在 setup / 另一个 composable 顶层 | **MUST**(上下文绑定失败) |
|
|
47
|
+
| setup 里 async 顶层 await | `<script setup async>` 但用在非 Suspense 组件 | 用 `onMounted` + 内部 async | **MUST** |
|
|
48
|
+
| `inject` 无默认值 | `const x = inject('key')` provider 未注册时 undefined | `inject('key', defaultValue)` + Symbol key | **SHOULD** |
|
|
49
|
+
| provide / inject 类型 | 用 string key,跨文件无类型 | `InjectionKey<T>` Symbol | **SHOULD** |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## §3 模板 / SFC
|
|
54
|
+
|
|
55
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
56
|
+
| --- | --- | --- | --- |
|
|
57
|
+
| **v-for 无 key** / key 用 index | ```<li v-for="item in list">```或 ```:key="idx"``` | `:key="item.id"` 稳定唯一 id | 列表动画/状态错乱 **MUST** |
|
|
58
|
+
| **v-for + v-if 同元素** | ```<li v-for="x" v-if="x.show">```(Vue 3 v-if 优先,访问 x 报错) | 外层 wrap `<template v-if>` 或 computed 过滤 | **MUST** |
|
|
59
|
+
| **v-html XSS** | ```<div v-html="userInput">``` | 严禁渲染用户输入;必要时 DOMPurify | **MUST**(XSS 高危) |
|
|
60
|
+
| **href=javascript:** | ```:href="user.link"``` 未校验协议 | 过滤 `^(https?|mailto):` | **MUST** |
|
|
61
|
+
| 大量 inline handler | `@click="() => { ... 10 行 ... }"` | 拆 method / composable | **SHOULD** |
|
|
62
|
+
| computed 里访问 DOM | `computed(() => document.querySelector(...))` | 用 template ref | **MUST** |
|
|
63
|
+
| Ref DOM 未判空 | `ref.value.focus()` 组件卸载后 | `onMounted` 后访问 + 判空 | **MUST** |
|
|
64
|
+
| slot 默认内容污染 | slot 里渲染 parent 数据但没 scoped slot | scoped slot `<slot :x="x" />` | **SHOULD** |
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## §4 组件通信
|
|
69
|
+
|
|
70
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
71
|
+
| --- | --- | --- | --- |
|
|
72
|
+
| props 类型 | 无类型 / `type: Object` 无泛型 | `defineProps<{ user: User }>()` | **SHOULD** |
|
|
73
|
+
| props 默认值复杂对象 | `default: []` / `default: {}` 共享引用 | `default: () => []` / `withDefaults` | **MUST**(跨实例污染) |
|
|
74
|
+
| emits 未声明 | `<script setup>` 里 `emit('click')` 无 `defineEmits` | `const emit = defineEmits<{...}>()` | **SHOULD** |
|
|
75
|
+
| v-model 多个未命名 | 旧 `.sync` 或 single v-model 强塞多字段 | `v-model:name` / `v-model:age` | **SHOULD** |
|
|
76
|
+
| $attrs 透传失控 | 用 `inheritAttrs: false` 但忘了手动 `v-bind="$attrs"` | 明确意图:自动继承或显式透传 | **SHOULD** |
|
|
77
|
+
| Event bus(Vue 2 遗留) | 全局 event bus 到处订阅 | Pinia / provide-inject / mitt 局部 | **SHOULD** |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## §5 Pinia / 状态管理
|
|
82
|
+
|
|
83
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
84
|
+
| --- | --- | --- | --- |
|
|
85
|
+
| 直接 mutate state 外部 | ```store.count++``` 在组件里 | action 里 mutate,组件调 action | **SHOULD** |
|
|
86
|
+
| 解构 store 丢响应 | `const { count } = store` | `const { count } = storeToRefs(store)`;方法直接解构 | **MUST** |
|
|
87
|
+
| SSR 跨请求污染 | 全局 singleton store 被请求共享 | 每请求 `createPinia()` + `provide` | SSR 场景 **MUST** |
|
|
88
|
+
| action 内部没 try/catch | 异步 action 失败不处理 | try/catch + 错误状态上报 | **SHOULD** |
|
|
89
|
+
| getters 复杂副作用 | getter 里发请求 | getter 纯函数;请求放 action | **MUST** |
|
|
90
|
+
| 跨 store 循环依赖 | Store A import Store B,B import A | 抽 composable 中转 | **MUST** |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## §6 路由 / 鉴权
|
|
95
|
+
|
|
96
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| 路由参数未 decode | `route.query.q` 直接当 URL | `decodeURIComponent` / 用 router 的类型化参数 | **SHOULD** |
|
|
99
|
+
| 鉴权只在前端 | 仅路由 `beforeEach` 判登录,接口无校验 | 前后端双重(后端为准) | **MUST**(安全红线) |
|
|
100
|
+
| 路由守卫 next 漏写 | `beforeEach((to, from, next) => { if (auth) next() })`(else 永久 pending) | 所有分支都 next | **MUST** |
|
|
101
|
+
| 懒加载 chunk name | `() => import('./X.vue')` 无 `webpackChunkName` | `/* webpackChunkName: "user-X" */` | **NIT** |
|
|
102
|
+
| 路由参数变化不重新获取 | `onMounted` 里 fetch,依赖 `route.params` 变化不生效 | `watch(() => route.params.id, fetch, { immediate: true })` | **MUST** |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## §7 网络 / 数据获取
|
|
107
|
+
|
|
108
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
109
|
+
| --- | --- | --- | --- |
|
|
110
|
+
| fetch 无 loading / error | `const data = ref(); onMounted(async () => data.value = await api())` | 封装 `useAsyncData` / `useRequest` 带 loading/error/retry | UX 降级 **SHOULD** |
|
|
111
|
+
| race condition | 快速切 tab 时上次请求晚返回覆盖新数据 | AbortController / key 校验 / 取 last | **MUST** |
|
|
112
|
+
| 接口类型 | 手写接口 any | OpenAPI / swagger-codegen 生成类型 | **SHOULD** |
|
|
113
|
+
| 错误统一 | 每处 try/catch 自己处理 | axios 拦截器 + 全局 toast | **SHOULD** |
|
|
114
|
+
| token 存储 | `localStorage` 存长期 token | httpOnly cookie / 短期 access + refresh | XSS 风险 **MUST** |
|
|
115
|
+
| CORS | 开发环境 proxy 生产也信任 | 生产通过后端 gateway | **MUST** |
|
|
116
|
+
| 重试 / 幂等 | POST 失败自动重试 | 只对幂等 GET 重试,POST 加 idempotency key | **MUST** |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## §8 性能
|
|
121
|
+
|
|
122
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
123
|
+
| --- | --- | --- | --- |
|
|
124
|
+
| v-for 大列表 | 1000 项直接渲染 | 虚拟列表(`vue-virtual-scroller` / `vue3-virtual-scroll-list`) | 卡顿可见 **MUST** |
|
|
125
|
+
| computed 滥用 | 纯计算写成 method,每次 re-render 执行 | computed 缓存 | **SHOULD** |
|
|
126
|
+
| v-memo | 大列表子项依赖少 | `v-memo="[item.id, item.updatedAt]"` 跳过相等更新 | 热点 **SHOULD** |
|
|
127
|
+
| keep-alive | 导航频繁的 tab 每次销毁重建 | `<KeepAlive>` + `include` | **SHOULD** |
|
|
128
|
+
| Image / font | 无 lazy / 无 preload | `loading="lazy"` + `<link rel="preload">` 关键字体 | **SHOULD** |
|
|
129
|
+
| Bundle 体积 | 全量引入 lodash / moment | 按需引入 + `day.js` 替代 moment | **SHOULD** |
|
|
130
|
+
| 未压缩资源 | 无 gzip / brotli | Vite `build.reportCompressedSize` + 服务端开启 | **SHOULD** |
|
|
131
|
+
| 首屏 SSR / 骨架 | 长白屏 | SSR(Nuxt) / 骨架屏 / code split | 对 SEO/LCP 关键 **MUST** |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## §9 可访问性(a11y) / 国际化
|
|
136
|
+
|
|
137
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
138
|
+
| --- | --- | --- | --- |
|
|
139
|
+
| button / a 语义 | `<div @click>` 当按钮 | 用 `<button>` / `<a>` + aria | **SHOULD** |
|
|
140
|
+
| 表单 label | input 无关联 label | `<label for="x">` 或 `aria-label` | **SHOULD** |
|
|
141
|
+
| 键盘操作 | modal 只能点击关不能 Esc | `keydown.esc` + focus trap | **SHOULD** |
|
|
142
|
+
| 颜色对比 | 文本对比度 < 4.5 | WCAG AA 达标 | **NIT** |
|
|
143
|
+
| i18n 硬编码 | 模板里直接写中文 | `i18n` + key 管理 | 国际化项目 **MUST** |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## §10 TypeScript 质量(Vue 项目)
|
|
148
|
+
|
|
149
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
150
|
+
| --- | --- | --- | --- |
|
|
151
|
+
| `any` 泛滥 | props / emit 标 any | 精确类型 | **SHOULD** |
|
|
152
|
+
| `as` 强转 | `user as User` 绕过检查 | 类型守卫 / zod 校验 | **SHOULD** |
|
|
153
|
+
| non-null `!` | `el.value!.focus()` | 判空 | NPE 风险 **MUST** |
|
|
154
|
+
| strict 关闭 | `strict: false` | `"strict": true` + `"noUncheckedIndexedAccess": true` | **SHOULD** |
|
|
155
|
+
| vue-tsc 未跑 | 只 tsc | CI `vue-tsc --noEmit`(模板类型检查) | **SHOULD** |
|
|
156
|
+
| 全局类型污染 | 全局 declare 多 | `env.d.ts` 限定 | **NIT** |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## §11 测试(Vitest + Vue Test Utils / Testing Library)
|
|
161
|
+
|
|
162
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
163
|
+
| --- | --- | --- | --- |
|
|
164
|
+
| 测试实现而非行为 | `wrapper.vm.internalState`(白盒) | 通过 DOM / aria 查询(Testing Library 理念) | **SHOULD** |
|
|
165
|
+
| shallow 滥用 | 业务组件也 shallow(子组件逻辑未测) | 必要时 mount,外部依赖 stub | **SHOULD** |
|
|
166
|
+
| async 未 flushPromises | `await wrapper.find('button').trigger('click')` 后立刻断言 | `await nextTick()` / `await flushPromises()` | 误报 **MUST** |
|
|
167
|
+
| Pinia 测试 | 真 store 串污 | `createTestingPinia()` + `initialState` | **MUST** |
|
|
168
|
+
| 路由 Mock | 用真 router 跑测试 | `createRouter({ history: createMemoryHistory() })` | **SHOULD** |
|
|
169
|
+
| 覆盖率 | 无 | `vitest --coverage` + CI 阈值 | L2 < 60% **SHOULD**;L3 < 75% **MUST** |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## §12 构建 / 部署(Vite)
|
|
174
|
+
|
|
175
|
+
| 检查点 | 反例 | 正例 | 判定 |
|
|
176
|
+
| --- | --- | --- | --- |
|
|
177
|
+
| 环境变量泄漏 | `VITE_SECRET=xxx`(所有 VITE_ 前缀会 bundle 到前端) | 敏感信息走后端代理,不用 VITE_ 前缀 | **MUST** |
|
|
178
|
+
| `.env` 提交 | `.env` 入库 | `.env.example` 入库,`.env` gitignore | **MUST** |
|
|
179
|
+
| 源码暴露 | 生产带 sourcemap 公开 | 只上传到监控平台,不公网暴露 | **SHOULD** |
|
|
180
|
+
| CSP 缺失 | 无 Content-Security-Policy | 后端 / CDN 配 CSP | XSS 防护 **SHOULD** |
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 快速映射到 `review-checklist.md`
|
|
185
|
+
|
|
186
|
+
| Cheatsheet 章节 | 对应通用 checklist |
|
|
187
|
+
| --- | --- |
|
|
188
|
+
| §1 响应性 | §1.1 正确性 + §1.4 边界 |
|
|
189
|
+
| §2 Composition / composables | §5 Maintainability |
|
|
190
|
+
| §3 模板 / SFC | §3 Security(v-html) + §5 Maintainability |
|
|
191
|
+
| §4 组件通信 | §1.3 接口契约(props/emit) |
|
|
192
|
+
| §5 Pinia | §1.5 并发(SSR 污染) + §5 Maintainability |
|
|
193
|
+
| §6 路由 / 鉴权 | §3 Security |
|
|
194
|
+
| §7 网络 | §1.3 接口契约 + §3 Security + §4 Performance |
|
|
195
|
+
| §8 性能 | §4 Performance |
|
|
196
|
+
| §9 a11y / i18n | §5 Maintainability |
|
|
197
|
+
| §10 TypeScript | §5 Maintainability |
|
|
198
|
+
| §11 测试 | §6 Tests |
|
|
199
|
+
| §12 构建 | §3 Security + §7 Observability |
|