Skip to content

Commit 2cb6a0a

Browse files
committed
feat(functions): add functions 冒烟版
1 parent 5ea8039 commit 2cb6a0a

File tree

10 files changed

+354
-14
lines changed

10 files changed

+354
-14
lines changed

action/node.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/glennliao/apijson-go/config"
66
"github.com/glennliao/apijson-go/consts"
77
"github.com/glennliao/apijson-go/db"
8+
"github.com/glennliao/apijson-go/functions"
89
"github.com/gogf/gf/v2/errors/gerror"
910
"github.com/gogf/gf/v2/frame/g"
1011
"github.com/gogf/gf/v2/util/gconv"
@@ -70,7 +71,7 @@ func (n *Node) parseReq(method string) {
7071
func (n *Node) parse(ctx context.Context, method string) error {
7172

7273
key := n.Key
73-
if strings.HasSuffix(key, "[]") {
74+
if strings.HasSuffix(key, consts.ListKeySuffix) {
7475
key = key[0 : len(key)-2] // todo 提取util, 获取非数组的key
7576
}
7677
access, err := db.GetAccess(key, true)
@@ -219,7 +220,28 @@ func (n *Node) reqUpdate() error {
219220

220221
for i, _ := range n.req {
221222
for key, updateVal := range n.structure.Update {
222-
n.Data[i][key] = updateVal
223+
224+
if strings.HasSuffix(key, consts.FunctionsKeySuffix) {
225+
functionName, paramKeys := functions.ParseFunctionsStr(updateVal.(string))
226+
var param = g.Map{}
227+
for _, key := range paramKeys {
228+
if key == "$req" {
229+
param[key] = n.Data[i]
230+
} else {
231+
param[key] = n.Data[i][key]
232+
}
233+
}
234+
k := key[0 : len(key)-2]
235+
val, err := functions.Call(n.ctx, functionName, param)
236+
if err != nil {
237+
return err
238+
}
239+
if val != nil {
240+
n.Data[i][k] = val
241+
}
242+
} else {
243+
n.Data[i][key] = updateVal
244+
}
223245
}
224246

225247
for key, updateVal := range n.structure.Insert {
@@ -236,7 +258,6 @@ func (n *Node) reqUpdate() error {
236258
} else {
237259
n.Data[i][k] = n.keyNode[refNodeKey].Data[0][config.GetDbFieldStyle()(n.ctx, n.TableName, refCol)]
238260
}
239-
240261
}
241262
}
242263
}
@@ -336,7 +357,7 @@ func (n *Node) do(ctx context.Context, method string, i int) (ret g.Map, err err
336357
func (n *Node) execute(ctx context.Context, method string) (g.Map, error) {
337358

338359
// 参数替换
339-
err := n.reqUpdate()
360+
err := n.reqUpdate() // todo 处理放到事务外, 减短事务时长
340361
if err != nil {
341362
return nil, err
342363
}
@@ -351,7 +372,7 @@ func (n *Node) execute(ctx context.Context, method string) (g.Map, error) {
351372
return ret, nil
352373
} else {
353374
for i, _ := range n.req {
354-
_, err := n.do(ctx, method, i)
375+
_, err = n.do(ctx, method, i)
355376
if err != nil {
356377
return nil, err
357378
}

consts/node.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ package consts
22

33
const MaxTreeWidth = 5
44
const MaxTreeDeep = 5
5+
6+
const (
7+
ListKeySuffix = "[]"
8+
FunctionsKeySuffix = "()"
9+
)

demo/todo/app/functions.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package app
2+
3+
import (
4+
"context"
5+
"errors"
6+
"github.com/glennliao/apijson-go/config"
7+
"github.com/glennliao/apijson-go/functions"
8+
"github.com/gogf/gf/v2/frame/g"
9+
"github.com/gogf/gf/v2/util/gconv"
10+
"strings"
11+
)
12+
13+
const (
14+
UserIdWM = "10001"
15+
UserIdSQ = "10002"
16+
)
17+
18+
func init() {
19+
20+
functions.Reg("sayHello", functions.Func{
21+
Handler: func(ctx context.Context, param g.Map) (res any, err error) {
22+
return "world", err
23+
//return nil, nil
24+
},
25+
})
26+
27+
functions.Reg("sayHi", functions.Func{
28+
Handler: func(ctx context.Context, param g.Map) (res any, err error) {
29+
return "你好:" + gconv.String(param["realname"]), err
30+
},
31+
})
32+
33+
functions.Reg("checkTodoTitle", functions.Func{
34+
Handler: func(ctx context.Context, param g.Map) (res any, err error) {
35+
user, _ := ctx.Value(config.UserIdKey).(*CurrentUser)
36+
37+
if user.UserId == UserIdSQ && strings.HasSuffix(gconv.String(param["title"]), "喝茶") {
38+
return nil, errors.New("操作不允许")
39+
}
40+
41+
return nil, nil
42+
},
43+
})
44+
45+
functions.Reg("updateTodoTitle", functions.Func{
46+
Handler: func(ctx context.Context, param g.Map) (res any, err error) {
47+
user, _ := ctx.Value(config.UserIdKey).(*CurrentUser)
48+
49+
if user.UserId == UserIdSQ && strings.HasSuffix(gconv.String(param["title"]), "找林云逛街") {
50+
return strings.Replace(gconv.String(param["title"]), "找", "保护", -1), nil
51+
}
52+
53+
return gconv.String(param["title"]), nil
54+
},
55+
})
56+
}

demo/todo/tests/action_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ func TestActionOneTableOneLine(t *testing.T) {
9393
})
9494
So(err, ShouldBeNil)
9595
So(one.IsEmpty(), ShouldBeTrue)
96+
97+
// 物理删除测试数据
98+
g.DB().Model("t_todo").Unscoped().Delete(g.Map{"todo_id": todoId})
99+
96100
})
97101
})
98102
}
@@ -249,6 +253,7 @@ func TestActionMoreTableMoreLine(t *testing.T) {
249253
})
250254
So(err, ShouldBeNil)
251255
So(cnt, ShouldEqual, 0)
256+
252257
})
253258
})
254259
}

