吹尽狂沙始到金——菜鸟第一天粗读长安链区块浏览器项目源码

712 阅读4分钟

吹尽狂沙始到金——菜鸟第一天粗读长安链区块浏览器项目源码

本文已参与「开源摘星计划」,欢迎正在阅读的你加入。

活动链接:https://github.com/weopenprojects/WeOpen-Star

一、介绍长安链

长安链是国内首个自主可控区块链软硬件一体技术体系,拥有自主可控的底层平台、完整便捷的配套工具、标准化下的开放生态、灵活高效的装配模式以及国际领先的处理性能。

其使用国密算法。拥有基于国密证书的加密通讯和国产CA。开源协议友好,是Apache License 2.0开源协议。性能强大,具备高并发、低延时、大规模节点组网等先进技术优势,交易吞吐能力可达10万TPS。长安链将区块链执行流程标准化、模块化,可插拔、可分离的自主可控核心框架,可快速接入优势底层模块/单一定制化开发模块。

今天开始读一读长安链区块浏览器项目chainmaker-explorer的源码,今天是第一天,我们来读读主程序main.go。

二、解读源码

先简单介绍一下该程序的流程:

  • 解析命令行参数:config,如果为空,就去看有没有config.yml配置文件,没有就报错结束程序。如果不为空就用flag库来解析。
  • 初始化浏览器配置
  • 初始化日志配置
  • 启动同步
  • 启动http服务

2.1 初始化日志配置

main.go里面这个操作的代码如下:

 loggers.SetLogConfig(conf.LogConf)

SetLogConfig的源码如下:

 func SetLogConfig(config *config.LogConf) {
     logConfig = config
 }

该函数的作用是设置Log配置对象。

2.2 初始化数据库配置

main.go里面这个操作的代码如下:

 dao.InitDbConn(conf.DBConf)

InitDbConn的源码如下:

 var (
     DB  *gorm.DB
     log = loggers.GetLogger(loggers.MODULE_WEB)
 )
 ​
 func InitDbConn(dbConfig *config.DBConf) {
     var err error
     DB, err = gorm.Open(config.MySql, dbConfig.ToUrl())
     if err != nil {
         panic(err)
     } else {
         DB.DB().SetMaxIdleConns(config.DbMaxIdleConns)
         DB.DB().SetMaxOpenConns(config.DbMaxOpenConns)
         DB.DB().SetConnMaxLifetime(time.Minute)
         DB.Set("gorm:association_autoupdate", false).Set("gorm:association_autocreate", false)
         DB.SingularTable(true)
         DB.LogMode(true)
     }
     InitDB(DB)
 }

这里使用了gorm进行数据库相关操作。

 DB, err = gorm.Open(config.MySql, dbConfig.ToUrl())

这条语句是用来连接数据库的,连接的是Mysql数据库,第二个参数是数据库的URL。

然后连接成功的话,就进行连接池相关配置。

SetMaxIdleConns 设置空闲连接池中连接的最大数量

SetMaxOpenConns 设置打开数据库连接的最大数量。

SetConnMaxLifetime 设置了连接可复用的最大时间。

 DB.Set("gorm:association_autoupdate", false).Set("gorm:association_autocreate", false)

设置gorm:association_autoupdatefalse说明关联已经存在,并且你不想更新它。如果你单单禁用了gorm:association_autoupdate,仍然需要创建没有主键的关联,保存其引用,所以我们需要设置gorm:association_autocreate为false。

DB.SingularTable(true)是禁用复数表名,如果设置为true,表名users将变成user

DB.LogMode(true)作用是启用Logger,每个数据库操作都会显示详细日志,无需一个个调用Debug方法。

