我开源了一个golang的web框架

go-sail是什么? Whats the go-sail?

go-sail是一个轻量的渐进式web框架,使用golang语言实现。它并不是重复造轮子的产物,而是站在巨人的肩膀上,整合现有的优秀组件,旨在帮助使用者以最简单的方式构建稳定可靠的服务。
正如它的名字一般,你可以把它视作自己在golang生态的一个开始。go-sail将助力你从轻出发,扬帆起航。

go-sail is a lightweight progressive web framework implemented using golang language. It is not the product of reinventing the wheel, but stands on the shoulders of giants and integrates existing excellent components to help users build stable and reliable services in the simplest way. As its name suggests, you can regard it as the beginning of your own journey in the golang ecosystem. go-sail will help you start lightly and set sail.

如何使用? How to use?

go version >= 1.19

go get -u github.com/keepchen/go-sail/v3

import (
    "net/http"
    "github.com/gin-gonic/gin"	
    "github.com/keepchen/go-sail/v3/constants"
    "github.com/keepchen/go-sail/v3/http/api"
    "github.com/keepchen/go-sail/v3/lib/logger"
    "github.com/keepchen/go-sail/v3/sail"
    "github.com/keepchen/go-sail/v3/sail/config"
)

var (
    conf = &config.Config{
        LoggerConf: logger.Conf{
            Filename: "examples/logs/running.log",
        },
        HttpServer: config.HttpServerConf{
            Debug: true,
            Addr:  ":8000",
            Swagger: config.SwaggerConf{
                Enable:      true,
                RedocUIPath: "examples/pkg/app/user/http/docs/docs.html",
                JsonPath:    "examples/pkg/app/user/http/docs/swagger.json",
            },
            Prometheus: config.PrometheusConf{
                Enable:     true,
                Addr:       ":19100",
                AccessPath: "/metrics",
	    },
	},
    }
    apiOption = &api.Option{
        EmptyDataStruct:  api.DefaultEmptyDataStructObject,
        ErrNoneCode:      constants.CodeType(200),
        ErrNoneCodeMsg:   "SUCCEED",
        ForceHttpCode200: true,
    }
    registerRoutes = func(ginEngine *gin.Engine) {
        ginEngine.GET("/hello", func(c *gin.Context){
            c.String(http.StatusOK, "%s", "hello, world!")
        })
    }
    before = func() {
	fmt.Println("call user function [before] to do something...")
    }
    after = func() {
	fmt.Println("call user function [after] to do something...")
    }
)

func main() {
    sail.WakeupHttp("go-sail", conf).
	SetupApiOption(apiOption).
	Hook(registerRoutes, before, after).
	Launch()
}

当你看到终端如下图所示内容就表示服务启动成功了: Console screenshot after launched like this:
go-sail launch.png

特性 Features

  • 获取组件 Get components

go-sail启动时,会根据配置文件启动相应的应用组件,可使用sail关键字统一获取

import (
    "github.com/keepchen/go-sail/v3/sail"
)

//日志组件
sail.GetLogger()

//数据库连接(读、写实例)
sail.GetDB()

//redis连接(standalone模式)
sail.GetRedis()

//redis连接(cluster模式)
sail.GetRedisCluster()

//nats连接
sail.GetNats()

//获取kafka完整连接实例
sail.GetKafkaInstance()

//获取etcd连接实例
sail.GetEtcdInstance()

更多组件持续开发中,也欢迎大家提PR👏🏻👏🏻 PR is welcome👏🏻👏🏻

  • 返回响应 Response
import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/keepchen/go-sail/v3/constants"
    "github.com/keepchen/go-sail/v3/sail"
)

//handler
func SayHello(c *gin.Context) {
    sail.Response(c).Builder(constants.ErrNone, nil, "OK").Send()
}

返回结果如下:

{
	"requestId": "8d33237f-ce1d-4f61-a9a0-fd6e4fd5211b",
	"code": 200,
	"success": true,
	"message": "OK",
	"ts": 1703758125020,
	"data": "hello, go-sail"
}
  • 返回响应实体 Response (entity)
import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/keepchen/go-sail/v3/constants"
    "github.com/keepchen/go-sail/v3/http/pojo/dto"
    "github.com/keepchen/go-sail/v3/sail"
)

type UserInfo struct {
    dto.Base
    Data struct {
        Nickname string `json:"nickname" validate:"required" format:"string"` //nickname
        Age int `json:"int" validate:"required" format:"number"` //age
    } `json:"data" validate:"required"` //body data
}

