@robsun/create-keystone-app 0.2.15 → 0.4.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/README.md +46 -44
- package/dist/create-keystone-app.js +347 -10
- package/dist/create-module.js +1217 -1187
- package/package.json +1 -1
- package/template/.claude/skills/keystone-implement/SKILL.md +113 -0
- package/template/.claude/skills/keystone-implement/references/CHECKLIST.md +91 -0
- package/template/.claude/skills/keystone-implement/references/PATTERNS.md +1088 -0
- package/template/.claude/skills/keystone-implement/references/SCHEMA.md +135 -0
- package/template/.claude/skills/keystone-implement/references/TESTING.md +231 -0
- package/template/.claude/skills/keystone-requirements/SKILL.md +296 -0
- package/template/.claude/skills/keystone-requirements/references/CONFIRM_TEMPLATE.md +170 -0
- package/template/.claude/skills/keystone-requirements/references/SCHEMA.md +135 -0
- package/template/.eslintrc.js +3 -0
- package/template/.github/workflows/ci.yml +30 -0
- package/template/.github/workflows/release.yml +32 -0
- package/template/.golangci.yml +11 -0
- package/template/README.md +82 -81
- package/template/apps/server/README.md +8 -0
- package/template/apps/server/cmd/server/main.go +27 -185
- package/template/apps/server/config.example.yaml +31 -1
- package/template/apps/server/config.yaml +31 -1
- package/template/apps/server/go.mod +61 -19
- package/template/apps/server/go.sum +185 -32
- package/template/apps/server/internal/frontend/embed.go +3 -8
- package/template/apps/server/internal/modules/example/README.md +18 -0
- package/template/apps/server/internal/modules/example/api/handler/handler_test.go +9 -0
- package/template/apps/server/internal/modules/example/api/handler/item_handler.go +468 -165
- package/template/apps/server/internal/modules/example/bootstrap/seeds/item.go +217 -8
- package/template/apps/server/internal/modules/example/domain/models/item.go +40 -7
- package/template/apps/server/internal/modules/example/domain/service/approval_callback.go +68 -0
- package/template/apps/server/internal/modules/example/domain/service/approval_schema.go +41 -0
- package/template/apps/server/internal/modules/example/domain/service/errors.go +20 -22
- package/template/apps/server/internal/modules/example/domain/service/item_service.go +267 -7
- package/template/apps/server/internal/modules/example/domain/service/item_service_test.go +281 -0
- package/template/apps/server/internal/modules/example/i18n/keys.go +32 -20
- package/template/apps/server/internal/modules/example/i18n/locales/en-US.json +30 -18
- package/template/apps/server/internal/modules/example/i18n/locales/zh-CN.json +30 -18
- package/template/apps/server/internal/modules/example/infra/exporter/item_exporter.go +119 -0
- package/template/apps/server/internal/modules/example/infra/importer/item_importer.go +77 -0
- package/template/apps/server/internal/modules/example/infra/repository/item_repository.go +99 -49
- package/template/apps/server/internal/modules/example/module.go +171 -97
- package/template/apps/server/internal/modules/example/tests/integration_test.go +7 -0
- package/template/apps/server/internal/modules/manifest.go +7 -7
- package/template/apps/web/README.md +4 -2
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/src/app.config.ts +8 -6
- package/template/apps/web/src/index.css +7 -3
- package/template/apps/web/src/main.tsx +2 -5
- package/template/apps/web/src/modules/example/help/en-US/faq.md +27 -0
- package/template/apps/web/src/modules/example/help/en-US/items.md +30 -0
- package/template/apps/web/src/modules/example/help/en-US/overview.md +31 -0
- package/template/apps/web/src/modules/example/help/zh-CN/faq.md +27 -0
- package/template/apps/web/src/modules/example/help/zh-CN/items.md +31 -0
- package/template/apps/web/src/modules/example/help/zh-CN/overview.md +32 -0
- package/template/apps/web/src/modules/example/locales/en-US/example.json +99 -32
- package/template/apps/web/src/modules/example/locales/zh-CN/example.json +85 -18
- package/template/apps/web/src/modules/example/pages/ExampleItemsPage.tsx +840 -237
- package/template/apps/web/src/modules/example/services/exampleItems.ts +79 -8
- package/template/apps/web/src/modules/example/types.ts +14 -1
- package/template/apps/web/src/modules/index.ts +1 -0
- package/template/apps/web/vite.config.ts +9 -3
- package/template/docs/CONVENTIONS.md +17 -17
- package/template/package.json +4 -5
- package/template/pnpm-lock.yaml +76 -5
- package/template/scripts/build.bat +15 -3
- package/template/scripts/build.sh +9 -3
- package/template/scripts/check-help.js +249 -0
- package/template/scripts/compress-assets.js +89 -0
- package/template/scripts/test.bat +23 -0
- package/template/scripts/test.sh +16 -0
- package/template/.claude/skills/keystone-dev/SKILL.md +0 -90
- package/template/.claude/skills/keystone-dev/references/ADVANCED_PATTERNS.md +0 -716
- package/template/.claude/skills/keystone-dev/references/APPROVAL.md +0 -121
- package/template/.claude/skills/keystone-dev/references/CAPABILITIES.md +0 -261
- package/template/.claude/skills/keystone-dev/references/CHECKLIST.md +0 -285
- package/template/.claude/skills/keystone-dev/references/GOTCHAS.md +0 -390
- package/template/.claude/skills/keystone-dev/references/PATTERNS.md +0 -605
- package/template/.claude/skills/keystone-dev/references/TEMPLATES.md +0 -2710
- package/template/.claude/skills/keystone-dev/references/TESTING.md +0 -44
- package/template/.codex/skills/keystone-dev/SKILL.md +0 -90
- package/template/.codex/skills/keystone-dev/references/ADVANCED_PATTERNS.md +0 -716
- package/template/.codex/skills/keystone-dev/references/APPROVAL.md +0 -121
- package/template/.codex/skills/keystone-dev/references/CAPABILITIES.md +0 -261
- package/template/.codex/skills/keystone-dev/references/CHECKLIST.md +0 -285
- package/template/.codex/skills/keystone-dev/references/GOTCHAS.md +0 -390
- package/template/.codex/skills/keystone-dev/references/PATTERNS.md +0 -605
- package/template/.codex/skills/keystone-dev/references/TEMPLATES.md +0 -2710
- package/template/.codex/skills/keystone-dev/references/TESTING.md +0 -44
- package/template/apps/server/internal/app/routes/module_routes.go +0 -16
- package/template/apps/server/internal/app/routes/routes.go +0 -226
- package/template/apps/server/internal/app/startup/startup.go +0 -74
- package/template/apps/server/internal/frontend/handler.go +0 -122
- package/template/apps/server/internal/modules/registry.go +0 -145
- package/template/apps/web/src/modules/example/help/faq.md +0 -23
- package/template/apps/web/src/modules/example/help/items.md +0 -26
- package/template/apps/web/src/modules/example/help/overview.md +0 -25
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
# Keystone 开发常见陷阱
|
|
2
|
-
|
|
3
|
-
> 开发中容易踩的坑和解决方案。
|
|
4
|
-
|
|
5
|
-
## 后端陷阱
|
|
6
|
-
|
|
7
|
-
### G1: 菜单/模块不显示
|
|
8
|
-
|
|
9
|
-
**症状**:模块代码写完,但菜单不出现或 API 404
|
|
10
|
-
|
|
11
|
-
**原因**:未在配置中启用模块
|
|
12
|
-
|
|
13
|
-
**解决**:
|
|
14
|
-
```yaml
|
|
15
|
-
# config.yaml
|
|
16
|
-
modules:
|
|
17
|
-
enabled:
|
|
18
|
-
- example
|
|
19
|
-
- your_new_module # 添加这行
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
// app.config.ts
|
|
24
|
-
modules: {
|
|
25
|
-
enabled: ['example', 'your_new_module'] // 添加这里
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
### G2: Handler panic: nil pointer
|
|
32
|
-
|
|
33
|
-
**症状**:调用 API 时服务 panic
|
|
34
|
-
|
|
35
|
-
**原因**:Handler 或 Service 未初始化
|
|
36
|
-
|
|
37
|
-
**错误代码**:
|
|
38
|
-
```go
|
|
39
|
-
func (h *Handler) List(c *gin.Context) {
|
|
40
|
-
items, _ := h.svc.List(c) // h.svc 可能为 nil
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
**正确代码**:
|
|
45
|
-
```go
|
|
46
|
-
func (h *Handler) List(c *gin.Context) {
|
|
47
|
-
if h == nil || h.svc == nil {
|
|
48
|
-
response.ServiceUnavailableI18n(c, modulei18n.MsgServiceUnavailable)
|
|
49
|
-
return
|
|
50
|
-
}
|
|
51
|
-
// ...
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
### G3: 数据跨租户泄露
|
|
58
|
-
|
|
59
|
-
**症状**:用户能看到其他租户的数据
|
|
60
|
-
|
|
61
|
-
**原因**:Repository 查询缺少租户过滤
|
|
62
|
-
|
|
63
|
-
**错误代码**:
|
|
64
|
-
```go
|
|
65
|
-
func (r *Repository) List(ctx context.Context) ([]Entity, error) {
|
|
66
|
-
var items []Entity
|
|
67
|
-
r.db.Find(&items) // 未过滤租户
|
|
68
|
-
return items, nil
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**正确代码**:
|
|
73
|
-
```go
|
|
74
|
-
func (r *Repository) List(ctx context.Context, tenantID uint) ([]Entity, error) {
|
|
75
|
-
var items []Entity
|
|
76
|
-
r.db.Where("tenant_id = ?", tenantID).Find(&items)
|
|
77
|
-
return items, nil
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
### G4: 翻译键显示为原始键
|
|
84
|
-
|
|
85
|
-
**症状**:界面显示 `example.item.created` 而非翻译文本
|
|
86
|
-
|
|
87
|
-
**原因**:
|
|
88
|
-
1. 未调用 `RegisterI18n()`
|
|
89
|
-
2. 翻译 JSON 文件路径错误
|
|
90
|
-
3. 键名不匹配
|
|
91
|
-
|
|
92
|
-
**检查**:
|
|
93
|
-
```go
|
|
94
|
-
// module.go 中必须有
|
|
95
|
-
func (m *Module) RegisterI18n() error {
|
|
96
|
-
return modulei18n.RegisterLocales() // 确保调用
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// i18n/i18n.go
|
|
100
|
-
//go:embed locales/*.json // 确保路径正确
|
|
101
|
-
var translations embed.FS
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
### G5: GORM 自动更新 UpdatedAt 失效
|
|
107
|
-
|
|
108
|
-
**症状**:更新记录后 `updated_at` 未变化
|
|
109
|
-
|
|
110
|
-
**原因**:使用 `Updates()` 时只更新传入字段
|
|
111
|
-
|
|
112
|
-
**解决**:使用 `Save()` 或显式设置
|
|
113
|
-
```go
|
|
114
|
-
// 方式一:使用 Save
|
|
115
|
-
r.db.Save(entity)
|
|
116
|
-
|
|
117
|
-
// 方式二:显式更新
|
|
118
|
-
r.db.Model(entity).Updates(map[string]interface{}{
|
|
119
|
-
"name": entity.Name,
|
|
120
|
-
"updated_at": time.Now(),
|
|
121
|
-
})
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
### G6: 软删除查不到数据
|
|
127
|
-
|
|
128
|
-
**症状**:数据库有记录但查询返回空
|
|
129
|
-
|
|
130
|
-
**原因**:GORM 自动添加 `deleted_at IS NULL` 条件
|
|
131
|
-
|
|
132
|
-
**解决**:
|
|
133
|
-
```go
|
|
134
|
-
// 查询包含已删除记录
|
|
135
|
-
r.db.Unscoped().Where("id = ?", id).First(&entity)
|
|
136
|
-
|
|
137
|
-
// 恢复已删除记录
|
|
138
|
-
r.db.Unscoped().Model(&Entity{}).
|
|
139
|
-
Where("id = ?", id).
|
|
140
|
-
Update("deleted_at", nil)
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
---
|
|
144
|
-
|
|
145
|
-
### G7: 循环依赖导致编译失败
|
|
146
|
-
|
|
147
|
-
**症状**:`import cycle not allowed`
|
|
148
|
-
|
|
149
|
-
**原因**:service 和 repository 互相导入
|
|
150
|
-
|
|
151
|
-
**错误结构**:
|
|
152
|
-
```
|
|
153
|
-
service/ → imports → repository/
|
|
154
|
-
repository/ → imports → service/ // 循环!
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
**解决**:在 service 定义接口,repository 实现
|
|
158
|
-
```go
|
|
159
|
-
// service/service.go
|
|
160
|
-
type EntityRepository interface {
|
|
161
|
-
FindByID(id uint) (*models.Entity, error)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// repository/repository.go
|
|
165
|
-
// 实现 service.EntityRepository 接口,但不导入 service 包
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
## 前端陷阱
|
|
171
|
-
|
|
172
|
-
### G8: Modal destroyOnClose 无效
|
|
173
|
-
|
|
174
|
-
**症状**:Modal 关闭后再打开,表单数据残留
|
|
175
|
-
|
|
176
|
-
**原因**:Ant Design v6 API 变更
|
|
177
|
-
|
|
178
|
-
**错误代码**:
|
|
179
|
-
```tsx
|
|
180
|
-
<Modal destroyOnClose> // v6 已废弃
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
**正确代码**:
|
|
184
|
-
```tsx
|
|
185
|
-
<Modal destroyOnHidden> // v6 使用这个
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
### G9: 翻译不生效
|
|
191
|
-
|
|
192
|
-
**症状**:`t('key')` 返回 key 本身
|
|
193
|
-
|
|
194
|
-
**原因**:
|
|
195
|
-
1. 未注册翻译文件
|
|
196
|
-
2. 命名空间错误
|
|
197
|
-
|
|
198
|
-
**检查**:
|
|
199
|
-
```typescript
|
|
200
|
-
// index.ts 中确保导入翻译文件
|
|
201
|
-
import './locales/zh-CN/module.json'
|
|
202
|
-
import './locales/en-US/module.json'
|
|
203
|
-
|
|
204
|
-
// 使用时指定正确命名空间
|
|
205
|
-
const { t } = useTranslation('module') // 不是 'common'
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
### G10: API 响应解构错误
|
|
211
|
-
|
|
212
|
-
**症状**:`TypeError: Cannot read property 'xxx' of undefined`
|
|
213
|
-
|
|
214
|
-
**原因**:响应结构是 `{ data: { data: {...} } }`
|
|
215
|
-
|
|
216
|
-
**错误代码**:
|
|
217
|
-
```typescript
|
|
218
|
-
const response = await api.get<ApiResponse<Entity>>('/entity/1')
|
|
219
|
-
return response.data // 这是外层 data
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
**正确代码**:
|
|
223
|
-
```typescript
|
|
224
|
-
const { data } = await api.get<ApiResponse<Entity>>('/entity/1')
|
|
225
|
-
return data.data // 这是内层 data
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
### G11: Table columns 类型错误
|
|
231
|
-
|
|
232
|
-
**症状**:TypeScript 报类型不兼容
|
|
233
|
-
|
|
234
|
-
**原因**:未使用 `ColumnsType<T>`
|
|
235
|
-
|
|
236
|
-
**错误代码**:
|
|
237
|
-
```tsx
|
|
238
|
-
const columns = [
|
|
239
|
-
{ title: 'Name', dataIndex: 'name' }
|
|
240
|
-
]
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
**正确代码**:
|
|
244
|
-
```tsx
|
|
245
|
-
import type { ColumnsType } from 'antd/es/table'
|
|
246
|
-
|
|
247
|
-
const columns: ColumnsType<Entity> = [
|
|
248
|
-
{ title: 'Name', dataIndex: 'name', key: 'name' }
|
|
249
|
-
]
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
---
|
|
253
|
-
|
|
254
|
-
### G12: Form 验证不触发
|
|
255
|
-
|
|
256
|
-
**症状**:点击提交按钮无反应
|
|
257
|
-
|
|
258
|
-
**原因**:未正确使用 `Form.useForm()` 或 `validateFields()`
|
|
259
|
-
|
|
260
|
-
**错误代码**:
|
|
261
|
-
```tsx
|
|
262
|
-
const handleSubmit = () => {
|
|
263
|
-
const values = form.getFieldsValue() // 不会触发验证
|
|
264
|
-
}
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
**正确代码**:
|
|
268
|
-
```tsx
|
|
269
|
-
const handleSubmit = async () => {
|
|
270
|
-
try {
|
|
271
|
-
const values = await form.validateFields() // 会触发验证
|
|
272
|
-
// 处理提交
|
|
273
|
-
} catch {
|
|
274
|
-
return // 验证失败,不提交
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
---
|
|
280
|
-
|
|
281
|
-
### G13: useCallback 依赖缺失
|
|
282
|
-
|
|
283
|
-
**症状**:函数行为不符合预期,使用旧闭包值
|
|
284
|
-
|
|
285
|
-
**原因**:useCallback 依赖数组不完整
|
|
286
|
-
|
|
287
|
-
**错误代码**:
|
|
288
|
-
```tsx
|
|
289
|
-
const fetchData = useCallback(async () => {
|
|
290
|
-
const data = await listItems({ status: filter }) // filter 可能是旧值
|
|
291
|
-
}, []) // 缺少 filter 依赖
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
**正确代码**:
|
|
295
|
-
```tsx
|
|
296
|
-
const fetchData = useCallback(async () => {
|
|
297
|
-
const data = await listItems({ status: filter })
|
|
298
|
-
}, [filter]) // 添加 filter 依赖
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
---
|
|
302
|
-
|
|
303
|
-
### G14: 权限菜单不显示
|
|
304
|
-
|
|
305
|
-
**症状**:有权限但菜单不出现
|
|
306
|
-
|
|
307
|
-
**原因**:
|
|
308
|
-
1. `app.config.ts` 未启用模块
|
|
309
|
-
2. 权限码不匹配
|
|
310
|
-
|
|
311
|
-
**检查**:
|
|
312
|
-
```typescript
|
|
313
|
-
// routes.tsx
|
|
314
|
-
handle: {
|
|
315
|
-
menu: {
|
|
316
|
-
permission: 'module:entity:view', // 确保格式正确
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// 后端权限注册
|
|
321
|
-
reg.CreateActionI18n(
|
|
322
|
-
"module:entity:view", // 必须与前端一致
|
|
323
|
-
...
|
|
324
|
-
)
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
---
|
|
328
|
-
|
|
329
|
-
## 性能陷阱
|
|
330
|
-
|
|
331
|
-
### G15: N+1 查询
|
|
332
|
-
|
|
333
|
-
**症状**:列表页加载慢,日志显示大量 SQL
|
|
334
|
-
|
|
335
|
-
**原因**:循环中查询关联数据
|
|
336
|
-
|
|
337
|
-
**错误代码**:
|
|
338
|
-
```go
|
|
339
|
-
entities, _ := repo.List(ctx, tenantID)
|
|
340
|
-
for i := range entities {
|
|
341
|
-
entities[i].Category, _ = repo.GetCategory(entities[i].CategoryID) // N次查询
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
**正确代码**:
|
|
346
|
-
```go
|
|
347
|
-
// 使用 Preload
|
|
348
|
-
r.db.Preload("Category").Find(&entities)
|
|
349
|
-
|
|
350
|
-
// 或批量查询
|
|
351
|
-
categoryIDs := extractIDs(entities)
|
|
352
|
-
categories, _ := repo.GetCategoriesByIDs(categoryIDs)
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
---
|
|
356
|
-
|
|
357
|
-
### G16: 无限重渲染
|
|
358
|
-
|
|
359
|
-
**症状**:页面卡死,控制台显示大量渲染日志
|
|
360
|
-
|
|
361
|
-
**原因**:useEffect 依赖数组包含每次渲染都变化的值
|
|
362
|
-
|
|
363
|
-
**错误代码**:
|
|
364
|
-
```tsx
|
|
365
|
-
useEffect(() => {
|
|
366
|
-
fetchData()
|
|
367
|
-
}, [{ page, pageSize }]) // 对象每次都是新引用
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
**正确代码**:
|
|
371
|
-
```tsx
|
|
372
|
-
useEffect(() => {
|
|
373
|
-
fetchData()
|
|
374
|
-
}, [page, pageSize]) // 使用原始值
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
---
|
|
378
|
-
|
|
379
|
-
## 快速排查表
|
|
380
|
-
|
|
381
|
-
| 现象 | 首先检查 |
|
|
382
|
-
|------|----------|
|
|
383
|
-
| 404 Not Found | config.yaml + manifest.go 注册 |
|
|
384
|
-
| 菜单不显示 | app.config.ts 启用 |
|
|
385
|
-
| 翻译键原样显示 | RegisterI18n() + JSON 文件路径 |
|
|
386
|
-
| nil pointer panic | Handler nil 检查 |
|
|
387
|
-
| 数据泄露 | 租户 ID 过滤 |
|
|
388
|
-
| TypeScript 报错 | 类型定义 + ColumnsType |
|
|
389
|
-
| 表单验证无效 | validateFields() 使用 |
|
|
390
|
-
| Modal 数据残留 | destroyOnHidden |
|