服务端白名单设计及sdk demo

190 阅读2分钟

服务端开发、测试过程中或多或少都会遇到白名单,来满足具备时效性的活动、用户命中条件特殊,以及线上测试账号进行构造等各种需求。

当越来越多的服务有白名单的需要时,如何高效的管理白名单呢?

梳理业务中存在白名单的地方,无非不同功能存在一批白名单账号配置,因此可以将配置抽象成账号标签,有的功能需要模拟到期、下线等情况,标签还需要支持有效期。不同标签用作不同场景。

白名单账号与标签关系图

可以新建一个单独的服务来管理维护众多的白名单账号及标签,或使用内部的配置平台来维护相关配置。

各业务调用时,可以以sdk的方式提供给业务方,下面将以golang为例

SDK代码示例
package sdk

import (
    "sync"
    "time"
)

type TagData struct {
    // 白名单标签,简短的英文字符表示
    Tag string `json:"tag"`
    // 描述信息,说明使用用途
    Desc string `json:"desc"`
    // 有效期开始时间,<=0 不进行校验
    Stime string `json:"stime"`
    // 有效期结束时间,<=0 不进行校验
    Etiem string `json:"etime"`
}

type AccountData struct {
    // 测试账号唯一标识,可以为用户ID或用户名
    User string `json:"user"`
    // 账号具有的白名单标签
    Tags map[string]bool `json:"tags"`
}

type WhiteAccountConf struct {
    Accounts map[string]*AccountData
    Tags map[string]*TagData
    // 读写锁
    lock sync.RWMutex
    // 初次加载标记,用于惰性加载
    first bool
    // 定时加载时间
    loop time.Duration
    // 退出时发送消息到此通道
    stop chan bool
    // 是否启动标记
    isStarted bool
}

var (
    WAConfig *WhiteAccountConf = &WhiteAccountConf{}
)

// 加载全量白名单配置到内存
func (w *WhiteAccountConf) loadConfig() {
    // 标记初次加载
    if !w.first {
        w.first = true
    }
    
    // 查询全量白名单配置
    allConf, err := ...
    if err != nil {
        return
    }
    
    // 写锁
    w.lock.Lock()
    defer w.lock.Unlock()
    
    // 根据查询到的全量配置更新内存
    w.Accounts = allConf.Accounts // 需根据实际进行调整
    w.Tags = allConf.Tags // 需根据实际进行调整
    
    return
}

// 初始化
func (w *WhiteAccountConf) Init(loop time.Duration) {
    w.loop = loop
    w.stop = make(chan bool)
}

// 定时加载配置
func (w *WhiteAccountConf) start() {
    if w.isStarted {
        return
    }
    tk := time.NewTicker(w.loop)
    defer tk.Stop()
    for {
        select {
        case <- tk.C:
            w.loadConfig()
        case <- w.stop:
            return
        }
    }
}

// 异步执行定时加载配置
func (w *WhiteAccountConf) Run() {
    w.isStarted = true
    go w.start()
}

// 停止加载白名单配置
func (w *WhiteAccountConf) Stop() {
    if !w.isStarted {
        return
    }
    w.stop <- true
}

// 检查是否是白名单账号
func (w *WhiteAccountConf) Check(user, tag string) bool {
    if !w.first {
        w.loadConfig()
    }
    tagData, ok := w.Tags[tag]
    if !ok {
        return false
    }
    now := time.Now().Unix()
    if (tag.Data.Stime > 0 && tag.Data.Stime > now) || (tag.Data.Etime > 0 && tag.Data.Etime < now) {
        return false
    }
    userData, ok := w.Accounts[user]
    if !ok {
        return false
    }
    return userData.Tags[tag]
}
业务方调用

main.go文件中需要先初始化加载

package main

import (
    "time"
    "xxx/white-account/sdk"
)

func xxxUse() {
    if sdk.WAConfig.Check("zhangsan", "tag1") {
        fmt.Println("zhangsan is a white account")
    } else {
        fmt.Println("zhangsan is not a  white account")
    }
}

func main() {
    // 初始化,每5分钟加载一次最新白名单配置,可以设置更长时间
    sdk.WAConfig.Init(300 * time.Second)
    // 开始定时拉取
    sdk.WAConfig.Run()
    // 服务停止时结束定时拉取
    defer sdk.WAConfig.Stop()
}