@deepstorm/cli 0.3.0 → 0.3.2

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.
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: reef-review-security
3
+ description: 对安全敏感变更执行专项安全审查,覆盖多租户隔离、认证授权、注入防护、敏感数据保护
4
+ tools: Bash(git:*), Read
5
+ permissionMode: plan
6
+ model: sonnet
7
+ color: red
8
+ ---
9
+
10
+ 你是一名安全代码审查员,负责审查变更中的安全风险,覆盖多租户隔离、认证授权、注入防护、敏感数据保护等维度。
11
+
12
+ ## Review Checklist
13
+
14
+ 按优先级从高到低逐项检查。
15
+
16
+ ### P0 — 多租户/数据隔离(数据安全事故)
17
+ - 多租户数据隔离是否被绕过(如缺少 `tenant_id` 过滤条件)
18
+ - 硬编码租户 ID 或跨租户数据泄露路径
19
+ - 操作日志是否完整记录租户上下文
20
+
21
+ ### P1 — 认证与会话
22
+ - 认证 bypass(未受保护的路由、缺少 auth 拦截器/中间件)
23
+ - 会话管理问题(Token 未设置过期、刷新 Token 未做轮换)
24
+ - 密码存储:是否为明文或弱哈希(必须 bcrypt / argon2)
25
+ - OAuth/OIDC 流程中的 CSRF / redirect_uri 未校验
26
+
27
+ ### P2 — 授权与越权
28
+ - IDOR(通过参数篡改访问他人数据)
29
+ - 垂直越权(普通用户访问管理员接口)
30
+ - 水平越权(用户 A 访问用户 B 的数据)
31
+ - 接口缺少 `@PreAuthorize` 或等效权限校验
32
+
33
+ ### P3 — 输入验证与注入
34
+ - 原始 SQL 拼接(必须使用 ORM 参数化查询)
35
+ - 命令注入(`Runtime.exec()` / `subprocess` / `os.system`)
36
+ - NoSQL 注入(MongoDB `$where`)
37
+ - XSS:用户输入未经转义直接渲染(`innerHTML` / `v-html` / dangerouslySetInnerHTML)
38
+ - SSRF:用户可控 URL 被服务端直接请求
39
+ - 文件上传:路径穿越、类型校验缺失
40
+ - 序列化漏洞(Java 反序列化 / pickle.loads)
41
+
42
+ ### P4 — 敏感数据保护
43
+ - 密钥/证书/Token 硬编码(环境变量替代)
44
+ - 日志输出中泄露 PII / 密钥 / Token
45
+ - 接口响应中返回了敏感字段(密码 hash、内部 IP、数据库连接串)
46
+ - 传输层:内部 API 未启用 TLS(mTLS)
47
+ - 前端存储:Token / 密钥存储在 localStorage 而非 httpOnly cookie
48
+
49
+ ### P5 — 依赖与配置安全
50
+ - 引入已知 CVE 的依赖版本
51
+ - CORS 配置过于宽泛(`Access-Control-Allow-Origin: *` + 含认证凭证)
52
+ - CSP / HSTS / X-Frame-Options 等安全头缺失
53
+ - 调试接口 / Swagger 暴露到生产环境
54
+ - Docker 容器以 root 用户运行
55
+
56
+ ### 🔴 禁止(Block)— 新增维度
57
+ - CLAUDE.md 明确规定的安全红线被变更违反(如"禁止手动拼 WHERE tenant_id = ?")
58
+ - 变更触及了 `// SECURITY:` / `// @audit` 标注的安全敏感区域但未做安全处理
59
+
60
+ ### 🟡 必须(Request Changes)
61
+ - 变更区域在 git 历史中曾因安全漏洞修复过(有 `security`/`CVE`/`vuln`/`fix` 标记的 commit),当前变更可能回归
62
+ - 代码注释中有 `// FIXME` / `// HACK` 安全标注但变更未处理
63
+
64
+ ## Workflow
65
+
66
+ 1. 阅读 prompt 中提供的 CLAUDE.md → 提取安全相关规范条款
67
+ 2. 阅读 prompt 中提供的代码注释标注上下文 → 查找变更附近的安全相关注释(`// SECURITY:` / `// @audit` / `// WARNING:`)
68
+ 3. 获取安全敏感文件的 diff:`git diff "<fork_point>"..HEAD --name-only`
69
+ 4. 阅读关键行 + git history 安全追踪:
70
+ - `git log --oneline -20 -- <file> | grep -i 'security\|CVE\|vuln\|fix\|audit\|CVE'` 查看安全相关历史
71
+ - 标记曾因安全原因修改过的区域
72
+ 5. 逐项通过 Checklist(P0 → P1 → P2 → P3 → P4 → P5 → 🔴 → 🟡)
73
+ 6. 额外维度:检查 CLAUDE.md 中明确禁止的安全模式是否在当前变更中出现
74
+ 7. 输出结构化报告(含证据链)
75
+
76
+ ## Output Format
77
+
78
+ 仅输出以下格式的审查报告(每个 issue 后附加证据来源):
79
+
80
+ ## 安全审查报告
81
+
82
+ ### P0 — 数据隔离(数据安全事故)
83
+ 1. **[文件:行号]** 问题描述 -> 修复建议
84
+ **证据**:🧾 CLAUDE.md → `CLAUDE.md`#L行号 "规范条款原文"
85
+
86
+ ### P1 — 认证与会话
87
+ 1. **[文件:行号]** 问题描述 -> 修复建议
88
+ **证据**:📜 git log → `commit_hash`: 该区域曾因安全问题修复过
89
+
90
+ ### P2 — 授权与越权
91
+ 1. **[文件:行号]** 问题描述 -> 修复建议
92
+ **证据**:📝 `// SECURITY:` 注释原文 at `文件:行号`
93
+
94
+ ### 🔴 禁止(Block)
95
+ 1. **[文件:行号]** 问题描述 -> 修复建议
96
+ **证据**:🧾 CLAUDE.md → `CLAUDE.md`#L行号 "规范条款原文"
97
+
98
+ ### 🟡 必须(Request Changes)
99
+ 1. **[文件:行号]** 问题描述 -> 修复建议
100
+
101
+ **证据类型符号**:
102
+ - 🧾 CLAUDE.md 规范条款
103
+ - 📜 git log 安全修复历史上下文
104
+ - 📝 代码注释(SECURITY / @audit / WARNING)
105
+
106
+ 评分:P0 存在(必须修复)| 存在 🔴/🟡(Request Changes)| 全通过(Approve)
package/dist/cli.js CHANGED
@@ -7285,6 +7285,14 @@ function copyFragmentsForSkill(skillId, srcDir, config, registry2, targetDir) {
7285
7285
  );
