parent
fa896b66b6
commit
973d88e77c
@ -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 |
||||
} |
||||
|
@ -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 |
||||
} |
@ -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…
Reference in new issue