diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b41ed3f..baf39e0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,4 +7,7 @@ on: jobs: simple-test: - run: cd test && go test -short + runs-on: ubuntu-latest + steps: + - name: test + run: cd test && go test -short diff --git a/.github/workflows/todo.yml b/.github/workflows/todo.yml deleted file mode 100644 index defa06c..0000000 --- a/.github/workflows/todo.yml +++ /dev/null @@ -1,45 +0,0 @@ -#name: Todo tests -# -#on: -# push: -# branches: [ "main" ,"dev"] -# pull_request: -# branches: [ "main", "dev" ] -# -#jobs: -# -# build: -# runs-on: ubuntu-latest -# services: -# mysql: -# image: mysql:5.7.37 -# env: -# MYSQL_ROOT_PASSWORD: 'yourpassword' -# MYSQL_DATABASE: 'my_apijson' -# ports: -# - 3306:3306 -# options: >- -# --health-cmd="mysqladmin ping" -# --health-interval=10s -# --health-timeout=5s -# --health-retries=3 -# steps: -# - uses: actions/checkout@v3 -# -# - name: Set up Go -# uses: actions/setup-go@v3 -# with: -# go-version: 1.18 -# -# -# -# - name: Test -# run: | -# -# mysql -uroot -h 127.0.0.1 --port 3306 -pyourpassword my_apijson < @demo/todo/doc/todo.sql -# -# cd ./@demo/todo -# mv config.yaml.example config.yaml -# -# cd tests -# go test -v \ No newline at end of file diff --git a/@doc/functions.md b/@doc/functions.md new file mode 100644 index 0000000..a16203e --- /dev/null +++ b/@doc/functions.md @@ -0,0 +1,21 @@ +## 1. 调用方式一 来自返回的字段 -> 因为默认simple字段会返回 -> 需要返回吗 +```json +{ + "name": "hi", + "aaa": "demo", + "ref()": "sayHello(name,aaa)", + "@a": 0, + "ref2()": "ret(@a)", + "User": { + "pic()": "getPic(userId)" // 来自当前User的字段, 需要分析函数依赖的字段和依赖函数字段的节点 + } +} +``` + +## 2 +```json +{ + "msg()": "sayHi", + "msg2()": "sayHi()" +} +``` \ No newline at end of file diff --git a/action/hook.go b/action/hook.go index e46d02b..4f8308d 100644 --- a/action/hook.go +++ b/action/hook.go @@ -28,7 +28,7 @@ func RegHook(h Hook) { func EmitHook(ctx context.Context, hookAt int, node *Node, method string) error { - hooks := append(hooksMap["*"], hooksMap[node.TableName]...) + hooks := append(hooksMap["*"], hooksMap[node.AccessName]...) for _, hook := range hooks { var handler func(ctx context.Context, n *Node, method string) error diff --git a/action/node.go b/action/node.go index 9408d2b..7fb1f3f 100644 --- a/action/node.go +++ b/action/node.go @@ -14,12 +14,13 @@ import ( ) type Node struct { - req []model.Map - ctx context.Context - action *Action - Key string - TableName string - Role string + req []model.Map + ctx context.Context + action *Action + Key string + tableName string + AccessName string + Role string Data []model.Map // 需写入数据库的数据 Where []model.Map // 条件 @@ -34,8 +35,14 @@ type Node struct { } func newNode(key string, req []model.Map, structure *config.Structure, executor string) Node { + + accessName := key + if strings.HasSuffix(accessName, "[]") { + accessName = accessName[0 : len(accessName)-2] + } + return Node{ - Key: key, req: req, structure: structure, executor: executor, + Key: key, req: req, structure: structure, executor: executor, AccessName: accessName, } } @@ -52,7 +59,7 @@ func (n *Node) parseReq(method string) { if key == consts.Role { n.Role = util.String(val) } else { - key = n.action.DbFieldStyle(n.ctx, n.TableName, key) + key = n.action.DbFieldStyle(n.ctx, n.tableName, key) if method == http.MethodDelete { n.Where[i][key] = val @@ -63,7 +70,6 @@ func (n *Node) parseReq(method string) { } else { n.Data[i][key] = val } - // Post 暂原则上不让传递这个rowKey值 // todo 可传递 } else { n.Data[i][key] = val } @@ -86,7 +92,7 @@ func (n *Node) parse(ctx context.Context, method string) error { return err } - n.TableName = access.Name + n.tableName = access.Name n.RowKey = access.RowKey n.parseReq(method) @@ -143,10 +149,8 @@ func (n *Node) roleUpdate() error { func (n *Node) checkAccess(ctx context.Context, method string, accessRoles []string) error { - // todo 可配置单次的内容, 而非直接使用整个的 - role, err := n.action.actionConfig.DefaultRoleFunc()(ctx, config.RoleReq{ - AccessName: n.TableName, + AccessName: n.tableName, Method: method, NodeRole: n.Role, }) @@ -170,7 +174,7 @@ func (n *Node) checkAccess(ctx context.Context, method string, accessRoles []str condition := config.NewConditionRet() conditionReq := config.ConditionReq{ - AccessName: n.TableName, + AccessName: n.tableName, TableAccessRoleList: accessRoles, Method: method, NodeRole: n.Role, @@ -281,9 +285,9 @@ func (n *Node) reqUpdateBeforeDo() error { if strings.HasSuffix(k, consts.RefKeySuffix) { refNodeKey, refCol := util.ParseRefCol(v.(string)) if strings.HasSuffix(refNodeKey, consts.ListKeySuffix) { // 双列表 - n.Data[i][k] = n.keyNode[refNodeKey].Data[i][n.action.DbFieldStyle(n.ctx, n.TableName, refCol)] + n.Data[i][k] = n.keyNode[refNodeKey].Data[i][n.action.DbFieldStyle(n.ctx, n.tableName, refCol)] } else { - n.Data[i][k] = n.keyNode[refNodeKey].Data[0][n.action.DbFieldStyle(n.ctx, n.TableName, refCol)] + n.Data[i][k] = n.keyNode[refNodeKey].Data[0][n.action.DbFieldStyle(n.ctx, n.tableName, refCol)] } } } @@ -314,7 +318,7 @@ func (n *Node) do(ctx context.Context, method string, dataIndex int) (ret model. if access.RowKeyGen != "" { for i, _ := range n.Data { - rowKeyVal, err = n.action.actionConfig.RowKeyGen(ctx, access.RowKeyGen, n.TableName, n.Data[i]) + rowKeyVal, err = n.action.actionConfig.RowKeyGen(ctx, access.RowKeyGen, n.AccessName, n.Data[i]) if err != nil { return nil, err } @@ -332,7 +336,7 @@ func (n *Node) do(ctx context.Context, method string, dataIndex int) (ret model. var id int64 - id, count, err = executor.GetActionExecutor(n.executor).Insert(ctx, n.TableName, n.Data) + id, count, err = executor.GetActionExecutor(n.executor).Insert(ctx, n.tableName, n.Data) if err != nil { return nil, err @@ -350,16 +354,16 @@ func (n *Node) do(ctx context.Context, method string, dataIndex int) (ret model. if rowKeyVal != nil { for k, v := range rowKeyVal { if k == consts.RowKey { - ret[jsonStyle(ctx, n.TableName, access.RowKey)] = v + ret[jsonStyle(ctx, n.tableName, access.RowKey)] = v } else { - ret[jsonStyle(ctx, n.TableName, k)] = v + ret[jsonStyle(ctx, n.tableName, k)] = v } } } } case http.MethodPut: - count, err = executor.GetActionExecutor(n.executor).Update(ctx, n.TableName, n.Data[dataIndex], n.Where[dataIndex]) + count, err = executor.GetActionExecutor(n.executor).Update(ctx, n.tableName, n.Data[dataIndex], n.Where[dataIndex]) if err != nil { return nil, err } @@ -369,7 +373,7 @@ func (n *Node) do(ctx context.Context, method string, dataIndex int) (ret model. "count": count, } case http.MethodDelete: - count, err = executor.GetActionExecutor(n.executor).Delete(ctx, n.TableName, n.Where[dataIndex]) + count, err = executor.GetActionExecutor(n.executor).Delete(ctx, n.tableName, n.Where[dataIndex]) if err != nil { return nil, err } diff --git a/config/access.go b/config/access.go index a0c4cb5..2034574 100644 --- a/config/access.go +++ b/config/access.go @@ -28,11 +28,11 @@ func NewConditionRet() *ConditionRet { return &c } -func (c *ConditionRet) Add(k string, v any) { // todo any? +func (c *ConditionRet) Add(k string, v any) { c.condition[k] = v } -func (c *ConditionRet) AddRaw(k string, v any) { // todo any? +func (c *ConditionRet) AddRaw(k string, v any) { c.rawCondition[k] = v } diff --git a/config/action_config.go b/config/action_config.go index ffd45ba..70f2241 100644 --- a/config/action_config.go +++ b/config/action_config.go @@ -50,79 +50,3 @@ func (c *ActionConfig) RowKeyGen(ctx context.Context, genFuncName string, access return nil, nil } - -// -//type ExecutorConfig struct { -// NoVerify bool -// accessConfig *AccessConfig -// method string -// role string -// DBMeta *DBMeta -// DbFieldStyle FieldStyle -// JsonFieldStyle FieldStyle -//} -// -//func NewExecutorConfig(accessConfig *AccessConfig, method string, noVerify bool) *ExecutorConfig { -// return &ExecutorConfig{ -// accessConfig: accessConfig, -// method: method, -// NoVerify: noVerify, -// } -//} -// -//func (c *ExecutorConfig) SetRole(role string) { -// c.role = role -//} -// -//func (c *ExecutorConfig) TableName() string { -// return c.accessConfig.Name -//} -// -//func (c *ExecutorConfig) TableColumns() []string { -// return c.DBMeta.GetTableColumns(c.accessConfig.Name) -//} -// -//func (c *ExecutorConfig) GetFieldsGetOutByRole() []string { -// var fieldsMap map[string]string -// -// if val, exists := c.accessConfig.FieldsGet[c.role]; exists { -// fieldsMap = val.Out -// } else { -// fieldsMap = c.accessConfig.FieldsGet["default"].Out -// } -// return lo.Keys(fieldsMap) -//} -// -//func (c *ExecutorConfig) GetFieldsGetInByRole() map[string][]string { -// var inFieldsMap map[string][]string -// -// if val, exists := c.accessConfig.FieldsGet[c.role]; exists { -// inFieldsMap = val.In -// } else { -// inFieldsMap = c.accessConfig.FieldsGet["default"].In -// } -// -// return inFieldsMap -//} -// -//func (c *ExecutorConfig) AccessRoles() []string { -// switch c.method { -// case http.MethodGet: -// return c.accessConfig.Get -// case http.MethodHead: -// return c.accessConfig.Head -// case http.MethodPost: -// return c.accessConfig.Post -// case http.MethodPut: -// return c.accessConfig.Put -// case http.MethodDelete: -// return c.accessConfig.Delete -// } -// return []string{} -// -//} -// -//func (c *ExecutorConfig) Executor() string { -// return c.accessConfig.Executor -// -//} diff --git a/config/config.go b/config/config.go index 6487a61..2d2f0fd 100644 --- a/config/config.go +++ b/config/config.go @@ -48,12 +48,11 @@ type Config struct { RequestListProvider string DbMetaProvider string - accessList []AccessConfig // todo to access + accessList []AccessConfig requestConfig *RequestConfig - - queryConfig *QueryConfig - actionConfig *ActionConfig + queryConfig *QueryConfig + actionConfig *ActionConfig } func New() *Config { diff --git a/config/functions.go b/config/functions.go index ef013ac..16ce7ab 100644 --- a/config/functions.go +++ b/config/functions.go @@ -8,7 +8,6 @@ import ( ) type Func struct { - // todo 调整成结构体 Handler func(ctx context.Context, param model.Map) (res any, err error) } diff --git a/config/request_config.go b/config/request_config.go index 5a7ec84..da362e7 100644 --- a/config/request_config.go +++ b/config/request_config.go @@ -48,43 +48,13 @@ func NewRequestConfig(requestList []Request) *RequestConfig { for _, _item := range requestList { item := _item - tag, _ := getTag(item.Tag) if item.Structure == nil { item.Structure = make(map[string]*Structure) } - // provider处理 - //if strings.ToLower(tag) != tag { - // // 本身大写, 如果没有外层, 则套一层 - // if _, ok := item.Structure[tag]; !ok { - // item.Structure = map[string]any{ - // tag: item.Structure, - // } - // } - //} - - for k, v := range item.Structure { - structure := Structure{} - err := gconv.Scan(v, &structure) - if err != nil { - panic(err) - } - - if structure.Must != nil { - structure.Must = strings.Split(structure.Must[0], ",") - } - if structure.Refuse != nil { - structure.Refuse = strings.Split(structure.Refuse[0], ",") - } - - item.Structure[k] = &structure - } - - if len(item.ExecQueue) > 0 { - // todo db provider处理 - //item.ExecQueue = strings.Split(item.ExecQueue[0], ",") - } else { + if len(item.ExecQueue) == 0 { + tag, _ := getTag(item.Tag) item.ExecQueue = []string{tag} } @@ -97,17 +67,6 @@ func NewRequestConfig(requestList []Request) *RequestConfig { return &c } -func getTag(tag string) (name string, isList bool) { - if strings.HasSuffix(tag, consts.ListKeySuffix) { - name = tag[0 : len(tag)-2] - isList = true - } else { - name = tag - } - - return -} - func getRequestFullKey(tag string, method string, version string) string { return tag + "@" + method + "@" + version } @@ -127,3 +86,14 @@ func (c *RequestConfig) GetRequest(tag string, method string, version string) (* return request, nil } + +func getTag(tag string) (name string, isList bool) { + if strings.HasSuffix(tag, consts.ListKeySuffix) { + name = tag[0 : len(tag)-2] + isList = true + } else { + name = tag + } + + return +} diff --git a/consts/node.go b/consts/node.go index 88f884a..5c9a1b8 100644 --- a/consts/node.go +++ b/consts/node.go @@ -11,3 +11,23 @@ const ( RowKey = "rowKey" Raw = "@raw" ) + +const ( + Role = "@role" + Page = "page" // page num + Count = "count" // page size + Query = "query" +) + +const ( + OpLike = "$" + OpRegexp = "~" + OpSub = "-" + OpPLus = "+" +) + +const ( + SqlLike = "LIKE" + SqlEqual = "=" + SqlRegexp = "REGEXP" +) diff --git a/consts/op.go b/consts/op.go deleted file mode 100644 index 916cb21..0000000 --- a/consts/op.go +++ /dev/null @@ -1,20 +0,0 @@ -package consts - -const ( - Role = "@role" - - Page = "page" // page num - Count = "count" // page size - Query = "query" -) - -const ( - OpLike = "$" - OpRegexp = "~" -) - -const ( - SqlLike = "LIKE" - SqlEqual = "=" - SqlRegexp = "REGEXP" -) diff --git a/drivers/config/goframe/config.go b/drivers/config/goframe/config.go index 708a882..ee142d7 100644 --- a/drivers/config/goframe/config.go +++ b/drivers/config/goframe/config.go @@ -4,6 +4,7 @@ import ( "context" "github.com/glennliao/apijson-go/config" "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" "strings" ) @@ -60,6 +61,49 @@ func init() { if err != nil { panic(err) } + + for i, item := range requestList { + item := item + + if item.Structure == nil { + item.Structure = make(map[string]*config.Structure) + } + + // provider处理 + //if strings.ToLower(tag) != tag { + // // 本身大写, 如果没有外层, 则套一层 + // if _, ok := item.Structure[tag]; !ok { + // item.Structure = map[string]any{ + // tag: item.Structure, + // } + // } + //} + + for k, v := range item.Structure { + structure := config.Structure{} + err := gconv.Scan(v, &structure) + if err != nil { + panic(err) + } + + if structure.Must != nil { + structure.Must = strings.Split(structure.Must[0], ",") + } + if structure.Refuse != nil { + structure.Refuse = strings.Split(structure.Refuse[0], ",") + } + + item.Structure[k] = &structure + } + + if len(item.ExecQueue) > 0 { + item.ExecQueue = strings.Split(item.ExecQueue[0], ",") + } + + requestList[i] = item + + } + return requestList }) diff --git a/query/node.go b/query/node.go index a61bc12..e2e6264 100644 --- a/query/node.go +++ b/query/node.go @@ -105,44 +105,38 @@ func newNode(query *Query, key string, path string, nodeReq any) *Node { } // 节点类型判断 - if key != "" { - - k, isList := util.ParseNodeKey(key) - - if util.IsFirstUp(k) { // 大写开头, 为查询节点(对应数据库) - node.Type = NodeTypeQuery - } else if strings.HasSuffix(k, consts.RefKeySuffix) { - node.Type = NodeTypeRef - } else if strings.HasSuffix(k, consts.FunctionsKeySuffix) { - node.Type = NodeTypeFunc - } else { - node.Type = NodeTypeStruct // 结构节点下应该必须存在查询节点 - - if query.NoAccessVerify == false { - if lo.Contains(query.DbMeta.GetTableNameList(), k) { - node.Type = NodeTypeQuery - } - } + k, isList := util.ParseNodeKey(key) + + if util.IsFirstUp(k) { // 大写开头, 为查询节点(对应数据库) + node.Type = NodeTypeQuery + } else if strings.HasSuffix(k, consts.RefKeySuffix) { + node.Type = NodeTypeRef + } else if strings.HasSuffix(k, consts.FunctionsKeySuffix) { + node.Type = NodeTypeFunc + } else { + node.Type = NodeTypeStruct // 结构节点下应该必须存在查询节点 + if query.NoAccessVerify == false { + if lo.Contains(query.DbMeta.GetTableNameList(), k) { + node.Type = NodeTypeQuery + } } - if isList || strings.HasSuffix(filepath.Dir(path), consts.ListKeySuffix) { - node.isList = true - } + } - switch node.Type { - case NodeTypeQuery: - node.nodeHandler = newQueryNode(node) - case NodeTypeRef: - node.nodeHandler = newRefNode(node) - case NodeTypeStruct: - node.nodeHandler = newStructNode(node) - case NodeTypeFunc: - node.nodeHandler = newFuncNode(node) - } - } else { - // todo 统一 + if isList || strings.HasSuffix(filepath.Dir(path), consts.ListKeySuffix) { + node.isList = true + } + + switch node.Type { + case NodeTypeQuery: + node.nodeHandler = newQueryNode(node) + case NodeTypeRef: + node.nodeHandler = newRefNode(node) + case NodeTypeStruct: node.nodeHandler = newStructNode(node) + case NodeTypeFunc: + node.nodeHandler = newFuncNode(node) } switch nodeReq.(type) { diff --git a/query/node_func.go b/query/node_func.go index f707719..4a4e10f 100644 --- a/query/node_func.go +++ b/query/node_func.go @@ -15,7 +15,6 @@ func newFuncNode(n *Node) *funcNode { func (h *funcNode) parse() { //n := h.node - // todo //functionName, _ := util.ParseFunctionsStr(n.simpleReqVal) //n.simpleReqVal = functionName } diff --git a/query/node_query.go b/query/node_query.go index 4570225..0391551 100644 --- a/query/node_query.go +++ b/query/node_query.go @@ -23,7 +23,7 @@ func newQueryNode(n *Node) *queryNode { func (q *queryNode) parse() { n := q.node - tableKey := parseTableKey(n.Key, n.Path) // todo + tableKey := parseTableKey(n.Key, n.Path) accessConfig, err := n.queryContext.queryConfig.GetAccessConfig(tableKey, n.queryContext.NoAccessVerify) if err != nil { @@ -58,7 +58,7 @@ func (q *queryNode) parse() { return } - accessWhereCondition = condition.Where() // todo + accessWhereCondition = condition.Where() } queryExecutor, err := executor.NewQueryExecutor(n.executorConfig.Executor(), n.ctx, n.executorConfig) diff --git a/query/util.go b/query/util.go index 63dab20..60882b5 100644 --- a/query/util.go +++ b/query/util.go @@ -68,7 +68,7 @@ func hasAccess(node *Node) (hasAccess bool, condition *config.ConditionRet, err condition = config.NewConditionRet() accessName := node.Key - if strings.HasSuffix(accessName, "[]") { // todo 统一处理 + if strings.HasSuffix(accessName, "[]") { // todo 统一处理 accessName = accessName[0 : len(accessName)-2] } err = node.queryContext.AccessCondition(node.ctx, config.ConditionReq{ diff --git a/test/func/z_test.go b/test/func/z_test.go new file mode 100644 index 0000000..6896269 --- /dev/null +++ b/test/func/z_test.go @@ -0,0 +1,111 @@ +package _func + +import ( + "context" + "fmt" + "github.com/glennliao/apijson-go/model" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "reflect" + "testing" +) + +// 1 +type Func1 struct { + ParamList []string + ParamTypeList []string + Handler func(ctx context.Context, param model.Map) (res interface{}, err error) +} + +type Func2 struct { + Handler any +} + +func basic(ctx context.Context, user string) (string, error) { + return user + " hi", nil +} + +var f1 Func1 +var f2 Func2 +var ctx = gctx.New() + +var funcValue reflect.Value + +func init() { + f1 = Func1{ + ParamList: []string{"req", "xx"}, + ParamTypeList: []string{"string", "string"}, // or any + Handler: func(ctx context.Context, param model.Map) (res interface{}, err error) { + return param["user"].(string) + " :hi", nil + }, + } + + f2 = Func2{Handler: func(ctx context.Context, user string) (string, error) { + return user + " hi", nil + }} + + // 将函数包装为反射值对象 + funcValue = reflect.ValueOf(f2.Handler) +} + +func TestName(t *testing.T) { + ret, err := f1.Handler(ctx, model.Map{"user": 1}) + if err != nil { + panic(err) + } + g.Dump(ret) +} + +func TestName2(t *testing.T) { + // 将函数包装为反射值对象 + funcValue := reflect.ValueOf(f2.Handler) + // 构造函数参数, 传入两个整型值 + paramList := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf("luc")} + // 反射调用函数 + retList := funcValue.Call(paramList) + // 获取第一个返回值, 取整数值 + fmt.Println(retList[0].String()) +} + +func BenchmarkBasic(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := basic(ctx, "user1") + if err != nil { + panic(err) + } + } +} + +func BenchmarkName(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := f1.Handler(ctx, model.Map{"user": "user1"}) + if err != nil { + panic(err) + } + } +} + +func BenchmarkName23(b *testing.B) { + + for i := 0; i < b.N; i++ { + // 构造函数参数, 传入两个整型值 + paramList := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf("luc")} + // 反射调用函数 + _ = funcValue.Call(paramList) + // 获取第一个返回值, 取整数值 + //fmt.Println(retList[0].String()) + } +} + +func BenchmarkName2(b *testing.B) { + + for i := 0; i < b.N; i++ { + funcValue := reflect.ValueOf(f2.Handler) + // 构造函数参数, 传入两个整型值 + paramList := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf("luc")} + // 反射调用函数 + _ = funcValue.Call(paramList) + // 获取第一个返回值, 取整数值 + //fmt.Println(retList[0].String()) + } +} diff --git a/test/z_main_test.go b/test/z_main_test.go index 9613984..ffc74f2 100644 --- a/test/z_main_test.go +++ b/test/z_main_test.go @@ -72,23 +72,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{ - //"id": "123", - //"id{}": []string{"123", "456"}, - //"id>": "222", - //"@column": "id", - }, - "User[]": model.Map{ - "@column": "id", - //"userId": "123", - }, - "a@": "User/username", - "b": model.Map{ - "User": model.Map{ - "id": 1, - }, - "c@": "/User/username", - }, + //"User": model.Map{ + // "id": 1, + // //"id": "123", + // //"id{}": []string{"123", "456"}, + // //"id>": "222", + // //"@column": "id", + //}, + //"User[]": model.Map{ + // "@column": "id", + // //"userId": "123", + //}, + //"a@": "User/username", + //"b": model.Map{ + // "User": model.Map{ + // "id": 1, + // }, + // //"c@": "/User/username", + //}, "say()": "test()", })