demo/todo/tests/functions_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package tests
2+
3+
import (
4+
"github.com/glennliao/apijson-go/consts"
5+
"github.com/gogf/gf/v2/frame/g"
6+
"github.com/gogf/gf/v2/util/gconv"
7+
. "github.com/smartystreets/goconvey/convey"
8+
"testing"
9+
)
10+
11+
func TestFunctionsQuery(t *testing.T) {
12+
iAmWM()
13+
Convey("functions in query", t, func() {
14+
15+
// ===================================================================
16+
Convey("sayHello", func() {
17+
18+
req := `
19+
{
20+
"User": {},
21+
"hello()":"sayHello"
22+
}
23+
`
24+
out, err := queryByJsonStr(req)
25+
26+
So(err, ShouldBeNil)
27+
So(hasKey(out, "hello"), ShouldBeTrue)
28+
So(gconv.String(out["hello"]) == "world", ShouldBeTrue)
29+
})
30+
31+
Convey("sayHello()", func() {
32+
req := `
33+
{
34+
"User": {},
35+
"hello()":"sayHello()"
36+
}
37+
`
38+
out, err := queryByJsonStr(req)
39+
40+
So(err, ShouldBeNil)
41+
So(hasKey(out, "hello"), ShouldBeTrue)
42+
So(gconv.String(out["hello"]) == "world", ShouldBeTrue)
43+
})
44+
45+
Convey("sayHi(realname)", func() {
46+
req := `
47+
{
48+
"User": {
49+
"hello()":"sayHi(realname)"
50+
}
51+
}
52+
`
53+
out, err := queryByJsonStr(req)
54+
55+
//g.Dump(out)
56+
57+
So(err, ShouldBeNil)
58+
So(hasKey(out, "User"), ShouldBeTrue)
59+
60+
user := gconv.Map(out["User"])
61+
62+
So(hasKey(user, "hello"), ShouldBeTrue)
63+
So(gconv.String(user["hello"]) == "你好:"+gconv.String(user["realname"]), ShouldBeTrue)
64+
})
65+
66+
Convey("sayHi(realname) in List", func() {
67+
req := `
68+
{
69+
"User[]": {
70+
"hello()":"sayHi(realname)"
71+
}
72+
}
73+
`
74+
out, err := queryByJsonStr(req)
75+
76+
//g.Dump(out)
77+
78+
So(err, ShouldBeNil)
79+
userList := gconv.Maps(out["User[]"])
80+
So(hasKey(userList[0], "hello"), ShouldBeTrue)
81+
So(gconv.String(userList[0]["hello"]) == "你好:"+gconv.String(userList[0]["realname"]), ShouldBeTrue)
82+
})
83+
84+
})
85+
}
86+
87+
func TestFunctionsInAction(t *testing.T) {
88+
iAmSQ()
89+
Convey("functions in action", t, func() {
90+
// ===================================================================
91+
Convey("check", func() {
92+
93+
req := `
94+
{
95+
"Todo": {
96+
"title": "去找林云喝茶"
97+
},
98+
"tag": "Todo",
99+
"version": 1
100+
}
101+
`
102+
103+
_, err := actionByJsonStr(req, consts.MethodPost)
104+
105+
So(err, ShouldNotBeNil)
106+
107+
})
108+
109+
Convey("replace", func() {
110+
111+
req := `
112+
{
113+
"Todo": {
114+
"title": "去找林云逛街"
115+
},
116+
"tag": "Todo",
117+
"version": 1
118+
}
119+
`
120+
121+
out, err := actionByJsonStr(req, consts.MethodPost)
122+
//g.Dump(out)
123+
So(err, ShouldBeNil)
124+
125+
// 删除测试数据
126+
todo := out["Todo"].(g.Map)
127+
todoId := todo["todoId"].(string)
128+
129+
g.DB().Model("t_todo").Unscoped().Delete(g.Map{"todo_id": todoId})
130+
131+
})
132+
})
133+
}

