create-dwy 0.8.0 → 0.9.1
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/package.json +1 -1
- package/src/create.js +50 -3
- package/src/utils.js +8 -0
- package/templates/claude-global/CLAUDE.md +81 -6
- package/templates/claude-global/rules/dwy-postgres.md +89 -0
- package/templates/claude-global/rules/dwy-python-backend.md +492 -0
- package/templates/claude-global/rules/dwy-python-core.md +94 -634
- package/templates/claude-global/rules/dwy-python-testing.md +115 -13
- package/templates/claude-global/rules/dwy-redis.md +88 -0
- package/templates/claude-global/rules/dwy-vue-core.md +341 -645
- package/templates/claude-global/rules/dwy-vue-pinia.md +47 -34
- package/templates/claude-global/rules/dwy-vue-testing.md +120 -55
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +1 -1
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +1 -1
- package/templates/claude-global/skills/dwy-dolphindb/SKILL.md +22 -15
- package/templates/claude-global/skills/dwy-dolphindb/evals/evals.json +23 -0
- package/templates/claude-global/skills/dwy-dolphindb/references/dolphindb-rules.md +100 -1
- package/templates/claude-global/skills/dwy-ekit/SKILL.md +142 -276
- package/templates/claude-global/skills/dwy-eui/SKILL.md +264 -182
- package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +117 -129
- package/templates/claude-global/skills/dwy-git-commit/SKILL.md +125 -0
- package/templates/claude-global/skills/dwy-publish/SKILL.md +1 -1
- package/templates/claude-global/{rules/dwy-tech-stack.md → skills/dwy-tech-stack/SKILL.md} +4 -3
- package/templates/project/fullstack-monorepo/CLAUDE.md.hbs +81 -0
- package/templates/project/fullstack-monorepo/_env.example.hbs +24 -0
- package/templates/project/fullstack-monorepo/_gitignore +37 -0
- package/templates/project/fullstack-monorepo/backend/.python-version +1 -0
- package/templates/project/fullstack-monorepo/backend/CLAUDE.md.hbs +68 -0
- package/templates/project/fullstack-monorepo/backend/Dockerfile.dev +23 -0
- package/templates/project/fullstack-monorepo/backend/alembic/env.py.hbs +68 -0
- package/templates/project/fullstack-monorepo/backend/alembic/script.py.mako +26 -0
- package/templates/project/fullstack-monorepo/backend/alembic.ini.hbs +43 -0
- package/templates/project/fullstack-monorepo/backend/packages/dwy-shared/pyproject.toml +16 -0
- package/templates/project/fullstack-monorepo/backend/packages/dwy-shared/src/dwy_shared/__init__.py +12 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/pyproject.toml.hbs +20 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/__init__.py.hbs +40 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/config.py +9 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/models.py.hbs +17 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/protocols.py +14 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/router.py.hbs +22 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/schemas.py +21 -0
- package/templates/project/fullstack-monorepo/backend/packages/{{scopePrefix}}-{{initialDomain}}/src/{{scopePrefix}}_{{initialDomain}}/service.py +11 -0
- package/templates/project/fullstack-monorepo/backend/pyproject.toml.hbs +84 -0
- package/templates/project/fullstack-monorepo/backend/src/app/__init__.py +3 -0
- package/templates/project/fullstack-monorepo/backend/src/app/config.py.hbs +21 -0
- package/templates/project/fullstack-monorepo/backend/src/app/exceptions.py +26 -0
- package/templates/project/fullstack-monorepo/backend/src/app/factories.py +16 -0
- package/templates/project/fullstack-monorepo/backend/src/app/infra/__init__.py +1 -0
- package/templates/project/fullstack-monorepo/backend/src/app/main.py.hbs +65 -0
- package/templates/project/fullstack-monorepo/backend/src/app/middleware/__init__.py +1 -0
- package/templates/project/fullstack-monorepo/backend/tests/__init__.py +0 -0
- package/templates/project/fullstack-monorepo/backend/tests/conftest.py.hbs +42 -0
- package/templates/project/fullstack-monorepo/dev.sh.hbs +83 -0
- package/templates/project/fullstack-monorepo/docker-compose.dev.yml.hbs +36 -0
- package/templates/project/fullstack-monorepo/frontend/CLAUDE.md.hbs +35 -0
- package/templates/project/fullstack-monorepo/frontend/Dockerfile +18 -0
- package/templates/project/fullstack-monorepo/frontend/_npmrc +3 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/index.html.hbs +13 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/package.json.hbs +31 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/App.vue.hbs +10 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/api/client.ts.hbs +11 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/features/{{initialDomain}}/.gitkeep +0 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/index.css +12 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/main.ts +17 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/router/index.ts.hbs +24 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/stores/.gitkeep +0 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/types/.gitkeep +0 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/src/views/HomeView.vue.hbs +36 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/tsconfig.json +7 -0
- package/templates/project/fullstack-monorepo/frontend/apps/web/vite.config.ts.hbs +27 -0
- package/templates/project/fullstack-monorepo/frontend/nginx.conf +11 -0
- package/templates/project/fullstack-monorepo/frontend/package.json.hbs +13 -0
- package/templates/project/fullstack-monorepo/frontend/packages/core/package.json.hbs +15 -0
- package/templates/project/fullstack-monorepo/frontend/packages/core/src/index.ts +3 -0
- package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/package.json.hbs +14 -0
- package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/src/index.ts.hbs +16 -0
- package/templates/project/fullstack-monorepo/frontend/packages/features/{{initialDomain}}/src/pages/IndexPage.vue.hbs +9 -0
- package/templates/project/fullstack-monorepo/frontend/packages/ui/package.json.hbs +15 -0
- package/templates/project/fullstack-monorepo/frontend/packages/ui/src/index.ts +3 -0
- package/templates/project/fullstack-monorepo/frontend/pnpm-workspace.yaml.hbs +23 -0
- package/templates/project/fullstack-monorepo/frontend/tsconfig.base.json.hbs +23 -0
- package/templates/claude-global/rules/dwy-api-design.md +0 -110
- package/templates/claude-global/rules/dwy-git-commit-convention.md +0 -82
- package/templates/claude-global/rules/dwy-git-security.md +0 -53
- package/templates/claude-global/rules/dwy-payload-limits.md +0 -186
- package/templates/claude-global/rules/dwy-prefer-open-source.md +0 -32
- package/templates/claude-global/rules/dwy-project-structure.md +0 -181
- package/templates/claude-global/rules/dwy-python-fastapi.md +0 -272
- package/templates/claude-global/rules/dwy-python-orm.md +0 -61
- package/templates/claude-global/rules/dwy-python-security.md +0 -636
- package/templates/claude-global/rules/dwy-server-security.md +0 -518
- package/templates/claude-global/rules/dwy-vue-api.md +0 -146
- package/templates/claude-global/skills/dwy-ekit/references/data.md +0 -135
- package/templates/claude-global/skills/dwy-ekit/references/form.md +0 -68
- package/templates/claude-global/skills/dwy-ekit/references/request.md +0 -183
- package/templates/claude-global/skills/dwy-ekit/references/ui.md +0 -109
- package/templates/claude-global/skills/dwy-vue-core/SKILL.md +0 -890
- /package/templates/{claude-global/commands → project/fullstack-monorepo/backend/alembic/versions}/.gitkeep +0 -0
|
@@ -1,75 +1,88 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Pinia Setup Store
|
|
2
|
+
description: Pinia Setup Store 状态管理规范(强制 Setup Store、storeToRefs、按业务聚合)
|
|
3
3
|
paths:
|
|
4
|
+
- "**/store.ts"
|
|
4
5
|
- "**/stores/**/*.ts"
|
|
5
|
-
- "**/store/**/*.ts"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Pinia 状态管理规范
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
适用于 Vue 3 + Pinia 项目。
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 一、强制使用 Setup Store
|
|
13
15
|
|
|
14
16
|
```typescript
|
|
15
|
-
//
|
|
17
|
+
// 正例:Setup Store
|
|
18
|
+
import { defineStore } from 'pinia'
|
|
19
|
+
import { ref, computed } from 'vue'
|
|
20
|
+
|
|
16
21
|
export const useAuthStore = defineStore('auth', () => {
|
|
17
|
-
const token = ref<string | null>(
|
|
22
|
+
const token = ref<string | null>(null)
|
|
18
23
|
const user = ref<UserInfo | null>(null)
|
|
19
24
|
|
|
20
25
|
const isLoggedIn = computed(() => !!token.value)
|
|
21
26
|
|
|
22
27
|
async function login(username: string, password: string) {
|
|
23
|
-
const res = await apiLogin(username, password)
|
|
24
|
-
token.value = res.
|
|
25
|
-
localStorage.setItem('token', token.value)
|
|
28
|
+
const res = await apiLogin({ username, password })
|
|
29
|
+
token.value = res.access_token
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
function logout() {
|
|
29
33
|
token.value = null
|
|
30
34
|
user.value = null
|
|
31
|
-
localStorage.removeItem('token')
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
return { token, user, isLoggedIn, login, logout }
|
|
35
38
|
})
|
|
36
39
|
|
|
37
|
-
//
|
|
40
|
+
// 反例:禁止 Options Store
|
|
38
41
|
export const useAuthStore = defineStore('auth', {
|
|
39
42
|
state: () => ({ token: '' }),
|
|
40
|
-
actions: { login() {} }
|
|
43
|
+
actions: { login() {} },
|
|
41
44
|
})
|
|
42
45
|
```
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 二、Store 设计原则
|
|
45
50
|
|
|
46
51
|
| 原则 | 说明 |
|
|
47
52
|
|------|------|
|
|
48
|
-
| 单一职责 | 一个 store 管理一个业务域(auth
|
|
49
|
-
|
|
|
50
|
-
| 异步操作放 store | API 调用封装在 store
|
|
51
|
-
|
|
|
53
|
+
| 单一职责 | 一个 store 管理一个业务域(auth / menu / users),**禁止**把多个业务塞同一个 store |
|
|
54
|
+
| 不在组件中直接改 state | 必须通过 store 方法修改,**禁止** `store.xxx = yyy` |
|
|
55
|
+
| 异步操作放 store | API 调用封装在 store 方法中,组件不直接调 API(除非纯展示页) |
|
|
56
|
+
| 业务聚合 | store 文件放在对应业务模块目录(`modules/users/store.ts`),**禁止**顶级 `stores/` 把不同业务混放 |
|
|
57
|
+
| 持久化 | 需要持久化的状态走 ekit storage / `useStorage`(具体 API 查 `dwy-ekit` skill),**禁止**直接 `localStorage` |
|
|
58
|
+
| HTTP 调用 | 走全局 ekit `createRequest` 客户端,**禁止**自建 axios 实例 |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 三、storeToRefs 解构
|
|
52
63
|
|
|
53
64
|
```typescript
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
65
|
+
import { storeToRefs } from 'pinia'
|
|
66
|
+
|
|
67
|
+
// 正例:响应式值用 storeToRefs,方法直接解构
|
|
68
|
+
const store = useAuthStore()
|
|
69
|
+
const { token, user, isLoggedIn } = storeToRefs(store)
|
|
70
|
+
const { login, logout } = store
|
|
58
71
|
|
|
59
|
-
//
|
|
60
|
-
const {
|
|
72
|
+
// 反例:直接解构丢失响应性
|
|
73
|
+
const { token, user } = store
|
|
61
74
|
```
|
|
62
75
|
|
|
63
76
|
---
|
|
64
77
|
|
|
65
|
-
##
|
|
66
|
-
|
|
67
|
-
|
|
|
68
|
-
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
## 四、违规检测清单
|
|
79
|
+
|
|
80
|
+
| 检查项 | 违规模式 | 严重程度 |
|
|
81
|
+
|--------|---------|---------|
|
|
82
|
+
| Options Store | `defineStore(id, { state, actions, getters })` | **致命 → STOP** |
|
|
83
|
+
| 组件直接改 state | 组件中 `store.xxx = yyy` 直接赋值 | 高 |
|
|
84
|
+
| 解构丢响应性 | `const { x } = store` 后在模板里用 `x` | 高 |
|
|
85
|
+
| store 跨业务塞功能 | 一个 store 同时管 auth + menu + user 等不相关业务 | 高 |
|
|
86
|
+
| 直接 localStorage | store 中直接 `localStorage.setItem(...)`,未走 ekit storage | 高 |
|
|
87
|
+
| 自建 HTTP 客户端 | store 中 `axios.create()` / `fetch(...)`,未走 ekit `createRequest` | 高 |
|
|
88
|
+
| 顶级 stores 散落 | 顶级 `stores/` 目录把不同业务 store 混放,未按业务聚合 | 高 |
|
|
@@ -1,39 +1,76 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Vitest
|
|
2
|
+
description: Vue Vitest 测试规范(目录组织、技术栈、AAA 结构、打包隔离)
|
|
3
3
|
paths:
|
|
4
4
|
- "**/*.test.ts"
|
|
5
5
|
- "**/*.spec.ts"
|
|
6
6
|
- "**/tests/**/*.ts"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
# Vitest
|
|
9
|
+
# Vue Vitest 测试规范
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
适用于 Vue 3 + Vitest + @vue/test-utils + happy-dom 项目。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 一、测试目录组织(强制)
|
|
16
|
+
|
|
17
|
+
### 核心约束
|
|
18
|
+
|
|
19
|
+
**测试目录与业务源码(`src/`)并列、独立存放。** 测试文件**禁止**混入 `src/`。
|
|
12
20
|
|
|
13
21
|
### 强制规则
|
|
14
|
-
- 使用 **Vitest** 作为测试框架,配合 **@vue/test-utils** + **happy-dom**
|
|
15
|
-
- 测试文件**必须放在项目根目录的 `tests/` 目录**,按模块镜像 `src/` 结构,**禁止**放在 `src/` 内
|
|
16
|
-
- `tsconfig.app.json` 的 `include` **只包含 `src/`**,确保打包不携带测试文件
|
|
17
|
-
- 测试文件命名:`*.test.ts`
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
- 测试根目录固定为 `tests/`,与 `src/` 平级
|
|
24
|
+
- **禁止**在 `src/` 中放任何 `*.test.ts` / `*.spec.ts` / `__tests__/` 文件
|
|
25
|
+
- 测试目录内部结构**镜像**源码业务聚合结构(见 `dwy-vue-core` rule 第一节)
|
|
26
|
+
- 测试文件命名:`*.test.ts`(vitest 默认发现规则)
|
|
27
|
+
- 测试函数命名:描述行为而非实现
|
|
28
|
+
- `vitest.config.ts` **独立文件**,不与 `vite.config.ts` 合并;显式声明 `test.include`
|
|
29
|
+
- `tsconfig.app.json` 的 `include` **只包含 `src/`**,**禁止**包含 `tests/`,确保打包不携带测试代码
|
|
30
|
+
- 测试相关依赖(`vitest` / `@vue/test-utils` / `happy-dom` 等)放 `devDependencies`,**禁止**进生产依赖
|
|
31
|
+
- 测试文件中通过 `@` 别名导入源码,**禁止**用 `../../../src/` 相对路径回溯
|
|
20
32
|
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
### 标准目录结构(参考)
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
project/
|
|
37
|
+
├── src/
|
|
38
|
+
│ ├── modules/
|
|
39
|
+
│ │ ├── users/
|
|
40
|
+
│ │ │ ├── api.ts
|
|
41
|
+
│ │ │ ├── store.ts
|
|
42
|
+
│ │ │ ├── views/UserListView.vue
|
|
43
|
+
│ │ │ └── components/UserCard.vue
|
|
44
|
+
│ │ └── orders/
|
|
45
|
+
│ ├── shared/
|
|
46
|
+
│ └── core/
|
|
47
|
+
├── tests/ # 测试根目录(与 src/ 并列)
|
|
48
|
+
│ ├── modules/
|
|
49
|
+
│ │ ├── users/
|
|
50
|
+
│ │ │ ├── api.test.ts
|
|
51
|
+
│ │ │ ├── store.test.ts
|
|
52
|
+
│ │ │ └── UserCard.test.ts
|
|
53
|
+
│ │ └── orders/
|
|
54
|
+
│ └── shared/
|
|
55
|
+
├── vitest.config.ts
|
|
56
|
+
├── tsconfig.app.json # include 只含 src/
|
|
57
|
+
└── package.json
|
|
23
58
|
```
|
|
24
59
|
|
|
25
|
-
|
|
60
|
+
> 具体目录布局由项目结构决定(单包、monorepo 子包、扁平 `src/views/` 等),AI 自行判断;唯一不变的是"测试目录独立、与源码并列"。
|
|
26
61
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 二、技术栈
|
|
65
|
+
|
|
66
|
+
- **Vitest** + **@vue/test-utils** + **happy-dom**
|
|
67
|
+
- 测试 API 请求时用 Vitest 内置 mock 或 `msw`,**禁止**真发起网络
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pnpm add -D vitest @vue/test-utils happy-dom
|
|
34
71
|
```
|
|
35
72
|
|
|
36
|
-
### vitest.config.ts
|
|
73
|
+
### vitest.config.ts
|
|
37
74
|
|
|
38
75
|
```typescript
|
|
39
76
|
import { fileURLToPath } from 'node:url'
|
|
@@ -43,9 +80,7 @@ import vue from '@vitejs/plugin-vue'
|
|
|
43
80
|
export default defineConfig({
|
|
44
81
|
plugins: [vue()],
|
|
45
82
|
resolve: {
|
|
46
|
-
alias: {
|
|
47
|
-
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
48
|
-
},
|
|
83
|
+
alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) },
|
|
49
84
|
},
|
|
50
85
|
test: {
|
|
51
86
|
environment: 'happy-dom',
|
|
@@ -54,49 +89,79 @@ export default defineConfig({
|
|
|
54
89
|
})
|
|
55
90
|
```
|
|
56
91
|
|
|
57
|
-
###
|
|
92
|
+
### tsconfig.app.json
|
|
58
93
|
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
|
97
|
+
}
|
|
59
98
|
```
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
99
|
+
|
|
100
|
+
### package.json 脚本
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"scripts": {
|
|
105
|
+
"test": "vitest",
|
|
106
|
+
"test:run": "vitest run"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
65
109
|
```
|
|
66
110
|
|
|
67
|
-
|
|
111
|
+
---
|
|
68
112
|
|
|
69
|
-
|
|
70
|
-
// ✅ 使用 @ 别名
|
|
71
|
-
import { formatBytes } from '@/utils/format'
|
|
72
|
-
import { useAuthStore } from '@/stores/auth'
|
|
113
|
+
## 三、测试编写规范
|
|
73
114
|
|
|
74
|
-
|
|
75
|
-
import { formatBytes } from '../../src/utils/format'
|
|
76
|
-
```
|
|
115
|
+
### 强制规则
|
|
77
116
|
|
|
78
|
-
|
|
117
|
+
- 测试结构遵循 **Arrange-Act-Assert**
|
|
118
|
+
- 一个测试只验证**一个行为**,**禁止**一个 test 函数堆多个不相关断言
|
|
119
|
+
- 断言必须具体(字段值),**禁止**只断言"不报错"
|
|
120
|
+
- 必须覆盖:成功路径、失败路径、边界值
|
|
121
|
+
- 测试中**必须**用 `@` 别名导入源码
|
|
79
122
|
|
|
80
|
-
|
|
123
|
+
```typescript
|
|
124
|
+
// tests/modules/users/store.test.ts
|
|
125
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
126
|
+
import { setActivePinia, createPinia } from 'pinia'
|
|
127
|
+
import { useAuthStore } from '@/modules/users/store'
|
|
81
128
|
|
|
82
|
-
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
```
|
|
129
|
+
describe('useAuthStore', () => {
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
setActivePinia(createPinia())
|
|
132
|
+
})
|
|
87
133
|
|
|
88
|
-
|
|
134
|
+
it('isLoggedIn 在 token 为 null 时返回 false', () => {
|
|
135
|
+
// Arrange
|
|
136
|
+
const store = useAuthStore()
|
|
89
137
|
|
|
90
|
-
|
|
138
|
+
// Act + Assert
|
|
139
|
+
expect(store.isLoggedIn).toBe(false)
|
|
140
|
+
})
|
|
91
141
|
|
|
142
|
+
it('logout 后清空 token 与 user', () => {
|
|
143
|
+
const store = useAuthStore()
|
|
144
|
+
store.$patch({ token: 'abc', user: { uuid: 'u1' } as any })
|
|
145
|
+
|
|
146
|
+
store.logout()
|
|
147
|
+
|
|
148
|
+
expect(store.token).toBeNull()
|
|
149
|
+
expect(store.user).toBeNull()
|
|
150
|
+
})
|
|
151
|
+
})
|
|
92
152
|
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 四、违规检测清单
|
|
157
|
+
|
|
158
|
+
| 检查项 | 违规模式 | 严重程度 |
|
|
159
|
+
|--------|---------|---------|
|
|
160
|
+
| 测试与源码混放 | 测试文件放 `src/` 内(`src/utils/format.test.ts` / `src/**/__tests__/`) | 高 |
|
|
161
|
+
| 打包污染 | `tsconfig.app.json` 的 `include` 包含 `tests/` | 高 |
|
|
162
|
+
| 相对路径回溯 | 测试中 `import ... from '../../../src/...'` 而非 `@/` 别名 | 中 |
|
|
163
|
+
| 测试目录不规范 | 项目无独立 `tests/` 目录或 `vitest.config.ts` 未显式声明 `include` | 高 |
|
|
164
|
+
| 测试无断言 | 只调用接口不断言响应字段 | 高 |
|
|
165
|
+
| 测试依赖泄漏 | `vitest` / `@vue/test-utils` 等出现在 `dependencies` | 高 |
|
|
166
|
+
| 真发起网络 | 测试中直接调真实后端 API,未 mock | 高 |
|
|
167
|
+
| 测试镜像断裂 | 测试目录结构与 `src/` 业务聚合结构不对应,难以定位 | 中 |
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
| `server_tokens` | `off` | medium |
|
|
36
36
|
| `access_log` | 已开启 | **high**(用户特别要求) |
|
|
37
37
|
| `error_log` | 已开启,级别 ≥ warn | high |
|
|
38
|
-
| `client_max_body_size` | 显式配置,业务非上传 ≤ 10m
|
|
38
|
+
| `client_max_body_size` | 显式配置,业务非上传 ≤ 10m | medium |
|
|
39
39
|
| `limit_req_zone` 已定义 | 至少 1 个 zone(防 CC 攻击 / 暴力请求) | high |
|
|
40
40
|
| `limit_req` 应用到敏感路由 | 登录 / 注册 / 验证码 / 高频 API 必须有 | **critical** |
|
|
41
41
|
| `limit_conn_zone` + `limit_conn` | 限制同 IP 高并发连接 | medium |
|
|
@@ -52,7 +52,7 @@ else
|
|
|
52
52
|
echo "$NGINX_DUMP" | grep -E "^[[:space:]]*(access_log|error_log)" | head -10
|
|
53
53
|
|
|
54
54
|
echo ""
|
|
55
|
-
echo "--- client_max_body_size (
|
|
55
|
+
echo "--- client_max_body_size (业务非上传 ≤ 10m) ---"
|
|
56
56
|
echo "$NGINX_DUMP" | grep -E "^[[:space:]]*client_max_body_size" || echo "[!] 未显式配置 client_max_body_size"
|
|
57
57
|
|
|
58
58
|
echo ""
|
|
@@ -3,12 +3,15 @@ name: dwy-dolphindb
|
|
|
3
3
|
description: >
|
|
4
4
|
DolphinDB 开发规范与最佳实践审查。当用户要求生成、修改、审查或优化涉及
|
|
5
5
|
DolphinDB 的 Python/SQL 代码、脚本拼接、批量写入、查询优化、连接管理、分区
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
裁剪、分区方案设计(OLAP/TSDB/PKEY 引擎单分区大小估算、范围/值/哈希分区选型)、
|
|
7
|
+
STRING/BLOB/SYMBOL 字段长度限制、数据类型转换、Docker 配置或测试 Mock 时,
|
|
8
|
+
**必须**使用此 skill。即使用户没有明确提到 "DolphinDB",但只要代码中出现
|
|
9
|
+
dolphindb、run_ddb、loadTable、append!、dropPartition、DBConnectionPool、
|
|
10
|
+
DDBSession、tickDB、factorDB、addRangePartitions、newValuePartitionPolicy
|
|
11
|
+
等关键字,或涉及 TSDB 分区、时间序列数据库存储、金融 tick 数据/因子数据写入,
|
|
12
|
+
也要触发此 skill。用于确保代码符合社区版 8GB 内存限制、2 节点集群限制、
|
|
13
|
+
分区裁剪(包括禁止链式比较 `a <= col <= b`)、输入校验、批量写入 ≤500K 行、
|
|
14
|
+
SYMBOL ≤255 字节、单分区大小落在引擎推荐区间等硬约束。
|
|
12
15
|
---
|
|
13
16
|
|
|
14
17
|
# DolphinDB 开发规范 Skill
|
|
@@ -39,12 +42,13 @@ description: >
|
|
|
39
42
|
| 任务类型 | 重点章节 |
|
|
40
43
|
|---|---|
|
|
41
44
|
| 查询脚本编写 | 三(分区裁剪)、八(查询优化) |
|
|
42
|
-
|
|
|
45
|
+
| 建库建表/分区方案设计 | 十二(分区设计原则) |
|
|
46
|
+
| 批量数据写入 | 二(连接管理)、四(批量写入)、十(数据类型映射)、十三(字符串长度限制) |
|
|
43
47
|
| 脚本拼接/动态生成 | 五(脚本语法)、六(输入校验) |
|
|
44
48
|
| 服务层 Python 代码 | 二(连接管理)、六(输入校验)、十(数据类型映射) |
|
|
45
49
|
| Docker/部署配置 | 九(Docker 环境) |
|
|
46
50
|
| 测试代码 | 十一(测试 Mock) |
|
|
47
|
-
| 代码审查/CR |
|
|
51
|
+
| 代码审查/CR | 全部,尤其是十四(检查清单) |
|
|
48
52
|
|
|
49
53
|
### 3. 执行规则审查
|
|
50
54
|
|
|
@@ -72,16 +76,19 @@ description: >
|
|
|
72
76
|
以下问题会直接导致生产故障或 OOM,必须零容忍:
|
|
73
77
|
|
|
74
78
|
1. **分区列被函数包裹**(如 `date(trade_date)`)→ 全表扫描 → 8GB OOM
|
|
75
|
-
2.
|
|
76
|
-
3.
|
|
77
|
-
4.
|
|
78
|
-
5.
|
|
79
|
-
6.
|
|
80
|
-
7.
|
|
79
|
+
2. **链式比较过滤**(如 `2022.12.01 <= TradeDate <= 2022.12.03`)→ 不触发分区剪枝 → 全表扫描
|
|
80
|
+
3. **单次 append 超过 500K 行** → TSDB 报错 `exceeds max limit`
|
|
81
|
+
4. **未校验的外部输入拼接到脚本** → 脚本注入风险
|
|
82
|
+
5. **连接池做 upload + run** → 变量跨连接不可见
|
|
83
|
+
6. **select *** → 传输全部 30+ 列,浪费带宽和内存
|
|
84
|
+
7. **分页使用 `LIMIT x OFFSET y`** → DolphinDB 不支持 OFFSET 关键字
|
|
85
|
+
8. **`asyncio.to_thread(pool.run, script)`** → SDK v3 的 `pool.run()` 已是原生 async,无需包装
|
|
86
|
+
9. **SYMBOL 列写入未校验长度** → 超 255 字节抛异常,整批写入失败
|
|
87
|
+
10. **建表分区方案过细**(如每天 5000+ 个 0.6MB 小分区)→ 命中 `maxPartitionNumPerQuery=65536` 限制
|
|
81
88
|
|
|
82
89
|
### 5. 检查清单应用
|
|
83
90
|
|
|
84
|
-
如果任务是**代码审查**或**最终确认**,逐条过一遍 `references/dolphindb-rules.md`
|
|
91
|
+
如果任务是**代码审查**或**最终确认**,逐条过一遍 `references/dolphindb-rules.md` 第十四章的检查清单(13 项),并给出通过/不通过的明确结论。
|
|
85
92
|
|
|
86
93
|
## 输出要求
|
|
87
94
|
|
|
@@ -39,6 +39,29 @@
|
|
|
39
39
|
"输出中明确指出了连接池 upload + run 的问题",
|
|
40
40
|
"输出中为每个问题都提供了修复后的代码片段"
|
|
41
41
|
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": 4,
|
|
45
|
+
"prompt": "审查以下 DolphinDB 查询脚本是否能触发分区剪枝,并给出修复后的脚本:\n```sql\nselect * from loadTable(\"dfs://tickDB\", \"snapshots\")\nwhere 2022.12.01 <= TradeDate <= 2022.12.03\n and order_book_id = \"000001.SZ\"\n```",
|
|
46
|
+
"expected_output": "审查必须指出:1) `2022.12.01 <= TradeDate <= 2022.12.03` 是链式比较,DolphinDB 不识别两端边界,无法做分区剪枝,会扫描全部分区;2) 给出修复方案:改为 `TradeDate between 2022.12.01 and 2022.12.03` 或拆成 `TradeDate >= 2022.12.01 and TradeDate <= 2022.12.03`;3) 同时指出 select * 浪费带宽,应只取需要的列。",
|
|
47
|
+
"files": [],
|
|
48
|
+
"assertions": [
|
|
49
|
+
"输出中明确指出了链式比较无法触发分区剪枝",
|
|
50
|
+
"输出中提供了 between ... and ... 或拆分为两个独立条件的修复方案",
|
|
51
|
+
"输出中提到了 select * 的问题"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": 5,
|
|
56
|
+
"prompt": "帮我设计一张存储股票 tick 数据的 DolphinDB 分布式表,每天约 5000 万条记录,覆盖国内 5000 只股票,使用 TSDB 引擎。给出分区方案并说明理由。",
|
|
57
|
+
"expected_output": "方案必须:1) 一级 VALUE 分区按 TradeDate,二级用 HASH 分区按 SecurityID(约 20 桶),而不是 VALUE 分区,避免每天 5000+ 个小分区命中 maxPartitionNumPerQuery=65536 限制;2) 估算单分区大小(每天 3GB / 20 桶 ≈ 150MB,落在 TSDB 推荐区间 400MB~1GB 之下但仍可接受,或调整桶数让单分区更接近 400MB);3) 提醒分区方案一经确定无法调整;4) 提醒 SYMBOL 列 SecurityID 写入前需校验 ≤255 字节。",
|
|
58
|
+
"files": [],
|
|
59
|
+
"assertions": [
|
|
60
|
+
"方案中二级分区使用 HASH 而非 VALUE",
|
|
61
|
+
"方案中包含单分区大小估算并参考 TSDB 100MB~1GB 推荐区间",
|
|
62
|
+
"方案中提到 maxPartitionNumPerQuery=65536 或避免分区数爆炸",
|
|
63
|
+
"方案中提到 SYMBOL 列长度校验或 ≤255 字节限制"
|
|
64
|
+
]
|
|
42
65
|
}
|
|
43
66
|
]
|
|
44
67
|
}
|
|
@@ -74,6 +74,26 @@ WHERE trade_date >= 2020.01.01, trade_date < 2020.02.01
|
|
|
74
74
|
WHERE order_book_id in [`000001.XSHE`000002.XSHE]
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
### 范围查询禁止链式过滤
|
|
78
|
+
|
|
79
|
+
DolphinDB 不识别链式比较的两端边界,无法做分区剪枝。必须用 `between ... and ...` 或拆成两个独立条件。
|
|
80
|
+
|
|
81
|
+
```sql
|
|
82
|
+
-- ❌ 链式过滤 → 全表扫描(实测扫描 10091 个分区)
|
|
83
|
+
select * from loadTable(dbName, tbName)
|
|
84
|
+
where 2022.12.01 <= TradeDate <= 2022.12.03
|
|
85
|
+
|
|
86
|
+
-- ✅ between ... and ... → 分区剪枝(实测扫描 90 个分区)
|
|
87
|
+
select * from loadTable(dbName, tbName)
|
|
88
|
+
where TradeDate between 2022.12.01 and 2022.12.03
|
|
89
|
+
|
|
90
|
+
-- ✅ 拆成两个独立条件 → 分区剪枝
|
|
91
|
+
select * from loadTable(dbName, tbName)
|
|
92
|
+
where TradeDate >= 2022.12.01 and TradeDate <= 2022.12.03
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
可用 `[HINT_EXPLAIN]` 查看实际扫描的分区数验证:`select [HINT_EXPLAIN] * from ...` 输出中的 `partitions.local` 数量应等于范围内的分区数,而不是全库分区数。
|
|
96
|
+
|
|
77
97
|
### tickDB 分区列
|
|
78
98
|
- 第一级:`trade_date`(VALUE 分区,按日)
|
|
79
99
|
- 第二级:`order_book_id`(HASH 分区,20 桶)
|
|
@@ -87,6 +107,7 @@ WHERE order_book_id in [`000001.XSHE`000002.XSHE]
|
|
|
87
107
|
- `date(...)` / `month(...)` / `year(...)` 包裹分区列
|
|
88
108
|
- `WHERE 1=1` 无分区条件(全表扫描)
|
|
89
109
|
- `select * from loadTable(...)` 无 WHERE 子句
|
|
110
|
+
- 链式比较 `a <= col <= b`(必须改为 `between a and b` 或拆成两个独立条件)
|
|
90
111
|
|
|
91
112
|
## 四、批量写入
|
|
92
113
|
|
|
@@ -326,11 +347,87 @@ patch("app.services.tick_archive.run_ddb", ...)
|
|
|
326
347
|
patch("app.services.tick_compute.run_ddb", ...)
|
|
327
348
|
```
|
|
328
349
|
|
|
329
|
-
##
|
|
350
|
+
## 十二、分区设计原则(建表时)
|
|
351
|
+
|
|
352
|
+
### 单分区合理大小(压缩前)
|
|
353
|
+
|
|
354
|
+
| 引擎 | 推荐范围 | 说明 |
|
|
355
|
+
|---|---|---|
|
|
356
|
+
| OLAP | 100MB ~ 300MB | 列存按分区粒度加载,过大易 OOM |
|
|
357
|
+
| TSDB / PKEY | 400MB ~ 1GB | 内部还有 LevelFile 二级组织,单分区可大一些 |
|
|
358
|
+
|
|
359
|
+
分区过小:元数据膨胀、查询时分区数爆炸、命中 `maxPartitionNumPerQuery` 限制(默认 65536)。
|
|
360
|
+
分区过大:单分区加载内存压力大、并行度下降、删除/重写代价高。
|
|
361
|
+
|
|
362
|
+
### 分区层级与类型
|
|
363
|
+
|
|
364
|
+
| 维度 | 规则 |
|
|
365
|
+
|---|---|
|
|
366
|
+
| 层级 | 最少 1 级,最多 3 级;分区方案一经确定无法调整,库下所有表共用 |
|
|
367
|
+
| 范围分区 (RANGE) | **只能向后扩展且无法自动扩展**,需手动 `addRangePartitions` |
|
|
368
|
+
| 范围分区越界数据 | `allowMissingPartitions=true`(默认)会**静默丢弃**范围外数据;需要报错改为 `false` |
|
|
369
|
+
| 值分区 (VALUE) | 可自动扩展(需 `newValuePartitionPolicy=add`);**不宜初始化过多空分区** |
|
|
370
|
+
| 哈希分区 (HASH) | 桶数固定,建库后不可改 |
|
|
371
|
+
|
|
372
|
+
### 分区方案设计反例
|
|
373
|
+
|
|
374
|
+
> 股票行情数据(每天 5000 万条记录),先按 `TradeDate` 值分区,再按 `SecurityID` 二级值分区。
|
|
375
|
+
> 假设全是国内股票(约 5000 只),一天分区数就达 5000+。每天数据 3GB,**每个分区只有约 0.6MB**。
|
|
376
|
+
|
|
377
|
+
后果:
|
|
378
|
+
- 默认 `maxPartitionNumPerQuery=65536`,每天 5000 个分区,**查 14 天就报错**:`The number of partitions relevant to the query is too large`
|
|
379
|
+
- 单分区 0.6MB 远低于 OLAP 100MB / TSDB 400MB 推荐下限,元数据开销远大于数据本身
|
|
380
|
+
- 修复方向:二级分区改用 `HASH(SecurityID, 20)`(按经验固定 20 桶),或合并到日粒度单级分区
|
|
381
|
+
|
|
382
|
+
### 设计检查项
|
|
383
|
+
|
|
384
|
+
- [ ] 单分区估算大小是否落在引擎推荐区间?
|
|
385
|
+
- [ ] 总分区数是否远低于单次查询 65536 上限?
|
|
386
|
+
- [ ] 二级是否用了高基数列做 VALUE 分区?(高基数列优先 HASH)
|
|
387
|
+
- [ ] RANGE 分区是否预留了 `addRangePartitions` 的扩展机制?是否需要 `allowMissingPartitions=false`?
|
|
388
|
+
|
|
389
|
+
## 十三、字符串/BLOB/SYMBOL 长度限制
|
|
390
|
+
|
|
391
|
+
自 **1.30.23 / 2.00.11** 版本起,分布式表写入对字符串类数据增加了硬性大小限制:
|
|
392
|
+
|
|
393
|
+
| 类型 | 上限 | 超限行为 |
|
|
394
|
+
|---|---|---|
|
|
395
|
+
| `STRING` | < 64 KB | 静默截断到 65,535 字节(64 KB - 1) |
|
|
396
|
+
| `BLOB` | < 64 MB | 静默截断到 67,108,863 字节(64 MB - 1) |
|
|
397
|
+
| `SYMBOL` | ≤ 255 字节 | **直接抛异常**,写入失败 |
|
|
398
|
+
|
|
399
|
+
### 强制规则
|
|
400
|
+
|
|
401
|
+
写入前必须做长度校验,**特别是 SYMBOL 列会让整批写入失败**:
|
|
402
|
+
|
|
403
|
+
```python
|
|
404
|
+
# ✅ 写入前校验 SYMBOL 列长度
|
|
405
|
+
MAX_SYMBOL_BYTES = 255
|
|
406
|
+
oversize_mask = batch_df["order_book_id"].str.encode("utf-8").str.len() > MAX_SYMBOL_BYTES
|
|
407
|
+
if oversize_mask.any():
|
|
408
|
+
raise BusinessError(
|
|
409
|
+
f"SYMBOL 列 order_book_id 有 {oversize_mask.sum()} 行超过 255 字节"
|
|
410
|
+
)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### 类型选择建议
|
|
414
|
+
|
|
415
|
+
- 短编码字段(股票代码、因子名)→ `SYMBOL`,但必须保证 ≤ 255 字节
|
|
416
|
+
- 用户可输入的中等长度文本(备注、描述)→ `STRING`,注意 64 KB 截断风险,超长字段做应用层校验
|
|
417
|
+
- 大文本/二进制(JSON、文件)→ `BLOB`,超长会被截断而不是报错,**截断不可逆**,必须在应用层校验长度
|
|
418
|
+
- 中文按 UTF-8 编码,1 字符 ≈ 3 字节,预估时按字节而非字符
|
|
419
|
+
|
|
420
|
+
### 违规检测
|
|
421
|
+
|
|
422
|
+
- 写入路径上对 `SYMBOL` 列没有长度校验 → 整批写入可能失败
|
|
423
|
+
- 写入 `STRING`/`BLOB` 时没有长度上限断言 → 数据被静默截断,事后无法恢复
|
|
424
|
+
|
|
425
|
+
## 十四、检查清单
|
|
330
426
|
|
|
331
427
|
每次涉及 DolphinDB 的代码变更,必须逐条检查:
|
|
332
428
|
|
|
333
429
|
- [ ] WHERE 条件是否命中分区列?是否有函数包裹分区列?
|
|
430
|
+
- [ ] 范围条件是否使用 `between ... and ...` 或两个独立条件?是否存在链式比较 `a <= col <= b`?
|
|
334
431
|
- [ ] 批量写入是否 ≤ 500K 行?是否有 `gc.collect()`?
|
|
335
432
|
- [ ] 外部输入是否经过 `validators.py` 校验?
|
|
336
433
|
- [ ] 使用 `run_ddb` 还是 `run_ddb_with_data`?场景是否匹配?
|
|
@@ -340,3 +437,5 @@ patch("app.services.tick_compute.run_ddb", ...)
|
|
|
340
437
|
- [ ] 脚本中 backtick 列名是否在单行内?
|
|
341
438
|
- [ ] 新增 service 文件的 `run_ddb` 导入是否在 conftest.py 中 mock?
|
|
342
439
|
- [ ] 分页查询是否使用 `limit offset, count` 语法?(禁止 `LIMIT x OFFSET y`)
|
|
440
|
+
- [ ] 新建库表时单分区估算大小是否在引擎推荐区间内?总分区数是否远低于 65536?
|
|
441
|
+
- [ ] 写入路径对 `SYMBOL` 列是否有 ≤ 255 字节长度校验?`STRING` / `BLOB` 是否有上限断言以避免静默截断?
|