在字节实习的时候,有两个小需求如下:
(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