//implement dto.IResponse interface
func (v UserInfo) GetData() interface{} {
    return v.Data
}

//handler
func GetUserInfo(c *gin.Context) {
    var resp UserInfo
    resp.Data.Nickname = "go-sail"
    resp.Data.Age = 18

    sail.Response(c).Builder(constants.ErrNone, resp).Send()
}

返回结果如下:

{
	"requestId": "2ea1c7db-cd13-4bcb-9c2d-cb129107998e",
	"code": 200,
	"success": true,
	"message": "SUCCEED",
	"ts": 1703758452250,
	"data": {
		"nickname": "go-sail",
		"age": 18
	}
}
  • 注册自定义错误码
import "github.com/keepchen/go-sail/v3/constants"

const (
	ErrNone                                  = CodeType(200)    //没有错误,占位
	ErrStatusGatewayTimeoutTimeOut           = CodeType(504)    //超时
	ErrInternalSeverError                    = CodeType(999999) //服务器内部错误
	ErrRequestParamsInvalid                  = CodeType(100000) //请求参数有误
	ErrAuthorizationTokenInvalid             = CodeType(100001) //令牌已失效
        ErrTypos                                 = CodeType(777777) //拼写错误
)

// 错误码信息表
//
// READONLY for concurrency safety
var codeMsgMap = map[CodeType]string{
	ErrNone:                                  "SUCCESS",
	ErrStatusGatewayTimeoutTimeOut:           "Timeout",
	ErrInternalSeverError:                    "Internal server error",
	ErrRequestParamsInvalid:                  "Bad request parameters",
	ErrAuthorizationTokenInvalid:             "Token invalid",
        ErrTypos:                                 "Oops! Typos",
}

for code, msg := range codeMsgMap {
    constants.RegisterCode(code, msg)
}

使用自定义错误码

sail.Response(c).Builder(constants.ErrTypos, resp).Send()
  • 修改response默认行为
apiOption = &api.Option{
    EmptyDataStruct:  api.DefaultEmptyDataStructObject,
    ErrNoneCode:      constants.CodeType(200),
    ErrNoneCodeMsg:   "SUCCEED",
    ForceHttpCode200: true,
}

其中:

  • ErrNoneCode为设定无错误的错误码,框架默认为0
  • ErrNoneCodeMsg为设定无错误的消息提示,框架默认为SUCCESS
  • ForceHttpCode200为设定是否所有错误码均返回200的http状态码,框架默认为凡是错误则http状态码为非200。
  • EmptyDataStruct为设定响应字段data为空时的值,框架默认为null

已集成的组件

日志 Logger

日志组件封装的是Uber提供的zap日志库,go-sail封装好的logger提供了以下功能:

  • 日志轮转
    支持按文件大小和文件数量上限进行留存文件,防止写满磁盘。
  • 日志导出器
    目前已内置了redis、nats-io、kafka三种方式的导出器,也支持在初始化的时候设置其他自定义的导出器。
  • 按模块写入不同的日志文件
    可以在初始化的时候,根据不同的逻辑或需求区分日志文件。

使用示例

//简单信息打印
sail.GetLogger().Info("print something...")

//带字段的信息打印
sail.GetLogger().Error("something went wrong...", zap.Errors("errors", []error{err1, err2}))

//json格式化后的信息打印
sail.GetLogger().Error("something went wrong...", 
zap.String("value", logger.MarshalInterfaceValue(SomethingStruct)), 
zap.Errors("errors", []error{err1, err2}))  

//指定打印到某个模块文件
sail.GetLogger("order-services").Info("order info...")

数据库 Database

数据库组件是对gorm的简单封装,实现了简单的读写实例分离(在调用时显式指定),并将其默认的logger整合到go-sail的logger组件中。

使用示例

// 调用读写实例
reader, writer := sail.GetDB()

reader.Where("id = ?", 1).First(&order)

writer.Where("id = ?", 1).Updates(&Order{Status: 1})

// 直接调用读实例
sail.GetDBR().Where("id = ?", 1).First(&order)

// 直接调用写实例
sail.GetDBW().Where("id = ?", 1).Updates(&Order{Status: 1})

Redis

redis组件是对go-redis的简单封装,可以根据配置自行决定使用standalone模式还是cluster模式的连接。go-sail的redis导出器使用到的就是这里的组件。

使用示例

// 调用redis单实例连接
sail.GetRedis().Set(ctx, "order-info", "1", time.Minute * 10)

