Categorygithub.com/hongker/framework
module
0.0.7
Repository: https://github.com/hongker/framework.git
Documentation: pkg.go.dev

# README

framework

提供快捷的运行web项目的golang脚手架

安装

暂时使用github放置,后续转成私有

go get github.com/hongker/framework

组件

  • 支持web服务,定时任务
  • 支持redis,mysql,elastics
  • 支持读取文件配置,环境变量配置等
  • 支持全局链路追踪,跨域,权限校验(rbac)等中间件
  • 支持日志,事件,http请求等常用组件
  • 支持swagger接口文档集成

Web服务

基于github.com/gin-gonic/gin实现的高性能,可扩展的web服务

  • 开始使用
func main() {
    server := http.NewServer()
    // 添加路由
	server.Router.GET("/test", func(ctx *gin.Context) {
		response.Wrap(ctx).Success(response.Data{
			"hello":"world",
		})
	})
    // 启动web服务。启动端口是读取的配置文件中的server.port参数,如果不配置,默认是8080
    server.Start()
    // 也可以指定8081端口启动
    // server.Start(8081)
}

用法都很简单,想要了解更多请查看:gin文档

  • 输出响应内容
    响应内容都是输出的json格式的数据
func DemoHander(ctx *gin.Context) {
    // 成功的输出: 
	// data : null
    response.Wrap(ctx).Success(nil)
    
    // data : hello
    response.Wrap(ctx).Success("hello")
    
    // data: {"hello":"world", "age": 1}
    response.Wrap(ctx).Success(response.Data{
        "hello":"world",
        "age": 1,
    })
    
    // 分页输出
    items := []int{1,2,3,4}
    // 分页组件,总行数为100,当前页数为1,每页行数为10
    pagination := paginate.Paginate(100,1,10)
    response.Wrap(ctx).Paginate(items, &pagination)
    
    // 错误的输出
    response.Wrap(ctx).Error(1001,"错误提示信息,比如用户名参数错误等等")
}
  • 请求参数
    介绍在handler里如何获取请求参数
// ctx 为 *gin.Context
// 获取url上的get参数,如url: /user/info?name=alice
router.Get("/user/info", func(ctx *gin.Context) {
    name := ctx.Query("name")
})

// 获取url的restful参数,如:
router.Get("/article/:id", func(ctx *gin.Context) {
    idStr := ctx.Param("id")
})

// 获取post参数,如果是通过raw的方式提交,用`json`标签,如果是form-data方式提交,则用`form`
type ArticleCreateRequest struct {
    Title string `json:"title" form:"json"`
    Content string `json:"content"`
}
router.Get("/article", func(ctx *gin.Context) {
    var req ArticleCreateRequest
    if err := ctx.ShouldBind(&req);err != nil {
        response.Wrap(ctx).Error(1001,"参数错误")
        return
    } 
    fmt.Println(req.Title)
})

中间件

使用中间件的方式嵌入一些非业务的逻辑,使得层次更为清晰,简单。
注:如果需要中间件在执行路由前执行,那么中间件的引入需要在定义路由之前。

  • Trace 全局链路追踪中间件
    一个用户发起的请求,会有唯一的全局traceId.该请求里执行的所有业务操作都可以用该traceId来关联。比如记录日志,发起http请求等,通过该ID形成一条完整的业务链路。
    web服务默认引入了该组件的,生效位置:响应内容的meta.trace_id,日志里的trace_id,http请求的header头部:trace_id
    该组件默认已引入了。

  • CORS 跨域中间件

server.Router.Use(middleware.CORS)
  • Recover 错误处理中间件
    类似于try .. catch里的catch,拦截panic错误,避免系统错误直接暴露给用户。
server.Router.Use(middleware.Recover)
  • Permission 权限
    基于rbac的权限校验中间件
// 可以给全部路由设置权限校验
server.Router.Use(middleware.Permission)
// 也可以给某个路由组设置权限校验,代表该组下的所有路由都需要进行权限校验
server.Router.Group("user").Use(middleware.Permission)
// 也可以给某个路由设置权限校验
server.Router.Get("/money", handler.MoneyHandler).Use(middleware.Permission)
  • RequestLog 请求日志中间件
    记录请求日志,包括请求header,body,响应body,以及接口消耗的时间等。
server.Router.Use(middleware.RequestLog)

Swagger接口文档生成

集成了github.com/swaggo/gin-swagger,通过注解自动生成接口文档,使用方式请查看:gin-swagger文档

// 生成docs目录下的文件
swag init
  • 在router里添加
// 引入包里的docs信息
import _ "packageName/docs"
// 提供访问入口
router.GET("/swagger/*any", middleware.SwaggerHandler())
// 在生产环境中,需要关闭。通过设置环境变量: export DisableSwagger=true 

最后访问: http://localhost:8080/swagger/index.html,即可看到文档

  • 备注
    个人不是很推荐使用这种方式做接口文档,因为go对注解的支持不够,会让项目的代码形成混乱的现象。

配置

  • 加载配置
package main
func init() {
    // 一般来说配置文件的加载都是放在main.go里的init函数里执行
    // 两种方式可以二选一,也可以两个都用
    config.ReadFromEnvironment() // 从环境变量中读取配置
    configFilePath := "app.yaml"
    err := config.ReadFromFile(configFilePath) // 通过文件读取配置.如果加载失败,建议中断启动:os.Exit(-1)
}
func main() {
    // 读取配置
}
  • 初始化配置 通过init方法指定配置的默认值
import (
 "github.com/spf13/viper"
)

func init() {
    // 如果配置文件或者环境变量里没有主动设置someKey这个配置,那么someValue就是someKey这个配置项的默认值
    viper.SetDefault("someKey", "someValue")
}
  • 系统配置说明 支持多环境配置
