refactor🎨: move app/jobs onto the generic actions

The five /sysjob routes use the generic actions. The detail route uses
ViewAs to keep answering SysJobItem, whose entryId the edit form reads
where the model marshals entry_id. GenerateM becomes ToModel, and the
Generate methods go.

Recorded before the change and replayed after it, the same 14 requests
get byte-identical responses; answering the detail with the model
instead of SysJobItem makes exactly the two detail requests differ.

With a real ToModel on both modules, a test now builds a package that
pairs the demo model with the jobs request and requires the build to
fail on that pairing, next to the same package with the pair corrected.
This commit is contained in:
zhangwenjian
2026-09-27 20:46:12 +08:00
parent f7c223e0dc
commit fda94c4965
6 changed files with 67 additions and 41 deletions
-5
View File
@@ -27,11 +27,6 @@ func (*SysJob) TableName() string {
return "sys_job"
}
func (e *SysJob) Generate() models.ActiveRecord {
o := *e
return &o
}
func (e *SysJob) GetId() interface{} {
return e.JobId
}
+7 -11
View File
@@ -19,17 +19,13 @@ func registerSysJobRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddlew
r := v1.Group("/sysjob").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole())
{
sysJob := &models2.SysJob{}
r.GET("", actions.PermissionAction(), actions.IndexAction(sysJob, new(dto2.SysJobSearch), func() interface{} {
list := make([]models2.SysJob, 0)
return &list
}))
r.GET("/:id", actions.PermissionAction(), actions.ViewAction(new(dto2.SysJobById), func() interface{} {
return &dto2.SysJobItem{}
}))
r.POST("", actions.CreateAction(new(dto2.SysJobControl)))
r.PUT("", actions.PermissionAction(), actions.UpdateAction(new(dto2.SysJobControl)))
r.DELETE("", actions.PermissionAction(), actions.DeleteAction(new(dto2.SysJobById)))
r.GET("", actions.PermissionAction(), actions.Index[models2.SysJob, dto2.SysJobSearch]())
// The detail answers SysJobItem, whose entryId the edit form reads;
// the model itself marshals it as entry_id.
r.GET("/:id", actions.PermissionAction(), actions.ViewAs[models2.SysJob, dto2.SysJobById, dto2.SysJobItem]())
r.POST("", actions.Create[models2.SysJob, dto2.SysJobControl]())
r.PUT("", actions.PermissionAction(), actions.Update[models2.SysJob, dto2.SysJobControl]())
r.DELETE("", actions.PermissionAction(), actions.Delete[models2.SysJob, dto2.SysJobById]())
}
sysJob := apis.SysJob{}
+1 -25
View File
@@ -6,7 +6,6 @@ import (
"go-admin/app/jobs/models"
"go-admin/common/dto"
common "go-admin/common/models"
)
type SysJobSearch struct {
@@ -32,11 +31,6 @@ func (m *SysJobSearch) Bind(ctx *gin.Context) error {
return err
}
func (m *SysJobSearch) Generate() dto.Index {
o := *m
return &o
}
type SysJobControl struct {
JobId int `json:"jobId"`
JobName string `json:"jobName" validate:"required"` // 名称
@@ -55,12 +49,7 @@ func (s *SysJobControl) Bind(ctx *gin.Context) error {
return ctx.ShouldBind(s)
}
func (s *SysJobControl) Generate() dto.Control {
cp := *s
return &cp
}
func (s *SysJobControl) GenerateM() (common.ActiveRecord, error) {
func (s *SysJobControl) ToModel() (*models.SysJob, error) {
return &models.SysJob{
JobId: s.JobId,
JobName: s.JobName,
@@ -76,23 +65,10 @@ func (s *SysJobControl) GenerateM() (common.ActiveRecord, error) {
}, nil
}
func (s *SysJobControl) GetId() interface{} {
return s.JobId
}
type SysJobById struct {
dto.ObjectById
}
func (s *SysJobById) Generate() dto.Control {
cp := *s
return &cp
}
func (s *SysJobById) GenerateM() (common.ActiveRecord, error) {
return &models.SysJob{}, nil
}
type SysJobItem struct {
JobId int `json:"jobId"`
JobName string `json:"jobName" validate:"required"` // 名称
+36
View File
@@ -0,0 +1,36 @@
package actions_test
import (
"os/exec"
"path/filepath"
"strings"
"testing"
)
// A model paired with a request that builds a different model does not
// compile - the thing the older actions left to a type assertion at run
// time. Checked by building a package that does it, next to a control that
// is the same package with the pair corrected.
func TestGenericActionsRefuseAMismatchedPair(t *testing.T) {
root, err := filepath.Abs("../..")
if err != nil {
t.Fatal(err)
}
vet := func(pkg string) (string, error) {
cmd := exec.Command("go", "vet", "./common/actions/testdata/"+pkg)
cmd.Dir = root
out, err := cmd.CombinedOutput()
return string(out), err
}
if out, err := vet("matched"); err != nil {
t.Fatalf("the control does not build, so the mismatch below proves nothing:\n%s", out)
}
out, err := vet("mismatch")
if err == nil {
t.Fatal("a model paired with another model's request compiled")
}
if !strings.Contains(out, "does not satisfy") || !strings.Contains(out, "ToModel") {
t.Fatalf("the build failed, but not on the pairing:\n%s", out)
}
}
+11
View File
@@ -0,0 +1,11 @@
// Package matched is mismatch with the pair corrected: the control that
// shows mismatch fails for the pairing, not for how it is built.
package matched
import (
"go-admin/app/demo/models"
"go-admin/app/demo/service/dto"
"go-admin/common/actions"
)
var _ = actions.Create[models.DemoProduct, dto.DemoProductControl]
+12
View File
@@ -0,0 +1,12 @@
// Package mismatch pairs a model with another model's request, which must
// not compile. TestGenericActionsRefuseAMismatchedPair builds it and expects
// the build to fail; testdata keeps it out of ./... .
package mismatch
import (
"go-admin/app/demo/models"
jobdto "go-admin/app/jobs/service/dto"
"go-admin/common/actions"
)
var _ = actions.Create[models.DemoProduct, jobdto.SysJobControl]