@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.
Files changed (96) hide show
  1. package/README.md +46 -44
  2. package/dist/create-keystone-app.js +347 -10
  3. package/dist/create-module.js +1217 -1187
  4. package/package.json +1 -1
  5. package/template/.claude/skills/keystone-implement/SKILL.md +113 -0
  6. package/template/.claude/skills/keystone-implement/references/CHECKLIST.md +91 -0
  7. package/template/.claude/skills/keystone-implement/references/PATTERNS.md +1088 -0
  8. package/template/.claude/skills/keystone-implement/references/SCHEMA.md +135 -0
  9. package/template/.claude/skills/keystone-implement/references/TESTING.md +231 -0
  10. package/template/.claude/skills/keystone-requirements/SKILL.md +296 -0
  11. package/template/.claude/skills/keystone-requirements/references/CONFIRM_TEMPLATE.md +170 -0
  12. package/template/.claude/skills/keystone-requirements/references/SCHEMA.md +135 -0
  13. package/template/.eslintrc.js +3 -0
  14. package/template/.github/workflows/ci.yml +30 -0
  15. package/template/.github/workflows/release.yml +32 -0
  16. package/template/.golangci.yml +11 -0
  17. package/template/README.md +82 -81
  18. package/template/apps/server/README.md +8 -0
  19. package/template/apps/server/cmd/server/main.go +27 -185
  20. package/template/apps/server/config.example.yaml +31 -1
  21. package/template/apps/server/config.yaml +31 -1
  22. package/template/apps/server/go.mod +61 -19
  23. package/template/apps/server/go.sum +185 -32
  24. package/template/apps/server/internal/frontend/embed.go +3 -8
  25. package/template/apps/server/internal/modules/example/README.md +18 -0
  26. package/template/apps/server/internal/modules/example/api/handler/handler_test.go +9 -0
  27. package/template/apps/server/internal/modules/example/api/handler/item_handler.go +468 -165
  28. package/template/apps/server/internal/modules/example/bootstrap/seeds/item.go +217 -8
  29. package/template/apps/server/internal/modules/example/domain/models/item.go +40 -7
  30. package/template/apps/server/internal/modules/example/domain/service/approval_callback.go +68 -0
  31. package/template/apps/server/internal/modules/example/domain/service/approval_schema.go +41 -0
  32. package/template/apps/server/internal/modules/example/domain/service/errors.go +20 -22
  33. package/template/apps/server/internal/modules/example/domain/service/item_service.go +267 -7
  34. package/template/apps/server/internal/modules/example/domain/service/item_service_test.go +281 -0
  35. package/template/apps/server/internal/modules/example/i18n/keys.go +32 -20
  36. package/template/apps/server/internal/modules/example/i18n/locales/en-US.json +30 -18
  37. package/template/apps/server/internal/modules/example/i18n/locales/zh-CN.json +30 -18
  38. package/template/apps/server/internal/modules/example/infra/exporter/item_exporter.go +119 -0
  39. package/template/apps/server/internal/modules/example/infra/importer/item_importer.go +77 -0
  40. package/template/apps/server/internal/modules/example/infra/repository/item_repository.go +99 -49
  41. package/template/apps/server/internal/modules/example/module.go +171 -97
  42. package/template/apps/server/internal/modules/example/tests/integration_test.go +7 -0
  43. package/template/apps/server/internal/modules/manifest.go +7 -7
  44. package/template/apps/web/README.md +4 -2
  45. package/template/apps/web/package.json +1 -1
  46. package/template/apps/web/src/app.config.ts +8 -6
  47. package/template/apps/web/src/index.css +7 -3
  48. package/template/apps/web/src/main.tsx +2 -5
  49. package/template/apps/web/src/modules/example/help/en-US/faq.md +27 -0
  50. package/template/apps/web/src/modules/example/help/en-US/items.md +30 -0
  51. package/template/apps/web/src/modules/example/help/en-US/overview.md +31 -0
  52. package/template/apps/web/src/modules/example/help/zh-CN/faq.md +27 -0
  53. package/template/apps/web/src/modules/example/help/zh-CN/items.md +31 -0
  54. package/template/apps/web/src/modules/example/help/zh-CN/overview.md +32 -0
  55. package/template/apps/web/src/modules/example/locales/en-US/example.json +99 -32
  56. package/template/apps/web/src/modules/example/locales/zh-CN/example.json +85 -18
  57. package/template/apps/web/src/modules/example/pages/ExampleItemsPage.tsx +840 -237
  58. package/template/apps/web/src/modules/example/services/exampleItems.ts +79 -8
  59. package/template/apps/web/src/modules/example/types.ts +14 -1
  60. package/template/apps/web/src/modules/index.ts +1 -0
  61. package/template/apps/web/vite.config.ts +9 -3
  62. package/template/docs/CONVENTIONS.md +17 -17
  63. package/template/package.json +4 -5
  64. package/template/pnpm-lock.yaml +76 -5
  65. package/template/scripts/build.bat +15 -3
  66. package/template/scripts/build.sh +9 -3
  67. package/template/scripts/check-help.js +249 -0
  68. package/template/scripts/compress-assets.js +89 -0
  69. package/template/scripts/test.bat +23 -0
  70. package/template/scripts/test.sh +16 -0
  71. package/template/.claude/skills/keystone-dev/SKILL.md +0 -90
  72. package/template/.claude/skills/keystone-dev/references/ADVANCED_PATTERNS.md +0 -716
  73. package/template/.claude/skills/keystone-dev/references/APPROVAL.md +0 -121
  74. package/template/.claude/skills/keystone-dev/references/CAPABILITIES.md +0 -261
  75. package/template/.claude/skills/keystone-dev/references/CHECKLIST.md +0 -285
  76. package/template/.claude/skills/keystone-dev/references/GOTCHAS.md +0 -390
  77. package/template/.claude/skills/keystone-dev/references/PATTERNS.md +0 -605
  78. package/template/.claude/skills/keystone-dev/references/TEMPLATES.md +0 -2710
  79. package/template/.claude/skills/keystone-dev/references/TESTING.md +0 -44
  80. package/template/.codex/skills/keystone-dev/SKILL.md +0 -90
  81. package/template/.codex/skills/keystone-dev/references/ADVANCED_PATTERNS.md +0 -716
  82. package/template/.codex/skills/keystone-dev/references/APPROVAL.md +0 -121
  83. package/template/.codex/skills/keystone-dev/references/CAPABILITIES.md +0 -261
  84. package/template/.codex/skills/keystone-dev/references/CHECKLIST.md +0 -285
  85. package/template/.codex/skills/keystone-dev/references/GOTCHAS.md +0 -390
  86. package/template/.codex/skills/keystone-dev/references/PATTERNS.md +0 -605
  87. package/template/.codex/skills/keystone-dev/references/TEMPLATES.md +0 -2710
  88. package/template/.codex/skills/keystone-dev/references/TESTING.md +0 -44
  89. package/template/apps/server/internal/app/routes/module_routes.go +0 -16
  90. package/template/apps/server/internal/app/routes/routes.go +0 -226
  91. package/template/apps/server/internal/app/startup/startup.go +0 -74
  92. package/template/apps/server/internal/frontend/handler.go +0 -122
  93. package/template/apps/server/internal/modules/registry.go +0 -145
  94. package/template/apps/web/src/modules/example/help/faq.md +0 -23
  95. package/template/apps/web/src/modules/example/help/items.md +0 -26
  96. package/template/apps/web/src/modules/example/help/overview.md +0 -25
