开启生长之旅!这是我参加「日新计划 12 月更文挑战」的第3天,点击检查活动详情

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。

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