Q&A
为啥用go重构?
deno太新了,很多功能没有,github isssu里都是future =- =
重新弄用了多久?
算上写blog,两天吧(16h)
改动大吗?
不算大,主要是ObjectID的格式和Deration,JS里是毫秒,go是纳秒, 1ms = 10^6ns
项目地图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
$ tree -I "vscode|bin"
.
|-- go.mod
|-- go.sum
|-- main.go
`-- src
|-- auth
| |-- auth.go
| |-- crypto.go
| `-- jwt.go
|-- cors
| `-- cors.go
|-- engine
| |-- RecordCtrl.go
| |-- TagCtrl.go
| |-- UserCtrl.go
| `-- db.go
|-- models
| |-- Record.go
| |-- Tag.go
| `-- User.go
|-- parsup
| `-- parsup.go
|-- resultor
| `-- resultor.go
`-- utils
`-- required.go
|
总的来说和Deno那套架构是一样的,只是多了个辅助类
,所以本文将重点说说它的设计思路和实现
辅助类
请随本文打开src/parsup/parsup.go
,我们一步一步的来。
结构和工厂方法
结构主要是一些开关,工厂方法默认关闭struct,因为诸如time.Time
这类的对象其实是一个struct,一不小心就误处理了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// ParamsSupport 参数辅助类
type ParamsSupport struct {
IsDeep *bool // 深度递归
IsConvOID *bool // 转化ObjectID
IsConvTime *bool // 转化时间对象
IsDenyInject *bool // 防注入
IsConvStruct *bool // 转结构
}
// ParSup 工厂方法
func ParSup() *ParamsSupport {
t := true
f := false
return &ParamsSupport{
IsDeep: &t,
IsConvOID: &t,
IsConvTime: &t,
IsDenyInject: &t,
IsConvStruct: &f,
}
}
|
链式setter
没啥好说的,就是方便开关
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// SetIsDeep 设置方法
func (p *ParamsSupport) SetIsDeep(b bool) *ParamsSupport {
p.IsDeep = &b
return p
}
// SetIsConvOID 设置方法
func (p *ParamsSupport) SetIsConvOID(b bool) *ParamsSupport {
p.IsConvOID = &b
return p
}
// SetIsConvTime 设置方法
func (p *ParamsSupport) SetIsConvTime(b bool) *ParamsSupport {
p.IsConvTime = &b
return p
}
// SetIsDenyInject 设置方法
func (p *ParamsSupport) SetIsDenyInject(b bool) *ParamsSupport {
p.IsDenyInject = &b
return p
}
// SetIsConvStruct 设置方法
func (p *ParamsSupport) SetIsConvStruct(b bool) *ParamsSupport {
p.IsConvStruct = &b
return p
}
|
ConvBase 基础处理器
用reflect的反射机制,获取到具体类型,然后分发(dispatch)给其它handler。只有设置了IsDeep = true
才深度递归。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
func (p *ParamsSupport) ConvBase(i interface{}) (interface{}, error) {
v := reflect.ValueOf(i)
switch v.Kind() {
case reflect.Invalid:
return nil, nil
case reflect.Map:
if *p.IsDeep {
return p.ConvMap(i.(map[string]interface{}))
}
break
case reflect.Slice:
if *p.IsDeep {
return p.ConvSlice(i.([]interface{}))
}
break
case reflect.Struct:
if *p.IsDeep && *p.IsConvStruct {
return p.ConvStruct(i)
}
break
case reflect.String:
return p.ConvStr(i.(string))
}
return i, nil
}
|
ConvStr 字符串处理器
防注入、时间处理、ObjectID处理都在这里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func (p *ParamsSupport) ConvStr(s string) (interface{}, error) {
if *p.IsDenyInject {
if strings.ContainsAny(s, "$[]{}()") {
return nil, errors.New("不能包含$[]{}()等特殊符号")
}
}
if *p.IsConvOID {
if oid, err := primitive.ObjectIDFromHex(s); err == nil {
return oid, nil
}
}
if *p.IsConvTime {
if t, err := time.Parse(time.RFC3339, s); err == nil {
return t.Local(), nil
}
}
return s, nil
}
|
ConvMap Map处理器
深度优先处理Map
1
2
3
4
5
6
7
8
9
10
11
|
func (p *ParamsSupport) ConvMap(m map[string]interface{}) (map[string]interface{}, error) {
res := make(map[string]interface{})
for k, v := range m {
dv, err := p.ConvBase(v)
if err != nil {
return nil, err
}
res[k] = dv
}
return res, nil
}
|
ConvSlice 数组处理器
跟ConvMap
逻辑类似,注意slice
要用make([]interface{}, len(s))
方法初始化,否则设置时会报边界错误。
1
2
3
4
5
6
7
8
9
10
11
|
func (p *ParamsSupport) ConvSlice(s []interface{}) ([]interface{}, error) {
res := make([]interface{}, len(s))
for k, v := range s {
dv, err := p.ConvBase(v)
if err != nil {
return nil, err
}
res[k] = dv
}
return res, nil
}
|
ConvJSON byte处理器
这个方便ioutil.ReadAll(r.Body)
返回的那个[]byte
1
2
3
4
5
6
7
8
|
func (p *ParamsSupport) ConvJSON(s []byte) (map[string]interface{}, error) {
m := make(map[string]interface{})
err := json.Unmarshal(s, &m)
if err != nil {
return nil, err
}
return p.ConvMap(m)
}
|
ConvStruct 结构体处理器
通过reflect反射机制,根据结构体tag(标签),完成一些行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
func (p *ParamsSupport) ConvStruct(s interface{}) (map[string]interface{}, error) {
val := reflect.ValueOf(s)
if val.Kind() != reflect.Struct {
return nil, errors.New("not a struct")
}
t := val.Type()
o := make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
var omitempty, skip, omitzero bool
tagsName := t.Field(i).Name
cur := val.Field(i)
if tags, ok := t.Field(i).Tag.Lookup("parsup"); ok {
tagsArr := strings.Split(tags, ",")
tagsName = tagsArr[0]
for _, v := range tagsArr {
switch v {
case "omitempty":
omitempty = true
case "omitzero":
omitzero = true
case "-":
skip = true
}
}
}
if skip || (cur.Kind() == reflect.Ptr && cur.IsNil() && omitempty) || (cur.IsZero() && omitzero) {
continue
}
ele, err := p.ConvBase(p.safeInterface(cur))
if err != nil {
return nil, err
}
o[tagsName] = ele
}
return o, nil
}
|
safeInterface 安全取值
1
2
3
4
5
6
7
|
多做几层判断 不容易报错
func (p *ParamsSupport) safeInterface(v reflect.Value) interface{} {
if v.IsValid() && v.CanInterface() {
return v.Interface()
}
return nil
}
|
其他篇章
前言
前端篇
deno后端篇【废弃】
go后端篇
部署篇
后语