然后是InitDB().

 /*
 Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.
 ​
 SPDX-License-Identifier: Apache-2.0
 */
 package dao
 ​
 import (
     "github.com/jinzhu/gorm"
 )
 ​
 const (
     TableBlock         = `cmb_block`
     TableTransaction   = `cmb_transaction`
     TableChain         = `cmb_chain`
     TableContract      = `cmb_contract`
     TableNode          = `cmb_node`
     TableNode2Chain    = `cmb_node2chain`
     TableSubscribe     = `cmb_subscribe`
     TableOrg           = "cmb_org"
     TableUser          = "cmb_user"
     TableContractEvent = `cmb_contract_event`
 )
 ​
 func InitDB(engine *gorm.DB) {
     err := engine.AutoMigrate(
         new(Block),
         new(Transaction),
         new(Contract),
         new(Node),
         new(NodeRefChain),
         new(Chain),
         new(Subscribe),
         new(ContractEvent),
         new(Org),
         new(User),
     ).Error
     if err != nil {
         log.Errorf(err.Error())
     }
 }

engine.AutoMigrate是自动迁移,可以维持你的表结构一直处于最新状态。

2.3 启动同步

 err := sync.Start()

其对应的源码是:

 var (
     log           = loggers.GetLogger(loggers.MODULE_SYNC)
     sdkClientPool *SdkClientPool
 )
 ​
 func Start() error {
     var err error
     sdkClientPool, err = InitSdkClientPool()
     if err != nil {
         log.Error("Initial sdk client pool failed", err)
         return err
     }
     return nil
 }

这个操作是初始化SDK客户端池。

下面是InitSdkClientPool的具体代码。

流程如下:

  • 初始化 客户端pool

  • 加载订阅状态为正常的链

  • 根据配置创建新链,如果创建失败,或订阅失败,修改订阅状态为1

 func InitSdkClientPool() (*SdkClientPool, error) {
     // 初始化 客户端pool
     if sdkClientPool == nil {
         sdkClients := make(map[string]*SdkClient)
         sdkClientPool = &SdkClientPool{
             SdkClients: sdkClients,
         }
     }
 ​
     // 加载订阅状态为正常的链
     sdkConfigs, err := dbhandle.GetActiveSubscribeChains()
     if err != nil {
         log.Info("failed get active chains")
         return nil, err
     }
 ​
     for _, sdkConfig := range sdkConfigs {
         err := NewSubscribeChain(sdkConfig)
         if err != nil {
             // 如果创建失败,或订阅失败,修改订阅状态为1
             log.Errorf("init client error for chain: %s, %s", sdkConfig.ChainId, err)
             sdkConfig.Status = dao.SubscribeFailed
             dao.DB.Save(&sdkConfig)
             continue
         }
     }
 ​
     return sdkClientPool, nil
 }

至于下面这个操作,我们来看看它的源码:

 go sync.GetSdkClientPool().PeriodicLoad()
 func (pool *SdkClientPool) PeriodicLoad() {
     ticker := time.NewTicker(time.Second * time.Duration(config.BrowserConfig.NodeConf.UpdateTime))
     for {
         select {
         case <-ticker.C:
             for chainId, client := range pool.SdkClients {
                 log.Infof("periodic work, load chain: %s", chainId)
                 go loadChainRefInfos(client)
             }
         }
     }
 }

设置一个定时器,然后管道疏通后就将池里面所有客户端的链和其他相关信息进行加载。

2.4 http-server启动

 router.HttpServe(conf.WebConf)
 func HttpServe(webConf *config.WebConf) {
     // 启动Web服务(默认Debug级别)
     gin.SetMode(gin.ReleaseMode)
     // 生成route
     ginRouter := gin.Default()
     // 初始化路由配置
     InitRouter(ginRouter, webConf)
     // 启动Http服务
     err := ginRouter.Run(webConf.ToUrl())
     if err != nil {
         panic(err)
     }
 }

是用Gin框架实现WEB服务的。

三、菜鸟结语

菜鸟第一次尝试粗读项目源码,如有错误,烦请大佬指出。

刘禹锡先生有诗句:千淘万漉虽辛苦,吹尽狂沙始到金。虽然从全文来看,可能用在此处不太合适,但是笔者将其单独拿出来:淘金要经过千遍万遍的过滤,要历尽千辛万苦,最终才能淘尽泥沙,得到闪闪发光的黄金。研读源码是一件十分辛苦的事情,但是当我阅读完成这部分代码,会感到收获满满,无论是代码风格,还是程序架构方面,都能让我学到一点东西。