doc/roadmap.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@
1313
- [x] 新增多个表时, structures 中 update可引用其他边的字段 (例如新建角色,并使用新增的id 设置给权限列表中)
1414
- [x] 批量修改(统一设置模式+单个设置模式)、批量删除
1515
- [x] request的version设置
16+
- [ ] 增加 functions , 用于对数据的处理 (字典转换、id转name等操作)
1617
- [ ] ...
1718

1819
## [ ] 摸石头 阶段
19-
20-
21-
## [ ] xxx 阶段
22-
- [ ] 增加 functions , 用于对数据的处理 (字典转换、id转name等操作)
20+
- [ ] 开发、测试环境下记录get请求记录
2321
- [ ] 可自定义get中查询节点的实现 (即从缓存或者其他数据源查询数据)
2422
- [ ] 可自定义非开放请求的具体实现
23+
24+
## [ ] xxx 阶段
25+
2526
- [ ] 提供 web UI 用于 _access 和 _request 的管理
26-
- [ ] 开发、测试环境下记录get请求记录
2727
- [ ] get请求的复杂度计算, 超过复杂度则拒绝执行(树节点计算)
2828
- [ ] 完善 apijson 规范的兼容实现
2929

functions/functions.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,31 @@ package functions
22

33
import (
44
"context"
5+
"fmt"
56
"github.com/gogf/gf/v2/frame/g"
67
)
78

89
type Func struct {
9-
Name string
10-
Handler func(ctx context.Context, param g.Var) (res g.Var, err error)
10+
Handler func(ctx context.Context, param g.Map) (res any, err error)
1111
}
1212

13-
func Reg(name string) {
13+
var funcMap = make(map[string]Func)
14+
15+
func Reg(name string, f Func) {
16+
if _, exists := funcMap[name]; exists {
17+
panic(fmt.Errorf(" function %s has exists", name))
18+
}
19+
funcMap[name] = f
20+
}
21+
22+
func Call(ctx context.Context, name string, param g.Map) (any, error) {
23+
return funcMap[name].Handler(ctx, param)
1424
}
25+
26+
// functions 提供的功能
27+
// 1. 增加响应字段 -> 该字段需要与系统中别的数据结合处理,如果只是静态处理(去空格,与常量拼接等可直接前端处理即可) 目前会不受_access_ext 中field_get控制, 需处理. 响应字段修改(脱敏、加密、字典转换) 不提供前端控制, 由_access_ext处理
28+
// 2. 通过func节点获取一些系统信息
29+
// 3. actions 中 自定义校验参数、自定义校验权限, 请求体修改(批量字段替换处理?)
30+
// 4. 其他需要自定义的地方 (在action中可看成是hook的替代)
31+
32+
// functions 可用于 field_get 使用, 用于修改请求、响应

functions/util.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package functions
2+
3+
import "strings"
4+
5+
// 解析ParseFunctionsStr字符串, 返回函数名和参数列表
6+
func ParseFunctionsStr(funcStr string) (functionsName string, paramKeys []string) {
7+
if !strings.Contains(funcStr, "(") { // 无参形式
8+
functionsName = funcStr
9+
return
10+
}
11+
12+
leftIndex := strings.Index(funcStr, "(")
13+
functionsName = funcStr[0:leftIndex]
14+
15+
paramsStr := strings.TrimSpace(funcStr[leftIndex+1 : len(funcStr)-1]) // str must endsWith )
16+
if paramsStr == "" {
17+
return
18+
}
19+
20+
for _, k := range strings.Split(paramsStr, ",") {
21+
paramKeys = append(paramKeys, k)
22+
}
23+
24+
return
25+
}

0 commit comments

Comments
 (0)