diff --git a/app/demo/models/demo_product.go b/app/demo/models/demo_product.go index fb616730..61bb852f 100644 --- a/app/demo/models/demo_product.go +++ b/app/demo/models/demo_product.go @@ -25,13 +25,6 @@ func (DemoProduct) TableName() string { return "demo_product" } -// Generate 返回副本,供通用 Action 使用。 -// 必须返回新实例:Action 在并发请求间复用同一个模型指针,就地返回会串数据。 -func (e *DemoProduct) Generate() models.ActiveRecord { - o := *e - return &o -} - func (e *DemoProduct) GetId() interface{} { return e.Id } diff --git a/app/demo/router/demo_product.go b/app/demo/router/demo_product.go index 4d721543..00c0840d 100644 --- a/app/demo/router/demo_product.go +++ b/app/demo/router/demo_product.go @@ -29,21 +29,12 @@ func registerDemoProductRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMi Use(authMiddleware.MiddlewareFunc()). // JWT 认证 Use(middleware.AuthCheckRole()) // Casbin 鉴权 { - m := &models.DemoProduct{} - // actions.PermissionAction() 注入数据权限上下文, // 列表与详情缺少它会绕过 DataScope 过滤 - r.GET("", actions.PermissionAction(), actions.IndexAction(m, new(dto.DemoProductSearch), func() interface{} { - list := make([]models.DemoProduct, 0) - return &list - })) - - r.GET("/:id", actions.PermissionAction(), actions.ViewAction(new(dto.DemoProductById), func() interface{} { - return &models.DemoProduct{} - })) - - r.POST("", actions.CreateAction(new(dto.DemoProductControl))) - r.PUT("/:id", actions.PermissionAction(), actions.UpdateAction(new(dto.DemoProductControl))) - r.DELETE("", actions.PermissionAction(), actions.DeleteAction(new(dto.DemoProductById))) + r.GET("", actions.PermissionAction(), actions.Index[models.DemoProduct, dto.DemoProductSearch]()) + r.GET("/:id", actions.PermissionAction(), actions.View[models.DemoProduct, dto.DemoProductById]()) + r.POST("", actions.Create[models.DemoProduct, dto.DemoProductControl]()) + r.PUT("/:id", actions.PermissionAction(), actions.Update[models.DemoProduct, dto.DemoProductControl]()) + r.DELETE("", actions.PermissionAction(), actions.Delete[models.DemoProduct, dto.DemoProductById]()) } } diff --git a/app/demo/service/dto/demo_product.go b/app/demo/service/dto/demo_product.go index 7516794b..7109b43d 100644 --- a/app/demo/service/dto/demo_product.go +++ b/app/demo/service/dto/demo_product.go @@ -36,15 +36,10 @@ func (m *DemoProductSearch) Bind(ctx *gin.Context) error { return ctx.ShouldBind(m) } -func (m *DemoProductSearch) Generate() dto.Index { - o := *m - return &o -} - -// DemoProductControl 新增与修改共用的入参 +// DemoProductControl is the request body of both create and update. // -// 通用 Action(Create / Update)通过 GenerateM 拿到落库对象, -// 因此这里不直接暴露 Model,字段校验用 validate tag 声明。 +// actions.Create / actions.Update get the row to write from ToModel, so the +// model itself is not exposed; field rules are declared with validate tags. type DemoProductControl struct { Id int `json:"id" comment:"主键"` Name string `json:"name" comment:"名称" validate:"required"` @@ -58,16 +53,9 @@ func (s *DemoProductControl) Bind(ctx *gin.Context) error { return ctx.ShouldBind(s) } -func (s *DemoProductControl) Generate() dto.Control { - o := *s - return &o -} - -func (s *DemoProductControl) GetId() interface{} { return s.Id } - -// GenerateM 组装落库对象。CreateBy / UpdateBy 由通用 Action 在此之后注入, -// 此处不要手动赋值。 -func (s *DemoProductControl) GenerateM() (common.ActiveRecord, error) { +// ToModel builds the row to write. The actions set CreateBy / UpdateBy +// afterwards, so do not set them here. +func (s *DemoProductControl) ToModel() (*models.DemoProduct, error) { return &models.DemoProduct{ Model: common.Model{Id: s.Id}, Name: s.Name, @@ -85,12 +73,3 @@ type DemoProductById struct { // Bind 与 GetId 由内嵌的 dto.ObjectById 提供:它已处理好 uri 绑定、 // DELETE 时的批量 ids 合并与参数校验,无需在此重复实现。 - -func (s *DemoProductById) Generate() dto.Control { - o := *s - return &o -} - -func (s *DemoProductById) GenerateM() (common.ActiveRecord, error) { - return &models.DemoProduct{}, nil -} diff --git a/app/demo/service/dto/demo_product_test.go b/app/demo/service/dto/demo_product_test.go index 32b1a7f3..ce1d19b0 100644 --- a/app/demo/service/dto/demo_product_test.go +++ b/app/demo/service/dto/demo_product_test.go @@ -4,83 +4,21 @@ import ( "testing" "go-admin/app/demo/models" - "go-admin/common/dto" - common "go-admin/common/models" ) -// 通用 Action 依赖 DTO 与 Model 实现一组接口。这些约束在编译期无法完全覆盖 -// (接口是在路由注册处才被要求的),因此用测试锁定,避免改动后在运行时才暴露。 - -func TestImplementsIndexInterface(t *testing.T) { - var _ dto.Index = (*DemoProductSearch)(nil) -} - -func TestImplementsControlInterface(t *testing.T) { - var _ dto.Control = (*DemoProductControl)(nil) - var _ dto.Control = (*DemoProductById)(nil) -} - -func TestModelImplementsActiveRecord(t *testing.T) { - var _ common.ActiveRecord = (*models.DemoProduct)(nil) -} - -// Generate 必须返回副本:通用 Action 在并发请求间复用同一个实例, -// 就地返回会导致请求之间串数据。 -func TestGenerateReturnsCopy(t *testing.T) { - src := &DemoProductControl{Id: 1, Name: "原始"} - got := src.Generate().(*DemoProductControl) - - if got == src { - t.Fatal("Generate 返回了同一指针,应返回副本") - } - got.Name = "被修改" - if src.Name != "原始" { - t.Errorf("修改副本影响了原对象:src.Name = %q", src.Name) - } -} - -func TestSearchGenerateReturnsCopy(t *testing.T) { - src := &DemoProductSearch{Name: "原始"} - got := src.Generate().(*DemoProductSearch) - - if got == src { - t.Fatal("Generate 返回了同一指针,应返回副本") - } - got.Name = "被修改" - if src.Name != "原始" { - t.Errorf("修改副本影响了原对象:src.Name = %q", src.Name) - } -} - -func TestModelGenerateReturnsCopy(t *testing.T) { - src := &models.DemoProduct{Name: "原始"} - got := src.Generate().(*models.DemoProduct) - - if got == src { - t.Fatal("Generate 返回了同一指针,应返回副本") - } - got.Name = "被修改" - if src.Name != "原始" { - t.Errorf("修改副本影响了原对象:src.Name = %q", src.Name) - } -} - -// GenerateM 组装落库对象,主键需正确传递,否则更新会退化成插入。 -func TestGenerateMCarriesId(t *testing.T) { +// ToModel builds the row to write; the key has to reach it, or an update +// matches no row. +func TestToModelCarriesId(t *testing.T) { c := &DemoProductControl{Id: 42, Name: "示例", Code: "P-42", Price: 9.9} - m, err := c.GenerateM() + p, err := c.ToModel() if err != nil { - t.Fatalf("GenerateM 返回错误: %v", err) - } - p, ok := m.(*models.DemoProduct) - if !ok { - t.Fatalf("GenerateM 返回类型错误: %T", m) + t.Fatalf("ToModel: %v", err) } if p.Id != 42 { - t.Errorf("主键未传递: got %d, want 42", p.Id) + t.Errorf("key not carried: got %d, want 42", p.Id) } if p.Name != "示例" || p.Code != "P-42" || p.Price != 9.9 { - t.Errorf("字段映射有误: %+v", p) + t.Errorf("fields mapped wrongly: %+v", p) } }