drone源码-gin-context实现多数据库系统


原文链接: drone源码-gin-context实现多数据库系统

多数据库 和 多remote是如何实现的?

  1. drone-server启动时通过数据库配置参数 进行初始化数据库
    --driver value database driver (default: "sqlite3") [$DRONE_DATABASE_DRIVER, $DATAB
    ASE_DRIVER]
    --datasource value database driver configuration string (default: "drone.sqlite") [$DRO
    NE_DATABASE_DATASOURCE, $DATABASE_CONFIG]


~/go/src/github.com/drone/drone/cmd/drone-server/server.go

//配置要使用git仓库
remote_, err := SetupRemote(c)
if err != nil {
	logrus.Fatal(err)
}
//配置要使用的数据库
store_ := setupStore(c)
setupEvilGlobals(c, store_, remote_)

// we are switching from gin to httpservermux|treemux,
// so if this code looks strange, that is why.
tree := setupTree(c)

// setup the server and start the listener
handler := router.Load(
	tree,
	ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true),
	middleware.Version,
	middleware.Config(c),         // ~/go/src/github.com/drone/drone/router/middleware/config.go
	middleware.Store(c, store_),  // ~/go/src/github.com/drone/drone/router/middleware/store.go
	middleware.Remote(remote_),   // ~/go/src/github.com/drone/drone/router/middleware/remote.go
)
  1. 数据库配置完成之后 store_ := setupStore(c) 得到 store.Store 对象接口,它包含了所有
    type Store interface {
    // GetUser gets a user by unique ID.
    GetUser(int64) (*model.User, error)
    ...
    }


~/go/src/github.com/drone/drone/cmd/drone-server/setup.go

func setupStore(c *cli.Context) store.Store {
	return datastore.New(
		c.String("driver"),
		c.String("datasource"),
	)
}

  通过router.Load(middleware.Store(c, store_)) 加载到gin的Context 中

  1. 这里 Store(cli *cli.Context, v store.Store) 返回的 gin.HandlerFunc 所以在drone-server 在每次处理 GET,POST等请求时
    都会将store_ 接口对象 存储到 gin 的context中
    ~/go/src/github.com/drone/drone/router/middleware/store.go

    // Store is a middleware function that initializes the Datastore and attaches to
    // the context of every http.Request.
    func Store(cli *cli.Context, v store.Store) gin.HandlerFunc {
    	return func(c *gin.Context) {
    		store.ToContext(c, v) 
    		c.Next()
    	}
    }
    

    store.ToContext(c, v) //
    这里ToContext(c Setter, store Store) 传入两个参数,第二个参数是实现Store接口的对象
    第一个参数是实现Set()方法的的接口就可以了,传递进来的是一个 *gin.Context, 是实现了Set()方法的。
    这样就把数据库的接口对象 保存到了 gin 的Context中了

    ~/go/src/github.com/drone/drone/store/context.go

    const key = "store"
    
    // Setter defines a context that enables setting values.
    type Setter interface {
    	Set(string, interface{})
    }
    // ToContext adds the Store to this context if it supports
    // the Setter interface.
    func ToContext(c Setter, store Store) {
    	c.Set(key, store)
    	//调用gin.Context 的func (c *Context) Set(key string, value interface{}) 方法 
    }
    
  2. 查看gin的Set()实现

~/go/src/github.com/drone/drone/vendor/github.com/gin-gonic/gin/context.go

// 1. 检测 gin.Context 是否实现 context.Context 接口
var _ context.Context = &Context{}

// 2. 158行 gin.Context 实现Setter接口
/************************************/
/******** METADATA MANAGEMENT********/
/************************************/

// Set is used to store a new key/value pair exclusivelly for this context.
// It also lazy initializes  c.Keys if it was not used previously.
func (c *Context) Set(key string, value interface{}) {
	if c.Keys == nil {
		c.Keys = make(map[string]interface{})
	}
	c.Keys[key] = value
}

// 3. 553行 gin.Context 重新实现 Context 接口
/************************************/
/***** GOLANG.ORG/X/NET/CONTEXT *****/
/************************************/

func (c *Context) Value(key interface{}) interface{} {
	if key == 0 {
		return c.Request
	}
	if keyAsString, ok := key.(string); ok {
		val, _ := c.Get(keyAsString)
		return val
	}
	return nil
}

经过以上步骤,gin在router加载的时候就将所需的remote和store写入gin.Context中

~/go/src/github.com/drone/drone/router/router.go

// Load loads the router
func Load(mux *httptreemux.ContextMux, middleware ...gin.HandlerFunc) http.Handler {

	e := gin.New()
	e.Use(gin.Recovery())

	e.Use(header.NoCache)
	e.Use(header.Options)
	e.Use(header.Secure)
	e.Use(middleware...)

	// 以依赖注入形式,注册进来 
	// middleware.Version,
	// middleware.Config(c),         // ~/go/src/github.com/drone/drone/router/middleware/config.go
	// middleware.Store(c, store_),  // ~/go/src/github.com/drone/drone/router/middleware/store.go
	// middleware.Remote(remote_),   // ~/go/src/github.com/drone/drone/router/middleware/remote.go


	e.Use(session.SetUser())
	e.Use(token.Refresh)

	e.NoRoute(func(c *gin.Context) {
		req := c.Request.WithContext(
			web.WithUser(
				c.Request.Context(),
				session.User(c),
			),
		)
		mux.ServeHTTP(c.Writer, req)
	})

	e.GET("/logout", server.GetLogout)
	e.GET("/login", server.HandleLogin)

	e.POST("/hook", server.PostHook)    //处理push event关键
	e.POST("/api/hook", server.PostHook)
	
	e.GET("/healthz", server.Health)

	return e
}

使用

/home/ubuntu/go/src/github.com/drone/drone/server/hook.go

func PostHook(c *gin.Context) {
	remote_ := remote.FromContext(c)

	user, err := store.GetUser(c, repo.UserID)
}
`