@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,91 @@
|
|
|
1
|
+
# SQLAlchemy ORM 使用规范(v2.0+)
|
|
2
|
+
|
|
3
|
+
## 异步会话管理
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
|
|
7
|
+
|
|
8
|
+
# ✅ 好:异步引擎 + sessionmaker
|
|
9
|
+
engine = create_async_engine(
|
|
10
|
+
"postgresql+asyncpg://user:pass@localhost/db",
|
|
11
|
+
echo=False,
|
|
12
|
+
pool_size=10,
|
|
13
|
+
max_overflow=20,
|
|
14
|
+
)
|
|
15
|
+
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ✅ 好:在 Service 中作为依赖
|
|
19
|
+
class UserService:
|
|
20
|
+
def __init__(self, db: AsyncSession = Depends(get_async_session)):
|
|
21
|
+
self.db = db
|
|
22
|
+
|
|
23
|
+
async def get_user(self, user_id: int) -> User | None:
|
|
24
|
+
stmt = select(User).where(User.id == user_id)
|
|
25
|
+
result = await self.db.execute(stmt)
|
|
26
|
+
return result.scalar_one_or_none()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**规范:**
|
|
30
|
+
- **强制**使用 `AsyncSession` + `async_sessionmaker`
|
|
31
|
+
- 禁止同步 `Session` 或 `scoped_session`
|
|
32
|
+
- `expire_on_commit=False` 避免 commit 后属性不可访问
|
|
33
|
+
- 不要在 Router 中直接操作 `db`,放到 Service 层
|
|
34
|
+
|
|
35
|
+
## 模型定义(v2.0 style)
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
|
39
|
+
from sqlalchemy import String, Integer, DateTime, func
|
|
40
|
+
|
|
41
|
+
class Base(DeclarativeBase):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
class User(Base):
|
|
45
|
+
__tablename__ = "users"
|
|
46
|
+
|
|
47
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
48
|
+
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
49
|
+
email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
|
|
50
|
+
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
|
51
|
+
updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**规范:**
|
|
55
|
+
- **强制**使用 `Mapped` + `mapped_column`(v2.0 style)
|
|
56
|
+
- 禁止旧版 `Column`(name, Type) 写法
|
|
57
|
+
- 所有模型继承共享 `Base`
|
|
58
|
+
- 必要字段声明 `nullable=False`
|
|
59
|
+
|
|
60
|
+
## 关联关系预加载(防 N+1)
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from sqlalchemy.orm import selectinload, joinedload
|
|
64
|
+
|
|
65
|
+
# ✅ 好:selectinload 预加载
|
|
66
|
+
stmt = select(Post).options(selectinload(Post.author)).where(Post.published == True)
|
|
67
|
+
result = await db.execute(stmt)
|
|
68
|
+
|
|
69
|
+
# ✅ 好:joinedload 用于单条查询
|
|
70
|
+
stmt = select(Post).options(joinedload(Post.author)).where(Post.id == post_id)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**规范:**
|
|
74
|
+
- 批量列表查询用 `selectinload()`(发出额外 IN 查询,对列表友好)
|
|
75
|
+
- 单条查询用 `joinedload()`(JOIN 一次)
|
|
76
|
+
- **禁止**在循环中逐条访问关联属性
|
|
77
|
+
|
|
78
|
+
## 查询最佳实践
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
# ✅ 好:参数化查询(防 SQL 注入)
|
|
82
|
+
stmt = select(User).where(User.email == email)
|
|
83
|
+
|
|
84
|
+
# ❌ 坏:⚠️ 禁止裸 SQL 拼接
|
|
85
|
+
text(f"SELECT * FROM users WHERE email = '{email}'") # ← SQL 注入风险
|
|
86
|
+
|
|
87
|
+
# ✅ 好:批量更新
|
|
88
|
+
stmt = update(User).where(User.is_active == False).values(status="inactive")
|
|
89
|
+
await db.execute(stmt)
|
|
90
|
+
await db.commit()
|
|
91
|
+
```
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# 后端代码折行示例(100 列限制)
|
|
2
|
+
|
|
3
|
+
Checkstyle 配置行宽 110,折行目标 100 列。
|
|
4
|
+
|
|
5
|
+
## 方法签名
|
|
6
|
+
|
|
7
|
+
一行放得下就一行,放不下则每个参数独立一行(4 格缩进)。不与「同行放多个参数」的混合分组混淆:
|
|
8
|
+
|
|
9
|
+
```java
|
|
10
|
+
public FormDetailsDto getForm(Long appId, Long formId) { ... }
|
|
11
|
+
|
|
12
|
+
public FormDetailsDto publishForm(
|
|
13
|
+
Long appId, Long formId, PublishFormRequest request) { ... }
|
|
14
|
+
|
|
15
|
+
public FormResponseDto updateFormResponse(
|
|
16
|
+
@PathVariable Long appId,
|
|
17
|
+
@PathVariable Long formId,
|
|
18
|
+
@PathVariable Long responseId,
|
|
19
|
+
@RequestBody UpdateFormResponseRequest request,
|
|
20
|
+
@AuthenticationPrincipal User user) { ... }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Stream 管道
|
|
24
|
+
|
|
25
|
+
每个算子一行,行首 `.`,4 格缩进(相对方法体 8 格):
|
|
26
|
+
|
|
27
|
+
```java
|
|
28
|
+
var forms = formRepository.findAllByAppId(appId.getId()).stream()
|
|
29
|
+
.map(FormMapper.INSTANCE::mapToSummary)
|
|
30
|
+
.toList();
|
|
31
|
+
|
|
32
|
+
var options = response.getRecords().stream()
|
|
33
|
+
.limit(FormExcelUtils.DATASET_PAGE_SIZE)
|
|
34
|
+
.collect(Collectors.toMap(
|
|
35
|
+
record -> String.valueOf(record.get(labelField)),
|
|
36
|
+
record -> new Option(...),
|
|
37
|
+
(existing, _) -> existing));
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Builder 模式
|
|
41
|
+
|
|
42
|
+
每个 `.method()` 一行,4 格缩进(相对方法体 8 格):
|
|
43
|
+
|
|
44
|
+
```java
|
|
45
|
+
var templateContext = TemplateContext.builder()
|
|
46
|
+
.context(formulaContext)
|
|
47
|
+
.urlQueryParams(request.getUrlQueryParams())
|
|
48
|
+
.build();
|
|
49
|
+
|
|
50
|
+
var csvMapper = CsvMapper.builder()
|
|
51
|
+
.enable(CsvParser.Feature.TRIM_SPACES)
|
|
52
|
+
.addModule(new SimpleModule().addDeserializer(
|
|
53
|
+
boolean.class, new CsvBooleanDeserializer()))
|
|
54
|
+
.build();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 注解
|
|
58
|
+
|
|
59
|
+
单属性一行。多属性 `uses` 每个一行:
|
|
60
|
+
|
|
61
|
+
```java
|
|
62
|
+
@Pattern(
|
|
63
|
+
regexp = "[a-zA-Z_][a-zA-Z0-9_]*",
|
|
64
|
+
message = "字段名称只能包含字母、数字和下划线")
|
|
65
|
+
|
|
66
|
+
@Mapper(
|
|
67
|
+
config = MapStructConfig.class,
|
|
68
|
+
uses = { FormRevisionMapper.class, FormItemMapper.class, PrintConfigMapper.class })
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Constructor / new 调用
|
|
72
|
+
|
|
73
|
+
参数放不下则每行一个(4 格缩进):
|
|
74
|
+
|
|
75
|
+
```java
|
|
76
|
+
var user = new InternalUser(
|
|
77
|
+
request.getUsername(),
|
|
78
|
+
passwordEncoder.encode(request.getPassword()),
|
|
79
|
+
request.getFullName(),
|
|
80
|
+
request.getRole());
|
|
81
|
+
|
|
82
|
+
var items = importedItems.stream()
|
|
83
|
+
.map(item -> new DictionaryItem(
|
|
84
|
+
null, item.getCode(), item.getValue(), item.isDisabled(), dict.getId()))
|
|
85
|
+
.toList();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 方法调用参数换行
|
|
89
|
+
|
|
90
|
+
所有参数在 100 列内能一行放完则一行,放不完则每个参数独立一行。不与 Google 风格的「混合分组」(几个参数一行、最后一个另起一行)混淆:
|
|
91
|
+
|
|
92
|
+
```java
|
|
93
|
+
// ✓ 正确:参数少,一行放得下
|
|
94
|
+
var result = MetricAnswer.found("急诊人次", "C001", "急诊人次", 1500, "人次", dateRange);
|
|
95
|
+
|
|
96
|
+
// ✓ 正确:参数较多且一行放不下,每个参数独立一行
|
|
97
|
+
var result = MetricAnswer.foundWithGrid(
|
|
98
|
+
query.metricName(),
|
|
99
|
+
matched.code(),
|
|
100
|
+
name,
|
|
101
|
+
apiGridResults,
|
|
102
|
+
apiLastGridResults,
|
|
103
|
+
apiChainGridResults,
|
|
104
|
+
dateRange,
|
|
105
|
+
chartType,
|
|
106
|
+
unresolvedDimensions);
|
|
107
|
+
|
|
108
|
+
// ✓ 正确:静态工厂方法同理
|
|
109
|
+
return MetricAnswer.foundWithComparison(
|
|
110
|
+
query.metricName(),
|
|
111
|
+
matched.code(),
|
|
112
|
+
name,
|
|
113
|
+
result.getValue(),
|
|
114
|
+
result.getUnit(),
|
|
115
|
+
lastResults,
|
|
116
|
+
chainResults,
|
|
117
|
+
dateRange,
|
|
118
|
+
chartType);
|
|
119
|
+
|
|
120
|
+
// ✗ 错误:混合分组,一行的结尾参数和后续参数混在一起
|
|
121
|
+
return MetricAnswer.foundWithGrid(
|
|
122
|
+
query.metricName(), matched.code(), name, // ← 混在同一行
|
|
123
|
+
apiGridResults, apiLastGridResults, apiChainGridResults, // ← 混在同一行
|
|
124
|
+
dateRange, chartType); // ← 混在同一行
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## `new ResponseEntity<>()` 换行 / `if` 条件
|
|
128
|
+
|
|
129
|
+
```java
|
|
130
|
+
return new ResponseEntity<>(
|
|
131
|
+
new CustomError(Status.NOT_FOUND, ex.getMessage(), ex.getResourceInfo()),
|
|
132
|
+
HttpStatus.NOT_FOUND);
|
|
133
|
+
|
|
134
|
+
if (control instanceof ChoiceControl choiceControl
|
|
135
|
+
&& choiceControl.getOptionsSource() instanceof DatasetOptionsSource source) {
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## `.orElseThrow()` / `formatted()`
|
|
139
|
+
|
|
140
|
+
```java
|
|
141
|
+
return repository.findByIdAndAppId(id, appId).orElseThrow(
|
|
142
|
+
() -> new NotFoundException("资源不存在", new ResourceInfo("MyEntity", id)));
|
|
143
|
+
|
|
144
|
+
return "jdbc:oracle:thin:@//%s:%d/%s?connectTimeout=%d".formatted(
|
|
145
|
+
getHost(), getPort(),
|
|
146
|
+
URLEncoder.encode(getDbName(), StandardCharsets.UTF_8),
|
|
147
|
+
TIMEOUT_SECONDS * 1000);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Try-with-resources / Lambda
|
|
151
|
+
|
|
152
|
+
```java
|
|
153
|
+
try (var inputStream = file.getInputStream();
|
|
154
|
+
var reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
|
|
155
|
+
...
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
Function<FieldAssignment, TextValue> valueMapper =
|
|
159
|
+
assignment -> new TextValue(TemplateEngines.SIMPLE.evaluate(...));
|
|
160
|
+
|
|
161
|
+
components.forEach(component -> {
|
|
162
|
+
if (!existingKeys.add(component.getKey())) {
|
|
163
|
+
throw new InvalidArgumentException("key 必须唯一");
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 链式调用(MockMvc / Mockito)
|
|
169
|
+
|
|
170
|
+
```java
|
|
171
|
+
var result = mvc.perform(get("/api/v1/apps")
|
|
172
|
+
.contentType(MediaType.APPLICATION_JSON))
|
|
173
|
+
.andExpect(status().isOk())
|
|
174
|
+
.andReturn();
|
|
175
|
+
|
|
176
|
+
when(service.publishForm(
|
|
177
|
+
eq(1L), eq(2L), any(PublishFormRequest.class)))
|
|
178
|
+
.thenReturn(FormMapper.INSTANCE.mapToDetails(form));
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Text Block(三引号多行字符串)
|
|
182
|
+
|
|
183
|
+
Java 15+ 的 Text Block 用于嵌入 JSON / SQL / XML / 模板等 DSL。缩进规则依赖 `closing """` 的位置:
|
|
184
|
+
|
|
185
|
+
```java
|
|
186
|
+
// ✅ opening """ 后直接换行,内容相对 opening 行缩进 4 格
|
|
187
|
+
// ✅ closing """ 决定 stripIndent() 的公共缩进基线
|
|
188
|
+
var json = """
|
|
189
|
+
{
|
|
190
|
+
"name": "example",
|
|
191
|
+
"value": 42,
|
|
192
|
+
"items": [1, 2, 3]
|
|
193
|
+
}
|
|
194
|
+
""";
|
|
195
|
+
|
|
196
|
+
// ✅ 嵌入 SQL:closing """ 与 SQL 内容的公共缩进最左列对齐
|
|
197
|
+
var sql = """
|
|
198
|
+
SELECT u.id, u.name, r.role_name
|
|
199
|
+
FROM users u
|
|
200
|
+
JOIN user_roles r ON r.user_id = u.id
|
|
201
|
+
WHERE u.status = 'ACTIVE'
|
|
202
|
+
ORDER BY u.name
|
|
203
|
+
""";
|
|
204
|
+
|
|
205
|
+
// ✅ 配合 formatted() 做变量替换
|
|
206
|
+
var message = """
|
|
207
|
+
Hello %s,
|
|
208
|
+
Your order #%d has been confirmed.
|
|
209
|
+
""".formatted(userName, orderId);
|
|
210
|
+
|
|
211
|
+
// ✅ 空行可用 \s 占位避免 stripIndent 清空:
|
|
212
|
+
var json = """
|
|
213
|
+
{
|
|
214
|
+
"title": "test",
|
|
215
|
+
\s
|
|
216
|
+
"description": ""
|
|
217
|
+
}
|
|
218
|
+
""";
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**规范:**
|
|
222
|
+
- `"""` 必须后跟换行,**禁止在同行放内容**
|
|
223
|
+
- `closing """` 的缩进决定公共缩进基线,放在最后一行内容之下,与之缩进对齐
|
|
224
|
+
- 禁止 text block 与 `+` 拼接混用;需要变量替换统一用 `formatted()`
|
|
225
|
+
- 短字符串(≤ 100 列单行)不使用 text block,直接使用普通字符串
|
|
226
|
+
- `\s` 用于显式保留 text block 中的空行(避免 `stripIndent()` 把空行清空)
|
|
227
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Reference Contributor 模式
|
|
2
|
+
|
|
3
|
+
当删除资源前需要查询哪些其他资源引用了它时,使用 Contributor 模式。
|
|
4
|
+
|
|
5
|
+
## 定义接口
|
|
6
|
+
|
|
7
|
+
在被引用资源所属模块中定义 `@FunctionalInterface`:
|
|
8
|
+
|
|
9
|
+
```java
|
|
10
|
+
// data/service/DataObjectReferenceContributor.java
|
|
11
|
+
@FunctionalInterface
|
|
12
|
+
public interface DataObjectReferenceContributor {
|
|
13
|
+
List<ResourceSummary> findReferences(Long dataObjectId);
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
统一返回值(`base/dto/` 中定义):
|
|
18
|
+
|
|
19
|
+
```java
|
|
20
|
+
public record ResourceSummary(String type, Long id, Long name) {}
|
|
21
|
+
public record ListResourceReferencesResponse(List<ResourceSummary> references) {}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 实现贡献者
|
|
25
|
+
|
|
26
|
+
引用方模块实现接口为 `@Component`,Spring 自动收集:
|
|
27
|
+
|
|
28
|
+
```java
|
|
29
|
+
@Component
|
|
30
|
+
@RequiredArgsConstructor
|
|
31
|
+
public class PageDataObjectReferenceContributor
|
|
32
|
+
implements DataObjectReferenceContributor {
|
|
33
|
+
private final PageRepository repository;
|
|
34
|
+
|
|
35
|
+
@Override
|
|
36
|
+
public List<ResourceSummary> findReferences(Long dataObjectId) {
|
|
37
|
+
return repository
|
|
38
|
+
.findAllByTableComponentDataObjectId(dataObjectId)
|
|
39
|
+
.stream()
|
|
40
|
+
.map(page -> new ResourceSummary("Page", page.getId(), page.getName()))
|
|
41
|
+
.toList();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
多途径查找时用 `Stream.of(...).flatMap(List::stream).distinct()` 去重合并:
|
|
47
|
+
|
|
48
|
+
```java
|
|
49
|
+
@Override
|
|
50
|
+
public List<ResourceSummary> findReferences(Long datasetId) {
|
|
51
|
+
var streams = Stream.of(
|
|
52
|
+
repository.findAllByChartDatasetId(datasetId),
|
|
53
|
+
repository.findAllByTableComponentDatasetId(datasetId));
|
|
54
|
+
return streams
|
|
55
|
+
.flatMap(List::stream)
|
|
56
|
+
.map(entity -> new ResourceSummary("Type", entity.getId(), entity.getName()))
|
|
57
|
+
.distinct()
|
|
58
|
+
.toList();
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Repository 查询
|
|
63
|
+
|
|
64
|
+
继承层次中的子类型查询使用 `TREAT` 语法:
|
|
65
|
+
|
|
66
|
+
```java
|
|
67
|
+
@Query(
|
|
68
|
+
"""
|
|
69
|
+
SELECT DISTINCT p FROM Page p JOIN p.components c
|
|
70
|
+
WHERE TREAT(c AS TableComponent).customDataConfig.dataObjectId = :dataObjectId
|
|
71
|
+
""")
|
|
72
|
+
List<Page> findAllByTableComponentDataObjectId(Long dataObjectId);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 聚合查询(Owner Service)
|
|
76
|
+
|
|
77
|
+
```java
|
|
78
|
+
private final List<DataObjectReferenceContributor> referenceContributors;
|
|
79
|
+
|
|
80
|
+
public ListResourceReferencesResponse listDataObjectReferences(Long appId, Long dataObjectId) {
|
|
81
|
+
getDataObjectEntity(appId, dataObjectId); // 验证存在 + 多租户
|
|
82
|
+
var references = referenceContributors.stream()
|
|
83
|
+
.flatMap(contributor -> contributor.findReferences(dataObjectId).stream())
|
|
84
|
+
.toList();
|
|
85
|
+
return new ListResourceReferencesResponse(references);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## REST 端点
|
|
90
|
+
|
|
91
|
+
```java
|
|
92
|
+
@GetMapping("/{objectId}/references")
|
|
93
|
+
public ListResourceReferencesResponse listDataObjectReferences(
|
|
94
|
+
@PathVariable Long appId, @PathVariable Long objectId) {
|
|
95
|
+
return service.listDataObjectReferences(appId, objectId);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# 后端编码快速参考
|
|
2
|
+
|
|
3
|
+
按需加载。仅当你需要编写对应组件类型时阅读相关章节。
|
|
4
|
+
|
|
5
|
+
> 已安装子维度的规范,通过该维度的 `{value}.md` 文件阅读。本页只包含语言通用的核心规范。
|
|
6
|
+
>
|
|
7
|
+
> **跨维度规范(适用所有后端代码):**
|
|
8
|
+
> - [API 规范](api-spec.md) — RESTful 命名、统一响应体、OpenAPI、版本策略
|
|
9
|
+
> - [依赖管理规范](dependency-management.md) — Version Catalog、版本一致性、CVE
|
|
10
|
+
> - [异常处理深度规范](exception-handling.md) — 异常层次、错误码、全局处理
|
|
11
|
+
> - [安全红线](security-redlines.md) — P0/P1 安全规则(必须遵守)
|
|
12
|
+
|
|
13
|
+
## 速查
|
|
14
|
+
|
|
15
|
+
| 场景 | 决策 |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| 字符串格式化 | 用 `formatted()`,不用 `+` 拼接 |
|
|
18
|
+
| 类型分发 | 用多态,不用 `instanceof` 链 |
|
|
19
|
+
| 控件能力查询 | 基类声明 `abstract boolean supportsXxx()`,子类按能力返回 `true/false` |
|
|
20
|
+
| 领域事件 / POJO | `@Getter @AllArgsConstructor`,不手写 getter/constructor |
|
|
21
|
+
| 参数顺序 | `appId` → 父级 ID → 自身 ID → name/描述 |
|
|
22
|
+
| 字段注释 | Model/Entity/Event 加 `/** */`,DTO/Record 不加 |
|
|
23
|
+
| 日志实体 | 继承 `LogEntry`(SINGLE_TABLE),不直接继承基类 |
|
|
24
|
+
| 弃用 API | 编译警告中的 `@Deprecated` API 在同一次 PR 中替换为新 API |
|
|
25
|
+
| 多行字符串 | 用 Text Block `"""..."""` + `formatted()` 嵌入 JSON/SQL/XML,禁止 `+` 拼接;详见 [code-wrapping.md](examples/code-wrapping.md) |
|
|
26
|
+
|
|
27
|
+
## 代码风格
|
|
28
|
+
|
|
29
|
+
### 通用约定
|
|
30
|
+
|
|
31
|
+
- 变量命名有意义,禁止单字母(循环计数器 `i`/`j`/`k` 除外)
|
|
32
|
+
- 局部变量用 `var`
|
|
33
|
+
- 字符串格式化用 `formatted()`,不用 `+` 拼接
|
|
34
|
+
- 多态替代 `instanceof` 链做类型分发
|
|
35
|
+
- 100 列折行,运算符/`.`/`::` 放行首。适用范围如下:
|
|
36
|
+
- **代码结构**(方法调用、签名、表达式)严格执行 100 列
|
|
37
|
+
- **Javadoc / 注释文本**以可读性优先,不硬断中文句子;仅单行 `/** ... */` 标记超长时拆成多行
|
|
38
|
+
- **方法调用参数换行**:所有参数能在列宽内一行放完则一行,放不完则每个参数独立一行(不混合分组);详见 [code-wrapping.md](examples/code-wrapping.md)「方法调用参数换行」
|
|
39
|
+
- 类内字段按用途逻辑分组,组间空行分隔,**禁止使用 `// ======`(或其他重复符号装饰)做视觉分隔线注释**。例如应避免 `// ========== 维度信息 ==========` 这种写法
|
|
40
|
+
- 注释与代码逻辑块之间:相邻的两个 `// 注释 + 代码块` 之间用空行分隔,使每个逻辑块保持独立。
|
|
41
|
+
```java
|
|
42
|
+
// ✅ 正确:逻辑块之间有空行
|
|
43
|
+
// PROPORTION → 饼图
|
|
44
|
+
if (queryIntent == QueryIntent.PROPORTION) {
|
|
45
|
+
return ChartType.PIE_CHART;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// GROUP_BY → 柱状图
|
|
49
|
+
if (queryIntent == QueryIntent.GROUP_BY) {
|
|
50
|
+
return ChartType.BAR_CHART;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
```java
|
|
54
|
+
// ❌ 错误:缺少分隔空行,两个逻辑块粘连
|
|
55
|
+
// PROPORTION → 饼图
|
|
56
|
+
if (queryIntent == QueryIntent.PROPORTION) {
|
|
57
|
+
return ChartType.PIE_CHART;
|
|
58
|
+
}
|
|
59
|
+
// GROUP_BY → 柱状图
|
|
60
|
+
if (queryIntent == QueryIntent.GROUP_BY) {
|
|
61
|
+
return ChartType.BAR_CHART;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 控件能力声明模式(Capability Pattern)
|
|
66
|
+
|
|
67
|
+
当实体层次(如 `FormControl` → `TextControl` / `NumberControl` / ...)需要按子类暴露能力时,在抽象基类中声明抽象方法,各子类返回 `true` / `false`:
|
|
68
|
+
|
|
69
|
+
```java
|
|
70
|
+
// FormControl.java — 基类声明
|
|
71
|
+
public abstract boolean supportsImporting();
|
|
72
|
+
|
|
73
|
+
// TextControl.java — 子类声明能力
|
|
74
|
+
@Override public boolean supportsImporting() { return true; }
|
|
75
|
+
|
|
76
|
+
// 其他子类按需返回 false 或 true
|
|
77
|
+
@Override public boolean supportsImporting() { return false; }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**适用场景:** 基类有多个子类,且某个操作只在部分子类上有意义。
|
|
81
|
+
**替代方案:** 用 `instanceof` 判断 → ✗ 不推荐,违反开闭原则,每加一个子类就要改判断链。
|
|
82
|
+
**优势:** 开闭原则 — 新增子类只需在自己的类中覆盖方法;新增能力只需在基类加方法 + 各子类实现。
|
|
83
|
+
|
|
84
|
+
## 字段注释规则
|
|
85
|
+
|
|
86
|
+
| 文件类型 | 需要 `/** */` 字段注释 |
|
|
87
|
+
|---------|----------------------|
|
|
88
|
+
| Model / Entity / Event | ✓ 是 |
|
|
89
|
+
| DTO / Record | ✗ 否 |
|
|
90
|
+
|
|
91
|
+
## 领域事件 / POJO
|
|
92
|
+
|
|
93
|
+
### 规则
|
|
94
|
+
|
|
95
|
+
- 使用 Lombok `@Getter @AllArgsConstructor`,不手写 getter/constructor
|
|
96
|
+
- `private final` 字段,保持不可变
|
|
97
|
+
- 参数顺序:`appId` → 父级 ID → 自身 ID → name/描述
|
|
98
|
+
- 事件配合 `@EventListener` 实现解耦,Service 层 `publishEvent(event)` 而不是直接调用 Repository
|
|
99
|
+
|
|
100
|
+
### 示例
|
|
101
|
+
|
|
102
|
+
```java
|
|
103
|
+
@Getter @AllArgsConstructor
|
|
104
|
+
public class FormDeletedEvent {
|
|
105
|
+
private final Long appId;
|
|
106
|
+
private final Long formId;
|
|
107
|
+
private final String formTitle;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 常见坑
|
|
112
|
+
|
|
113
|
+
| 场景 | 问题 | 正确做法 |
|
|
114
|
+
|------|------|---------|
|
|
115
|
+
| `instanceof` 分发 | Service 层用 `instanceof` 链判断所有子类型 | 优先在实体/领域模型中用多态 |
|
|
116
|
+
| 控件能力硬编码 | 用 `if (control instanceof TextControl)` 判断是否支持某能力 | 基类加 `abstract boolean supportsXxx()`,子类按能力覆盖 |
|
|
117
|
+
| 弃用 API | 编译出现 `@Deprecated` 警告但不处理 | 同一次 PR 中替换为新 API |
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# 示例:FastAPI CRUD Router
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
# app/api/v1/users.py
|
|
5
|
+
from fastapi import APIRouter, Depends, Query, HTTPException
|
|
6
|
+
from app.schemas.users import (
|
|
7
|
+
CreateUserRequest,
|
|
8
|
+
UpdateUserRequest,
|
|
9
|
+
UserResponse,
|
|
10
|
+
UserListResponse,
|
|
11
|
+
)
|
|
12
|
+
from app.services.users import UserService
|
|
13
|
+
|
|
14
|
+
router = APIRouter(prefix="/api/v1/users", tags=["users"])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.get("/", response_model=UserListResponse)
|
|
18
|
+
async def list_users(
|
|
19
|
+
skip: int = Query(0, ge=0),
|
|
20
|
+
limit: int = Query(20, ge=1, le=100),
|
|
21
|
+
user_service: UserService = Depends(),
|
|
22
|
+
):
|
|
23
|
+
"""获取用户列表"""
|
|
24
|
+
users, total = await user_service.list_users(skip=skip, limit=limit)
|
|
25
|
+
return UserListResponse(items=users, total=total, skip=skip, limit=limit)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@router.get("/{user_id}", response_model=UserResponse)
|
|
29
|
+
async def get_user(
|
|
30
|
+
user_id: int,
|
|
31
|
+
user_service: UserService = Depends(),
|
|
32
|
+
):
|
|
33
|
+
"""获取单个用户"""
|
|
34
|
+
user = await user_service.get_user(user_id)
|
|
35
|
+
if user is None:
|
|
36
|
+
raise HTTPException(status_code=404, detail="User not found")
|
|
37
|
+
return user
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@router.post("/", response_model=UserResponse, status_code=201)
|
|
41
|
+
async def create_user(
|
|
42
|
+
request: CreateUserRequest,
|
|
43
|
+
user_service: UserService = Depends(),
|
|
44
|
+
):
|
|
45
|
+
"""创建用户"""
|
|
46
|
+
return await user_service.create_user(request)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.put("/{user_id}", response_model=UserResponse)
|
|
50
|
+
async def update_user(
|
|
51
|
+
user_id: int,
|
|
52
|
+
request: UpdateUserRequest,
|
|
53
|
+
user_service: UserService = Depends(),
|
|
54
|
+
):
|
|
55
|
+
"""更新用户"""
|
|
56
|
+
user = await user_service.update_user(user_id, request)
|
|
57
|
+
if user is None:
|
|
58
|
+
raise HTTPException(status_code=404, detail="User not found")
|
|
59
|
+
return user
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@router.delete("/{user_id}", status_code=204)
|
|
63
|
+
async def delete_user(
|
|
64
|
+
user_id: int,
|
|
65
|
+
user_service: UserService = Depends(),
|
|
66
|
+
):
|
|
67
|
+
"""删除用户"""
|
|
68
|
+
deleted = await user_service.delete_user(user_id)
|
|
69
|
+
if not deleted:
|
|
70
|
+
raise HTTPException(status_code=404, detail="User not found")
|
|
71
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 示例:Pydantic Schema
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
# app/schemas/user.py
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pydantic import BaseModel, Field, EmailStr
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from app.models.user import UserRole
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ── Request Schemas ──
|
|
12
|
+
|
|
13
|
+
class CreateUserRequest(BaseModel):
|
|
14
|
+
name: str = Field(..., min_length=1, max_length=100, description="用户名")
|
|
15
|
+
email: EmailStr = Field(..., description="邮箱")
|
|
16
|
+
password: str = Field(..., min_length=8, max_length=128, description="密码")
|
|
17
|
+
role: UserRole = Field(default=UserRole.USER, description="角色")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UpdateUserRequest(BaseModel):
|
|
21
|
+
name: Optional[str] = Field(None, min_length=1, max_length=100)
|
|
22
|
+
email: Optional[EmailStr] = None
|
|
23
|
+
role: Optional[UserRole] = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ── Response Schemas ──
|
|
27
|
+
|
|
28
|
+
class UserResponse(BaseModel):
|
|
29
|
+
id: int
|
|
30
|
+
name: str
|
|
31
|
+
email: str
|
|
32
|
+
role: UserRole
|
|
33
|
+
is_active: bool
|
|
34
|
+
created_at: datetime
|
|
35
|
+
updated_at: datetime
|
|
36
|
+
|
|
37
|
+
model_config = {"from_attributes": True} # ORM 模式
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class UserListResponse(BaseModel):
|
|
41
|
+
items: list[UserResponse]
|
|
42
|
+
total: int
|
|
43
|
+
skip: int
|
|
44
|
+
limit: int
|
|
45
|
+
```
|