7286
7286
  }
7287
7287
  }
7288
+ const allFiles = fs10.readdirSync(srcPath);
7289
+ for (const file of allFiles) {
7290
+ if (file === ".DS_Store") continue;
7291
+ if (file === "quick-reference.md") continue;
7292
+ if (file === "examples") continue;
7293
+ if (!file.endsWith(".md")) continue;
7294
+ fs10.cpSync(path6.join(srcPath, file), path6.join(targetDir, file), { force: true });
7295
+ }
7288
7296
  }
7289
7297
  }
7290
7298
  function copyReferencesForSkill(srcDir, targetDir) {
@@ -12,7 +12,7 @@ deepstorm:
12
12
 
13
13
  ## 工作流
14
14
 
15
- ### Step 1: 检测变更范围
15
+ ### Step 1: 检测变更范围 + Eligibility 预检
16
16
 
17
17
  ```bash
18
18
  # 获取 fork-point(检测当前分支基于哪个分支创建)
@@ -28,6 +28,14 @@ BACKEND_FILES=$(git diff "$FORK_POINT"..HEAD --name-only -- {{reef.backend.langu
28
28
  FRONTEND_FILES=$(git diff "$FORK_POINT"..HEAD --name-only -- {{reef.frontend.framework.sourcePath}})
29
29
  INFRA_FILES=$(git diff "$FORK_POINT"..HEAD --name-only | grep -v '^{{reef.backend.language.sourcePath}}' | grep -v '^{{reef.frontend.framework.sourcePath}}')
30
30
  SECURITY_FILES=$(git diff "$FORK_POINT"..HEAD --name-only -- {{reef.backend.language.sourcePath}} {{reef.frontend.framework.sourcePath}} | grep -iE 'auth|tenant|security|oauth|token|password|permission' || true)
31
+
32
+ # Eligibility 预检:明显不需要审查的变更跳过派发
33
+ LOCK_ONLY=$(echo "$FILE_LIST" | grep -cE 'package-lock\.json|yarn\.lock|pnpm-lock\.yaml' || true)
34
+ DOCS_ONLY=$(echo "$FILE_LIST" | grep -vE 'package-lock\.json|yarn\.lock|pnpm-lock\.yaml' | grep -cE '\.md$|\.txt$|^docs/' || true)
35
+ if [ "$LOCK_ONLY" -gt 0 ] && [ "$(echo "$FILE_LIST" | wc -l | tr -d ' ')" -eq "$LOCK_ONLY" ]; then
36
+ echo "ELIGIBLE=false"
37
+ echo "REASON=仅 lock 文件变更,跳过代码审查"
38
+ fi
31
39
  ```
32
40
 
33
41
  判断哪些类别有变更:
@@ -40,7 +48,7 @@ SECURITY_FILES=$(git diff "$FORK_POINT"..HEAD --name-only -- {{reef.backend.lang
40
48
  | 安全敏感 | `SECURITY_FILES` 非空 |
41
49
  | 无匹配 | 全部为空 |
42
50
 
43
- 据此决定派发哪些 agent
51
+ 据此决定派发哪些 agent(如果 ELIGIBLE=false 则不派发):
44
52
 
45
53
  | 有变更的类别 | 派发 agent |
46
54
  |-------------|-----------|
@@ -56,24 +64,71 @@ SECURITY_FILES=$(git diff "$FORK_POINT"..HEAD --name-only -- {{reef.backend.lang
56
64
 
57
65
  > 安全敏感变更判定:SECURITY_FILES 非空 或 变更涉及权限/认证逻辑(由调用的 fork-point 范围决定)。
58
66
 
59
- ### Step 2: 派发 Sub‑Agent
60
-
61
- 每个 agent 按各自类别的文件清单构造 prompt。后端 agent 的清单含后端源文件,前端和 infra agent 只含自己的文件。
67
+ ### Step 2: 收集上下文 + 派发 Sub‑Agent
62
68
 
63
- | Agent | prompt 中的文件清单 |
64
- |-------|-------------------|
65
- | `backend-code-audit` | 后端文件 |
66
- | `frontend-code-audit` | 前端文件 |
67
- | `infra-code-audit` | 基础配置文件 |
68
- | `security-code-audit` | 安全敏感文件 + 所有变更文件中有安全风险的 diff |
69
+ 在派发前,收集 CLAUDE.md git 上下文,透传给每个 agent:
69
70
 
71
+ ```bash
72
+ # 收集根目录 CLAUDE.md 和变更文件夹下的 CLAUDE.md
73
+ CLAUDE_MD_FILES=$(echo "$FILE_LIST" | xargs -I{} dirname {} | sort -u | xargs -I{} sh -c '
74
+ if [ -f "{}/CLAUDE.md" ]; then echo "{}/CLAUDE.md"; fi
75
+ ' 2>/dev/null)
76
+ [ -f "CLAUDE.md" ] && CLAUDE_MD_FILES="CLAUDE.md $CLAUDE_MD_FILES"
77
+
78
+ # 收集 git blame 摘要(取前 5 个变更文件的 git log 历史)
79
+ GIT_HISTORY_SUMMARY=$(echo "$BACKEND_FILES $FRONTEND_FILES" | tr ' ' '\n' | sort -u | grep -v '^$' | head -5 | while IFS= read -r f; do
80
+ echo "--- $f ---"
81
+ git log --oneline -10 -- "$f" 2>/dev/null | head -5
82
+ done)
83
+
84
+ # 收集代码注释上下文(FIXME / HACK / WARNING 标注)
85
+ COMMENT_CONTEXT=$(echo "$BACKEND_FILES $FRONTEND_FILES" | tr ' ' '\n' | sort -u | grep -v '^$' | head -10 | while IFS= read -r f; do
86
+ if [ -f "$f" ]; then
87
+ matches=$(git diff "$FORK_POINT"..HEAD -- "$f" | grep -E '^\+' | grep -iE 'FIXME|HACK|WARNING|SECURITY|@audit|TODO' || true)
88
+ [ -n "$matches" ] && echo "--- $f ---" && echo "$matches"
89
+ fi
90
+ done)
70
91
  ```
92
+
93
+ 每个 agent 的 prompt 构造为标准前缀 + 上下文数据 + false positive 规则:
94
+
95
+ ```markdown
96
+ ## 变更上下文
97
+
71
98
  Fork point: {FORK_POINT}
72
99
  变更文件数: {count}
73
100
  变更文件清单:
74
101
  {file_list}
102
+
103
+ ## 相关规范文件(CLAUDE.md)
104
+
105
+ {CLAUDE_MD_FILES_content}
106
+
107
+ ## Git 历史上下文
108
+
109
+ {GIT_HISTORY_SUMMARY}
110
+
111
+ ## 代码注释标注
112
+
113
+ {COMMENT_CONTEXT}
114
+
115
+ ## 不算 issues 的情况(不要误报)
116
+
117
+ - 已经有 lint/typecheck/CI 保障的问题(import 错误、类型错误、格式问题)
118
+ - 新增功能的测试覆盖率不足(非本变更范围)
119
+ - 变更前的已有问题(pre-existing issue),除非变更使之更严重
120
+ - NPM/Gradle 版本更新中的上游 breaking change
121
+ - 与模块内已有实现一致的新增代码(一致性值得保留,除非原实现就有 bug)
122
+ - 纯格式/空白/注释变更
75
123
  ```
76
124
 
125
+ | Agent | prompt 中的文件清单 |
126
+ |-------|-------------------|
127
+ | `backend-code-audit` | 后端文件 |
128
+ | `frontend-code-audit` | 前端文件 |
129
+ | `infra-code-audit` | 基础配置文件 |
130
+ | `security-code-audit` | 安全敏感文件 |
131
+
77
132
  Agent 的 system prompt(定义在 `.claude/agents/` 目录中)包含完整的 Checklist + Rules + 输出格式。
78
133
 
79
134
  | Agent | 定义文件 |
@@ -85,10 +140,15 @@ Agent 的 system prompt(定义在 `.claude/agents/` 目录中)包含完整
85
140
 
86
141
  多 agent 场景全部使用 `run_in_background: true` 并行执行。每个 agent 设置超时 300 秒(5 分钟),超时未返回则标记为超时,继续等待其他 agent。
87
142
 
88
- ### Step 3: 汇总报告
143
+ ### Step 3: 汇总报告(含证据链评分)
89
144
 
90
145
  1. 等待所有已派发的 agent 返回(收到全部 task-notification 后才汇总)。任一 agent 超时 300 秒未返回则标记为超时,继续等待其他 agent
91
- 2. 分章节输出各 agent 的审查报告:
146
+ 2. 聚合评分规则:
147
+ - 若 Block 项附有证据链(🧾 `.md` / 📜 `git log` / 📝 `// comment` / 📚 `context7` / 🛠 `style-*`),保留原评级
148
+ - 若 Block 项**无**证据链,降级为 Request Changes
149
+ - 若 Request Changes 项无证据链,降级为 Suggestion
150
+ - 确保报告不膨胀,每个 issue 一句话 + 一个链接
151
+ 3. 分章节输出各 agent 的审查报告:
92
152
 
93
153
  ```
94
154
  ## 后端代码审查报告
@@ -104,4 +164,4 @@ Agent 的 system prompt(定义在 `.claude/agents/` 目录中)包含完整
104
164
  {security agent 输出(仅安全敏感变更时)}
105
165
  ```
106
166
 
107
- 3. 最终结论取最低评分。若某 agent 失败(API Error / 超时等),标注失败原因,忽略其评分。仅有派发过的 agent 输出对应章节。
167
+ 4. 最终结论取最低评分。若某 agent 失败(API Error / 超时等),标注失败原因,忽略其评分。仅有派发过的 agent 输出对应章节。
@@ -122,6 +122,7 @@ deepstorm:
122
122
  | `quick-reference.md` | **编码规范速查**。包含核心规则和约定 |
123
123
  | `{value}.md` | **维度规范**(如 `spring-boot.md`、`hibernate.md`)。按上方链接加载 |
124
124
  | `api-spec.md` | **API 规范**。RESTful 命名、统一响应体、版本策略、OpenAPI |
125
+ | `jackson-polymorphism.md` | **DTO 多态序列化规范**。Jackson `@JsonTypeInfo` + TS Discriminated Union |
125
126
  | `dependency-management.md` | **依赖管理规范**。Version Catalog、版本一致性、CVE |
126
127
  | `exception-handling.md` | **异常处理深度规范**。异常层次、错误码、全局处理 |
127
128
  | `security-redlines.md` | **安全红线**。P0/P1 安全规则及代码示例 |
@@ -0,0 +1,205 @@
1
+ # Jackson 多态序列化与 TypeScript 联合类型
2
+
3
+ > 适用于后端枚举驱动的图表类型分发 → Jackson `@JsonTypeInfo` 多态 + 前端 discriminated union 的模式。
4
+ > 目标:删除后端 `enum` + `chartType` 字段,改用抽象基类 + `@JsonProperty("type")` 实现自动分发。
5
+
6
+ ```mermaid
7
+ flowchart LR
8
+ A["后端 Java<br/>@JsonTypeInfo(property = &quot;type&quot;)<br/>@JsonSubTypes"] -->|"JSON 携带<br/>type 判别值"| B["前端 TypeScript<br/>Discriminated Union"]
9
+ B --> C["@switch(answer.chartView.type)<br/>类型自动窄化"]
10
+ ```
11
+
12
+ ---
13
+
14
+ ## 一、问题场景
15
+
16
+ 后端接口返回 JSON 中包含一个"类型"字段,前端根据该类型渲染不同的组件:
17
+
18
+ ```java
19
+ // ❌ 旧模式:枚举 + switch
20
+ public record MetricAnswer(ChartType chartType, ...) {}
21
+ public enum ChartType { METRIC_CARD, LINE_CHART, BAR_CHART, PIE_CHART }
22
+
23
+ // 前端每次新增类型都需要:
24
+ // 1. 加 enum 值
25
+ // 2. 改 switch
26
+ // 3. 不在同一个地方维护
27
+ ```
28
+
29
+ ## 二、推荐模式:Jackson 多态序列化
30
+
31
+ ### 2.1 后端
32
+
33
+ **步骤 1:定义抽象基类**
34
+
35
+ ```java
36
+ @JsonTypeInfo(use = JsonTypeInfo.Id.SIMPLE_NAME, property = "type")
37
+ @JsonSubTypes({
38
+ @JsonSubTypes.Type(value = MetricCardView.class, name = "MetricCardView"),
39
+ @JsonSubTypes.Type(value = LineChartView.class, name = "LineChartView"),
40
+ @JsonSubTypes.Type(value = BarChartView.class, name = "BarChartView"),
41
+ @JsonSubTypes.Type(value = PieChartView.class, name = "PieChartView"),
42
+ })
43
+ public abstract class ChartView {}
44
+ ```
45
+
46
+ - `property = "type"` — 序列化时自动在 JSON 中写入 `"type": "MetricCardView"` 等 discriminator
47
+ - `name = "..."` — discriminator 的值,应与 TypeScript 端 literal type 保持一致
48
+ - 新增类型只需:新建子类 + 注册到 `@JsonSubTypes`
49
+
50
+ **步骤 2:定义子类**
51
+
52
+ ```java
53
+ @JsonInclude(Include.NON_NULL)
54
+ public class MetricCardView extends ChartView {
55
+ private final String title;
56
+ private final Object value;
57
+ private final String unit;
58
+
59
+ public MetricCardView(String title, Object value, String unit) {
60
+ this.title = title;
61
+ this.value = value;
62
+ this.unit = unit;
63
+ }
64
+ // getters ...
65
+ }
66
+ ```
67
+
68
+ ```java
69
+ public class LineChartView extends ChartView {
70
+ private final String title;
71
+ @JsonProperty("xAxisName") private final String categoryAxisLabel;
72
+ @JsonProperty("yAxisName") private final String valueAxisLabel;
73
+ private final String seriesName;
74
+ private final List<ChartDataPoint> dataPoints;
75
+ // constructor + getters ...
76
+ }
77
+ ```
78
+
79
+ > **💡 Import 说明:**
80
+ > - `@JsonTypeInfo`、`@JsonSubTypes`、`@JsonInclude` 来自 `com.fasterxml.jackson.annotation.*`
81
+ > - `@JsonInclude(Include.NON_NULL)` 需导入 `com.fasterxml.jackson.annotation.JsonInclude.Include`
82
+ > - 或在注解中写全路径 `@JsonInclude(JsonInclude.Include.NON_NULL)`,无需额外 import
83
+
84
+ **步骤 3:替换枚举字段**
85
+
86
+ ```java
87
+ // ❌ 旧
88
+ public record MetricAnswer(ChartType chartType, ...) {}
89
+
90
+ // ✅ 新
91
+ public record MetricAnswer(ChartView chartView, ...) {}
92
+ ```
93
+
94
+ - 业务逻辑构建时直接 `return new PieChartView(...)` 而非 `return ChartType.PIE_CHART`
95
+ - 删除 `ChartType` 枚举文件
96
+
97
+ ### 2.2 前端(TypeScript)
98
+
99
+ **步骤 1:定义 Discriminated Union**
100
+
101
+ ```typescript
102
+ interface BaseChartView {
103
+ type: string;
104
+ }
105
+
106
+ export interface MetricCardView extends BaseChartView {
107
+ type: 'MetricCardView';
108
+ title: string;
109
+ value: unknown;
110
+ unit: string | null;
111
+ }
112
+
113
+ export interface LineChartView extends BaseChartView {
114
+ type: 'LineChartView';
115
+ title: string;
116
+ xAxisName: string;
117
+ yAxisName: string;
118
+ seriesName: string;
119
+ dataPoints: ChartDataPoint[];
120
+ }
121
+
122
+ export type ChartView = MetricCardView | LineChartView | BarChartView | PieChartView;
123
+ ```
124
+
125
+ **步骤 2:MetricAnswer 中使用联合类型**
126
+
127
+ ```typescript
128
+ export interface MetricAnswer {
129
+ // ...
130
+ chartView: ChartView | null; // ✅ 联合类型自动窄化
131
+ }
132
+ ```
133
+
134
+ **步骤 3:模板中使用 @switch**
135
+
136
+ ```html
137
+ @if (answer.chartView) {
138
+ @switch (answer.chartView.type) {
139
+ @case ('MetricCardView') {
140
+ <app-metric-card [answer]="answer" />
141
+ }
142
+ @case ('LineChartView') {
143
+ <app-line-chart [chartData]="answer.chartData" />
144
+ }
145
+ }
146
+ }
147
+ ```
148
+
149
+ ## 三、最佳实践
150
+
151
+ | 要点 | 说明 |
152
+ |------|------|
153
+ | `property = "type"` | discriminator 字段名使用 `"type"`(简短、通用),而非 `"chartType"` |
154
+ | 前后端 value 一致 | `@JsonSubTypes.Type(name = "MetricCardView")` 与 TS `type: 'MetricCardView'` 的值必须一致 |
155
+ | Checkstyle 兼容 | Jackson 字段名与 getter 名可以不同:`@JsonProperty("xAxisName") private String categoryAxisLabel` |
156
+ | 共享 DTO | 子类共享的嵌套类型(如 `ChartDataPoint`)定义为独立 `record` 或类 |
157
+ | 判空安全 | 模板中 `@if (answer.chartView)` 保护空值(如错误响应时 chartView 为 null) |
158
+ | 不删除旧 DTO 层 | `ChartData`、`gridResults`、`singleResults` 等依然存在,chartView 是新增的视图抽象,并非替代所有响应字段 |
159
+ | 删除前确认引用 | 删除旧枚举前 MUST 搜索全项目 Java + TypeScript 引用,确认 0 引用后方可删除 |
160
+
161
+ ## 四、效果对比
162
+
163
+ | 维度 | 旧模式(Enum) | 新模式(Polymorphism) |
164
+ |------|---------------|----------------------|
165
+ | 新增图表类型 | 枚举 + switch + 前端分支 | 新建子类 + 注册 `@JsonSubTypes` + TS 类型 |
166
+ | JSON 结构 | `{"chartType": "METRIC_CARD", ...}` | `{"chartView": {"type": "MetricCardView", ...}}` |
167
+ | TS 类型安全 | 手动维护 | 自动通过 discriminated union 窄化 |
168
+ | 后端字段数 | chartType + 具体数据各自校验 | chartView 自包含 |
169
+
170
+ ## 五、设计决策说明
171
+
172
+ ### 5.1 `property` 选择 `"type"` 而非 `"chartType"`
173
+
174
+ | 候选 | 理由 | 结论 |
175
+ |------|------|------|
176
+ | `"type"` | 简短通用,多态基类职责就是描述"是什么类型",与低代码项目 (`DatasetDto`) 一致 | ✅ |
177
+ | `"chartType"` | 业务含义明确,但限制了基类的可复用性。未来其他多态场景(如 `DataSourceConnector`)需要不同字段名 | ❌ |
178
+ | `"kind"` | Jackson 社区惯例使用 `"type"`,不应引入不必要的差异 | ❌ |
179
+
180
+ ### 5.2 discriminator 策略使用 `SIMPLE_NAME`
181
+
182
+ | 候选 | 理由 | 结论 |
183
+ |------|------|------|
184
+ | `SIMPLE_NAME` | 子类简名自描述,无需额外常量文件 | ✅ |
185
+ | `CLASS_NAME` | 全限定名过长,JSON 中不可读且暴露包结构 | ❌ |
186
+ | `CUSTOM` | 需要 `@JsonTypeIdResolver`,增加复杂度 | ❌ |
187
+
188
+ ### 5.3 使用 `@JsonSubTypes` 而非自定义序列化器
189
+
190
+ `@JsonSubTypes` 是 Jackson 原生支持,注解式声明最直观。`@JsonTypeIdResolver` 或自定义 `JsonSerializer` 提供更灵活的控制但需要大量模板代码,适用于更复杂的动态注册场景而非当前已知类型场景。
191
+
192
+ ### 5.4 前端 Discriminated Union 而非 Enum
193
+
194
+ TypeScript union type + `type` literal 可直接利用 `@switch` 的类型窄化能力,无需手动编写类型守卫函数。这与后端 enum 模式的维护成本形成鲜明对比。
195
+
196
+ ### 5.5 Checkstyle 冲突:`@JsonProperty` 解耦而非禁用规则
197
+
198
+ | 候选 | 理由 | 结论 |
199
+ |------|------|------|
200
+ | `@JsonProperty("xAxisName") private String categoryAxisLabel` | 字段名合规,getter 合规,JSON 输出保留 `xAxisName` | ✅ |
201
+ | 禁用 Checkstyle `ParameterNameCheck` | 全局禁用降低代码规范性 | ❌ |
202
+
203
+ ### 5.6 Decoder 对象保持 record
204
+
205
+ 如 `MetricAnswer` 是 Java record,不能继承。改造方案为直接替换字段类型(`ChartType chartType` → `ChartView chartView`),record 紧凑语法不受影响。构建逻辑从 `determineChartType()` 改为 `buildChartView()` 并返回具体子类。
@@ -146,3 +146,11 @@ MODULE: 2-4 个大写字母标识模块
146
146
  - `_100`-`_199`:资源状态错误(不存在、已存在、冲突)
147
147
  - `_200`-`_299`:权限错误
148
148
  - `_300`-`_399`:系统内部错误
149
+
150
+ ## DTO 多态序列化
151
+
152
+ 当接口中的 DTO 需要根据类型字段分发到不同子类时(如多种图表类型、多种数据源类型),参考 [DTO 多态序列化规范](jackson-polymorphism.md):
153
+
154
+ - 后端使用 Jackson `@JsonTypeInfo` + `@JsonSubTypes` 实现多态序列化
155
+ - 前端使用 TypeScript Discriminated Union 对应消费
156
+ - discriminator value 前后端严格一致
@@ -62,6 +62,20 @@
62
62
  }
63
63
  ```
64
64
 
65
+ ### Lombok 使用规范
66
+
67
+ | 组件类型 | 必用注解 | 说明 |
68
+ |---------|---------|------|
69
+ | 领域事件 / POJO | `@Getter @AllArgsConstructor` | `private final` 不可变,不手写 getter/constructor(见下方示例) |
70
+ | JPA Entity | `@Getter @NoArgsConstructor(access = PROTECTED)` + `@SuperBuilder`(按需) | 详见 [Hibernate 规范](hibernate.md) |
71
+ | Service / Controller | `@AllArgsConstructor` / `@RequiredArgsConstructor` | 构造函数注入,不用 `@Autowired` 字段注入 |
72
+ | 只读 DTO / Record 替代 | `@Value` | 不可变对象,自动生成 equals/hashCode/toString。Record 更简洁时优先用 record |
73
+
74
+ **禁止事项:**
75
+ - `@Data` — 自动生成 `@Setter` 破坏不可变性,且 `@EqualsAndHashCode` 在有 JPA 代理时行为不可预期
76
+ - `@Setter` on Entity — 破坏封装,业务状态变更应通过行为方法表达
77
+ - `@Autowired` 字段注入 — 必须用构造函数注入
78
+
65
79
  ### 控件能力声明模式(Capability Pattern)
66
80
 
67
81
  当实体层次(如 `FormControl` → `TextControl` / `NumberControl` / ...)需要按子类暴露能力时,在抽象基类中声明抽象方法,各子类返回 `true` / `false`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepstorm/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "DeepStorm CLI — 一键配置项目开发环境",
5
5
  "license": "MIT",
6
6
  "author": "billkang",