code-Review

1.chainweaver-did

接口(post-get的包装部分)

login
  • 调用了postRequest:json解析,用utils的post到指定的url

    func Post(url string, body []byte, header map[string]string) (resp []byte, err error)

    //其中为了测试跳过了https证书验证

  • 解析postRequest的返回值

    if r.Code != module.SUCCESS {
    return c.login(phoneNumber, password)
    } else {
    if r.Data.OldAccessToken != "" {
    r.Data.AccessToken = r.Data.OldAccessToken
    }
    if r.Data.OldExpiresIn != 0 {
    r.Data.ExpiresIn = r.Data.OldExpiresIn
    }
    c.token = r.Data.AccessToken
    c.logger.Infof("[Login] success, token: %s", c.token)
    //go c.loginTimedExecution(login, 2)
    duration := r.Data.ExpiresIn - int(time.Now().Unix())
    go c.loginTimedExecution(login, duration)
    }
    return normalResponse(c, r.Data, r.Msg, r.Code)
    • 后台持续续签

    goroutine 基本概念

    • go 关键字:用于启动一个新的 goroutine(轻量级线程)
    • 非阻塞执行:使用 go 启动的函数会在后台异步执行,不会阻塞当前函数继续执行
    • 轻量级:goroutine 比系统线程更轻量,Go 运行时会自动管理它们

    为什么使用 goroutine

    在这个场景中使用 goroutine 的好处:

    1. 不阻塞主流程:登录操作完成后,主函数可以立即返回,而令牌续期在后台处理
    2. 长时间运行loginTimedExecution 包含一个无限循环,在客户端的整个生命周期内持续运行
    3. 计时器机制:使用 [time.NewTicker](vscode-file://vscode-app/a:/MyProgram/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 实现定时触发,在令牌即将过期时自动更新

    参数解释

    • [login](vscode-file://vscode-app/a:/MyProgram/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html):包含登录凭证的请求对象,用于自动重新登录
    • [duration](vscode-file://vscode-app/a:/MyProgram/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html):首次登录的令牌有效期(以秒为单位),用于设置定时器

    这种模式在需要长时间维持会话,自动处理令牌续期的场景非常常见,特别是在与远程API交互的客户端中。

    func (c *Client) loginTimedExecution(login *request.Login, after int) {
    if !c.loginConfig.EnableAutoLogin {
    return
    }
    if c.loginConfig.AutoLoginStatus {
    return
    }
    c.loginConfig.AutoLoginStatus = true
    // 设置定时器
    ticker := time.NewTicker(time.Duration(after) * time.Second)
    defer ticker.Stop()
    for {
    select {
    case <-ticker.C:
    r, msg, code := c.Login(login.PhoneNumber, login.Password)
    if code != module.SUCCESS {
    c.logger.Debug("[Login] Auto Login Failed: ", msg)
    }
    c.logger.Debug("[Login] Login again ")
    if r != nil && r.ExpiresIn > 0 {
    newDuration := r.ExpiresIn - int(time.Now().Unix())
    ticker.Stop()
    c.logger.Debug("[Login] Log back in after ", newDuration)
    ticker = time.NewTicker(time.Duration(newDuration) * time.Second)
    }
    case <-c.stop:
    c.logger.Infof("[Login] timed execution stop")
    return
    }
    }
    }

    前置条件验证

    函数首先检查两个条件:

    1. EnableAutoLogin 标志:确认用户是否启用了自动登录功能

      if !c.loginConfig.EnableAutoLogin {

      return

      }

    2. AutoLoginStatus 状态:确保不会启动多个自动登录 goroutine

      if c.loginConfig.AutoLoginStatus {

      return

      }

    3. 设置状态标志:标记自动登录已激活

      c.loginConfig.AutoLoginStatus = true

    定时器设置

    ticker := time.NewTicker(time.Duration(after) * time.Second)

    defer ticker.Stop()

    这里使用 [time.NewTicker](vscode-file://vscode-app/a:/MyProgram/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 创建一个定时器,它会定期向 channel [ticker.C](vscode-file://vscode-app/a:/MyProgram/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 发送时间值:

    • 初始延迟:[after](vscode-file://vscode-app/a:/MyProgram/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 参数决定首次登录后的等待时间(一般是令牌到期前的一段时间)
    • **[defer ticker.Stop()](vscode-file://vscode-app/a:/MyProgram/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)**:确保在函数退出时停止定时器,防止资源泄露

    循环监听机制

    for {

    select {

    case <-ticker.C:

    ​ // 定时器触发的操作

    case <-c.stop:

    ​ // 停止信号触发的操作

    }

    }

    这是Go并发编程的经典模式,使用 select 语句同时监听多个 channel:

    1. [ticker.C](vscode-file://vscode-app/a:/MyProgram/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) channel
      • 当定时器到期时,这个 channel 会收到一个时间值
      • Go 运行时会选择对应的 case 分支执行
    2. [c.stop](vscode-file://vscode-app/a:/MyProgram/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) channel
      • 这是一个用于停止 goroutine 的信号 channel
      • 当客户端调用 Close() 方法时会发送信号到这个 channel

    定时器触发分支处理

    当定时器触发时,执行以下操作:

    r, msg, code := c.Login(login.PhoneNumber, login.Password)

    if code != module.SUCCESS {

    c.logger.Debug(“[Login] Auto Login Failed: “, msg)

    }

    c.logger.Debug(“[Login] Login again “)

    if r != nil && r.ExpiresIn > 0 {

    newDuration := r.ExpiresIn - int(time.Now().Unix())

    ticker.Stop()

    c.logger.Debug(“[Login] Log back in after “, newDuration)

    ticker = time.NewTicker(time.Duration(newDuration) * time.Second)

    }

    1. 执行登录操作:使用保存的凭证再次调用登录接口

    2. 检查登录结果:记录任何登录失败的情况

    3. 动态调整定时器

      • 计算新令牌的有效期:newDuration := r.ExpiresIn - int(time.Now().Unix())
      • 停止旧定时器:[ticker.Stop()](vscode-file://vscode-app/a:/MyProgram/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)
      • 创建新定时器:ticker = time.NewTicker(time.Duration(newDuration) * time.Second)

    这种动态调整确保令牌在即将到期前自动续期,而不是使用固定的时间间隔。

    停止信号处理

    case <-c.stop:

    c.logger.Infof(“[Login] timed execution stop”)

    return

    c.stop channel 接收到值时(通常在客户端的 Close() 方法中发送):

    1. 记录停止日志
    2. 立即返回,结束 goroutine
    3. defer ticker.Stop() 会在函数退出前执行,确保定时器资源被释放
documentGet

直接调用postRequest[*request.DocumentGet, *core.Document](c, module.DocumentGetUrl, req),业务函数最终会调用的golang泛型post

MsgVerify
func (c *Client) MsgVerify(req *module.Plaintext) (bool, string, int32) {

if utils.IsAnyBlank(req.Plaintext, req.Signature, req.VerificationMethod) {
return errorResponse[bool](c, module.ErrorParamNullMsg, module.ErrorParamNullCode)
}

return postRequest[*module.Plaintext, bool](c, module.MsgVerifyUrl, req)
}
VpVerify
func (c *Client) VpVerify(vp *core.VerifiablePresentation) (bool, string, int32) {

return postRequest[*core.VerifiablePresentation, bool](c, module.VpVerifyUrl, vp)
}
Health
func (c *Client) Health() (*response.Health, string, int32) {

return getResponse[*response.Health](c, module.HealthUrl)
}

略有不同,用了get

VcIssue
func (c *Client) VcIssue(req *request.VcIssue) (*core.VerifiableCredential, string, int32) {

if utils.IsAnyBlank(req.TemplateId, req.Holder, req.ExpirationDate) {
return errorResponse[*core.VerifiableCredential](c, module.ErrorParamNullMsg, module.ErrorParamNullCode)
}
expirationDate, _ := time.Parse(module.TimeFormat, req.ExpirationDate)
if expirationDate.Before(time.Now()) {
return errorResponse[*core.VerifiableCredential](c, module.ErrorDataExpirationMsg, module.ErrorDataExpirationCode)
}

return postRequest[*request.VcIssue, *core.VerifiableCredential](c, module.VcIssueUrl, req)
}

有了个时间的检测

VcRevoke
func (c *Client) VcRevoke(id string) (bool, string, int32) {

if utils.IsAnyBlank(id) {
return errorResponse[bool](c, module.ErrorParamNullMsg, module.ErrorParamNullCode)
}
req := &request.VcRevoke{Id: id}

return postRequest[*request.VcRevoke, bool](c, module.VcRevokeUrl, req)
}
···

很多的类似都是直接post/get一个request,除了两个例外的VpCreate

// VpCreateApplyLogin - 生成请求登录VP
// @param *core.VerifiablePresentation 请求信息
// @return *core.VerifiablePresentation VP信息
// @return string 错误信息
// @return int 错误码
func (c *Client) VpCreateApplyLogin(req *core.VerifiablePresentation) (*core.VerifiablePresentation, string, int32) {

return c.VpSign(req)
}

// VpCreateApplyBusinessLicense - 生成请求企业实名VP
// @param *core.VerifiablePresentation 请求信息
// @return *core.VerifiablePresentation VP信息
// @return string 错误信息
// @return int 错误码
func (c *Client) VpCreateApplyBusinessLicense(req *core.VerifiablePresentation) (*core.VerifiablePresentation, string, int32) {

return c.VpSign(req)
}
//但是只是包装了一下
func (c *Client) VpSign(req *core.VerifiablePresentation) (*core.VerifiablePresentation, string, int32) {

return postRequest[*core.VerifiablePresentation, *core.VerifiablePresentation](c, module.VpSignUrl, req)
}

核心数据结构

客户端配置和配置方法
type loginConfig struct {
Username string `json:"username"`
Password string `json:"password"`
Token string `json:"token"`
ExpiresIn int `json:"expires_in"`
EnableAutoLogin bool `json:"enable_auto_login"`
AutoLoginStatus bool `json:"auto_login_status"`
Header map[string]string `json:"header"`
}

type ClientConfig struct {
address string
connTimeout int
respTimeout int
requestLog bool
logger utils.Logger
loginConfig *loginConfig
//retryCount int
}

会用这个结构来添加配置数据

// ClientOption define client option func
type ClientOption func(config *ClientConfig)

// WithAddr 设置节点地址
func WithAddr(address string) ClientOption {
return func(config *ClientConfig) {
config.address = address
}
}

用来初始化客户端,可以带一些Option函数修改配置

func NewDIDClient(opts ...ClientOption) (*Client, error) {
config := &ClientConfig{
loginConfig: &loginConfig{
EnableAutoLogin: false,
},
requestLog: true,
}
for _, opt := range opts {
opt(config)
}

// 校验config参数合法性
if err := checkConfig(config); err != nil {
return nil, err
}

c := &Client{
c: config,
address: config.address,
connTimeout: config.connTimeout,
respTimeout: config.respTimeout,
showRequestLog: config.requestLog,
logger: config.logger,
loginConfig: config.loginConfig,
stop: make(chan struct{}),
//retryCount: config.retryCount,
}
return c, nil
}
Vc相关
type VcIssue struct {
TemplateId string `json:"templateId"` // 凭证模板编号
Holder string `json:"holder"` // 凭证持有者
ExpirationDate string `json:"expirationDate"` // 过期时间
VerificationMethod string `json:"verificationMethod,omitempty"` // 验签公钥地址
CredentialSubject json.RawMessage `json:"credentialSubject"` // 模板填充内容
Version string `json:"version,optional"` // 模板版本号
}
密钥管理

没写居然,看得出来原本想写SM2类型的

Health
type Health struct {
Version string `json:"version"` // 系统版本号
Name string `json:"name"` // 系统名称
BuildTime string `json:"buildTime"` // 编译时间
GitBranch string `json:"gitBranch"` // Git分支
GitCommit string `json:"gitCommit"` // Git提交号
StartTime string `json:"startTime"` // 服务启动时间
Now string `json:"now"` // 服务器当前时间
}

utils

test
type extend struct {
RequestId string `json:"requestId,omitempty"` // 请求ID
CallBackUrl string `json:"callBackUrl,omitempty"` // 回调地址
Method string `json:"method"` // http方式, GET/POST 默认GET
}

omitempty可以在字段为空的时候忽略

// BenchmarkMarshalSortedJSON 基准测试:测量标准实现的性能
func BenchmarkMarshalSortedJSON(b *testing.B) {
// 创建测试对象
example := &extend{
RequestId: "123",
CallBackUrl: "http://localhost:8080",
Method: "GET",
}

// 重复运行b.N次,测量性能
for i := 0; i < b.N; i++ {
_, err := MarshalSorted(example)
if err != nil {
b.Fatal(err) // 出错时终止测试
}
}
}

// BenchmarkMarshalSortedReflect 基准测试:测量优化实现的性能
// 用于与标准实现(MarshalSorted)进行性能对比
func BenchmarkMarshalSortedReflect(b *testing.B) {
// 创建测试对象
example := &extend{
RequestId: "123",
CallBackUrl: "http://localhost:8080",
Method: "GET",
}

// 重复运行b.N次,测量性能
for i := 0; i < b.N; i++ {
_, err := MarshalSortedFast(example) // 使用优化版本的函数
if err != nil {
b.Fatal(err) // 出错时终止测试
}
}
}

在普通的测试外有benchmark

json

两种实现方式,一种直接包装json库,另一种自己实现的,性能快但是支持不够完善

// MarshalSortedFast 自定义Marshal函数,用于生成按键排序的JSON字符串,只适用于结构体,基本数据类型或切片不行
// @Description: 此方法使用了反射转换到map,性能更快,但是对JSON tag支持不够完善
// @param v
// @return []byte
// @return error
func MarshalSortedFast(v interface{}) ([]byte, error) {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}

m := make(map[string]interface{})

// 遍历结构体字段,转换为map
for i := 0; i < val.NumField(); i++ {
field := val.Type().Field(i)
fieldValue := val.Field(i)

jsonTag := field.Tag.Get("json")
if jsonTag == "-" {
continue
}

tagParts := strings.Split(jsonTag, ",")
if len(tagParts) > 0 && tagParts[0] != "" {
jsonTag = tagParts[0]
} else {
jsonTag = field.Name
}

// 检查是否设置了omitempty选项,并且字段值是否为空
omitempty := false
for _, part := range tagParts {
if part == "omitempty" {
omitempty = true
break
}
}
if omitempty {
if isEmptyValue(fieldValue) {
continue // 跳过这个字段,不包含在输出中
}
}

m[jsonTag] = fieldValue.Interface()
}

return json.Marshal(m)
}