@deepstorm/cli 0.1.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 +72 -0
- package/dist/agents/reef-inspect-figma.md +77 -0
- package/dist/agents/reef-review-backend.md.tmpl +112 -0
- package/dist/agents/reef-review-frontend.md.tmpl +78 -0
- package/dist/agents/reef-review-infra.md +47 -0
- package/dist/agents/reef-review-security.md.tmpl +80 -0
- package/dist/agents/reef-scope-analysis.md +64 -0
- package/dist/build-registry.js +375 -0
- package/dist/cli.js +8581 -0
- package/dist/config-schema.json +133 -0
- package/dist/env-examples/context7.env-example +19 -0
- package/dist/env-examples/feishu-wiki.env-example +16 -0
- package/dist/env-examples/figma.env-example +16 -0
- package/dist/env-examples/github.env-example +20 -0
- package/dist/env-examples/jira.env-example +20 -0
- package/dist/hooks/mcp-hook.sh +77 -0
- package/dist/hooks/reef-auto-format.sh.tmpl +72 -0
- package/dist/hooks/reef-block-dangerous.sh +70 -0
- package/dist/hooks/reef-hooks.json +72 -0
- package/dist/hooks/reef-intent-detect.sh +129 -0
- package/dist/hooks/reef-protect-files.sh +55 -0
- package/dist/hooks/reef-run-tests.sh +84 -0
- package/dist/hooks/reef-scope-check.sh +386 -0
- package/dist/hooks/reef-scope-ci.sh +28 -0
- package/dist/hooks/reef-scope-gate.sh +115 -0
- package/dist/hooks/reef-scope-pre-commit.sh.tmpl +28 -0
- package/dist/hooks/reef-scope-setup.sh +204 -0
- package/dist/hooks/reef-scope-split.sh +203 -0
- package/dist/hooks/sweep-hooks.json +14 -0
- package/dist/hooks/sweep-mcp-hook.sh +77 -0
- package/dist/hooks/tide-hooks.json +14 -0
- package/dist/hooks/tide-session-preload.sh +17 -0
- package/dist/mcp/code-hosting/github.json +20 -0
- package/dist/mcp/design-tools/figma.json +19 -0
- package/dist/mcp/docs-reference/context7.json +28 -0
- package/dist/mcp/e2e-testing/playwright.json +13 -0
- package/dist/mcp/knowledge-base/feishu-wiki.json +19 -0
- package/dist/mcp/project-management/jira.json +27 -0
- package/dist/mcp-skills/deepflow-mcp-feishu-wiki-read/SKILL.md +65 -0
- package/dist/mcp-skills/deepflow-mcp-feishu-wiki-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepflow-mcp-figma-read/SKILL.md +98 -0
- package/dist/mcp-skills/deepflow-mcp-github-read/SKILL.md +62 -0
- package/dist/mcp-skills/deepflow-mcp-github-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepflow-mcp-jira-read/SKILL.md +80 -0
- package/dist/mcp-skills/deepflow-mcp-jira-write/SKILL.md +74 -0
- package/dist/mcp-skills/deepflow-mcp-playwright-read/SKILL.md +79 -0
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +65 -0
- package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +98 -0
- package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +62 -0
- package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +63 -0
- package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +80 -0
- package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +74 -0
- package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +79 -0
- package/dist/registry.json +818 -0
- package/dist/skills/atoll-ops/SKILL.md +46 -0
- package/dist/skills/reef-commit/SKILL.md +127 -0
- package/dist/skills/reef-gen-backend/SKILL.md.tmpl +87 -0
- package/dist/skills/reef-gen-backend/variants/java/steps.md +28 -0
- package/dist/skills/reef-gen-backend/variants/python/steps.md +70 -0
- package/dist/skills/reef-gen-frontend/SKILL.md.tmpl +83 -0
- package/dist/skills/reef-gen-frontend/variants/angular/steps.md +30 -0
- package/dist/skills/reef-harden/EXAMPLES.md +89 -0
- package/dist/skills/reef-harden/SKILL.md +136 -0
- package/dist/skills/reef-pr/SKILL.md +97 -0
- package/dist/skills/reef-review/SKILL.md.tmpl +107 -0
- package/dist/skills/reef-scope/SKILL.md +134 -0
- package/dist/skills/reef-start/SKILL.md.tmpl +562 -0
- package/dist/skills/reef-start/references/jira-start-subagent.md +60 -0
- package/dist/skills/reef-style-backend/SKILL.md.tmpl +134 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/chat-client.md +96 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/rag.md +94 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +62 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/tool-calling.md +68 -0
- package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +220 -0
- package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +148 -0
- package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +131 -0
- package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/quick-reference.md +103 -0
- package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +119 -0
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +101 -0
- package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +181 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/controller.md +95 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +121 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/infrastructure.md +179 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +202 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +107 -0
- package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +150 -0
- package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +197 -0
- package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/examples/user-repository-test.md +69 -0
- package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +101 -0
- package/dist/skills/reef-style-backend/fragments/java/test/junit5/examples/user-service-test.md +61 -0
- package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +100 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/examples/user-controller-test.md +61 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +85 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/examples/user-service-integration-test.md +56 -0
- package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +77 -0
- package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +164 -0
- package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +139 -0
- package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +177 -0
- package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +101 -0
- package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +135 -0
- package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +111 -0
- package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +83 -0
- package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +207 -0
- package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +91 -0
- package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +227 -0
- package/dist/skills/reef-style-backend/variants/java/examples/contributor-pattern.md +97 -0
- package/dist/skills/reef-style-backend/variants/java/quick-reference.md +117 -0
- package/dist/skills/reef-style-backend/variants/python/examples/crud-router.md +71 -0
- package/dist/skills/reef-style-backend/variants/python/examples/pydantic-schema.md +45 -0
- package/dist/skills/reef-style-backend/variants/python/examples/pytest-fixture.md +60 -0
- package/dist/skills/reef-style-backend/variants/python/examples/sqlalchemy-model.md +31 -0
- package/dist/skills/reef-style-backend/variants/python/quick-reference.md +111 -0
- package/dist/skills/reef-style-frontend/SKILL.md.tmpl +70 -0
- package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +79 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +150 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +141 -0
- package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +112 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +134 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/quick-reference.md +66 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +252 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +56 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +100 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +119 -0
- package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +95 -0
- package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +100 -0
- package/dist/skills/reef-testcase/SKILL.md +138 -0
- package/dist/skills/reef-testcase/references/coverage-dimensions.md +99 -0
- package/dist/skills/reef-testcase/references/test-case-template.md +63 -0
- package/dist/skills/sweep-init/SKILL.md +286 -0
- package/dist/skills/sweep-init/scripts/flow-selector.mjs +305 -0
- package/dist/skills/sweep-plan/SKILL.md.tmpl +312 -0
- package/dist/skills/sweep-plan/references/test-flow-template.md +46 -0
- package/dist/skills/sweep-run/SKILL.md +437 -0
- package/dist/skills/sweep-run/scripts/env-manager.mjs +208 -0
- package/dist/skills/sweep-run/scripts/flow-parser.mjs +329 -0
- package/dist/skills/sweep-run/scripts/flow-selector.mjs +483 -0
- package/dist/skills/sweep-run/scripts/mcp-manager.mjs +208 -0
- package/dist/skills/sweep-run/scripts/spec-compiler.mjs +303 -0
- package/dist/skills/tide-discuss/SKILL.md.tmpl +449 -0
- package/dist/skills/tide-discuss/references/checklists.md +88 -0
- package/dist/skills/tide-discuss/references/data-format.md +237 -0
- package/dist/skills/tide-discuss/references/prd-template.md +134 -0
- package/dist/skills/tide-discuss/references/publish-flow.md +167 -0
- package/dist/skills/tide-discuss/references/role-prompts.md +105 -0
- package/package.json +38 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# 异常处理规范
|
|
2
|
+
|
|
3
|
+
## 速查
|
|
4
|
+
|
|
5
|
+
| 场景 | 决策 |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| 业务异常 | 继承 `BusinessException` → 按场景选子类:`ResourceNotFoundException` / `AlreadyExistsException` / `InvalidArgumentException` / `PermissionDeniedException` / `FailedPreconditionException` |
|
|
8
|
+
| 全局捕获 | `@RestControllerAdvice` + `GlobalExceptionHandler` |
|
|
9
|
+
| 错误响应 | `ErrorResponse(code, message, detail)` — AIP-193 兼容 |
|
|
10
|
+
| 错误码 | `{MODULE}_{NNN}` 枚举:`ErrorCode` enum 统一管理 |
|
|
11
|
+
| 参数校验失败 | `MethodArgumentNotValidException` → `InvalidArgumentException` 统一处理 |
|
|
12
|
+
| 未知异常 | 兜底 500 — `GENERIC_001`,记录完整堆栈 |
|
|
13
|
+
|
|
14
|
+
## 核心规范
|
|
15
|
+
|
|
16
|
+
### 业务异常继承层次
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
RuntimeException
|
|
20
|
+
└── BusinessException ← 抽象基类,含 errorCode + httpStatus
|
|
21
|
+
├── ResourceNotFoundException (404)
|
|
22
|
+
├── AlreadyExistsException (409)
|
|
23
|
+
├── InvalidArgumentException (400)
|
|
24
|
+
├── PermissionDeniedException (403)
|
|
25
|
+
└── FailedPreconditionException (400)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```java
|
|
29
|
+
// 基类
|
|
30
|
+
public abstract class BusinessException extends RuntimeException {
|
|
31
|
+
private final String errorCode;
|
|
32
|
+
private final HttpStatus httpStatus;
|
|
33
|
+
private final Map<String, Object> detail;
|
|
34
|
+
|
|
35
|
+
protected BusinessException(String errorCode, HttpStatus httpStatus, String message) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.errorCode = errorCode;
|
|
38
|
+
this.httpStatus = httpStatus;
|
|
39
|
+
this.detail = Map.of();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected BusinessException(String errorCode, HttpStatus httpStatus,
|
|
43
|
+
String message, Map<String, Object> detail) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.errorCode = errorCode;
|
|
46
|
+
this.httpStatus = httpStatus;
|
|
47
|
+
this.detail = detail;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public String getErrorCode() { return errorCode; }
|
|
51
|
+
public HttpStatus getHttpStatus() { return httpStatus; }
|
|
52
|
+
public Map<String, Object> getDetail() { return detail; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 子类示例
|
|
56
|
+
public class ResourceNotFoundException extends BusinessException {
|
|
57
|
+
public ResourceNotFoundException(String errorCode, String resourceType, Object id) {
|
|
58
|
+
super(errorCode, HttpStatus.NOT_FOUND,
|
|
59
|
+
resourceType + " not found: " + id,
|
|
60
|
+
Map.of("resourceType", resourceType, "id", id));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**规范:**
|
|
66
|
+
- 业务异常**必须**继承 `BusinessException`,不得直接抛 `RuntimeException` 或 `ResponseStatusException`
|
|
67
|
+
- 每个构造函数传入 `ErrorCode` 枚举值,而非硬编码字符串
|
|
68
|
+
- 可选的 `detail` map 携带上下文信息(如资源 ID、字段名)用于调试
|
|
69
|
+
|
|
70
|
+
### 错误码枚举
|
|
71
|
+
|
|
72
|
+
```java
|
|
73
|
+
public enum ErrorCode {
|
|
74
|
+
// User module — 001~099 输入验证, 100~199 资源状态, 200~299 权限
|
|
75
|
+
USER_001("USER_001", "用户名已存在"),
|
|
76
|
+
USER_002("USER_002", "邮箱格式无效"),
|
|
77
|
+
USER_100("USER_100", "用户不存在"),
|
|
78
|
+
USER_101("USER_101", "用户已禁用"),
|
|
79
|
+
USER_200("USER_200", "无权操作该用户"),
|
|
80
|
+
|
|
81
|
+
// Order module
|
|
82
|
+
ORDER_001("ORDER_001", "订单金额无效"),
|
|
83
|
+
ORDER_100("ORDER_100", "订单不存在"),
|
|
84
|
+
ORDER_101("ORDER_101", "订单状态不允许操作"),
|
|
85
|
+
|
|
86
|
+
// Auth module
|
|
87
|
+
AUTH_001("AUTH_001", "Token 已过期"),
|
|
88
|
+
AUTH_002("AUTH_002", "Token 无效"),
|
|
89
|
+
AUTH_201("AUTH_201", "无权限访问"),
|
|
90
|
+
|
|
91
|
+
// Generic
|
|
92
|
+
GENERIC_001("GENERIC_001", "系统内部错误");
|
|
93
|
+
|
|
94
|
+
private final String code;
|
|
95
|
+
private final String defaultMessage;
|
|
96
|
+
|
|
97
|
+
ErrorCode(String code, String defaultMessage) {
|
|
98
|
+
this.code = code;
|
|
99
|
+
this.defaultMessage = defaultMessage;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public String code() { return code; }
|
|
103
|
+
public String defaultMessage() { return defaultMessage; }
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### @RestControllerAdvice 全局处理
|
|
108
|
+
|
|
109
|
+
```java
|
|
110
|
+
@RestControllerAdvice
|
|
111
|
+
public class GlobalExceptionHandler {
|
|
112
|
+
|
|
113
|
+
// 业务异常
|
|
114
|
+
@ExceptionHandler(BusinessException.class)
|
|
115
|
+
public ResponseEntity<ErrorResponse> handleBusiness(BusinessException ex) {
|
|
116
|
+
return ResponseEntity
|
|
117
|
+
.status(ex.getHttpStatus())
|
|
118
|
+
.body(ErrorResponse.of(ex.getErrorCode(), ex.getMessage(), ex.getDetail()));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 参数校验失败
|
|
122
|
+
@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
123
|
+
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex) {
|
|
124
|
+
var errors = ex.getBindingResult().getFieldErrors().stream()
|
|
125
|
+
.map(e -> Map.of("field", e.getField(), "message", e.getDefaultMessage()))
|
|
126
|
+
.toList();
|
|
127
|
+
return ResponseEntity
|
|
128
|
+
.status(HttpStatus.BAD_REQUEST)
|
|
129
|
+
.body(ErrorResponse.of("VALIDATION_001", "参数校验失败", Map.of("errors", errors)));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 404 未命中路由
|
|
133
|
+
@ExceptionHandler(NoHandlerFoundException.class)
|
|
134
|
+
public ResponseEntity<ErrorResponse> handleNotFound(NoHandlerFoundException ex) {
|
|
135
|
+
return ResponseEntity
|
|
136
|
+
.status(HttpStatus.NOT_FOUND)
|
|
137
|
+
.body(ErrorResponse.of("GENERIC_404", "接口不存在: " + ex.getRequestURL()));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 兜底:未捕获异常 → 500
|
|
141
|
+
@ExceptionHandler(Exception.class)
|
|
142
|
+
public ResponseEntity<ErrorResponse> handleUnexpected(Exception ex) {
|
|
143
|
+
log.error("Unexpected error", ex); // 完整堆栈只打日志,不返回客户端
|
|
144
|
+
return ResponseEntity
|
|
145
|
+
.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
146
|
+
.body(ErrorResponse.of("GENERIC_001", "系统内部错误"));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**规范:**
|
|
152
|
+
- 不要在每个 Controller 中写 try-catch
|
|
153
|
+
- 不要吞异常后返回 null 或空集合
|
|
154
|
+
- 兜底 handler 必须 `log.error` 记录完整堆栈
|
|
155
|
+
|
|
156
|
+
### 异常使用示例
|
|
157
|
+
|
|
158
|
+
```java
|
|
159
|
+
// Service 层
|
|
160
|
+
@Service
|
|
161
|
+
public class UserService {
|
|
162
|
+
public UserResponse getUser(Long id) {
|
|
163
|
+
return userRepository.findById(id)
|
|
164
|
+
.map(UserResponse::from)
|
|
165
|
+
.orElseThrow(() -> new ResourceNotFoundException(
|
|
166
|
+
ErrorCode.USER_100.code(), "User", id));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Controller 层不需要 try-catch
|
|
171
|
+
@RestController
|
|
172
|
+
@RequestMapping("/api/v1/users")
|
|
173
|
+
public class UserController {
|
|
174
|
+
private final UserService userService;
|
|
175
|
+
|
|
176
|
+
@GetMapping("/{id}")
|
|
177
|
+
public ResponseEntity<ApiResponse<UserResponse>> getUser(@PathVariable Long id) {
|
|
178
|
+
return ResponseEntity.ok(ApiResponse.success(userService.getUser(id)));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/controller.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# 后端 Controller 示例
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 1. 标准 Controller(CRUD + 自定义方法)
|
|
6
|
+
|
|
7
|
+
```java
|
|
8
|
+
@RestController
|
|
9
|
+
@RequestMapping("/api/v1/apps/{appId}/forms")
|
|
10
|
+
@AllArgsConstructor
|
|
11
|
+
public class FormController {
|
|
12
|
+
private final FormService service;
|
|
13
|
+
|
|
14
|
+
@GetMapping
|
|
15
|
+
public ListFormsResponse listForms(@PathVariable Long appId) {
|
|
16
|
+
return service.listForms(appId);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@GetMapping("/{formId}")
|
|
20
|
+
public FormDetailsDto getForm(
|
|
21
|
+
@PathVariable Long appId, @PathVariable Long formId) {
|
|
22
|
+
return service.getForm(appId, formId);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@PostMapping
|
|
26
|
+
public FormDetailsDto createForm(
|
|
27
|
+
@PathVariable Long appId, @Valid @RequestBody CreateFormRequest request) {
|
|
28
|
+
return service.createForm(appId, request);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@PatchMapping("/{formId}")
|
|
32
|
+
public FormDetailsDto updateForm(
|
|
33
|
+
@PathVariable Long appId,
|
|
34
|
+
@PathVariable Long formId,
|
|
35
|
+
@Valid @RequestBody UpdateFormRequest request) {
|
|
36
|
+
return service.updateForm(appId, formId, request);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@DeleteMapping("/{formId}")
|
|
40
|
+
public void deleteForm(
|
|
41
|
+
@PathVariable Long appId, @PathVariable Long formId) {
|
|
42
|
+
service.deleteForm(appId, formId);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// AIP-136 自定义方法:冒号语法
|
|
46
|
+
@PostMapping("/{formId}:publish")
|
|
47
|
+
public FormDetailsDto publishForm(
|
|
48
|
+
@PathVariable Long appId,
|
|
49
|
+
@PathVariable Long formId,
|
|
50
|
+
@RequestBody PublishFormRequest request) {
|
|
51
|
+
return service.publishForm(appId, formId, request);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 2. Controller 含 @AuthenticationPrincipal
|
|
59
|
+
|
|
60
|
+
```java
|
|
61
|
+
@RestController
|
|
62
|
+
@RequestMapping("/api/v1/apps/{appId}/forms/{formId}")
|
|
63
|
+
@AllArgsConstructor
|
|
64
|
+
public class FormResponseController {
|
|
65
|
+
private final FormResponseService service;
|
|
66
|
+
|
|
67
|
+
@GetMapping("/responses")
|
|
68
|
+
public ListFormResponsesResponse listFormResponses(
|
|
69
|
+
@PathVariable Long appId,
|
|
70
|
+
@PathVariable Long formId,
|
|
71
|
+
@AuthenticationPrincipal User user) {
|
|
72
|
+
return service.listFormResponses(appId, formId, user);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@PostMapping("/responses")
|
|
76
|
+
public FormResponseDto createFormResponse(
|
|
77
|
+
@PathVariable Long appId,
|
|
78
|
+
@PathVariable Long formId,
|
|
79
|
+
@RequestBody CreateFormResponseRequest request,
|
|
80
|
+
@AuthenticationPrincipal User user) {
|
|
81
|
+
return service.createFormResponse(appId, formId, request, user);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// AIP-136 自定义方法
|
|
85
|
+
@PostMapping("/responses/{responseId}:approve")
|
|
86
|
+
public FormResponseDto approveFormResponse(
|
|
87
|
+
@PathVariable Long appId,
|
|
88
|
+
@PathVariable Long formId,
|
|
89
|
+
@PathVariable Long responseId,
|
|
90
|
+
@RequestBody ApproveFormResponseRequest request,
|
|
91
|
+
@AuthenticationPrincipal User user) {
|
|
92
|
+
return service.approveFormResponse(appId, formId, responseId, request, user);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# 后端 DTO/MapStruct 示例
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 1. DTO 层次
|
|
6
|
+
|
|
7
|
+
DTO 层次镜像实体层次,基类定义通用字段:
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
classDiagram
|
|
11
|
+
class AbstractImmutableDto {
|
|
12
|
+
+id
|
|
13
|
+
+createdAt
|
|
14
|
+
}
|
|
15
|
+
class AbstractDto {
|
|
16
|
+
+lastModifiedAt
|
|
17
|
+
}
|
|
18
|
+
class AbstractAuditableDto~U~ {
|
|
19
|
+
+createdBy
|
|
20
|
+
+lastModifiedBy
|
|
21
|
+
}
|
|
22
|
+
class AbstractImmutableAuditableDto~U~ {
|
|
23
|
+
+createdBy
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
AbstractImmutableDto <|-- AbstractDto
|
|
27
|
+
AbstractDto <|-- AbstractAuditableDto~U~
|
|
28
|
+
AbstractImmutableDto <|-- AbstractImmutableAuditableDto~U~
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`<U>` 为解析后的用户对象类型(如 `UserSummaryDto`),与实体的 `Long createdById` 不同。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 2. DTO 定义
|
|
36
|
+
|
|
37
|
+
### 读 DTO(@Value,不可变)
|
|
38
|
+
|
|
39
|
+
```java
|
|
40
|
+
@Value
|
|
41
|
+
public class FormSummaryDto extends AbstractDto {
|
|
42
|
+
String title;
|
|
43
|
+
boolean approvalEnabled;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 含审计字段:<U> 为 UserSummaryDto
|
|
47
|
+
@Value
|
|
48
|
+
public class FormResponseDto extends AbstractAuditableDto<UserSummaryDto> {
|
|
49
|
+
Long formId;
|
|
50
|
+
Map<String, Object> data;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 写 record(可变,独立于读 DTO)
|
|
55
|
+
|
|
56
|
+
```java
|
|
57
|
+
public record CreateFormRequest(boolean approvalEnabled) {}
|
|
58
|
+
|
|
59
|
+
public record UpdateFormRequest(
|
|
60
|
+
@NotBlank String title,
|
|
61
|
+
@Valid PrintConfigDto printConfig,
|
|
62
|
+
@NotEmpty List<@Valid FormActionDto> actions) {}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. MapStruct Mapper
|
|
68
|
+
|
|
69
|
+
### 基础 Mapper
|
|
70
|
+
|
|
71
|
+
```java
|
|
72
|
+
@Mapper(config = MapStructConfig.class, uses = { FormRevisionMapper.class, FormItemMapper.class })
|
|
73
|
+
public interface FormMapper {
|
|
74
|
+
FormMapper INSTANCE = Mappers.getMapper(FormMapper.class);
|
|
75
|
+
|
|
76
|
+
FormSummaryDto mapToSummary(Form form);
|
|
77
|
+
FormDetailsDto mapToDetails(Form form);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 嵌套子映射(uses)
|
|
82
|
+
|
|
83
|
+
包含关联实体的 Mapper 通过 `uses` 组合子 Mapper,MapStruct 自动递归映射:
|
|
84
|
+
|
|
85
|
+
```java
|
|
86
|
+
// 父 Mapper:声明依赖子 Mapper
|
|
87
|
+
@Mapper(config = MapStructConfig.class, uses = DataObjectFieldMapper.class)
|
|
88
|
+
public interface DataObjectMapper {
|
|
89
|
+
DataObjectMapper INSTANCE = Mappers.getMapper(DataObjectMapper.class);
|
|
90
|
+
|
|
91
|
+
DataObjectSummaryDto mapToSummary(DataObject entity);
|
|
92
|
+
DataObjectDetailsDto mapToDetails(DataObject entity);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 子 Mapper:处理嵌套字段
|
|
96
|
+
@Mapper(config = MapStructConfig.class)
|
|
97
|
+
public interface DataObjectFieldMapper {
|
|
98
|
+
DataObjectFieldMapper INSTANCE = Mappers.getMapper(DataObjectFieldMapper.class);
|
|
99
|
+
|
|
100
|
+
DataObjectFieldDto mapToDto(DataObjectField field);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
当 `DataObject` 包含 `List<DataObjectField> fields` 时,`DataObjectMapper` 自动通过 `DataObjectFieldMapper` 映射到 `List<DataObjectFieldDto>`,无需额外配置。
|
|
105
|
+
|
|
106
|
+
### 命名约定
|
|
107
|
+
|
|
108
|
+
| 方法名 | 用途 |
|
|
109
|
+
|--------|------|
|
|
110
|
+
| `mapToSummary(entity)` | 精简 DTO(列表用) |
|
|
111
|
+
| `mapToDetails(entity)` | 完整 DTO(详情用) |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 4. MapStructConfig
|
|
116
|
+
|
|
117
|
+
```java
|
|
118
|
+
@MapperConfig(
|
|
119
|
+
subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION)
|
|
120
|
+
public interface MapStructConfig {}
|
|
121
|
+
```
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# 后端基础设施示例
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 1. 异常定义
|
|
6
|
+
|
|
7
|
+
```java
|
|
8
|
+
public class NotFoundException extends RuntimeException {
|
|
9
|
+
public NotFoundException(String message) {
|
|
10
|
+
super(message);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public NotFoundException(String message, ResourceInfo resourceInfo) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.resourceInfo = resourceInfo;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
使用:`throw new NotFoundException("资源不存在", new ResourceInfo("Form", id));`
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 2. Error Status 枚举
|
|
25
|
+
|
|
26
|
+
```java
|
|
27
|
+
public enum Status {
|
|
28
|
+
OK(200),
|
|
29
|
+
INVALID_ARGUMENT(400),
|
|
30
|
+
FAILED_PRECONDITION(400),
|
|
31
|
+
NOT_FOUND(404),
|
|
32
|
+
ALREADY_EXISTS(409),
|
|
33
|
+
PERMISSION_DENIED(403),
|
|
34
|
+
INTERNAL(500),
|
|
35
|
+
;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 3. 多租户基类
|
|
42
|
+
|
|
43
|
+
### AbstractTenantAwareEntity(无审计)
|
|
44
|
+
|
|
45
|
+
```java
|
|
46
|
+
@MappedSuperclass
|
|
47
|
+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
|
48
|
+
public abstract class AbstractTenantAwareEntity extends AbstractEntity {
|
|
49
|
+
@Getter
|
|
50
|
+
@org.hibernate.annotations.TenantId
|
|
51
|
+
private Long tenantId;
|
|
52
|
+
|
|
53
|
+
protected AbstractTenantAwareEntity(Long id) {
|
|
54
|
+
super(id);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
protected AbstractTenantAwareEntity(Long id, Long tenantId) {
|
|
58
|
+
super(id);
|
|
59
|
+
this.tenantId = tenantId;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### AbstractTenantAwareAuditable(有审计字段)
|
|
65
|
+
|
|
66
|
+
审计字段来自 `AbstractAuditable`(`AbstractEntity` → `AbstractAuditable` → `AbstractTenantAwareAuditable`):
|
|
67
|
+
|
|
68
|
+
```java
|
|
69
|
+
@MappedSuperclass
|
|
70
|
+
@EntityListeners(AuditingEntityListener.class)
|
|
71
|
+
@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
|
|
72
|
+
@Getter
|
|
73
|
+
public abstract class AbstractAuditable extends AbstractEntity {
|
|
74
|
+
@NotNull @CreatedBy private Long createdById;
|
|
75
|
+
@NotNull @LastModifiedBy private Long lastModifiedById;
|
|
76
|
+
|
|
77
|
+
protected AbstractAuditable(Long id, Long createdById) {
|
|
78
|
+
super(id);
|
|
79
|
+
this.createdById = createdById;
|
|
80
|
+
this.lastModifiedById = createdById;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@MappedSuperclass
|
|
85
|
+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
|
86
|
+
public abstract class AbstractTenantAwareAuditable extends AbstractAuditable {
|
|
87
|
+
@org.hibernate.annotations.TenantId
|
|
88
|
+
private Long tenantId;
|
|
89
|
+
|
|
90
|
+
protected AbstractTenantAwareAuditable(Long id, Long createdById) {
|
|
91
|
+
super(id, createdById);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- `createdById` / `lastModifiedById` 由 `AuditingEntityListener` 通过 Spring Security 自动填充
|
|
97
|
+
- 实体构造函数传一次 `createdById`,后续修改由 `@LastModifiedBy` 自动更新
|
|
98
|
+
- `AbstractTenantAwareAuditable` 同时具备审计字段 + 多租户过滤
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 4. 校验
|
|
103
|
+
|
|
104
|
+
### DTO 字段校验
|
|
105
|
+
|
|
106
|
+
用 `jakarta.validation` 注解声明在 DTO/record 字段上:
|
|
107
|
+
|
|
108
|
+
```java
|
|
109
|
+
public record CreateFormRequest(
|
|
110
|
+
@NotBlank String title,
|
|
111
|
+
|
|
112
|
+
@Size(max = 200)
|
|
113
|
+
String description,
|
|
114
|
+
|
|
115
|
+
@Valid // 嵌套对象必须加 @Valid 才能递归校验
|
|
116
|
+
PrintConfigDto printConfig,
|
|
117
|
+
|
|
118
|
+
@NotEmpty
|
|
119
|
+
List<@Valid FormActionDto> actions
|
|
120
|
+
) {}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Controller 参数加 `@Valid` 触发校验:
|
|
124
|
+
|
|
125
|
+
```java
|
|
126
|
+
@PostMapping
|
|
127
|
+
public FormDetailsDto createForm(
|
|
128
|
+
@PathVariable Long appId,
|
|
129
|
+
@Valid @RequestBody CreateFormRequest request) { ... }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 跨字段校验
|
|
133
|
+
|
|
134
|
+
跨字段逻辑(如结束日期 ≥ 开始日期)在 Service 中显式判断:
|
|
135
|
+
|
|
136
|
+
```java
|
|
137
|
+
@Transactional
|
|
138
|
+
public void updateForm(Long appId, Long formId, UpdateFormRequest request) {
|
|
139
|
+
if (request.endDate() != null && request.startDate() != null
|
|
140
|
+
&& request.endDate().isBefore(request.startDate())) {
|
|
141
|
+
throw new InvalidArgumentException("结束日期不能早于开始日期");
|
|
142
|
+
}
|
|
143
|
+
...
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 程序化校验
|
|
148
|
+
|
|
149
|
+
复杂校验注入 `jakarta.validation.Validator` 手动调用:
|
|
150
|
+
|
|
151
|
+
```java
|
|
152
|
+
@Service
|
|
153
|
+
@AllArgsConstructor
|
|
154
|
+
public class ImportService {
|
|
155
|
+
private final Validator validator;
|
|
156
|
+
|
|
157
|
+
public void importForms(List<ImportItem> items) {
|
|
158
|
+
for (var item : items) {
|
|
159
|
+
var violations = validator.validate(item);
|
|
160
|
+
if (!violations.isEmpty()) {
|
|
161
|
+
throw new InvalidArgumentException(
|
|
162
|
+
"导入数据校验失败: " + violations.iterator().next().getMessage());
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 5. 分页响应
|
|
172
|
+
|
|
173
|
+
```java
|
|
174
|
+
@Value
|
|
175
|
+
public final class PagedResponse<T extends AbstractImmutableDto> {
|
|
176
|
+
List<T> items;
|
|
177
|
+
long totalItems;
|
|
178
|
+
}
|
|
179
|
+
```
|