@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.
Files changed (148) hide show
  1. package/README.md +72 -0
  2. package/dist/agents/reef-inspect-figma.md +77 -0
  3. package/dist/agents/reef-review-backend.md.tmpl +112 -0
  4. package/dist/agents/reef-review-frontend.md.tmpl +78 -0
  5. package/dist/agents/reef-review-infra.md +47 -0
  6. package/dist/agents/reef-review-security.md.tmpl +80 -0
  7. package/dist/agents/reef-scope-analysis.md +64 -0
  8. package/dist/build-registry.js +375 -0
  9. package/dist/cli.js +8581 -0
  10. package/dist/config-schema.json +133 -0
  11. package/dist/env-examples/context7.env-example +19 -0
  12. package/dist/env-examples/feishu-wiki.env-example +16 -0
  13. package/dist/env-examples/figma.env-example +16 -0
  14. package/dist/env-examples/github.env-example +20 -0
  15. package/dist/env-examples/jira.env-example +20 -0
  16. package/dist/hooks/mcp-hook.sh +77 -0
  17. package/dist/hooks/reef-auto-format.sh.tmpl +72 -0
  18. package/dist/hooks/reef-block-dangerous.sh +70 -0
  19. package/dist/hooks/reef-hooks.json +72 -0
  20. package/dist/hooks/reef-intent-detect.sh +129 -0
  21. package/dist/hooks/reef-protect-files.sh +55 -0
  22. package/dist/hooks/reef-run-tests.sh +84 -0
  23. package/dist/hooks/reef-scope-check.sh +386 -0
  24. package/dist/hooks/reef-scope-ci.sh +28 -0
  25. package/dist/hooks/reef-scope-gate.sh +115 -0
  26. package/dist/hooks/reef-scope-pre-commit.sh.tmpl +28 -0
  27. package/dist/hooks/reef-scope-setup.sh +204 -0
  28. package/dist/hooks/reef-scope-split.sh +203 -0
  29. package/dist/hooks/sweep-hooks.json +14 -0
  30. package/dist/hooks/sweep-mcp-hook.sh +77 -0
  31. package/dist/hooks/tide-hooks.json +14 -0
  32. package/dist/hooks/tide-session-preload.sh +17 -0
  33. package/dist/mcp/code-hosting/github.json +20 -0
  34. package/dist/mcp/design-tools/figma.json +19 -0
  35. package/dist/mcp/docs-reference/context7.json +28 -0
  36. package/dist/mcp/e2e-testing/playwright.json +13 -0
  37. package/dist/mcp/knowledge-base/feishu-wiki.json +19 -0
  38. package/dist/mcp/project-management/jira.json +27 -0
  39. package/dist/mcp-skills/deepflow-mcp-feishu-wiki-read/SKILL.md +65 -0
  40. package/dist/mcp-skills/deepflow-mcp-feishu-wiki-write/SKILL.md +63 -0
  41. package/dist/mcp-skills/deepflow-mcp-figma-read/SKILL.md +98 -0
  42. package/dist/mcp-skills/deepflow-mcp-github-read/SKILL.md +62 -0
  43. package/dist/mcp-skills/deepflow-mcp-github-write/SKILL.md +63 -0
  44. package/dist/mcp-skills/deepflow-mcp-jira-read/SKILL.md +80 -0
  45. package/dist/mcp-skills/deepflow-mcp-jira-write/SKILL.md +74 -0
  46. package/dist/mcp-skills/deepflow-mcp-playwright-read/SKILL.md +79 -0
  47. package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-read/SKILL.md +65 -0
  48. package/dist/mcp-skills/deepstorm-mcp-feishu-wiki-write/SKILL.md +63 -0
  49. package/dist/mcp-skills/deepstorm-mcp-figma-read/SKILL.md +98 -0
  50. package/dist/mcp-skills/deepstorm-mcp-github-read/SKILL.md +62 -0
  51. package/dist/mcp-skills/deepstorm-mcp-github-write/SKILL.md +63 -0
  52. package/dist/mcp-skills/deepstorm-mcp-jira-read/SKILL.md +80 -0
  53. package/dist/mcp-skills/deepstorm-mcp-jira-write/SKILL.md +74 -0
  54. package/dist/mcp-skills/deepstorm-mcp-playwright-read/SKILL.md +79 -0
  55. package/dist/registry.json +818 -0
  56. package/dist/skills/atoll-ops/SKILL.md +46 -0
  57. package/dist/skills/reef-commit/SKILL.md +127 -0
  58. package/dist/skills/reef-gen-backend/SKILL.md.tmpl +87 -0
  59. package/dist/skills/reef-gen-backend/variants/java/steps.md +28 -0
  60. package/dist/skills/reef-gen-backend/variants/python/steps.md +70 -0
  61. package/dist/skills/reef-gen-frontend/SKILL.md.tmpl +83 -0
  62. package/dist/skills/reef-gen-frontend/variants/angular/steps.md +30 -0
  63. package/dist/skills/reef-harden/EXAMPLES.md +89 -0
  64. package/dist/skills/reef-harden/SKILL.md +136 -0
  65. package/dist/skills/reef-pr/SKILL.md +97 -0
  66. package/dist/skills/reef-review/SKILL.md.tmpl +107 -0
  67. package/dist/skills/reef-scope/SKILL.md +134 -0
  68. package/dist/skills/reef-start/SKILL.md.tmpl +562 -0
  69. package/dist/skills/reef-start/references/jira-start-subagent.md +60 -0
  70. package/dist/skills/reef-style-backend/SKILL.md.tmpl +134 -0
  71. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/chat-client.md +96 -0
  72. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/rag.md +94 -0
  73. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/structured-output.md +62 -0
  74. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/examples/tool-calling.md +68 -0
  75. package/dist/skills/reef-style-backend/fragments/java/ai/spring-ai/quick-reference.md +220 -0
  76. package/dist/skills/reef-style-backend/fragments/java/api-spec/quick-reference.md +148 -0
  77. package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/examples/database-migration.md +131 -0
  78. package/dist/skills/reef-style-backend/fragments/java/db-migration/liquibase/quick-reference.md +103 -0
  79. package/dist/skills/reef-style-backend/fragments/java/dependency-management/quick-reference.md +119 -0
  80. package/dist/skills/reef-style-backend/fragments/java/exception-handling/examples/error-code-enum.md +101 -0
  81. package/dist/skills/reef-style-backend/fragments/java/exception-handling/quick-reference.md +181 -0
  82. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/controller.md +95 -0
  83. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/dto-mapper.md +121 -0
  84. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/infrastructure.md +179 -0
  85. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/service-entity.md +202 -0
  86. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/examples/testing.md +107 -0
  87. package/dist/skills/reef-style-backend/fragments/java/framework/spring-boot/quick-reference.md +83 -0
  88. package/dist/skills/reef-style-backend/fragments/java/orm/hibernate/quick-reference.md +150 -0
  89. package/dist/skills/reef-style-backend/fragments/java/security-redlines/quick-reference.md +197 -0
  90. package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/examples/user-repository-test.md +69 -0
  91. package/dist/skills/reef-style-backend/fragments/java/test/data-jpa-test/quick-reference.md +101 -0
  92. package/dist/skills/reef-style-backend/fragments/java/test/junit5/examples/user-service-test.md +61 -0
  93. package/dist/skills/reef-style-backend/fragments/java/test/junit5/quick-reference.md +100 -0
  94. package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/examples/user-controller-test.md +61 -0
  95. package/dist/skills/reef-style-backend/fragments/java/test/spring-mvc-test/quick-reference.md +85 -0
  96. package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/examples/user-service-integration-test.md +56 -0
  97. package/dist/skills/reef-style-backend/fragments/java/test/spring-service-test/quick-reference.md +83 -0
  98. package/dist/skills/reef-style-backend/fragments/python/alembic-migration/quick-reference.md +77 -0
  99. package/dist/skills/reef-style-backend/fragments/python/api-spec/quick-reference.md +164 -0
  100. package/dist/skills/reef-style-backend/fragments/python/dependency-management/quick-reference.md +139 -0
  101. package/dist/skills/reef-style-backend/fragments/python/exception-handling/quick-reference.md +177 -0
  102. package/dist/skills/reef-style-backend/fragments/python/fastapi-quick-reference/quick-reference.md +101 -0
  103. package/dist/skills/reef-style-backend/fragments/python/langchain/quick-reference.md +135 -0
  104. package/dist/skills/reef-style-backend/fragments/python/pytest-testing/quick-reference.md +111 -0
  105. package/dist/skills/reef-style-backend/fragments/python/ruff-mypy-toolchain/quick-reference.md +83 -0
  106. package/dist/skills/reef-style-backend/fragments/python/security-redlines/quick-reference.md +207 -0
  107. package/dist/skills/reef-style-backend/fragments/python/sqlalchemy-orm/quick-reference.md +91 -0
  108. package/dist/skills/reef-style-backend/variants/java/examples/code-wrapping.md +227 -0
  109. package/dist/skills/reef-style-backend/variants/java/examples/contributor-pattern.md +97 -0
  110. package/dist/skills/reef-style-backend/variants/java/quick-reference.md +117 -0
  111. package/dist/skills/reef-style-backend/variants/python/examples/crud-router.md +71 -0
  112. package/dist/skills/reef-style-backend/variants/python/examples/pydantic-schema.md +45 -0
  113. package/dist/skills/reef-style-backend/variants/python/examples/pytest-fixture.md +60 -0
  114. package/dist/skills/reef-style-backend/variants/python/examples/sqlalchemy-model.md +31 -0
  115. package/dist/skills/reef-style-backend/variants/python/quick-reference.md +111 -0
  116. package/dist/skills/reef-style-frontend/SKILL.md.tmpl +70 -0
  117. package/dist/skills/reef-style-frontend/fragments/css/tailwind/quick-reference.md +79 -0
  118. package/dist/skills/reef-style-frontend/fragments/test/vitest/examples/testing.md +150 -0
  119. package/dist/skills/reef-style-frontend/fragments/test/vitest/quick-reference.md +141 -0
  120. package/dist/skills/reef-style-frontend/fragments/ts-config/strict/quick-reference.md +112 -0
  121. package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/examples/ui-components.md +134 -0
  122. package/dist/skills/reef-style-frontend/fragments/ui-lib/primeng/quick-reference.md +66 -0
  123. package/dist/skills/reef-style-frontend/variants/angular/examples/code-wrapping.md +252 -0
  124. package/dist/skills/reef-style-frontend/variants/angular/examples/component-types-pipes.md +56 -0
  125. package/dist/skills/reef-style-frontend/variants/angular/examples/entity-types.md +100 -0
  126. package/dist/skills/reef-style-frontend/variants/angular/examples/forms-layer.md +119 -0
  127. package/dist/skills/reef-style-frontend/variants/angular/examples/service-routing.md +95 -0
  128. package/dist/skills/reef-style-frontend/variants/angular/quick-reference.md +100 -0
  129. package/dist/skills/reef-testcase/SKILL.md +138 -0
  130. package/dist/skills/reef-testcase/references/coverage-dimensions.md +99 -0
  131. package/dist/skills/reef-testcase/references/test-case-template.md +63 -0
  132. package/dist/skills/sweep-init/SKILL.md +286 -0
  133. package/dist/skills/sweep-init/scripts/flow-selector.mjs +305 -0
  134. package/dist/skills/sweep-plan/SKILL.md.tmpl +312 -0
  135. package/dist/skills/sweep-plan/references/test-flow-template.md +46 -0
  136. package/dist/skills/sweep-run/SKILL.md +437 -0
  137. package/dist/skills/sweep-run/scripts/env-manager.mjs +208 -0
  138. package/dist/skills/sweep-run/scripts/flow-parser.mjs +329 -0
  139. package/dist/skills/sweep-run/scripts/flow-selector.mjs +483 -0
  140. package/dist/skills/sweep-run/scripts/mcp-manager.mjs +208 -0
  141. package/dist/skills/sweep-run/scripts/spec-compiler.mjs +303 -0
  142. package/dist/skills/tide-discuss/SKILL.md.tmpl +449 -0
  143. package/dist/skills/tide-discuss/references/checklists.md +88 -0
  144. package/dist/skills/tide-discuss/references/data-format.md +237 -0
  145. package/dist/skills/tide-discuss/references/prd-template.md +134 -0
  146. package/dist/skills/tide-discuss/references/publish-flow.md +167 -0
  147. package/dist/skills/tide-discuss/references/role-prompts.md +105 -0
  148. 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
+ ```
@@ -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
+ ```
@@ -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
+ ```