责任链+白名单实现业务验证逻辑

在字节实习的时候,有两个小需求如下:
(1)APP 冷启动后需要由客户端调用服务端 API,进行一系列业务校验后,调用气泡中台推送继续阅读气泡;目的是:优化复访路径
(2)弹窗弹出业务逻辑校验

以上需求都有一个共同点,有一连串的业务校验,有些业务校验测试同学难以复现,例如:需 30 天未进入小说频道。所以基于以上特点,我使用责任链设计模式实现这一连串业务判断逻辑,同时为了方便测试同学测试,构建了较细粒度的配置化白名单测试能力。可以通过配置中心的配置,对每一个责任即每一个业务判断进行跳过

而且这一套代码,可以使用在任何类似的业务逻辑上面,达到复用,减少开发的收益

具体代码

 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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
type Checker interface {
	Check() bool
}

type DirectChecker interface {
	DirectCheck() bool
}

type DChecker interface {
	Checker
	DirectChecker
}

type CheckerFun func() bool

func (check CheckerFun) Check() bool {
	return check()
}

type DirectCheckerFun func() bool

func (check DirectCheckerFun) DirectCheck() bool {
	return check()
}

func Check(ctx context.Context, checkers ...interface{}) bool {
	for _, check := range checkers {
		switch check.(type) {
		case DChecker:
			c1 := check.(DirectChecker)
			if ok := c1.DirectCheck(); ok {
				continue
			}
			c2 := check.(Checker)
			if ok := c2.Check(); !ok {
				return false
			}
		case DirectChecker:
			c := check.(DirectChecker)
			if ok := c.DirectCheck(); ok {
				return true
			}
		case Checker:
			c := check.(Checker)
			if ok := c.Check(); !ok {
				return false
			}
		default:
			panic("do not implement Checker or DirectChecker interface")
		}

	}
	return true
}

type CheckerWhiteList struct {
	preName string
	did     int64
}

func NewCheckerWhiteList(preName string, did int64) *CheckerWhiteList {
	return &CheckerWhiteList{
		preName: preName,
		did:     did,
	}
}

func (checker *CheckerWhiteList) DirectCheck() bool {
	// 这里是模拟根据 preName 从配置中心中读取白名单配置
	whiteList := []int64{123}
	for _, did := range whiteList {
		if checker.did == did {
			// log.info(ctx, "direct check success")
			return true
		}
	}
	return false
}

举例使用

下面是简单的使用,第一个责任是白名单校验,可以跳过整个校验逻辑,粒度较大

 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
type CheckerReadTime struct {
	readTime int
}

func NewCheckerReadTime(readTime int) *CheckerReadTime {
	return &CheckerReadTime{
		readTime: readTime,
	}
}

func (c *CheckerReadTime) Check() bool {
	if c.readTime > 100 {
		return true
	}
	return false
}

func TestCheck(t *testing.T) {
	c1 := NewCheckerWhiteList("snack bar", 123)
	c2 := func() bool {
		// 做简单业务校验
		return false
	}
	c3 := NewCheckerReadTime(200)
	println(Check(context.Background(), c1, CheckerFun(c2), c3))
}

下面的较细粒度的白名单,通过 Go 的匿名嵌入实现每个责任都拥有白名单的前置检验

 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
type DCheckerReadTime struct {
	CheckerWhiteList
	readTime int
}

func NewDCheckerReadTime(readTime int, preName string, did int64) *DCheckerReadTime {
	c := &DCheckerReadTime{
		readTime: readTime,
	}
	c.preName = preName
	c.did = did
	return c
}

func (c *DCheckerReadTime) Check() bool {
	if c.readTime > 100 {
		return true
	}
	return false
}

func TestCheck2(t *testing.T) {
	c1 := NewDCheckerReadTime(1, "test_check_2", 123) // 命中白名单,跳过检测
	c2 := NewDCheckerReadTime(1, "test_check_2", 111) // 没命中白名单,且业务校验不通过
	println(Check(context.Background(), c1, c2))
}

End

0%