@@ -1,605 +0,0 @@
1
- # Keystone 后端开发模式
2
-
3
- > 本文档补充 TEMPLATES.md 中未覆盖的高级开发模式。
4
-
5
- ## 1. 分页与过滤
6
-
7
- ### 1.1 带分页的列表 Handler
8
-
9
- ```go
10
- import "github.com/robsuncn/keystone/infra/pagination"
11
-
12
- func (h *Handler) List(c *gin.Context) {
13
- tenantID := resolveTenantID(c)
14
-
15
- // 解析分页参数
16
- pageReq := pagination.ParseRequest(c) // 自动从 query string 获取 page, page_size
17
-
18
- // 解析过滤参数
19
- filter := parseFilter(c)
20
-
21
- items, total, err := h.svc.List(c.Request.Context(), tenantID, filter, pageReq)
22
- if err != nil {
23
- response.InternalErrorI18n(c, modulei18n.MsgLoadFailed)
24
- return
25
- }
26
-
27
- // 返回分页响应
28
- response.Success(c, pagination.NewResponse(items, total, pageReq))
29
- }
30
-
31
- type ListFilter struct {
32
- Status *string `form:"status"`
33
- Keyword *string `form:"keyword"`
34
- DateFrom *string `form:"date_from"`
35
- DateTo *string `form:"date_to"`
36
- }
37
-
38
- func parseFilter(c *gin.Context) ListFilter {
39
- var filter ListFilter
40
- _ = c.ShouldBindQuery(&filter)
41
- return filter
42
- }
43
- ```
44
-
45
- ### 1.2 Repository 分页查询
46
-
47
- ```go
48
- import "github.com/robsuncn/keystone/infra/pagination"
49
-
50
- func (r *Repository) List(
51
- ctx context.Context,
52
- tenantID uint,
53
- filter ListFilter,
54
- pageReq pagination.Request,
55
- ) ([]models.Entity, int64, error) {
56
- query := r.db.WithContext(ctx).
57
- Model(&models.Entity{}).
58
- Where("tenant_id = ?", tenantID)
59
-
60
- // 应用过滤条件
61
- if filter.Status != nil {
62
- query = query.Where("status = ?", *filter.Status)
63
- }
64
- if filter.Keyword != nil && *filter.Keyword != "" {
65
- keyword := "%" + *filter.Keyword + "%"
66
- query = query.Where("name LIKE ? OR description LIKE ?", keyword, keyword)
67
- }
68
- if filter.DateFrom != nil {
69
- query = query.Where("created_at >= ?", *filter.DateFrom)
70
- }
71
- if filter.DateTo != nil {
72
- query = query.Where("created_at <= ?", *filter.DateTo)
73
- }
74
-
75
- // 使用统一分页工具
76
- var items []models.Entity
77
- total, err := pagination.Paginate(query.Order("created_at desc"), pageReq, &items)
78
- return items, total, err
79
- }
80
- ```
81
-
82
- ---
83
-
84
- ## 2. 事务处理
85
-
86
- ### 2.1 Service 层事务
87
-
88
- ```go
89
- func (s *Service) CreateWithRelations(
90
- ctx context.Context,
91
- tenantID uint,
92
- input CreateInput,
93
- ) (*models.Entity, error) {
94
- var result *models.Entity
95
-
96
- err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
97
- // 创建主实体
98
- entity := &models.Entity{
99
- Name: input.Name,
100
- }
101
- entity.TenantID = tenantID
102
-
103
- if err := tx.Create(entity).Error; err != nil {
104
- return err
105
- }
106
-
107
- // 创建关联实体
108
- for _, itemInput := range input.Items {
109
- item := &models.EntityItem{
110
- EntityID: entity.ID,
111
- Name: itemInput.Name,
112
- }
113
- if err := tx.Create(item).Error; err != nil {
114
- return err // 自动回滚
115
- }
116
- }
117
-
118
- result = entity
119
- return nil
120
- })
121
-
122
- return result, err
123
- }
124
- ```
125
-
126
- ### 2.2 Repository 层事务(推荐)
127
-
128
- ```go
129
- // Repository 接收 *gorm.DB,可以是普通 db 或事务 tx
130
- type Repository struct {
131
- db *gorm.DB
132
- }
133
-
134
- // 在 Service 中管理事务
135
- func (s *Service) Transfer(ctx context.Context, fromID, toID uint, amount int) error {
136
- return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
137
- // 创建使用事务的临时 repository
138
- txRepo := repository.NewRepository(tx)
139
-
140
- from, err := txRepo.FindByID(fromID)
141
- if err != nil {
142
- return err
143
- }
144
-
145
- to, err := txRepo.FindByID(toID)
146
- if err != nil {
147
- return err
148
- }
149
-
150
- from.Balance -= amount
151
- to.Balance += amount
152
-
153
- if err := txRepo.Update(ctx, from); err != nil {
154
- return err
155
- }
156
- return txRepo.Update(ctx, to)
157
- })
158
- }
159
- ```
160
-
161
- ---
162
-
163
- ## 3. 批量操作
164
-
165
- ### 3.1 批量创建
166
-
167
- ```go
168
- func (h *Handler) BatchCreate(c *gin.Context) {
169
- tenantID := resolveTenantID(c)
170
-
171
- var input struct {
172
- Items []createInput `json:"items" binding:"required,min=1,max=100"`
173
- }
174
- if err := c.ShouldBindJSON(&input); err != nil {
175
- response.BadRequestI18n(c, modulei18n.MsgInvalidPayload)
176
- return
177
- }
178
-
179
- results, err := h.svc.BatchCreate(c.Request.Context(), tenantID, input.Items)
180
- if err != nil {
181
- response.InternalErrorI18n(c, modulei18n.MsgCreateFailed)
182
- return
183
- }
184
-
185
- response.CreatedI18n(c, modulei18n.MsgBatchCreated, gin.H{
186
- "created": len(results),
187
- "items": results,
188
- })
189
- }
190
-
191
- // Service
192
- func (s *Service) BatchCreate(ctx context.Context, tenantID uint, inputs []Input) ([]models.Entity, error) {
193
- entities := make([]models.Entity, 0, len(inputs))
194
-
195
- for _, input := range inputs {
196
- entity := models.Entity{
197
- Name: strings.TrimSpace(input.Name),
198
- }
199
- entity.TenantID = tenantID
200
- entities = append(entities, entity)
201
- }
202
-
203
- // GORM 批量插入
204
- if err := s.repo.db.WithContext(ctx).CreateInBatches(&entities, 100).Error; err != nil {
205
- return nil, err
206
- }
207
-
208
- return entities, nil
209
- }
210
- ```
211
-
212
- ### 3.2 批量删除
213
-
214
- ```go
215
- func (h *Handler) BatchDelete(c *gin.Context) {
216
- tenantID := resolveTenantID(c)
217
-
218
- var input struct {
219
- IDs []uint `json:"ids" binding:"required,min=1,max=100"`
220
- }
221
- if err := c.ShouldBindJSON(&input); err != nil {
222
- response.BadRequestI18n(c, modulei18n.MsgInvalidPayload)
223
- return
224
- }
225
-
226
- count, err := h.svc.BatchDelete(c.Request.Context(), tenantID, input.IDs)
227
- if err != nil {
228
- response.InternalErrorI18n(c, modulei18n.MsgDeleteFailed)
229
- return
230
- }
231
-
232
- response.SuccessI18n(c, modulei18n.MsgBatchDeleted, gin.H{"deleted": count})
233
- }
234
-
235
- // Repository
236
- func (r *Repository) BatchDelete(ctx context.Context, tenantID uint, ids []uint) (int64, error) {
237
- result := r.db.WithContext(ctx).
238
- Where("tenant_id = ? AND id IN ?", tenantID, ids).
239
- Delete(&models.Entity{})
240
- return result.RowsAffected, result.Error
241
- }
242
- ```
243
-
244
- ---
245
-
246
- ## 4. 软删除与恢复
247
-
248
- ### 4.1 模型定义
249
-
250
- ```go
251
- import "gorm.io/gorm"
252
-
253
- type Entity struct {
254
- models.BaseModel // 已包含 DeletedAt gorm.DeletedAt
255
- Name string
256
- }
257
-
258
- // 查询时自动排除已删除记录
259
- // 如需查询已删除记录,使用 db.Unscoped()
260
- ```
261
-
262
- ### 4.2 恢复已删除记录
263
-
264
- ```go
265
- func (r *Repository) Restore(ctx context.Context, tenantID, id uint) error {
266
- return r.db.WithContext(ctx).
267
- Unscoped().
268
- Model(&models.Entity{}).
269
- Where("tenant_id = ? AND id = ?", tenantID, id).
270
- Update("deleted_at", nil).Error
271
- }
272
-
273
- func (r *Repository) FindDeletedByID(tenantID, id uint) (*models.Entity, error) {
274
- var entity models.Entity
275
- err := r.db.Unscoped().
276
- Where("tenant_id = ? AND id = ? AND deleted_at IS NOT NULL", tenantID, id).
277
- First(&entity).Error
278
- if errors.Is(err, gorm.ErrRecordNotFound) {
279
- return nil, ErrEntityNotFound
280
- }
281
- return &entity, err
282
- }
283
- ```
284
-
285
- ---
286
-
287
- ## 5. 关联查询
288
-
289
- ### 5.1 预加载关联
290
-
291
- ```go
292
- func (r *Repository) FindByIDWithItems(tenantID, id uint) (*models.Entity, error) {
293
- var entity models.Entity
294
- err := r.db.
295
- Preload("Items"). // 预加载 has-many 关联
296
- Preload("Category"). // 预加载 belongs-to 关联
297
- Where("tenant_id = ? AND id = ?", tenantID, id).
298
- First(&entity).Error
299
- if errors.Is(err, gorm.ErrRecordNotFound) {
300
- return nil, ErrEntityNotFound
301
- }
302
- return &entity, err
303
- }
304
-
305
- // 带条件的预加载
306
- func (r *Repository) FindWithActiveItems(tenantID, id uint) (*models.Entity, error) {
307
- var entity models.Entity
308
- err := r.db.
309
- Preload("Items", "status = ?", "active").
310
- Where("tenant_id = ? AND id = ?", tenantID, id).
311
- First(&entity).Error
312
- return &entity, err
313
- }
314
- ```
315
-
316
- ### 5.2 分页带预加载
317
-
318
- ```go
319
- func (r *Repository) ListWithCategory(
320
- ctx context.Context,
321
- tenantID uint,
322
- pageReq pagination.Request,
323
- ) ([]models.Entity, int64, error) {
324
- var items []models.Entity
325
- query := r.db.WithContext(ctx).
326
- Model(&models.Entity{}).
327
- Where("tenant_id = ?", tenantID)
328
-
329
- total, err := pagination.PaginateWithPreload(
330
- query.Order("created_at desc"),
331
- pageReq,
332
- &items,
333
- "Category", // 预加载的关联名
334
- )
335
- return items, total, err
336
- }
337
- ```
338
-
339
- ---
340
-
341
- ## 6. 乐观锁
342
-
343
- ### 6.1 模型添加版本字段
344
-
345
- ```go
346
- type Entity struct {
347
- models.BaseModel
348
- Name string
349
- Version int `gorm:"default:1" json:"version"`
350
- }
351
- ```
352
-
353
- ### 6.2 更新时检查版本
354
-
355
- ```go
356
- func (r *Repository) UpdateWithVersion(ctx context.Context, entity *models.Entity) error {
357
- result := r.db.WithContext(ctx).
358
- Model(&models.Entity{}).
359
- Where("id = ? AND version = ?", entity.ID, entity.Version).
360
- Updates(map[string]interface{}{
361
- "name": entity.Name,
362
- "version": entity.Version + 1,
363
- })
364
-
365
- if result.RowsAffected == 0 {
366
- return ErrConcurrentModification
367
- }
368
- return result.Error
369
- }
370
- ```
371
-
372
- ---
373
-
374
- ## 7. 审计日志
375
-
376
- ### 7.1 自动记录操作
377
-
378
- ```go
379
- import "github.com/robsuncn/keystone/infra/audit"
380
-
381
- func (h *Handler) Update(c *gin.Context) {
382
- // ... 业务逻辑 ...
383
-
384
- // 记录审计日志
385
- audit.Log(c, audit.Entry{
386
- Action: audit.ActionUpdate,
387
- Module: "module_name",
388
- Resource: "entity",
389
- ResourceID: fmt.Sprintf("%d", id),
390
- Details: map[string]interface{}{
391
- "old": oldEntity,
392
- "new": newEntity,
393
- },
394
- })
395
-
396
- response.Success(c, entity)
397
- }
398
- ```
399
-
400
- ---
401
-
402
- ## 8. 导入导出 Job
403
-
404
- ### 8.1 注册导入 Job
405
-
406
- ```go
407
- func (m *Module) RegisterJobs(reg *jobs.Registry) error {
408
- return reg.Register("import_entities", &ImportEntitiesJob{
409
- repo: m.repo,
410
- })
411
- }
412
-
413
- type ImportEntitiesJob struct {
414
- repo *repository.Repository
415
- }
416
-
417
- func (j *ImportEntitiesJob) Execute(ctx context.Context, params jobs.Params) error {
418
- fileID := params.GetString("file_id")
419
- tenantID := params.GetUint("tenant_id")
420
-
421
- // 读取文件、解析、批量插入
422
- // 更新 job 进度
423
- params.UpdateProgress(50)
424
-
425
- // ... 处理逻辑 ...
426
-
427
- params.UpdateProgress(100)
428
- return nil
429
- }
430
- ```
431
-
432
- ### 8.2 创建导入 Job
433
-
434
- ```go
435
- func (h *Handler) StartImport(c *gin.Context) {
436
- tenantID := resolveTenantID(c)
437
-
438
- var input struct {
439
- FileID string `json:"file_id" binding:"required"`
440
- }
441
- if err := c.ShouldBindJSON(&input); err != nil {
442
- response.BadRequestI18n(c, modulei18n.MsgInvalidPayload)
443
- return
444
- }
445
-
446
- job, err := h.jobs.Create(c.Request.Context(), jobs.CreateInput{
447
- Type: "import_entities",
448
- TenantID: tenantID,
449
- Params: map[string]interface{}{
450
- "file_id": input.FileID,
451
- "tenant_id": tenantID,
452
- },
453
- })
454
- if err != nil {
455
- response.InternalErrorI18n(c, modulei18n.MsgJobCreateFailed)
456
- return
457
- }
458
-
459
- response.Success(c, job)
460
- }
461
- ```
462
-
463
- ---
464
-
465
- ## 9. 审批集成
466
-
467
- ### 9.1 业务提交时创建审批实例
468
-
469
- ```go
470
- func (s *Service) Submit(ctx context.Context, tenantID, id uint) error {
471
- entity, err := s.repo.FindByID(tenantID, id)
472
- if err != nil {
473
- return err
474
- }
475
-
476
- if entity.Status != StatusDraft {
477
- return ErrInvalidStatus
478
- }
479
-
480
- // 创建审批实例
481
- instance, err := s.approval.CreateInstance(ctx, approval.CreateInstanceInput{
482
- TenantID: tenantID,
483
- BusinessType: "entity_approval",
484
- BusinessID: id,
485
- Context: map[string]interface{}{
486
- "name": entity.Name,
487
- "amount": entity.Amount,
488
- },
489
- })
490
- if err != nil {
491
- return err
492
- }
493
-
494
- // 更新业务状态
495
- entity.Status = StatusPending
496
- entity.ApprovalInstanceID = &instance.ID
497
- return s.repo.Update(ctx, entity)
498
- }
499
- ```
500
-
501
- ### 9.2 注册审批回调
502
-
503
- ```go
504
- func (m *Module) RegisterApprovalCallback(registry *approval.CallbackRegistry) {
505
- callback := &EntityApprovalCallback{repo: m.repo}
506
-
507
- // 使用带重试的回调
508
- retryable := approval.NewRetryableCallback(callback, approval.DefaultRetryConfig())
509
- registry.Register("entity_approval", retryable)
510
- }
511
-
512
- type EntityApprovalCallback struct {
513
- repo *repository.Repository
514
- }
515
-
516
- func (c *EntityApprovalCallback) OnApproved(ctx context.Context, tenantID, businessID, approverID uint) error {
517
- entity, err := c.repo.FindByID(tenantID, businessID)
518
- if err != nil {
519
- return err
520
- }
521
-
522
- entity.Status = StatusApproved
523
- return c.repo.Update(ctx, entity)
524
- }
525
-
526
- func (c *EntityApprovalCallback) OnRejected(ctx context.Context, tenantID, businessID, approverID uint, reason string) error {
527
- entity, err := c.repo.FindByID(tenantID, businessID)
528
- if err != nil {
529
- return err
530
- }
531
-
532
- entity.Status = StatusRejected
533
- entity.RejectReason = reason
534
- return c.repo.Update(ctx, entity)
535
- }
536
- ```
537
-
538
- ---
539
-
540
- ## 10. 错误处理最佳实践
541
-
542
- ### 10.1 使用 I18n 错误
543
-
544
- ```go
545
- import (
546
- "github.com/robsuncn/keystone/infra/i18n"
547
- modulei18n "your-app/modules/xxx/i18n"
548
- )
549
-
550
- // 定义错误
551
- var (
552
- ErrEntityNotFound = &i18n.I18nError{Key: modulei18n.MsgEntityNotFound}
553
- ErrNameRequired = &i18n.I18nError{Key: modulei18n.MsgNameRequired}
554
- ErrAmountInvalid = &i18n.I18nError{
555
- Key: modulei18n.MsgAmountInvalid,
556
- Params: map[string]interface{}{"min": 0, "max": 1000000},
557
- }
558
- )
559
- ```
560
-
561
- ### 10.2 Handler 错误处理
562
-
563
- ```go
564
- func (h *Handler) Create(c *gin.Context) {
565
- // ...
566
-
567
- entity, err := h.svc.Create(ctx, tenantID, input)
568
- if err != nil {
569
- // 检查是否为 I18n 错误
570
- var i18nErr *i18n.I18nError
571
- if errors.As(err, &i18nErr) {
572
- // 根据错误类型返回不同 HTTP 状态码
573
- switch i18nErr.Key {
574
- case modulei18n.MsgEntityNotFound:
575
- response.NotFoundI18n(c, i18nErr.Key)
576
- case modulei18n.MsgNameRequired, modulei18n.MsgAmountInvalid:
577
- response.BadRequestI18n(c, i18nErr.Key)
578
- default:
579
- response.BadRequestI18n(c, i18nErr.Key)
580
- }
581
- return
582
- }
583
-
584
- // 未知错误
585
- response.InternalErrorI18n(c, modulei18n.MsgCreateFailed)
586
- return
587
- }
588
-
589
- response.CreatedI18n(c, modulei18n.MsgCreated, entity)
590
- }
591
- ```
592
-
593
- ---
594
-
595
- ## 快速参考
596
-
597
- | 模式 | 使用场景 | 关键 API |
598
- |------|---------|----------|
599
- | 分页 | 列表接口 | `pagination.ParseRequest()`, `pagination.Paginate()` |
600
- | 事务 | 多表操作 | `db.Transaction(func(tx *gorm.DB) error { ... })` |
601
- | 批量 | 导入/批量操作 | `db.CreateInBatches()`, `WHERE id IN ?` |
602
- | 软删除 | 数据保留 | `db.Unscoped()`, `Update("deleted_at", nil)` |
603
- | 预加载 | 关联查询 | `db.Preload("Relation")` |
604
- | 乐观锁 | 并发控制 | `WHERE version = ?`, `version + 1` |
605
- | 审批 | 业务流程 | `approval.CreateInstance()`, `CallbackRegistry` |