server:
  runmode: local # 运行环境
  name : activity # 项目名称
  port : 8085 #端口号

local:  # 本地运行环境的配置
  db:     # mysql数据库配置,支持多连接,支持读写分离,第一个默认为写库
    default: # 默认的库连接
        maxIdleConnections: 10    # 连接池配置
        maxOpenConnections: 40
        maxLifeTime: 10
        dsn:    # 数据库连接
        - host : 127.0.0.1
          port : 3306
          user : root
          password : 123456
          name : activity
    other:  # 另外一个库连接
        maxIdleConnections: 10    # 连接池配置
        maxOpenConnections: 40
        maxLifeTime: 10
        dsn:    # 数据库连接
        - host : 127.0.0.1
          port : 3306
          user : root
          password : 123456
          name : other    
  redis:        # redis配置
    host: 127.0.0.1 # 地址
    port: 6379      # 端口
    db: 5           # db,最好是用0
    sessionDB: 4    # 额外指定sessionDB
dev:
  db:     # mysql数据库配置,支持读写分离,第一个默认为写库
    default: #默认连接
        maxIdleConnections: 10    # 连接池配置
        maxOpenConnections: 40
        maxLifeTime: 10
        dsn:    # 数据库连接
          - host: 127.0.0.1   # 写库
            port: 3306
            user: root
            password: 123456
            name: activity
          - host: 127.0.0.2   # 从库
            port: 3306
            user: root
            password: 123456
            name: activity
  • 读取配置
// 简单的读取
someConfig := viper.GetString("someKey")
// 读取int
someIntConfig := viper.GetInt("someIntKey")

更多请查看:viper文档

基础服务

基于github.com/uber/dig的依赖注入模式,通过统一的app模块管理如mysql连接,redis连接等全局变量。

数据库

集成业界好评且强大的github.com/jinzhu/gorm,实现对mysql数据的操作。支持读写分离,在配置文件中配置多个连接即可。

// 在成功加载配置后,初始化数据库连接,连接失败时panic
secure.Panic(app.InitDB())

// 获取默认的数据库连接
app.DB()
// 根据名称获取数据库连接
app.GetDB("other")

Redis

Elastic

日志

基于github.com/sirupsen/logrus实现的日志组件

func main() {
    // 默认输出到控制台,也可以通过log.SetOutput()设置日志输出到文件
    log.Info("message", log.Content{
        "content":"this is log content",
        "params": "some other params",
    })
    log.Info("error message", log.Content{
        "content":"this is log content",
        "err": someError.Error(),
    })
}

参数校验器

通过强大的参数校验器校验请求参数,可以大量减少请求参数的判断逻辑代码,提高代码的可读性。

  • 初始化
// 在main函数前配置自定义的参数校验器
binding.Validator = new(validator.Validator)
  • 设置校验规则 标签里的binding为关键字,指定规则。 comment为字段名称,用于错误输出的显示
type AuthRequest struct {
	// 如果是表单提交,使用form,否则获取不到数据
    
    // 验证邮箱,必填,格式为邮箱
	Email string `json:"email" binding:"required,email" comment:"邮箱"` 
    // 验证密码,必填,长度为6~10
	Pass string `json:"pass" binding:"required,min=6,max=10" comment:"密码"` 
}

// 在handler里使用
router.Post("/user/auth", func(ctx *gin.Context) {
    var req AuthRequest
    if err := ctx.ShouldBind(&req);err != nil {
        response.Wrap(ctx).Error(1001,"参数错误:"+err.Error())
        return
    } 
    fmt.Println(req.Pass)
})

Session

实现基于文件存储和redis存储的session组件.注:session和Request,Writer关联,故最好是放在接口层获取或保存。 建议不使用session,而是用jwt这种无状态的校验机制。

  • 文件存储
store := session.NewCookieStore("userState")
// 添加路由
server.Router.GET("/login", func(ctx *gin.Context) {
    // 获取一个key为user的session
	s , _ := store.Get(ctx.Request, "user")
    // 指定元素name的值
	s.Values["name"] = ctx.Query("name")
    // 保存session
	if err := s.Save(ctx.Request, ctx.Writer); err != nil {
		response.Wrap(ctx).Error(500, "login failed")
		return
	}
	response.Wrap(ctx).Success(response.Data{
		"hello":"world",
	})
})
server.Router.GET("/user", func(ctx *gin.Context) {
    // 读取session
	s , _ := store.Get(ctx.Request, "user")
	fmt.Println(s.Values)
})
  • Redis存储
    很多情况下,服务都是以分布式的方式部署,文件存储并不适用于该场景,需要借助Redis实现。
// 初始化,一般在init函数里执行
secure.Panic(app.InitSessionStore())
// 使用
server.Router.GET("/redis/login", func(ctx *gin.Context) {
    // 获取一个key为user的session
	s , _ := app.SessionStore().Get(ctx.Request, "user")
    // 赋值
	s.Values["name"] = ctx.Query("name")
    // 保存
	if err := s.Save(ctx.Request, ctx.Writer); err != nil {
		response.Wrap(ctx).Error(500, "login failed")
		return
	}
	response.Wrap(ctx).Success(response.Data{
		"hello":"world",
	})
})

server.Router.GET("/redis/user", func(ctx *gin.Context) {
    // 读取session
	s , _ := app.SessionStore().Get(ctx.Request, "user")
	fmt.Println(s.Values)
})

# Packages

app 公共服务 提供数据库,redis,权限管理,http等公共服务 通过依赖注入的设计模式管理全局变量.
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author
No description provided by the author