diff --git a/action/action.go b/action/action.go index e762ce4..afa5d69 100755 --- a/action/action.go +++ b/action/action.go @@ -7,9 +7,8 @@ import ( "github.com/glennliao/apijson-go/config" "github.com/glennliao/apijson-go/consts" "github.com/glennliao/apijson-go/model" - "github.com/gogf/gf/v2/database/gdb" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/frame/g" + "github.com/glennliao/apijson-go/query" + "github.com/glennliao/apijson-go/util" "github.com/gogf/gf/v2/util/gconv" ) @@ -40,6 +39,9 @@ type Action struct { JsonFieldStyle config.FieldStyle ActionConfig *config.ActionConfig + + NewQuery func(ctx context.Context, req model.Map) *query.Query + NewAction func(ctx context.Context, method string, req model.Map) *Action } func New(ctx context.Context, actionConfig *config.ActionConfig, method string, req model.Map) *Action { @@ -50,7 +52,7 @@ func New(ctx context.Context, actionConfig *config.ActionConfig, method string, } delete(req, consts.Tag) - delete(req, "version") + delete(req, consts.Version) a := &Action{ ctx: ctx, @@ -72,12 +74,13 @@ func (a *Action) parse() error { structuresKey := key if strings.HasSuffix(key, consts.ListKeySuffix) { - structuresKey = structuresKey[0 : len(structuresKey)-2] + structuresKey = util.RemoveSuffix(key, consts.ListKeySuffix) } + structure, ok := structures[key] if !ok { if structure, ok = structures[structuresKey]; !ok { // User[]可读取User或者User[] - return gerror.New("structure错误: 400, 缺少" + key) + return consts.NewStructureKeyNoFoundErr(key) } } @@ -133,7 +136,21 @@ func (a *Action) Result() (model.Map, error) { } } - err = g.DB().Transaction(a.ctx, func(ctx context.Context, tx gdb.TX) error { + transactionHandler := noTransactionHandler + + if *a.tagRequest.Transaction == true { + transactionResolver = GetTransactionHandler + h := transactionResolver(a.ctx, a) + if h != nil { + err = consts.NewSysErr("transaction handler is nil") + return nil, err + } + + transactionHandler = h + + } + + err = transactionHandler(a.ctx, func(ctx context.Context) error { for _, k := range a.tagRequest.ExecQueue { node := a.children[k] ret[k], err = node.execute(ctx, a.method) @@ -162,11 +179,11 @@ func (a *Action) Result() (model.Map, error) { func checkTag(req model.Map, method string, requestCfg *config.ActionConfig) (*config.RequestConfig, error) { _tag, ok := req[consts.Tag] if !ok { - return nil, gerror.New("tag 缺失") + return nil, consts.ErrNoTag } tag := gconv.String(_tag) - version := req["version"] + version := req[consts.Version] request, err := requestCfg.GetRequest(tag, method, gconv.String(version)) if err != nil { diff --git a/action/executor.go b/action/executor.go new file mode 100755 index 0000000..3ef5721 --- /dev/null +++ b/action/executor.go @@ -0,0 +1,65 @@ +package action + +import ( + "context" + + "github.com/glennliao/apijson-go/config" + "github.com/glennliao/apijson-go/consts" + "github.com/glennliao/apijson-go/model" + "github.com/glennliao/apijson-go/query" + "github.com/samber/lo" +) + +type ActionExecutorReq struct { + Method string + Table string + Data []model.Map + Where []model.Map + Access *config.AccessConfig + Config *config.ActionConfig + NewQuery func(ctx context.Context, req model.Map) *query.Query +} + +var actionExecutorMap = map[string]ActionExecutor{} + +func RegExecutor(name string, e ActionExecutor) { + actionExecutorMap[name] = e +} + +func GetActionExecutor(name string) (ActionExecutor, error) { + if name == "" { + name = "default" + } + if v, exists := actionExecutorMap[name]; exists { + return v, nil + } + return nil, consts.NewSysErr("action executor not found: " + name) +} + +func ActionExecutorList() []string { + return lo.Keys(actionExecutorMap) +} + +type ActionExecutor interface { + Do(ctx context.Context, req ActionExecutorReq) (ret model.Map, err error) +} + +// TransactionHandler 事务处理函数 + +type TransactionHandler func(ctx context.Context, action func(ctx context.Context) error) error + +type TransactionResolver func(ctx context.Context, req *Action) TransactionHandler + +var noTransactionHandler = func(ctx context.Context, action func(ctx context.Context) error) error { + return action(ctx) +} + +var transactionResolver TransactionResolver + +func RegTransactionResolver(r TransactionResolver) { + transactionResolver = r +} + +func GetTransactionHandler(ctx context.Context, req *Action) TransactionHandler { + return transactionResolver(ctx, req) +} diff --git a/action/node.go b/action/node.go index a758071..60348c1 100755 --- a/action/node.go +++ b/action/node.go @@ -6,11 +6,9 @@ import ( "strings" "github.com/glennliao/apijson-go/config" - "github.com/glennliao/apijson-go/config/executor" "github.com/glennliao/apijson-go/consts" "github.com/glennliao/apijson-go/model" "github.com/glennliao/apijson-go/util" - "github.com/gogf/gf/v2/errors/gerror" "github.com/samber/lo" ) @@ -45,7 +43,6 @@ func newNode(key string, req []model.Map, structure *config.Structure, executor n.Data = []model.Map{} n.Where = []model.Map{} - // ? for _ = range n.req { n.Data = append(n.Data, model.Map{}) @@ -54,7 +51,7 @@ func newNode(key string, req []model.Map, structure *config.Structure, executor } if strings.HasSuffix(key, consts.ListKeySuffix) { - n.Key = key[0 : len(key)-len(consts.ListKeySuffix)] + n.Key = util.RemoveSuffix(key, consts.ListKeySuffix) n.IsList = true } @@ -65,10 +62,6 @@ func newNode(key string, req []model.Map, structure *config.Structure, executor func (n *Node) parseReq(method string) { for i, item := range n.req { - - // n.Data = append(n.Data, model.Map{}) - // n.Where = append(n.Where, model.Map{}) - for key, val := range item { if key == consts.Role { @@ -84,7 +77,7 @@ func (n *Node) parseReq(method string) { case http.MethodDelete: n.Where[i][key] = val case http.MethodPut: - if key == n.RowKey || key == n.RowKey+"{}" { + if key == n.RowKey || key == n.RowKey+consts.OpIn { n.Where[i][key] = val } else { n.Data[i][key] = val @@ -100,7 +93,7 @@ func (n *Node) parse(ctx context.Context, method string) error { key := n.Key if strings.HasSuffix(key, consts.ListKeySuffix) { - key = key[0 : len(key)-2] + key = util.RemoveSuffix(key, consts.ListKeySuffix) } access, err := n.Action.ActionConfig.GetAccessConfig(key, true) @@ -179,13 +172,13 @@ func (n *Node) checkAccess(ctx context.Context, method string, accessRoles []str } if role == consts.DENY { - return gerror.Newf("deny node: %s with %s", n.Key, n.Role) + return consts.NewDenyErr(n.Key, n.Role) } n.Role = role if !lo.Contains(accessRoles, role) { - return gerror.Newf("node not access: %s with %s", n.Key, n.Role) + return consts.NewNoAccessErr(n.Key, n.Role) } return nil @@ -231,26 +224,26 @@ func (n *Node) checkReq() error { // must for _, key := range n.structure.Must { if _, exists := item[key]; !exists { - return gerror.New("structure错误: 400, 缺少" + n.Key + "." + key) + return consts.NewStructureKeyNoFoundErr(n.Key + "." + key) } } // refuse if len(n.structure.Refuse) > 0 && n.structure.Refuse[0] == "!" { if len(n.structure.Must) == 0 { - return gerror.New("structure错误: 400, REFUSE为!时必须指定MUST" + n.Key) + return consts.NewValidStructureErr("REFUSE为!时必须指定MUST:" + n.Key) } for key, _ := range item { if !lo.Contains(n.structure.Must, key) { - return gerror.New("structure错误: 400, 不能包含" + n.Key + "." + key) + return consts.NewValidStructureErr("不能包含:" + n.Key + "." + key) } } } else { for _, key := range n.structure.Refuse { if _, exists := item[key]; exists { - return gerror.New("structure错误: 400, 不能包含" + n.Key + "." + key) + return consts.NewValidStructureErr("不能包含:" + n.Key + "." + key) } } } @@ -375,16 +368,22 @@ func (n *Node) do(ctx context.Context, method string) (ret model.Map, err error) case http.MethodDelete: default: - return nil, gerror.New("undefined method:" + method) + return nil, consts.NewMethodNotSupportErr(method) + } + + executor, err := GetActionExecutor(n.executor) + if err != nil { + return nil, err } - ret, err = executor.GetActionExecutor(n.executor).Do(ctx, executor.ActionExecutorReq{ - Method: method, - Table: n.tableName, - Data: n.Data, - Where: n.Where, - Access: access, - Config: n.Action.ActionConfig, + ret, err = executor.Do(ctx, ActionExecutorReq{ + Method: method, + Table: n.tableName, + Data: n.Data, + Where: n.Where, + Access: access, + Config: n.Action.ActionConfig, + NewQuery: n.Action.NewQuery, }) if err != nil { diff --git a/apijson.go b/apijson.go index 4143838..308037a 100755 --- a/apijson.go +++ b/apijson.go @@ -2,6 +2,7 @@ package apijson import ( "context" + "github.com/glennliao/apijson-go/action" "github.com/glennliao/apijson-go/config" "github.com/glennliao/apijson-go/model" @@ -20,9 +21,6 @@ type ApiJson struct { var DefaultApiJson = New() -type App struct { -} - func New() *ApiJson { a := &ApiJson{} a.config = config.New() @@ -76,5 +74,7 @@ func (a *ApiJson) NewAction(ctx context.Context, method string, req model.Map) * act.DbFieldStyle = a.config.DbFieldStyle act.JsonFieldStyle = a.config.JsonFieldStyle + act.NewQuery = a.NewQuery + return act } diff --git a/config/access_config.go b/config/access_config.go index 9709568..7380735 100755 --- a/config/access_config.go +++ b/config/access_config.go @@ -1,10 +1,11 @@ package config import ( - "github.com/gogf/gf/v2/errors/gerror" + "net/http" + + "github.com/glennliao/apijson-go/consts" "github.com/gogf/gf/v2/os/gtime" "github.com/samber/lo" - "net/http" ) type FieldsGetValue struct { @@ -56,28 +57,28 @@ func (a *AccessConfig) GetFieldsGetInByRole(role string) map[string][]string { return inFieldsMap } -func (a *Access) GetAccess(tableAlias string, noVerify bool) (*AccessConfig, error) { - access, ok := a.accessConfigMap[tableAlias] +func (a *Access) GetAccess(accessName string, noVerify bool) (*AccessConfig, error) { + access, ok := a.accessConfigMap[accessName] if !ok { if noVerify { return &AccessConfig{ Debug: 0, - Name: tableAlias, - Alias: tableAlias, + Name: accessName, + Alias: accessName, }, nil } - return nil, gerror.Newf("access[%s]: 404", tableAlias) + return nil, consts.NewAccessNoFoundErr(accessName) } return &access, nil } -func (a *Access) GetAccessRole(table string, method string) ([]string, string, error) { - access, ok := a.accessConfigMap[table] +func (a *Access) GetAccessRole(accessName string, method string) ([]string, string, error) { + access, ok := a.accessConfigMap[accessName] if !ok { - return nil, "", gerror.Newf("access[%s]: 404", table) + return nil, "", consts.NewAccessNoFoundErr(accessName) } switch method { diff --git a/config/executor/action.go b/config/executor/action.go deleted file mode 100755 index 799df98..0000000 --- a/config/executor/action.go +++ /dev/null @@ -1,39 +0,0 @@ -package executor - -import ( - "context" - - "github.com/glennliao/apijson-go/config" - "github.com/glennliao/apijson-go/model" - "github.com/samber/lo" -) - -type ActionExecutor interface { - Do(ctx context.Context, req ActionExecutorReq) (ret model.Map, err error) -} - -type ActionExecutorReq struct { - Method string - Table string - Data []model.Map - Where []model.Map - Access *config.AccessConfig - Config *config.ActionConfig -} - -var actionExecutorMap = map[string]ActionExecutor{} - -func RegActionExecutor(name string, e ActionExecutor) { - actionExecutorMap[name] = e -} - -func GetActionExecutor(name string) ActionExecutor { - if v, exists := actionExecutorMap[name]; exists { - return v - } - return actionExecutorMap["default"] -} - -func ActionExecutorList() []string { - return lo.Keys(actionExecutorMap) -} diff --git a/config/request_config.go b/config/request_config.go index 466d2d7..5804c44 100755 --- a/config/request_config.go +++ b/config/request_config.go @@ -21,6 +21,8 @@ type RequestConfig struct { // 节点执行顺序 ExecQueue []string Executor map[string]string + // 是否开启事务 + Transaction *bool } type Structure struct { diff --git a/consts/errors.go b/consts/errors.go index 93290a9..eda3fc0 100755 --- a/consts/errors.go +++ b/consts/errors.go @@ -1,18 +1,78 @@ package consts -const ( - ErrCode = 40001 +// action + +var ( + ErrNoTag = Err{ + code: 400, + message: "no tag", + } ) +func NewStructureKeyNoFoundErr(k string) Err { + return NewValidStructureErr("field " + k + " is not found") +} + +func NewValidStructureErr(msg string) Err { + return Err{ + code: 400, + message: msg, + } +} + +func NewValidReqErr(msg string) Err { + return Err{ + code: 400, + message: msg, + } +} + +func NewMethodNotSupportErr(msg string) Err { + return Err{ + code: 400, + message: msg, + } +} + +// access + +func NewDenyErr(key, role string) Err { + return Err{ + code: 403, + message: "deny node: " + key + " with " + role, + } +} + +func NewNoAccessErr(key, role string) Err { + return Err{ + code: 403, + message: "node not access: " + key + " with " + role, + } +} + +func NewAccessNoFoundErr(key string) Err { + return Err{ + code: 404, + message: "access no found: " + key, + } +} + +func NewSysErr(msg string) Err { + return Err{ + code: 500, + message: msg, + } +} + type Err struct { code int message string } -func (e *Err) Code() int { +func (e Err) Code() int { return e.code } -func (e *Err) Error() string { +func (e Err) Error() string { return e.message } diff --git a/consts/node.go b/consts/node.go index 62c68f1..40f71a1 100755 --- a/consts/node.go +++ b/consts/node.go @@ -13,13 +13,16 @@ const ( ) const ( - Role = "@role" - Page = "page" // page num - Count = "count" // page size - Total = "total" - Query = "query" - Alias = "@alias" - Tag = "tag" + CtrlKeyPrefix = "@" + Role = "@role" + Page = "page" // page num + Count = "count" // page size + Total = "total" + Query = "query" + Alias = "@alias" + Column = "@column" + Tag = "tag" + Version = "version" ) const ( diff --git a/drivers/goframe/all.go b/drivers/goframe/all.go index e1f2739..6a6bf95 100644 --- a/drivers/goframe/all.go +++ b/drivers/goframe/all.go @@ -1,10 +1,15 @@ package goframe import ( + "context" + + "github.com/glennliao/apijson-go/action" "github.com/glennliao/apijson-go/config" - "github.com/glennliao/apijson-go/config/executor" gfConfig "github.com/glennliao/apijson-go/drivers/goframe/config" gfExecutor "github.com/glennliao/apijson-go/drivers/goframe/executor" + "github.com/glennliao/apijson-go/query" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" ) func init() { @@ -14,8 +19,16 @@ func init() { config.RegDbMetaProvider(gfConfig.ProviderName, gfConfig.DbMetaProvider) - executor.RegQueryExecutor("default", gfExecutor.New) - executor.RegActionExecutor("default", &gfExecutor.ActionExecutor{}) + query.RegExecutor("default", gfExecutor.New) + action.RegExecutor("default", &gfExecutor.ActionExecutor{}) + + action.RegTransactionResolver(func(ctx context.Context, req *action.Action) action.TransactionHandler { + return func(ctx context.Context, action func(ctx context.Context) error) error { + // TODO g.DB() -> resolver + return g.DB().Ctx(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + return action(ctx) + }) + } + }) - // todo 添加未配置driver时的报错信息, 而不是 invalid memory address or nil pointer dereference } diff --git a/drivers/goframe/executor/action.go b/drivers/goframe/executor/action.go index bb0e86f..f3b8b09 100755 --- a/drivers/goframe/executor/action.go +++ b/drivers/goframe/executor/action.go @@ -5,12 +5,11 @@ import ( "net/http" "strings" - "github.com/glennliao/apijson-go/config/executor" + "github.com/glennliao/apijson-go/action" "github.com/glennliao/apijson-go/consts" "github.com/glennliao/apijson-go/model" "github.com/glennliao/apijson-go/util" "github.com/gogf/gf/v2/database/gdb" - "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" ) @@ -19,7 +18,7 @@ type ActionExecutor struct { DbName string } -func (a *ActionExecutor) Do(ctx context.Context, req executor.ActionExecutorReq) (ret model.Map, err error) { +func (a *ActionExecutor) Do(ctx context.Context, req action.ActionExecutorReq) (ret model.Map, err error) { switch req.Method { case http.MethodPost: return a.Insert(ctx, req.Table, req.Data) @@ -53,7 +52,7 @@ func (a *ActionExecutor) Do(ctx context.Context, req executor.ActionExecutorReq) } return ret, err } - return nil, gerror.New("method not support") + return nil, consts.NewMethodNotSupportErr(req.Method) } func (a *ActionExecutor) Insert(ctx context.Context, table string, data []model.Map) (ret model.Map, err error) { @@ -84,7 +83,7 @@ func (a *ActionExecutor) Update(ctx context.Context, table string, data model.Ma if strings.HasSuffix(k, consts.OpIn) { if vStr, ok := v.(string); ok { if vStr == "" { - return nil, gerror.New("where的值不能为空") + return nil, consts.NewValidReqErr("where的值不能为空") } } m = m.WhereIn(k[0:len(k)-2], v) @@ -98,7 +97,7 @@ func (a *ActionExecutor) Update(ctx context.Context, table string, data model.Ma } if v == nil || gconv.String(v) == "" { // 暂只处理字符串为空的情况 - return nil, gerror.New("where的值不能为空:" + k) + return nil, consts.NewValidReqErr("where的值不能为空:" + k) } } @@ -143,7 +142,7 @@ func (a *ActionExecutor) Update(ctx context.Context, table string, data model.Ma func (a *ActionExecutor) Delete(ctx context.Context, table string, where model.Map) (ret model.Map, err error) { if len(where) == 0 { - return nil, gerror.New("where不能为空") + return nil, consts.NewValidReqErr("where的值不能为空") } m := g.DB(a.DbName).Model(table).Ctx(ctx) @@ -162,7 +161,7 @@ func (a *ActionExecutor) Delete(ctx context.Context, table string, where model.M } if gconv.String(v) == "" || v == nil { // 暂只处理字符串为空的情况 - return nil, gerror.New("where的值不能为空") + return nil, consts.NewValidReqErr("where的值不能为空:" + k) } m = m.Where(k, v) diff --git a/drivers/goframe/executor/query.go b/drivers/goframe/executor/query.go index fbc94be..1787d2a 100755 --- a/drivers/goframe/executor/query.go +++ b/drivers/goframe/executor/query.go @@ -6,9 +6,9 @@ import ( "strings" "github.com/glennliao/apijson-go/config" - "github.com/glennliao/apijson-go/config/executor" "github.com/glennliao/apijson-go/consts" "github.com/glennliao/apijson-go/model" + "github.com/glennliao/apijson-go/query" "github.com/glennliao/apijson-go/util" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" @@ -35,7 +35,7 @@ type SqlExecutor struct { config *config.ExecutorConfig } -func New(ctx context.Context, config *config.ExecutorConfig) (executor.QueryExecutor, error) { +func New(ctx context.Context, config *config.ExecutorConfig) (query.QueryExecutor, error) { return &SqlExecutor{ ctx: ctx, @@ -110,11 +110,12 @@ func (e *SqlExecutor) ParseCondition(conditions model.MapStrAny, accessVerify bo } if !lo.Contains(val, op) { - panic("不允许使用" + where[0].(string) + "的搜索方式:" + op) + + return consts.NewValidReqErr("不允许使用" + where[0].(string) + "的搜索方式:" + op) } } else { - panic("不允许使用" + where[0].(string) + "搜索") + return consts.NewValidReqErr("不允许使用" + where[0].(string) + "搜索") } } diff --git a/drivers/goframe/web/gf.go b/drivers/goframe/web/gf.go index 99220e8..ec8af50 100755 --- a/drivers/goframe/web/gf.go +++ b/drivers/goframe/web/gf.go @@ -18,12 +18,14 @@ import ( ) type GF struct { - apijson *apijson.ApiJson + apijson *apijson.ApiJson + ResponseResolver func(handler func(ctx context.Context, req model.Map) (res model.Map, err error), mode Mode, debug bool) func(req *ghttp.Request) } func New(a *apijson.ApiJson) *GF { return &GF{ - apijson: a, + apijson: a, + ResponseResolver: CommonResponse, } } @@ -40,6 +42,7 @@ func (gf *GF) Run(s ...*ghttp.Server) { server.Group("/", func(group *ghttp.RouterGroup) { gf.Bind(group) }) + server.Run() } @@ -47,11 +50,11 @@ func (gf *GF) Bind(group *ghttp.RouterGroup, mode ...Mode) { if len(mode) == 0 { mode = []Mode{InDataMode} } - group.POST("/get", gf.CommonResponse(gf.Get, mode[0])) - group.POST("/post", gf.CommonResponse(gf.Post, mode[0])) - group.POST("/head", gf.CommonResponse(gf.Head, mode[0])) - group.POST("/put", gf.CommonResponse(gf.Put, mode[0])) - group.POST("/delete", gf.CommonResponse(gf.Delete, mode[0])) + group.POST("/get", gf.ResponseResolver(gf.Get, mode[0], gf.apijson.Debug)) + group.POST("/post", gf.ResponseResolver(gf.Post, mode[0], gf.apijson.Debug)) + group.POST("/head", gf.ResponseResolver(gf.Head, mode[0], gf.apijson.Debug)) + group.POST("/put", gf.ResponseResolver(gf.Put, mode[0], gf.apijson.Debug)) + group.POST("/delete", gf.ResponseResolver(gf.Delete, mode[0], gf.apijson.Debug)) } func (gf *GF) Get(ctx context.Context, req model.Map) (res model.Map, err error) { @@ -78,7 +81,33 @@ func (gf *GF) Delete(ctx context.Context, req model.Map) (res model.Map, err err return act.Result() } -func (gf *GF) CommonResponse(handler func(ctx context.Context, req model.Map) (res model.Map, err error), mode Mode) func(req *ghttp.Request) { +// 调试模式开启, 使用orderedmap输出结果 +func sortMap(ctx context.Context, body []byte, res *gmap.ListMap, ret model.Map) *orderedmap.OrderedMap { + reqSortMap := orderedmap.New() + err := reqSortMap.UnmarshalJSON(body) + if err != nil { + g.Log().Warning(ctx, err) + } + + for _, k := range reqSortMap.Keys() { + if strings.HasPrefix(k, consts.RefKeySuffix) { + continue + } + if k == consts.Tag { + continue + } + + if strings.HasSuffix(k, consts.RefKeySuffix) { + k = k[:len(k)-1] + } + + res.Set(k, ret[k]) + } + + return reqSortMap +} + +func CommonResponse(handler func(ctx context.Context, req model.Map) (res model.Map, err error), mode Mode, debug bool) func(req *ghttp.Request) { return func(req *ghttp.Request) { metaRes := &gmap.ListMap{} code := 200 @@ -87,16 +116,9 @@ func (gf *GF) CommonResponse(handler func(ctx context.Context, req model.Map) (r err := g.Try(req.Context(), func(ctx context.Context) { - ret, err := handler(req.Context(), req.GetMap()) - - if err == nil { - code = 200 - } else { - code = 500 - msg = err.Error() - } + ret, err := handler(ctx, req.GetMap()) - if gf.apijson.Debug { + if debug { sortMap(ctx, req.GetBody(), metaRes, ret) } else { for k, v := range ret { @@ -111,8 +133,15 @@ func (gf *GF) CommonResponse(handler func(ctx context.Context, req model.Map) (r }) if err != nil { - code = 500 + + if e, ok := err.(consts.Err); ok { + code = e.Code() + } else { + code = 500 + } + msg = err.Error() + if e, ok := err.(*gerror.Error); ok { g.Log().Stack(false).Error(req.Context(), err, e.Stack()) } else { @@ -129,29 +158,3 @@ func (gf *GF) CommonResponse(handler func(ctx context.Context, req model.Map) (r req.Response.WriteJson(res.String()) } } - -// 调试模式开启, 使用orderedmap输出结果 -func sortMap(ctx context.Context, body []byte, res *gmap.ListMap, ret model.Map) *orderedmap.OrderedMap { - reqSortMap := orderedmap.New() - err := reqSortMap.UnmarshalJSON(body) - if err != nil { - g.Log().Warning(ctx, err) - } - - for _, k := range reqSortMap.Keys() { - if strings.HasPrefix(k, consts.RefKeySuffix) { - continue - } - if k == consts.Tag { - continue - } - - if strings.HasSuffix(k, consts.RefKeySuffix) { - k = k[:len(k)-1] - } - - res.Set(k, ret[k]) - } - - return reqSortMap -} diff --git a/config/executor/query.go b/query/executor.go similarity index 70% rename from config/executor/query.go rename to query/executor.go index 65c8cb9..ec728e0 100755 --- a/config/executor/query.go +++ b/query/executor.go @@ -1,8 +1,10 @@ -package executor +package query import ( "context" + "github.com/glennliao/apijson-go/config" + "github.com/glennliao/apijson-go/consts" "github.com/glennliao/apijson-go/model" "github.com/samber/lo" ) @@ -20,15 +22,20 @@ type queryExecutorBuilder func(ctx context.Context, config *config.ExecutorConfi var queryExecutorBuilderMap = map[string]queryExecutorBuilder{} -func RegQueryExecutor(name string, e queryExecutorBuilder) { +func RegExecutor(name string, e queryExecutorBuilder) { queryExecutorBuilderMap[name] = e } -func NewQueryExecutor(name string, ctx context.Context, config *config.ExecutorConfig) (QueryExecutor, error) { +func NewExecutor(name string, ctx context.Context, config *config.ExecutorConfig) (QueryExecutor, error) { + if name == "" { + name = "default" + } + if v, exists := queryExecutorBuilderMap[name]; exists { return v(ctx, config) } - return queryExecutorBuilderMap["default"](ctx, config) + + return nil, consts.NewSysErr("query executor not found: " + name) } func QueryExecutorList() []string { diff --git a/query/node.go b/query/node.go index 81bb742..4d59cce 100755 --- a/query/node.go +++ b/query/node.go @@ -2,16 +2,15 @@ package query import ( "context" + "fmt" "path/filepath" "strings" "time" "github.com/glennliao/apijson-go/config" - "github.com/glennliao/apijson-go/config/executor" "github.com/glennliao/apijson-go/consts" "github.com/glennliao/apijson-go/model" "github.com/glennliao/apijson-go/util" - "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" "github.com/samber/lo" @@ -63,7 +62,7 @@ type Node struct { simpleReqVal string // 非对象结构 // 节点数据执行器 - executor executor.QueryExecutor + executor QueryExecutor startAt time.Time endAt time.Time @@ -183,7 +182,7 @@ func (n *Node) buildChild() error { // 最大深度检查 maxDeep := n.queryContext.queryConfig.MaxTreeDeep() if len(strings.Split(n.Path, "/")) > maxDeep { - return gerror.Newf("deep(%s) > %d", n.Path, maxDeep) + return consts.NewValidReqErr(fmt.Sprintf("deep(%s) > %d", n.Path, maxDeep)) } children := make(map[string]*Node) @@ -230,7 +229,7 @@ func (n *Node) buildChild() error { if path == "" { path = "root" } - return gerror.Newf("width(%s) > %d", path, maxWidth) + return consts.NewValidReqErr(fmt.Sprintf("width(%s) > %d", path, maxWidth)) } n.children = children diff --git a/query/node_func.go b/query/node_func.go index 5e50e57..56198eb 100755 --- a/query/node_func.go +++ b/query/node_func.go @@ -1,6 +1,11 @@ package query import ( + "fmt" + "path/filepath" + "strings" + + "github.com/glennliao/apijson-go/consts" "github.com/glennliao/apijson-go/model" "github.com/glennliao/apijson-go/util" ) @@ -18,6 +23,10 @@ func (h *funcNode) parse() { } func (h *funcNode) fetch() { + +} + +func (h *funcNode) result() { n := h.node queryConfig := n.queryContext.queryConfig @@ -33,24 +42,43 @@ func (h *funcNode) fetch() { param := model.Map{} for i, item := range _func.ParamList { - valNode := n.queryContext.pathNodes[paramKeys[i]] - // if valNode == nil { - // continue - // } + paramName := paramKeys[i] + if strings.HasPrefix(paramName, "/") { // 这里/开头是相对同级 + dir := filepath.Dir(n.Path) + if dir == "." { + dir = "" + paramName = paramName[1:] + } + paramName = dir + paramName + } + refPath, paramName := util.ParseRefCol(paramName) + if refPath == n.Path { // 不能依赖自身 + n.err = consts.NewValidReqErr(fmt.Sprintf("node cannot ref self: (%s)", refPath)) + return + } + + valNode := n.queryContext.pathNodes[refPath] + if valNode == nil { + h.node.err = consts.NewValidReqErr(fmt.Sprintf("param %s no found on %s", paramKeys[i], functionName)) + return + } if valNode.ret != nil { - param[item.Name] = valNode.ret + + switch valNode.ret.(type) { + case model.Map: + param[item.Name] = util.String(valNode.ret.(model.Map)[paramName]) + case string: + param[item.Name] = valNode.ret.(string) + } + } else { - param[item.Name] = valNode.simpleReqVal + param[item.Name] = util.String(valNode.simpleReqVal) } } n.ret, n.err = _func.Handler(n.ctx, param) } -func (h *funcNode) result() { - -} - func (h *funcNode) nodeType() int { return NodeTypeStruct } diff --git a/query/node_query.go b/query/node_query.go index 0b33806..749b54a 100755 --- a/query/node_query.go +++ b/query/node_query.go @@ -8,11 +8,9 @@ import ( "strings" "github.com/glennliao/apijson-go/config" - "github.com/glennliao/apijson-go/config/executor" "github.com/glennliao/apijson-go/consts" "github.com/glennliao/apijson-go/model" "github.com/glennliao/apijson-go/util" - "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/util/gconv" ) @@ -42,7 +40,8 @@ func (q *queryNode) parse() { fieldsGet := n.executorConfig.GetFieldsGetByRole() if *fieldsGet.MaxCount != 0 { if n.page.Count > *fieldsGet.MaxCount { - n.err = gerror.New(" > maxCount: " + n.Path) + + n.err = consts.NewValidReqErr(" > maxCount: " + n.Path) return } } @@ -54,7 +53,7 @@ func (q *queryNode) parse() { n.executorConfig.SetRole(n.role) if n.role == consts.DENY { - n.err = gerror.New("deny node: " + n.Path) + n.err = consts.NewDenyErr(n.Key, n.role) return } @@ -66,14 +65,14 @@ func (q *queryNode) parse() { } if !has { - n.err = gerror.New("无权限访问:" + n.Key + " by " + n.role) + n.err = consts.NewNoAccessErr(n.Key, n.role) return } accessWhereCondition = condition.Where() } - queryExecutor, err := executor.NewQueryExecutor(n.executorConfig.Executor(), n.ctx, n.executorConfig) + queryExecutor, err := NewExecutor(n.executorConfig.Executor(), n.ctx, n.executorConfig) if err != nil { n.err = err return @@ -86,7 +85,7 @@ func (q *queryNode) parse() { n.executor.ParseCtrl(ctrlMap) - if v, exists := ctrlMap["@column"]; exists { + if v, exists := ctrlMap[consts.Column]; exists { var exp = regexp.MustCompile(`^[\s\w][\w()]+`) // 匹配 field, COUNT(field) fieldStr := strings.ReplaceAll(gconv.String(v), ";", ",") @@ -142,13 +141,14 @@ func (q *queryNode) parse() { } if refPath == n.Path { // 不能依赖自身 - n.err = gerror.Newf("node cannot ref self: (%s) {%s:%s}", refPath, refKey, refStr) + + n.err = consts.NewValidReqErr(fmt.Sprintf("node cannot ref self: (%s) {%s:%s}", refPath, refKey, refStr)) return } refNode := n.queryContext.pathNodes[refPath] if refNode == nil { - n.err = gerror.Newf(" node %s is nil, but ref by %s", refPath, n.Path) + n.err = consts.NewValidReqErr(fmt.Sprintf(" node %s is nil, but ref by %s", refPath, n.Path)) return } @@ -159,7 +159,7 @@ func (q *queryNode) parse() { for _, _refN := range refNode.refKeyMap { if _refN.node.Path == n.Path { - n.err = gerror.Newf("circle ref %s & %s", refNode.Path, n.Path) + n.err = consts.NewValidReqErr(fmt.Sprintf("circle ref %s & %s", refNode.Path, n.Path)) return } } @@ -266,12 +266,13 @@ func (q *queryNode) fetch() { if n.isList { for i, item := range n.ret.([]model.Map) { + // todo 统一functions调用处理 param := model.Map{} for paramI, paramItem := range _func.ParamList { if paramItem.Name == consts.FunctionOriReqParam { - param[paramItem.Name] = item + param[paramItem.Name] = util.String(item) } else { - param[paramItem.Name] = item[paramKeys[paramI]] + param[paramItem.Name] = util.String(item[paramKeys[paramI]]) } } diff --git a/query/node_ref.go b/query/node_ref.go index 254d6ab..4271a51 100755 --- a/query/node_ref.go +++ b/query/node_ref.go @@ -1,12 +1,13 @@ package query import ( + "fmt" + "path/filepath" + "strings" + "github.com/glennliao/apijson-go/consts" "github.com/glennliao/apijson-go/model" "github.com/glennliao/apijson-go/util" - "github.com/gogf/gf/v2/errors/gerror" - "path/filepath" - "strings" ) const ( @@ -30,12 +31,14 @@ func (r *RefNode) parse() { } refPath, refCol := util.ParseRefCol(refStr) if refPath == n.Path { // 不能依赖自身 - panic(gerror.Newf("node cannot ref self: (%s)", refPath)) + n.err = consts.NewValidReqErr(fmt.Sprintf("node cannot ref self: (%s)", refPath)) + return } refNode := n.queryContext.pathNodes[refPath] if refNode == nil { - panic(gerror.Newf(" node %s is nil, but ref by %s", refPath, n.Path)) + n.err = consts.NewValidReqErr(fmt.Sprintf(" node %s is nil, but ref by %s", refPath, n.Path)) + return } n.refKeyMap = make(map[string]NodeRef) diff --git a/query/node_struct.go b/query/node_struct.go index 9e8ba0d..ca422c0 100755 --- a/query/node_struct.go +++ b/query/node_struct.go @@ -6,7 +6,6 @@ import ( "github.com/glennliao/apijson-go/consts" "github.com/glennliao/apijson-go/model" - "github.com/gogf/gf/v2/errors/gerror" ) type structNode struct { @@ -37,7 +36,8 @@ func (h *structNode) parse() { if child.primaryTableKey != "" { if hasPrimary { - panic(gerror.Newf("node must only one primary table: (%s)", n.Path)) + n.err = consts.NewValidReqErr("node must only one primary table: " + n.Path) + return } hasPrimary = true @@ -48,7 +48,8 @@ func (h *structNode) parse() { } if n.Key == consts.ListKeySuffix && !hasPrimary { - panic(gerror.Newf("node must have primary table: (%s)", n.Path)) + n.err = consts.NewValidReqErr("node must have primary table: " + n.Path) + return } } @@ -150,7 +151,12 @@ func (h *structNode) result() { } } - n.ret = retMap + if len(retMap) > 0 && n.simpleReqVal == "" { + n.ret = retMap + } else { + n.ret = n.simpleReqVal + } + } } diff --git a/query/util.go b/query/util.go index dfb6949..39c7b68 100755 --- a/query/util.go +++ b/query/util.go @@ -1,6 +1,9 @@ package query import ( + "net/http" + "strings" + "github.com/glennliao/apijson-go/config" "github.com/glennliao/apijson-go/consts" "github.com/glennliao/apijson-go/model" @@ -8,8 +11,6 @@ import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" "github.com/samber/lo" - "net/http" - "strings" ) // parseQueryNodeReq 解析节点请求内容 @@ -23,9 +24,9 @@ func parseQueryNodeReq(reqMap model.Map, isList bool) (refMap model.MapStrStr, w continue } - if strings.HasSuffix(k, consts.RefKeySuffix) { //引用 + if strings.HasSuffix(k, consts.RefKeySuffix) { // 引用 refMap[k[0:len(k)-1]] = gconv.String(v) - } else if strings.HasPrefix(k, "@") { // @column等ctrl字段 + } else if strings.HasPrefix(k, consts.CtrlKeyPrefix) { // @column等ctrl字段 ctrlMap[k] = v } else { if isList { diff --git a/test/z_app_test.go b/test/z_app_test.go index 3e945bf..706f6fd 100755 --- a/test/z_app_test.go +++ b/test/z_app_test.go @@ -75,6 +75,22 @@ func App(ctx context.Context, a *apijson.ApiJson) { }, }) - //a.Config().AccessListProvider = "custom" + a.Config().Functions.Bind("concat", config.Func{ + ParamList: []config.ParamItem{ + { + Name: "a", + Type: "string", + }, + { + Name: "b", + Type: "string", + }, + }, + Handler: func(ctx context.Context, param model.Map) (res any, err error) { + return param["a"].(string) + param["b"].(string), nil + }, + }) + + // a.Config().AccessListProvider = "custom" } diff --git a/test/z_main_test.go b/test/z_main_test.go index 12b5c55..be7c5f4 100755 --- a/test/z_main_test.go +++ b/test/z_main_test.go @@ -36,24 +36,29 @@ func TestQuery(t *testing.T) { q := a.NewQuery(ctx, model.Map{ "User": model.Map{ - //"id": "123", - //"id{}": []string{"123", "456"}, - //"id>": "222", - //"@column": "id", + // "id": "123", + // "id{}": []string{"123", "456"}, + // "id>": "222", + // "@column": "id", }, "User[]": model.Map{ - "@column": "id", - //"userId": "123", + "@column": "id,username", + "concatTest()": "concat(username,c)", + // "userId": "123", }, - //"user2": model.Map{}, - "a@": "User/username", - "b": model.Map{ - "User": model.Map{ - "id": 1, - }, - "c@": "/User/username", - }, - "say()": "test()", + // "user2": model.Map{}, + // "a@": "User/username", + // "b": model.Map{ + // "User": model.Map{ + // "id": 1, + // }, + // "c@": "/User/username", + // }, + // "say()": "test()", + // "a": "12", + // "c": "34", + // "concatTest()": "concat(/User/username,c)", + // "concatTest()": "concat(User/username,c)", }) q.NoAccessVerify = true @@ -75,7 +80,7 @@ func TestAlias(t *testing.T) { q := a.NewQuery(ctx, model.Map{ "User[]": model.Map{ "@column": "id,password:username", - //"userId": "123", + // "userId": "123", }, }) @@ -94,24 +99,24 @@ func BenchmarkName(b *testing.B) { for i := 0; i < b.N; i++ { ctx := context.Background() q := a.NewQuery(ctx, model.Map{ - //"User": model.Map{ + // "User": model.Map{ // "id": 1, // //"id": "123", // //"id{}": []string{"123", "456"}, // //"id>": "222", // //"@column": "id", - //}, - //"User[]": model.Map{ + // }, + // "User[]": model.Map{ // "@column": "id", // //"userId": "123", - //}, - //"a@": "User/username", - //"b": model.Map{ + // }, + // "a@": "User/username", + // "b": model.Map{ // "User": model.Map{ // "id": 1, // }, // //"c@": "/User/username", - //}, + // }, "say()": "test()", }) diff --git a/util/node.go b/util/node.go index 1fdfc70..284b085 100755 --- a/util/node.go +++ b/util/node.go @@ -1,9 +1,10 @@ package util import ( - "github.com/glennliao/apijson-go/model" - "github.com/gogf/gf/v2/errors/gerror" "path/filepath" + + "github.com/glennliao/apijson-go/consts" + "github.com/glennliao/apijson-go/model" ) func IsFirstUp(str string) bool { @@ -31,7 +32,10 @@ func RemoveSuffix(key string, suffix string) string { // ParseRefCol 解析引用字段 // 将 "id@":"[]/User/userId" 解析出引用信息 func ParseRefCol(refStr string) (refPath string, refCol string) { - refCol = filepath.Base(refStr) // userId + refCol = filepath.Base(refStr) // userId + if refCol == refStr { + return refStr, "" + } refPath = refStr[0 : len(refStr)-len(refCol)-1] // []/User return refPath, refCol } @@ -76,7 +80,7 @@ func AnalysisOrder(prerequisites [][]string) ([]string, error) { } if len(result) != pointNum { - return nil, gerror.New("依赖循环, 请检查请求") + return nil, consts.NewValidReqErr("nodes cycle") } return result, nil