# gogo **Repository Path**: dolpstar/gogo ## Basic Information - **Project Name**: gogo - **Description**: 轻量 web 开发框架,特点是一行代码搞定 RESTFul,无需一堆的 controllers 和 models 文件,快速使用,性能优秀。v4重点部分性能优于spring、kafka、nginx,README有介绍 - **Primary Language**: Go - **License**: MIT - **Default Branch**: v2 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 29 - **Created**: 2024-11-21 - **Last Updated**: 2024-11-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ![GoGo Logo](https://gitee.com/konyshe/gogo/raw/v1.1.2/logo.jpg "GoGo") Go 语言轻量 web 开发框架,特点是一行代码搞定 RESTFul,不依赖第三方 ORM,也不需要生成一堆的 controllers 和 models 文件,快速使用,性能优秀。 ## 版本介绍 ### v1(已发布) - [x] 基于Google的http框架, 改写了router,支持正则URL,支持restful。无需针对每张表写一个go文件。 ### v2(已发布) - [x] 基于Google的http框架,新增了websocket,独立端口号,数据包缓冲队列,定时发送心跳包。 ### v3(main分支,测试中) - [x] 基于epoll重写了http框架,针对低性能设备优化。自测十万并发时,相比Google的http框架,客户端响应慢了0.1秒(因为目前只用到单核),但服务端CPU消耗下降了50%。 - [x] gogo.StartHTTP使用Google的http,gogo.StartV3HTTP使用v3方案,使用方法一致。 注:Google的http针对每个请求,都会产生一个goroutine。如果后端业务来不及,就会积压goroutine。GoGo v3目前只用了单线程处理http请求,不积压goroutine。计划多线程和CPU绑定,减少上下文切换。 ### v4(开发中,即将开源) - [ ] 基于网卡驱动重写了epoll,利用DMA将数据从网卡直接到用户态,搭配用户态tcp/ip协议栈,零拷贝,bbr流量控制。 注:按照内核设计,数据包到网卡寄存器后,由DMA拷贝到指定地址,再由驱动拷贝到skbuf等内核处理,内核处理后拷贝进队列等协议栈处理,协议栈处理后拷贝进队列,然后发送中断等用户态处理,最后用户态通过recv函数拷贝到指定地址。除第一次DMA拷贝,之后有4次CPU拷贝,特别是最后一次内核态到用户态的拷贝,加上中断,性能消耗很大。v4只有一次DMA拷贝。 ### 需求和规划 - [ ] v4目前只支持e1000网卡,计划支持更多网卡 - [ ] 支持DPDK,因为DPDK已支持很多网卡,性能顶尖 - [ ] 无论是v4或DPDK,有个问题就是独占网卡,这对只有一个网卡的云主机而言,是无法使用的。计划支持RING方案,先通过DMA将数据从网卡拷贝到内核,再将数据从内核映射到用户态处理,一次DMA拷贝和一次CPU拷贝,不独占网卡。 ## 快速使用 备注:由于嵌入了C代码,若遇到提示gcc无法编译的问题,可删掉UtilsBuild_linux.go文件后再尝试 ```go package main import ( "net/http" "strconv" "gitee.com/konyshe/gogo" ) func main() { //初始化日志输出功能,DEBUG为日志输出级别,具体参考函数说明 gogo.LogInit("DEBUG", 1024) // 初始化数据库连接 if err := gogo.SQLInit("mysql", "数据库用户名:数据库密码@tcp(数据库地址:数据库端口)/表名?charset=utf8", 10, 1); err != nil { gogo.CheckErrorExit(err) return } // 增 gogo.POST("/restful/:tablename", func(ctx *gogo.HTTPContext) { affect, err := gogo.SQLInsert( ctx.GetPathParam(":tablename"), ctx.GetPostBody()) if err != nil { gogo.LogError(err) ctx.WriteString(err.Error()) } else { ctx.WriteString(strconv.FormatInt(affect, 10)) } }) // 删 gogo.DELETE("/restful/:tablename/:id", func(ctx *gogo.HTTPContext) { affect, err := gogo.SQLDelete( ctx.GetPathParam(":tablename"), "id="+ctx.GetPathParam(":id")) if err != nil { gogo.LogError(err) ctx.WriteString(err.Error()) } else { ctx.WriteString(strconv.FormatInt(affect, 10)) } }) // 改 gogo.PUT("/restful/:tablename/:id", func(ctx *gogo.HTTPContext) { affect, err := gogo.SQLUpdate( ctx.GetPathParam(":tablename"), "id="+ctx.GetPathParam(":id"), ctx.GetPostBody()) if err != nil { gogo.LogError(err) ctx.WriteString(err.Error()) } else { ctx.WriteString(strconv.FormatInt(affect, 10)) } }) // 查 gogo.GET("/restful/:tablename/:id", func(ctx *gogo.HTTPContext) { queryData, err := gogo.SQLQueryByMap( "", ctx.GetString("feilds"), ctx.GetPathParam(":tablename"), "id="+ctx.GetPathParam(":id"), "", 0, 1) if err != nil { gogo.LogError(err) ctx.WriteString(err.Error()) } else { ctx.WriteJSON(queryData) } }) // 查 gogo.GET("/restful/:tablename", func(ctx *gogo.HTTPContext) { queryData, err := gogo.SQLQueryByMap( ctx.GetString("columnname"), ctx.GetString("feilds"), ctx.GetPathParam(":tablename"), ctx.GetString("where"), ctx.GetString("order"), ctx.GetInt("offset", 0), ctx.GetInt("count", 10)) if err != nil { gogo.LogError(err) ctx.WriteString(err.Error()) } else { ctx.WriteJSON(queryData) } }) // 404页面 gogo.STATUS(http.StatusNotFound, func(ctx *gogo.HTTPContext) { ctx.WriteHeaderStatus(http.StatusNotFound) ctx.WriteString("Page Not Found !") }) // 启动HTTP服务 gogo.StartHTTP(3009) } ``` ## 数据库表结构 ```mysql mysql> desc dede_flink; +----------+----------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+----------------------+------+-----+---------+----------------+ | id | smallint(5) unsigned | NO | PRI | NULL | auto_increment | | sortrank | smallint(6) | NO | | 0 | | | url | char(60) | NO | | | | | webname | char(30) | NO | | | | | msg | char(200) | NO | | | | | email | char(50) | NO | | | | | logo | char(60) | NO | | | | | dtime | int(10) unsigned | NO | | 0 | | | typeid | smallint(5) unsigned | NO | | 0 | | | ischeck | smallint(6) | NO | | 1 | | +----------+----------------------+------+-----+---------+----------------+ 10 rows in set ``` ## Windows ``` go build -o app.exe app.exe ``` ## Linux/MacOS ``` go build -o app ./app ``` ## 浏览器访问测试 - 增 ``` POST http://localhost:3009/restful/dede_flink ``` 以下是 application/json 内容,可以批量添加多条数据 ``` [ { "id": 20, "ischeck": 1, "webname": "hxyw", "url":"http://www.hxyw.org" }, { "id": 21, "ischeck": 1, "webname": "bejson", "url":"http://www.bejson.com" } ] ``` - 改 ``` PUT http://localhost:3009/restful/dede_flink/21 ``` 以下是 application/json 内容,这里只将 id=21 的数据,webname 字段修改为"hello" ``` { "webname": "hello" } ``` - 查 ``` GET http://localhost:3009/restful/dede_flink/21 GET http://localhost:3009/restful/dede_flink?order=-id GET http://localhost:3009/restful/dede_flink?offset=10&&count=100&&columnname=webname ``` - 删 ``` DELETE http://localhost:3009/restful/dede_flink/1 ``` - 404 ``` GET http://localhost:3009/weqwe ``` ## Start using it 1. Download and install it: ```sh $ go get -u gitee.com/konyshe/gogo ``` 2. Import it in your code: ```go import "gitee.com/konyshe/gogo" ``` 3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusNotFound`. ```go import "net/http" ``` ## 示例中的方法介绍,更全的请看代码中的注释 ```go // LogInit 初始化日志输出功能 // level 日志输出级别 // 配置文件指定日志级别  ALL,DEBUG,INFO,WARN,ERROR,FATAL,OFF 级别由低到高 // 其中 ALL表示所有调用打印日志的方法都会打出,而OFF则表示都不会打出。 // 一般习惯是测试阶段为debug,生成环境为info以上 // maxSize 日志文件最大体积,单位MB gogo.LogInit(level string, maxSize int64) // 系统会优先判断是否命中固定URL,当固定URL无法命中的情况下,才会去判断是否命中正则URL(开发中部分支持) gogo.GET("/restful/:tablename/:columnname", func(ctx *gogo.HTTPContext) // 注册没有命中任何URL规则时进入的函数 gogo.STATUS(http.StatusNotFound, func(ctx *gogo.HTTPContext) // SQLInit 初始化数据库操作句柄,这里要提供: // driverName string: 数据库类型,例如mysql、sqlite、postgres等,参考github.com/go-sql-driver/mysql官方介绍 // dataSourceName string: 数据库地址,参考github.com/go-sql-driver/mysql官方介绍 // MaxOpenConns int: 最大缓存连接数,这个数值包含了MaxIdleConns // MaxIdleConns int:预备的最大空闲连接数 gogo.SQLInit(driverName string, dataSourceName string, maxOpenConns int, maxIdleConns int) error // SQLInsert 增加一条数据 // tableName string: 操作的表名 // data []byte: 需要更新的内容,用string转换后是json格式 gogo.SQLInsert(tableName string, data []byte) (int64, error) // SQLDelete 根据where条件删除数据 // tableName string: 操作的表名 // where string: 过滤条件,就是where后面跟着的部分 gogo.SQLDelete(tableName, where string) (int64, error) // SQLUpdate 更新一条数据 // tableName string: 操作的表名 // where string: 过滤条件,就是where后面跟着的部分 // data []byte: 需要更新的内容,用string转换后是json格式 gogo.SQLUpdate(tableName, where string, data []byte) (int64, error) // SQLQueryByMap 将查询到的数据,按照指定字段的值做为索引构建map并返回 // columnName string: 作为索引的字段名称 // feilds string: 查询需要获取哪些字段的值,就是select后面跟着的部分,一般用"*" // tableName string: 查询的表名 // where string: 过滤条件,就是where后面跟着的部分 // order string: 排序条件,就是order by后面跟着的部分。默认是ASC排序,除非"-"开头则DESC排序 // offset string: limit后面逗号相隔的两个数值,前者就是offset,后者就是count // count string: limit后面逗号相隔的两个数值,前者就是offset,后者就是count gogo.SQLQueryByMap(columname, feilds, tableName, where, order string, offset, count int) (interface{}, error) // StartHTTP 启动HTTP服务 // port 端口号 func StartHTTP(port int) // StartWebSocket 启动websocket服务 // pattern 服务路径 // port 端口号 // handler 注册回调函数 func StartWebSocket(pattern string, port int, handler func([]byte, *ImplConnection) ([]byte, error)) // GetResponseWriter 获取原始的http.ResponseWriter指针 (ctx *HTTPContext) GetResponseWriter() http.ResponseWriter // GetRequest 获取原始的http.Request指针 (ctx *HTTPContext) GetRequest() *http.Request // GetPathParam 获取GET请求路径中的String格式参数值,例如/restful/:table_name/:id // key string: 参数名称 (ctx *HTTPContext) GetPathParam(key string) string // GetPostBody 获取POST请求的内容 (ctx *HTTPContext) GetPostBody() []byte // WriteExecute 将官方原始的ParseFiles和Execute接口做了合并 // data interface{}: 渲染模板需要的数据 // filenames ...string: 模板文件路径 (ctx *HTTPContext) WriteExecute(data interface{}, filenames ...string) error // WriteByte 将[]byte格式的数据输出给HTTP客户端 // content []byte: 需要输出HTTP客户端的[]byte格式数据 (ctx *HTTPContext) WriteByte(content []byte) // WriteString 将String格式的数据输出给HTTP客户端 // content string: 需要输出HTTP客户端的String格式数据 (ctx *HTTPContext) WriteString(content string) // WriteJSON 将Struct结构体的数据转换成Json输出给HTTP客户端 // v interface{}: 需要输出HTTP客户端的Struct结构体数据 (ctx *HTTPContext) WriteJSON(v interface{}) error // Log GoGo使用的第三方go_logger库,输出到日志文件的同时还会在控制台输出,并根据日志级别显示不同颜色 // 日志输出和官方fmt.Print、fmt.Printf使用一致 func LogDebug(a ...interface{}) func LogInfo(a ...interface{}) func LogWarning(a ...interface{}) func LogError(a ...interface{}) func LogDebugF(format string, a ...interface{}) func LogInfoF(format string, a ...interface{}) func LogWarning(format string, a ...interface{}) func LogErrorF(format string, a ...interface{}) // GlobalSignalWait 全局信号等待 // 需要程序等待协程运行的时候使用 func GlobalSignalWait() // GlobalSignalRelease 全局信号继续 // 需要程序不再等待协程运行的时候使用 func GlobalSignalRelease() ``` ## 欢迎贡献者加入