likes
comments
collection
share

亲手写一个gin server项目-1项目启动

作者站长头像
站长
· 阅读数 12

go的web框架很多,单体的,微服务的,admin的,工具类的层出不穷。

其实干web开发搞个几年,人人都可以写出一个框架,前提是整个web服务前端后端都搞过一遍,明白其中各种原理。要是天天crud,不在我说的人人里。

框架的好坏不仅仅是性能高,更重要的是面对开发者友好,面对开发者友好我觉的有两个方面,一方面不过度封装,想要实现什么功能,马上能知道如何实现,而不是去翻源码,翻文档。另一方面,在项目需要修改的紧急情况下,能马上定位到代码并顺利进行修改。

面对框架开发这事,我也有过一段惨痛经历,深知简单比复杂还要困难。而我的水平远远没有达到“最简”。

标题之所以不叫“框架”,而是叫项目,是因为我接下来要输出的是一个admin项目,通过这个项目,把我对http server服务的整个流程所涉及到的思考过的知识点同大家讨论讨论。

整个项目包括:项目结构,项目启动,配置文件,中间件,日志,格式化响应,表单校验,检索,jwt会话,rbac鉴权,单元测试,文档生成,基于es的单体日志采集,基于prometheus的指标监控......。

所涉及到的技术栈包括:gin、zap、mysql、gorm、redis、es、prometheus 等。

都是一些常用的技术或者框架,整个项目也都是对各种组件或框架的组合及应用,能使用原生框架api的绝不进行封装,就算封装也是白话封装,因为太高级的封装甚至修改框架,我驾驭不了 ^_^。但是使用效果还行。

如果有精力后边还会在输出一些配套前端内容,主要基于vue3 vben。

因为创作都是在下班后赶工出来的,难免有所不足甚至错误,欢迎大家多多留言指正。

http server服务

web开发其实就是开发一个http协议的服务器。能实现http服务器的技术很多,如python,一般都是前边开个nginx,通过uwsig技术把python脚本挂载在nginx后边。如golang,自己就能实现一个http server服务器。对于请求流直接进行处理响应。(因为只用过这两个,所以没有其他举例了。)

请求流底层都是基于tcp,在上层就是http或者https协议数据了。每次前端请求服务器,服务器对请求流进行处理,然后进行响应。编写的程序就是对这个请求流进行处理并响应了。最长见的的处理就是数据库增删查改。

当然,关于http协议解析,完全没必要自己处理甚至都不用去理解,现成的框架都已经封装做好了,比如我主要用的,大名鼎鼎的go框架,gin。

gin实现一个简单服务器,代码如下:

package main

import "github.com/gin-gonic/gin"

func main() {
   r := gin.Default()

   r.GET("/", func(c *gin.Context) {
      c.JSON(200, gin.H{
         "Example": "Hello Gin",
      })
   })
   r.Run(":8000")
}

获取参数

项目启动都是一条命令即可,比如上边的那个代码,直接golang run main.go 就可以启动一个服务器,监听8000端口的server服务器。

要是监听不是8000端口呢?总需要外部传一些参数对程序进行控制吧。golang中这类库最著名的就是cobra

但是我这只是一个http server,明明一条命令能启动的东西,非要上个高大上的东西,有点不合适。

用最简单的获取参数方案就可以,官方库flag。

我个人认为像工具类的应用需要各种参数的项目用cobra才是用对了地方,另外ui类的应用没法用cobra库,曾经我大汗淋淋的把一个cobra项目加了一个ui壳,结果一运行,提示只支持terminal应用,然后又费了半天劲把cobra去掉了。

另一种获取参数方法是os.Arg cmder := os.Args[1]

用flag和os.Args 改造一下代码如下:

var port = flag.Int("p", 0, "端口")

func main() {

  flag.Usage = Usage
  flag.Parse()

  if *port == 0 {
     flag.Usage()
     os.Exit(1)
  }

  if len(os.Args) < 2 {
     flag.Usage()
     os.Exit(1)
  }

  cmder := os.Args[1]
  switch cmder {
  case "run":
     server()
  default:
     flag.Usage()
     os.Exit(1)
  }
}

func server() {
  r := gin.Default()
  r.GET("/", func(c *gin.Context) {
     c.JSON(200, gin.H{
        "Example": "Hello Gin",
     })
  })
  r.Run(":" + strconv.Itoa(*port))
}

func Usage() {
  fmt.Println("http server v1.0")
  fmt.Println("main run -p 8000")
  fmt.Println()
  fmt.Println("参数:")
  flag.PrintDefaults()
}

context

一个http服务会依赖其他服务,比如mysql、redis等。要想使用这些服务,必须建立对应的cli端,甚至维护一个全生命周期的连接池。

如何才能方便的初始化这些服务,如何才能快速使用这些服务?

一般有两种方案。

  1. 建立全局对象,创建初始化函数,程序启动时候,调用该函数并实例化该对象,在其他地方都可以导入该对象进行使用。
  2. 创建一个context对象,把所有资源对象都放在该context对象里,初始化该context对象时候,即把所有资源对象进行初始化,然后把这个context对象注入到所有要使用的地方。

我开始时候就是用的第一种方案,开发时候没问题,就是后边要修改代码时候有点头疼,由于这些初始化函数是散落在项目各个地方,控制其顺序启动就要在搞一套东西,有点复杂,加上时间长了,后边找起来就不是那么方便了。

后来看了go-zero的源码,这些资源东西完全可以在同一个地方用同一个对象进行管理啊。于是有了下边的context对象。

type ServerContext struct {
   
}

func NewServerContext() (*ServerContext, error) {
   return sc, nil
}

目前阶段没有讲到config mysql redis log这些东西,所以这里只有个“空壳”。

这个context和golang中的context还不一样,和gin的gin.Context也不一样,这里这个context只是放置项目中用到的一些资源及初始化用的。

之所以没有放在golang的context和gin.Context里是因为使用时候需要从那些地方拿东西都是interface,需要进行类型转换,到处进行类型转换不是我的风格,所以就干脆弄个定制context,直接拿资源使用。

应用代码如下:

var port = flag.Int("p", 0, "端口")

func main() {
    
   //参数处理
   flag.Usage = Usage
   flag.Parse()

   if *port == 0 {
      flag.Usage()
      os.Exit(1)
   }

   if len(os.Args) < 2 {
      flag.Usage()
      os.Exit(1)
   }

   cmder := os.Args[1]
   switch cmder {
   case "run":
      server()
   default:
      flag.Usage()
      os.Exit(1)
   }
}

//资源上下文
type ServerContext struct {
   Src1 string //演示资源
}

func NewServerContext() *ServerContext {
   return &ServerContext{
      Src1: "这是资源1",
   }
}

func server() {
   r := gin.Default()
   svc := NewServerContext()

   r.GET("/", Handler(svc))
   r.Run(":" + strconv.Itoa(*port))
}

//资源context注入handler中
func Handler(svc *ServerContext) gin.HandlerFunc {
   return func(c *gin.Context) {
      c.JSON(200, gin.H{
         "Example": "Hello Gin",
         "src":     svc.Src1,
      })
   }
}

func Usage() {
   fmt.Println("http server v1.0")
   fmt.Println("main run -p 8000")
   fmt.Println()
   fmt.Println("参数:")
   flag.PrintDefaults()
}

结语

目前说到的参数获取和资源context只是很简单的东西,但是可以在很多程序中应用,不单单是http server。

明天抽个时间把配置文件再写写。