parent
fa896b66b6
commit
973d88e77c
@ -1,24 +1,49 @@ |
|||||||
package services |
package services |
||||||
|
|
||||||
import ( |
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" |
"sorbet/pkg/app" |
||||||
) |
) |
||||||
|
|
||||||
type Service struct { |
var applets []app.Applet |
||||||
inners []Service |
|
||||||
} |
|
||||||
|
|
||||||
func (s Service) Init(ctx *app.Context) error { |
func Init() { |
||||||
//TODO implement me
|
applets = []app.Applet{ |
||||||
panic("implement me") |
&config.Service{}, |
||||||
|
&company.Service{}, |
||||||
|
&resource.Service{}, |
||||||
|
&feature.Service{}, |
||||||
|
&system.Service{}, |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
func (s Service) Start() error { |
func Start(ctx context.Context) error { |
||||||
//TODO implement me
|
e := ctx.Value("echo_framework").(*echo.Echo) |
||||||
panic("implement me") |
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 { |
func Stop() error { |
||||||
//TODO implement me
|
for _, service := range applets { |
||||||
panic("implement me") |
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