// 调用redis集群实例连接
sail.GetRedisCluster().Set(ctx, "order-info", "1", time.Minute * 10)

本地缓存 Local Cache

由go程序自行管理

  • key-value
// 设置key-value
cache.Set("key", "value", time.Seconds* 5)

// 删除key
cache.Forget("key")

// 设置过期时间
cache.Expire("key", time.Seconds* 5)
  • list
// 初始化list
cache.InitList()

// 新建list
cache.NewList("new-list-key")

// 向左追加
cache.GetListInstance().LPush("a")
cache.GetListInstance().LPush(1)
cache.GetListInstance().LPush(map[string]string{"name":"go-sail"})

// 向右追加
cache.GetListInstance().RPush("a")
cache.GetListInstance().RPush(1)
cache.GetListInstance().RPush(map[string]string{"name":"go-sail"})

// 从左侧取出
cache.GetListInstance().LPop()

// 从右侧取出
cache.GetListInstance().RPop()

// 清空并销毁list
cache.GetListInstance().FlushDB()

消息队列 Message Queue

消息队列组件目前封装了两个库支持:
1.简单封装的nats连接客户端。go-sail日志组件的nats导出器使用到的就是这里的组件。
2.简单封装的kafka连接客户端。

配置中心 Configuration Center

配置中心组件目前封装了两个库支持:
1.简单封装的nacos客户端,由于自身业务的发展历史,配置中心使用的java生态的nacos而不是etcd,因此go-sail封装的是nacos的连接客户端。实现了一些常用功能的语法糖:监听配置、注册/下线服务、获取健康实例。
2.简单封装的etcd客户端。

使用示例

注意:需要先调用nacos.InitClient()方法进行初始化

// 使用nacos监听配置文件  
nacos.ListenConfig("order", "order.yaml", orderConfig, "yaml")

localIP, _ := utils.GetLocalIP()
// 使用nacos注册服务
ok, err := nacos.RegisterService("order", "order-service", localIP, 8080, nil)

// 使用nacos下线服务
ok, err := nacos.UnregisterService("order", "order-service", localIP, 8080)

// 使用etcd监听key
fn := func(k, v []byte) {
  config.XX = v
}
etcd.Watch("order-key", fn)

路由中间件 Route Middleware

go-sail封装了一些常用的路由中间件,包括:

  • 跨域配置
  • Prometheus指标收集
  • 请求载荷打印
  • 解析客户端语言
  • 请求入口
    (注入请求到达时间、注入请求id(用于调用链日志查询调试)、注入带请求id的日志组件。)

使用示例

ginEngine.Use(
middleware.RequestEntry(), // <- 前置注入上下文
middleware.WithCors(),  // <- 开启跨域
middleware.PrometheusExporter()) // <- 配置Prometheus收集器

计划任务 Task Schedule

计划任务分为两种,一种是常规时间间隔型,这一类使用golang原生计时器(time.Ticker)实现;一种是Linux Crontab风格的表达式型,这一类封装的robfig/cron库来实现的。

使用示例

fn := func() {
  fmt.Println("to do something...")
}
// 启动任务
schedule.Job("notify-order-status", fn).EveryFiveSeconds()

// 启动任务(单态运行)
schedule.Job("notify-order-status", fn).WithoutOverlapping().EveryFiveSeconds()

// 启动任务(Linux crontab风格的时间配置)
schedule.Job("notify-order-status", fn).WithoutOverlapping().RunAt(schedule.FirstDayOfMonth)

工具类

  • MD5摘要计算
  • SM4国标加解密
  • AES加解密
  • Base64转义
  • File文件操作
    (保存、读取、逐行读取、存在检测、扩展名获取等)
  • IP地址获取
    (获取本地ip地址(全局单播地址))
  • Redis分布式锁
    (锁定、释放、自动续期)
  • RSA加签验签
  • Signal信号量监听
  • String字符串处理
    (随机字符串、包装公私钥)
  • Time时区封装
  • Webpush浏览器消息推送
  • Datetime时间解析、格式化
  • Swagger文档注释生成
  • Version软件版本打印

框架常量

  • 错误码
    内置错误码、注册自定义错误码,框架的响应组件使用的就是这里的错误码。
  • 时区
    从-11 ~ +12时区和默认时区。

其他插件 Other Plugins

README.md

源代码仓库地址 Source Code Repo

keepchen/go-sail

Go CodeQL Go Report Card
GitHub Repo stars GitHub watchers GitHub forks Latest release