调整模块化结构

main
熊二 1 year ago
parent fa896b66b6
commit 973d88e77c
  1. 9
      go.mod
  2. 12
      go.sum
  3. 4
      internal/services/company/controller/company_controller.go
  4. 4
      internal/services/company/controller/company_department_controller.go
  5. 4
      internal/services/company/controller/company_staff_controller.go
  6. 6
      internal/services/config/controller/config_controller.go
  7. 4
      internal/services/config/controller/config_group_controller.go
  8. 4
      internal/services/feature/controller/feature_category_controller.go
  9. 4
      internal/services/feature/controller/feature_config_controller.go
  10. 4
      internal/services/feature/controller/feature_content_chapter_controller.go
  11. 4
      internal/services/feature/controller/feature_content_controller.go
  12. 4
      internal/services/feature/controller/feature_content_detail_controller.go
  13. 4
      internal/services/feature/controller/feature_controller.go
  14. 4
      internal/services/resource/controller/resource_category_controller.go
  15. 4
      internal/services/resource/controller/resource_controller.go
  16. 49
      internal/services/service.go
  17. 4
      internal/services/system/controller/system_log_controller.go
  18. 4
      internal/services/system/controller/system_menu_controller.go
  19. 4
      internal/services/system/controller/system_permission_controller.go
  20. 4
      internal/services/system/controller/system_role_controller.go
  21. 4
      internal/services/system/controller/system_role_power_controller.go
  22. 4
      internal/services/system/controller/system_user_controller.go
  23. 43
      internal/util/echo_bind.go
  24. 2
      pkg/app/applet.go
  25. 14
      pkg/app/context.go
  26. 151
      pkg/app/controller.go
  27. 15
      pkg/app/echo.go
  28. 239
      pkg/app/echo_utils.go

@ -6,15 +6,12 @@ require (
github.com/go-resty/resty/v2 v2.7.0
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/joho/godotenv v1.5.1
github.com/labstack/echo-jwt/v4 v4.2.0
github.com/labstack/echo/v4 v4.11.1
github.com/labstack/gommon v0.4.0
github.com/mattn/go-isatty v0.0.19
github.com/mattn/go-sqlite3 v1.14.17
github.com/mitchellh/mapstructure v1.5.0
github.com/rs/xid v1.5.0
github.com/spf13/cobra v1.7.0
github.com/swaggo/echo-swagger v1.4.1
github.com/swaggo/swag v1.16.2
golang.org/x/time v0.3.0
gorm.io/driver/mysql v1.5.1
gorm.io/driver/postgres v1.5.2
@ -37,6 +34,7 @@ require (
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.3.1 // indirect
@ -45,10 +43,13 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/microsoft/go-mssqldb v1.1.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.2 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect
github.com/swaggo/swag v1.16.2 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.11.0 // indirect

@ -8,6 +8,7 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -46,6 +47,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
@ -74,8 +77,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo-jwt/v4 v4.2.0 h1:odSISV9JgcSCuhgQSV/6Io3i7nUmfM/QkBeR5GVJj5c=
github.com/labstack/echo-jwt/v4 v4.2.0/go.mod h1:MA2RqdXdEn4/uEglx0HcUOgQSyBaTh5JcaHIan3biwU=
github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=
github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
@ -96,8 +97,6 @@ github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/microsoft/go-mssqldb v1.1.0 h1:jsV+tpvcPTbNNKW0o3kiCD69kOHICsfjZ2VcVu2lKYc=
github.com/microsoft/go-mssqldb v1.1.0/go.mod h1:LzkFdl4z2Ck+Hi+ycGOTbL56VEfgoyA2DvYejrNGbRk=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
@ -108,6 +107,11 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/company/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type CompanyController struct {
util.Controller[entities.Company, request.CompanyUpsertRequest]
app.Controller[entities.Company, request.CompanyUpsertRequest]
}
func (ctr *CompanyController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/company/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type CompanyDepartmentController struct {
util.Controller[entities.CompanyDepartment, request.CompanyDepartmentUpsertRequest]
app.Controller[entities.CompanyDepartment, request.CompanyDepartmentUpsertRequest]
}
func (c *CompanyDepartmentController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/company/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type CompanyStaffController struct {
util.Controller[entities.CompanyStaff, request.CompanyStaffUpsertRequest]
app.Controller[entities.CompanyStaff, request.CompanyStaffUpsertRequest]
}
func (c *CompanyStaffController) InitRoutes(r *echo.Group) {

@ -4,13 +4,13 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/config/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type ConfigController struct {
util.Controller[entities.Config, request.ConfigUpsertRequest]
app.Controller[entities.Config, request.ConfigUpsertRequest]
}
func (ctr *ConfigController) InitRoutes(r *echo.Group) {
ctr.RegisterRoutes("/config", r)
ctr.RegisterRoutes("/configs", r)
}

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/config/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type ConfigGroupController struct {
util.Controller[entities.ConfigGroup, request.ConfigGroupUpsertRequest]
app.Controller[entities.ConfigGroup, request.ConfigGroupUpsertRequest]
}
// InitRoutes 实现路由注册接口

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type FeatureCategoryController struct {
util.Controller[entities.FeatureCategory, request.FeatureCategoryUpsertRequest]
app.Controller[entities.FeatureCategory, request.FeatureCategoryUpsertRequest]
}
func (f *FeatureCategoryController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type FeatureConfigController struct {
util.Controller[entities.FeatureConfig, request.FeatureConfigUpsertRequest]
app.Controller[entities.FeatureConfig, request.FeatureConfigUpsertRequest]
}
func (f *FeatureConfigController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type FeatureContentChapterController struct {
util.Controller[entities.FeatureContentChapter, request.FeatureContentChapterUpsertRequest]
app.Controller[entities.FeatureContentChapter, request.FeatureContentChapterUpsertRequest]
}
func (f *FeatureContentChapterController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type FeatureContentController struct {
util.Controller[entities.FeatureContent, request.FeatureContentUpsertRequest]
app.Controller[entities.FeatureContent, request.FeatureContentUpsertRequest]
}
func (f *FeatureContentController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type FeatureContentDetailController struct {
util.Controller[entities.FeatureContentDetail, request.FeatureContentDetailUpsertRequest]
app.Controller[entities.FeatureContentDetail, request.FeatureContentDetailUpsertRequest]
}
func (f *FeatureContentDetailController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/feature/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type FeatureController struct {
util.Controller[entities.Feature, request.FeatureUpsertRequest]
app.Controller[entities.Feature, request.FeatureUpsertRequest]
}
func (f *FeatureController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/resource/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type ResourceCategoryController struct {
util.Controller[entities.Resource, request.ResourceCategoryUpsertRequest]
app.Controller[entities.Resource, request.ResourceCategoryUpsertRequest]
}
func (ctr *ResourceCategoryController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/resource/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type ResourceController struct {
util.Controller[entities.Resource, request.ResourceUpsertRequest]
app.Controller[entities.Resource, request.ResourceUpsertRequest]
}
func (ctr *ResourceController) InitRoutes(r *echo.Group) {

@ -1,24 +1,49 @@
package services
import (
"context"
"github.com/labstack/echo/v4"
"sorbet/internal/services/company"
"sorbet/internal/services/config"
"sorbet/internal/services/feature"
"sorbet/internal/services/resource"
"sorbet/internal/services/system"
"sorbet/pkg/app"
)
type Service struct {
inners []Service
}
var applets []app.Applet
func (s Service) Init(ctx *app.Context) error {
//TODO implement me
panic("implement me")
func Init() {
applets = []app.Applet{
&config.Service{},
&company.Service{},
&resource.Service{},
&feature.Service{},
&system.Service{},
}
}
func (s Service) Start() error {
//TODO implement me
panic("implement me")
func Start(ctx context.Context) error {
e := ctx.Value("echo_framework").(*echo.Echo)
for _, service := range applets {
err := service.Init(app.NewContext(ctx, e.Group("")))
if err != nil {
return err
}
err = service.Start()
if err != nil {
return err
}
}
return nil
}
func (s Service) Stop() error {
//TODO implement me
panic("implement me")
func Stop() error {
for _, service := range applets {
err := service.Stop()
if err != nil {
return err
}
}
return nil
}

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type SystemLogController struct {
util.Controller[entities.SystemLog, request.SystemLogUpsertRequest]
app.Controller[entities.SystemLog, request.SystemLogUpsertRequest]
}
func (s *SystemLogController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type SystemMenuController struct {
util.Controller[entities.SystemMenu, request.SystemMenuUpsertRequest]
app.Controller[entities.SystemMenu, request.SystemMenuUpsertRequest]
}
func (s *SystemMenuController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type SystemPermissionController struct {
util.Controller[entities.SystemPermission, request.SystemPermissionUpsertRequest]
app.Controller[entities.SystemPermission, request.SystemPermissionUpsertRequest]
}
func (s *SystemPermissionController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type SystemRoleController struct {
util.Controller[entities.SystemRole, request.SystemRoleUpsertRequest]
app.Controller[entities.SystemRole, request.SystemRoleUpsertRequest]
}
func (s *SystemRoleController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type SystemRolePowerController struct {
util.Controller[entities.SystemRolePower, request.SystemRolePowerUpsertRequest]
app.Controller[entities.SystemRolePower, request.SystemRolePowerUpsertRequest]
}
func (s *SystemRolePowerController) InitRoutes(r *echo.Group) {

@ -4,11 +4,11 @@ import (
"github.com/labstack/echo/v4"
"sorbet/internal/entities"
"sorbet/internal/services/system/request"
"sorbet/internal/util"
"sorbet/pkg/app"
)
type SystemUserController struct {
util.Controller[entities.SystemUser, request.SystemUserUpsertRequest]
app.Controller[entities.SystemUser, request.SystemUserUpsertRequest]
}
func (s *SystemUserController) InitRoutes(r *echo.Group) {

@ -1,43 +0,0 @@
package util
import (
"github.com/labstack/echo/v4"
"sorbet/pkg/rsp"
)
// RequestGuarder 参数守卫函数签名
type RequestGuarder[T any] func(c echo.Context, req *T) error
// Bind 将提交的参数绑定到泛型 T 的实例上
func Bind[T any](c echo.Context, guards ...RequestGuarder[T]) (*T, error) {
var req T
if err := c.Bind(&req); err != nil {
return nil, err
}
if err := c.Validate(&req); err != nil {
return nil, err
}
for _, guard := range guards {
if err := guard(c, &req); err != nil {
return nil, err
}
}
return &req, nil
}
func BindId(c echo.Context, must bool) (uint, error) {
request, err := Bind[struct {
ID uint `json:"id" xml:"id" path:"id"`
}](c)
if err != nil {
return 0, err
}
if must {
if request.ID <= 0 {
return 0, rsp.ErrBadParams
}
} else if request.ID < 0 {
return 0, rsp.ErrBadParams
}
return request.ID, err
}

@ -2,7 +2,7 @@ package app
import "github.com/labstack/echo/v4"
type Service interface {
type Applet interface {
// Init 初始化服务
Init(ctx *Context) error
// Start 启动服务

@ -8,20 +8,18 @@ import (
type Context struct {
context.Context
prefix string
store map[any]any
router *echo.Group
mu sync.RWMutex
}
// Prefix 设置路由前缀
func (c *Context) Prefix(prefix string) {
c.mu.Lock()
defer c.mu.Unlock()
if c.prefix != "" {
panic("already prefixed")
func NewContext(ctx context.Context, router *echo.Group) *Context {
return &Context{
Context: ctx,
store: make(map[any]any),
router: router,
mu: sync.RWMutex{},
}
c.prefix = prefix
}
// Routes 注册路由

@ -1,148 +1,35 @@
package util
package app
import (
"github.com/labstack/echo/v4"
"gorm.io/gorm"
"net/url"
"reflect"
"sorbet/pkg/db"
"sorbet/pkg/ioc"
"sorbet/pkg/rsp"
"strconv"
"strings"
)
type GetID interface {
// Upsertable 数据创建和更新需要接口
// 用于控制器 Create、Update 方法
type Upsertable interface {
// GetID 用于 Controller.Update 方法,
// 定位被更新的数据编号
GetID() any
}
type ToMap interface {
// ToMap 用于 Controller.Update 方法,
// 返回被更新的数据,支持零值
ToMap() map[string]any
}
type ToEntity interface {
// ToEntity 用于 Controller.Create 方法,
// 返回被创建的实体的数据
ToEntity() any
}
type ControllerRequest interface {
GetID
ToMap
ToEntity
}
func ParseQuery[T any](query url.Values, qb *db.QueryBuilder[T]) (page, limit int, err error) {
var paginating bool
for key, values := range query {
switch key {
case "sortby":
for _, s := range values {
if s[0] == '+' {
qb.AscentBy(s[1:])
} else if s[0] == '-' {
qb.DescentBy(s[1:])
} else {
qb.AscentBy(s)
}
}
case "limit", "page":
var v int
if values[0] != "" {
v, err = strconv.Atoi(values[0])
if err != nil {
return
}
}
if v <= 0 {
return 0, 0, rsp.ErrInternal
}
if key == "limit" {
qb.Limit(v)
limit = v
} else {
paginating = true
page = max(v, 1)
}
default:
v := values[0]
i := strings.IndexByte(key, '#')
if i == -1 {
qb.Eq(key, v)
continue
}
switch k, op := key[:i], key[i+1:]; op {
case "=":
qb.Eq(k, v)
case "!=":
qb.Neq(k, v)
case "<":
qb.Lt(k, v)
case "<=":
qb.Lte(k, v)
case ">":
qb.Gt(k, v)
case ">=":
qb.Gte(k, v)
case "<>", "><":
var less, more any
switch len(values) {
case 2:
less, more = values[0], values[1]
case 1:
vs := strings.Split(v, ",")
if len(vs) != 2 || vs[0] == "" || vs[1] == "" {
return 0, 0, rsp.ErrBadParams
}
less, more = vs[0], vs[1]
}
if op == "<>" {
qb.Between(k, less, more)
} else {
qb.NotBetween(k, key, more)
}
case "nil":
qb.IsNull(k)
case "!nil":
qb.NotNull(k)
case "~":
qb.Like(k, v)
case "!~":
qb.NotLike(k, v)
case "in", "!in":
if len(values) == 1 {
values = strings.Split(v, ",")
}
vs := make([]any, len(values))
for i, value := range values {
vs[i] = value
}
if op == "in" {
qb.In(k, vs...)
} else {
qb.NotIn(k, vs...)
}
default:
qb.Eq(key, v)
}
}
}
if paginating {
return
}
if limit == 0 {
limit = 30
qb.Limit(limit)
}
qb.Offset((page - 1) * limit)
return
}
func getID(req any) (val any) {
defer func() {
if recover() != nil {
val = nil
}
}()
val = req.(GetID).GetID()
val = req.(Upsertable).GetID()
rv := reflect.ValueOf(val)
if rv.IsZero() || rv.IsNil() {
val = nil
@ -151,19 +38,18 @@ func getID(req any) (val any) {
}
func getValues(req any) map[string]any {
if v, ok := req.(ToMap); ok {
if v, ok := req.(Upsertable); ok {
return v.ToMap()
}
return nil
}
func getEntity[T any](request any) *T {
v, ok := request.(ToEntity)
v, ok := request.(Upsertable)
if !ok {
return nil
}
ent, ok := v.ToEntity().(*T)
if ok {
if ent, ok := v.ToEntity().(*T); ok {
return ent
}
return nil
@ -175,6 +61,7 @@ func getEntity[T any](request any) *T {
// 泛型 [Upsert] 表示创建或更新时需要的数据。
type Controller[Entity any, Upsert any] struct{}
// RegisterRoutes 注册路由
func (ctr *Controller[Entity, Upsert]) RegisterRoutes(path string, r *echo.Group) {
r.PUT(path, ctr.Create)
r.DELETE(path+"/:id", ctr.Delete)
@ -183,6 +70,7 @@ func (ctr *Controller[Entity, Upsert]) RegisterRoutes(path string, r *echo.Group
r.GET(path, ctr.List)
}
// ORM 获取 gorm.DB 实例
func (ctr *Controller[Entity, Upsert]) ORM(c echo.Context) (*gorm.DB, error) {
orm, err := ioc.Get[gorm.DB]()
if err != nil {
@ -191,6 +79,7 @@ func (ctr *Controller[Entity, Upsert]) ORM(c echo.Context) (*gorm.DB, error) {
return orm.WithContext(c.Request().Context()), nil
}
// Repository 获取 Repository 实例
func (ctr *Controller[Entity, Upsert]) Repository() (*db.Repository[Entity], error) {
orm, err := ioc.Get[gorm.DB]()
if err != nil {
@ -249,7 +138,7 @@ func (ctr *Controller[Entity, Upsert]) upsert(c echo.Context, isCreate bool) err
// Delete 通过ID删除数据
func (ctr *Controller[Entity, Upsert]) Delete(c echo.Context) error {
id, err := BindId(c, true)
id, err := BindID(c)
if err != nil {
return err
}
@ -266,7 +155,7 @@ func (ctr *Controller[Entity, Upsert]) Delete(c echo.Context) error {
// Get 通过ID获取数据
func (ctr *Controller[Entity, Upsert]) Get(c echo.Context) error {
id, err := BindId(c, true)
id, err := BindID(c)
if err != nil {
return err
}
@ -288,7 +177,7 @@ func (ctr *Controller[Entity, Upsert]) List(c echo.Context) error {
return err
}
qb := repo.NewQueryBuilder(c.Request().Context())
_, _, err = ParseQuery[Entity](c.QueryParams(), qb)
_, _, err = BindQuery[Entity](c, qb)
if err != nil {
return err
}

@ -1,15 +0,0 @@
package app
import "github.com/labstack/echo/v4"
type echoContext struct {
echo.Context
ctx *Context
}
func (e *echoContext) Get(key string) any {
if val, ok := e.ctx.Get(key); ok && val != nil {
return val
}
return e.Context.Get(key)
}

@ -0,0 +1,239 @@
package app
import (
"github.com/labstack/echo/v4"
"net/http"
"reflect"
"sorbet/pkg/db"
"sorbet/pkg/rsp"
"strconv"
"strings"
)
type (
// RequestGuarder 参数守卫函数签名
RequestGuarder func(c echo.Context, req any) error
// BodyBinder 将请求体绑定到结构体上
BodyBinder interface {
BindBody(c echo.Context, i any) (err error)
}
)
// 使用内置数据绑定函数
// 默认使用 echo.DefaultBinder
var binder BodyBinder
// Validate 验证数据
func Validate(c echo.Context, t any, guards ...RequestGuarder) error {
err := c.Validate(t)
if err != nil {
return err
}
for _, guard := range guards {
err = guard(c, t)
if err != nil {
return err
}
}
return nil
}
// Bind 将提交的参数绑定到泛型 T 的实例上
func Bind[T any](c echo.Context, guards ...RequestGuarder) (*T, error) {
var req T
if err := c.Bind(&req); err != nil {
return nil, err
}
if err := Validate(c, &req, guards...); err != nil {
return nil, err
}
return &req, nil
}
// BindBody 将提交请求体绑定到泛型 T 的实例上
func BindBody[T any](c echo.Context, guards ...RequestGuarder) (*T, error) {
b, ok := c.Echo().Binder.(BodyBinder)
if !ok || b == nil {
if binder == nil {
binder = &echo.DefaultBinder{}
}
b = binder
}
var req T
if err := b.BindBody(c, &req); err != nil {
return nil, err
}
if err := Validate(c, &req, guards...); err != nil {
return nil, err
}
return &req, nil
}
type identifier struct {
ID any `json:"id" xml:"id"`
}
// BindID 自请求中获取需要的数据编号
// 查找的是名称为 `id` 的数据,查找顺序如下
// 1. 查看路由中是否定义,比如:`/configs/:id`
// 2. 查看查询字符串中是否存在,比如:`?id=123`
// 3. 查看请求体中是否存在,支持 json 和 xml 两种格式。
func BindID(c echo.Context) (id any, err error) {
defer func() {
if recovered := recover(); recovered != nil {
c.Logger().Debugf("%#v", recovered)
id = nil
err = rsp.ErrBadParams
}
}()
for i, name := range c.ParamNames() {
if name == "id" {
id = c.ParamValues()[i]
if id != "" {
return
}
break
}
}
switch c.Request().Method {
case http.MethodGet, http.MethodDelete, http.MethodHead:
for key, values := range c.QueryParams() {
if key == "id" {
id = values[0]
if id != "" {
return
}
break
}
}
}
if c.Request().ContentLength == 0 {
err = rsp.ErrBadParams
return
}
var data *identifier
data, err = BindBody[identifier](c)
if err != nil {
return nil, err
}
if data.ID == nil {
return nil, rsp.ErrBadParams
}
rv := reflect.ValueOf(data.ID)
if rv.IsZero() || rv.IsNil() {
return nil, rsp.ErrBadParams
}
return data.ID, nil
}
func BindQuery[T any](c echo.Context, qb *db.QueryBuilder[T]) (page, limit int, err error) {
query := c.QueryParams()
var paginating bool
for key, values := range query {
switch key {
case "sortby":
for _, s := range values {
if s[0] == '+' {
qb.AscentBy(s[1:])
} else if s[0] == '-' {
qb.DescentBy(s[1:])
} else {
qb.AscentBy(s)
}
}
case "limit", "page":
var v int
if values[0] != "" {
v, err = strconv.Atoi(values[0])
if err != nil {
return
}
}
if v <= 0 {
err = rsp.ErrInternal
return
}
if key == "limit" {
qb.Limit(v)
limit = v
} else {
paginating = true
page = max(v, 1)
}
default:
v := values[0]
i := strings.IndexByte(key, '#')
if i == -1 {
qb.Eq(key, v)
continue
}
switch k, op := key[:i], key[i+1:]; op {
case "=":
qb.Eq(k, v)
case "!=":
qb.Neq(k, v)
case "<":
qb.Lt(k, v)
case "<=":
qb.Lte(k, v)
case ">":
qb.Gt(k, v)
case ">=":
qb.Gte(k, v)
case "<>", "><":
var less, more any
switch len(values) {
case 2:
less, more = values[0], values[1]
case 1:
vs := strings.Split(v, ",")
if len(vs) != 2 || vs[0] == "" || vs[1] == "" {
err = rsp.ErrBadParams
return
}
less, more = vs[0], vs[1]
default:
err = rsp.ErrBadParams
return
}
if op == "<>" {
qb.Between(k, less, more)
} else {
qb.NotBetween(k, key, more)
}
case "nil":
qb.IsNull(k)
case "!nil":
qb.NotNull(k)
case "~":
qb.Like(k, v)
case "!~":
qb.NotLike(k, v)
case "in", "!in":
if len(values) == 1 {
values = strings.Split(v, ",")
}
vs := make([]any, len(values))
for i, value := range values {
vs[i] = value
}
if op == "in" {
qb.In(k, vs...)
} else {
qb.NotIn(k, vs...)
}
default:
qb.Eq(key, v)
}
}
}
if paginating {
if limit == 0 {
limit = 30
qb.Limit(limit)
}
qb.Offset((page - 1) * limit)
}
return
}
Loading…
Cancel
Save