Repository: lexkong/apiserver_demos Branch: master Commit: 87e2a93052ef Files: 417 Total size: 459.0 KB Directory structure: gitextract_0wqow4l0/ ├── Makefile ├── README.md ├── a.go ├── demo01/ │ ├── README.md │ ├── handler/ │ │ └── sd/ │ │ └── check.go │ ├── main.go │ └── router/ │ ├── middleware/ │ │ └── header.go │ └── router.go ├── demo02/ │ ├── README.md │ ├── conf/ │ │ └── config.yaml │ ├── config/ │ │ └── config.go │ ├── handler/ │ │ └── sd/ │ │ └── check.go │ ├── main.go │ └── router/ │ ├── middleware/ │ │ └── header.go │ └── router.go ├── demo03/ │ ├── README.md │ ├── conf/ │ │ └── config.yaml │ ├── config/ │ │ └── config.go │ ├── handler/ │ │ └── sd/ │ │ └── check.go │ ├── main.go │ └── router/ │ ├── middleware/ │ │ └── header.go │ └── router.go ├── demo04/ │ ├── README.md │ ├── conf/ │ │ └── config.yaml │ ├── config/ │ │ └── config.go │ ├── handler/ │ │ └── sd/ │ │ └── check.go │ ├── main.go │ ├── model/ │ │ └── init.go │ └── router/ │ ├── middleware/ │ │ └── header.go │ └── router.go ├── demo05/ │ ├── README.md │ ├── conf/ │ │ └── config.yaml │ ├── config/ │ │ └── config.go │ ├── handler/ │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ └── create.go │ ├── main.go │ ├── model/ │ │ └── init.go │ ├── pkg/ │ │ └── errno/ │ │ ├── code.go │ │ └── errno.go │ └── router/ │ ├── middleware/ │ │ └── header.go │ └── router.go ├── demo06/ │ ├── README.md │ ├── conf/ │ │ └── config.yaml │ ├── config/ │ │ └── config.go │ ├── handler/ │ │ ├── handler.go │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ ├── create.go │ │ └── user.go │ ├── main.go │ ├── model/ │ │ └── init.go │ ├── pkg/ │ │ └── errno/ │ │ ├── code.go │ │ └── errno.go │ └── router/ │ ├── middleware/ │ │ └── header.go │ └── router.go ├── demo07/ │ ├── README.md │ ├── conf/ │ │ └── config.yaml │ ├── config/ │ │ └── config.go │ ├── db.sql │ ├── handler/ │ │ ├── handler.go │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── list.go │ │ ├── update.go │ │ └── user.go │ ├── main.go │ ├── model/ │ │ ├── init.go │ │ ├── model.go │ │ └── user.go │ ├── pkg/ │ │ ├── auth/ │ │ │ └── auth.go │ │ ├── constvar/ │ │ │ └── constvar.go │ │ └── errno/ │ │ ├── code.go │ │ └── errno.go │ ├── router/ │ │ ├── middleware/ │ │ │ └── header.go │ │ └── router.go │ ├── service/ │ │ └── service.go │ └── util/ │ └── util.go ├── demo08/ │ ├── README.md │ ├── conf/ │ │ └── config.yaml │ ├── config/ │ │ └── config.go │ ├── db.sql │ ├── handler/ │ │ ├── handler.go │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── list.go │ │ ├── update.go │ │ └── user.go │ ├── main.go │ ├── model/ │ │ ├── init.go │ │ ├── model.go │ │ └── user.go │ ├── pkg/ │ │ ├── auth/ │ │ │ └── auth.go │ │ ├── constvar/ │ │ │ └── constvar.go │ │ └── errno/ │ │ ├── code.go │ │ └── errno.go │ ├── router/ │ │ ├── middleware/ │ │ │ ├── header.go │ │ │ ├── logging.go │ │ │ └── requestid.go │ │ └── router.go │ ├── service/ │ │ └── service.go │ └── util/ │ └── util.go ├── demo09/ │ ├── README.md │ ├── conf/ │ │ └── config.yaml │ ├── config/ │ │ └── config.go │ ├── db.sql │ ├── handler/ │ │ ├── handler.go │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── list.go │ │ ├── login.go │ │ ├── update.go │ │ └── user.go │ ├── main.go │ ├── model/ │ │ ├── init.go │ │ ├── model.go │ │ └── user.go │ ├── pkg/ │ │ ├── auth/ │ │ │ └── auth.go │ │ ├── constvar/ │ │ │ └── constvar.go │ │ ├── errno/ │ │ │ ├── code.go │ │ │ └── errno.go │ │ └── token/ │ │ └── token.go │ ├── router/ │ │ ├── middleware/ │ │ │ ├── auth.go │ │ │ ├── header.go │ │ │ ├── logging.go │ │ │ └── requestid.go │ │ └── router.go │ ├── service/ │ │ └── service.go │ └── util/ │ └── util.go ├── demo10/ │ ├── README.md │ ├── conf/ │ │ ├── config.yaml │ │ ├── server.crt │ │ └── server.key │ ├── config/ │ │ └── config.go │ ├── db.sql │ ├── handler/ │ │ ├── handler.go │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── list.go │ │ ├── login.go │ │ ├── update.go │ │ └── user.go │ ├── main.go │ ├── model/ │ │ ├── init.go │ │ ├── model.go │ │ └── user.go │ ├── pkg/ │ │ ├── auth/ │ │ │ └── auth.go │ │ ├── constvar/ │ │ │ └── constvar.go │ │ ├── errno/ │ │ │ ├── code.go │ │ │ └── errno.go │ │ └── token/ │ │ └── token.go │ ├── router/ │ │ ├── middleware/ │ │ │ ├── auth.go │ │ │ ├── header.go │ │ │ ├── logging.go │ │ │ └── requestid.go │ │ └── router.go │ ├── service/ │ │ └── service.go │ └── util/ │ └── util.go ├── demo11/ │ ├── Makefile │ ├── README.md │ ├── conf/ │ │ ├── config.yaml │ │ ├── server.crt │ │ └── server.key │ ├── config/ │ │ └── config.go │ ├── db.sql │ ├── handler/ │ │ ├── handler.go │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── list.go │ │ ├── login.go │ │ ├── update.go │ │ └── user.go │ ├── main.go │ ├── model/ │ │ ├── init.go │ │ ├── model.go │ │ └── user.go │ ├── pkg/ │ │ ├── auth/ │ │ │ └── auth.go │ │ ├── constvar/ │ │ │ └── constvar.go │ │ ├── errno/ │ │ │ ├── code.go │ │ │ └── errno.go │ │ └── token/ │ │ └── token.go │ ├── router/ │ │ ├── middleware/ │ │ │ ├── auth.go │ │ │ ├── header.go │ │ │ ├── logging.go │ │ │ └── requestid.go │ │ └── router.go │ ├── service/ │ │ └── service.go │ └── util/ │ └── util.go ├── demo12/ │ ├── Makefile │ ├── README.md │ ├── conf/ │ │ ├── config.yaml │ │ ├── server.crt │ │ └── server.key │ ├── config/ │ │ └── config.go │ ├── db.sql │ ├── handler/ │ │ ├── handler.go │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── list.go │ │ ├── login.go │ │ ├── update.go │ │ └── user.go │ ├── main.go │ ├── model/ │ │ ├── init.go │ │ ├── model.go │ │ └── user.go │ ├── pkg/ │ │ ├── auth/ │ │ │ └── auth.go │ │ ├── constvar/ │ │ │ └── constvar.go │ │ ├── errno/ │ │ │ ├── code.go │ │ │ └── errno.go │ │ ├── token/ │ │ │ └── token.go │ │ └── version/ │ │ ├── base.go │ │ ├── doc.go │ │ └── version.go │ ├── router/ │ │ ├── middleware/ │ │ │ ├── auth.go │ │ │ ├── header.go │ │ │ ├── logging.go │ │ │ └── requestid.go │ │ └── router.go │ ├── service/ │ │ └── service.go │ └── util/ │ └── util.go ├── demo13/ │ ├── Makefile │ ├── README.md │ ├── admin.sh │ ├── conf/ │ │ ├── config.yaml │ │ ├── server.crt │ │ └── server.key │ ├── config/ │ │ └── config.go │ ├── db.sql │ ├── handler/ │ │ ├── handler.go │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── list.go │ │ ├── login.go │ │ ├── update.go │ │ └── user.go │ ├── main.go │ ├── model/ │ │ ├── init.go │ │ ├── model.go │ │ └── user.go │ ├── pkg/ │ │ ├── auth/ │ │ │ └── auth.go │ │ ├── constvar/ │ │ │ └── constvar.go │ │ ├── errno/ │ │ │ ├── code.go │ │ │ └── errno.go │ │ ├── token/ │ │ │ └── token.go │ │ └── version/ │ │ ├── base.go │ │ ├── doc.go │ │ └── version.go │ ├── router/ │ │ ├── middleware/ │ │ │ ├── auth.go │ │ │ ├── header.go │ │ │ ├── logging.go │ │ │ └── requestid.go │ │ └── router.go │ ├── service/ │ │ └── service.go │ └── util/ │ └── util.go ├── demo14/ │ ├── Makefile │ ├── README.md │ ├── admin.sh │ ├── conf/ │ │ ├── config.yaml │ │ ├── server.crt │ │ └── server.key │ ├── config/ │ │ └── config.go │ ├── db.sql │ ├── handler/ │ │ ├── handler.go │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── list.go │ │ ├── login.go │ │ ├── update.go │ │ └── user.go │ ├── main.go │ ├── model/ │ │ ├── init.go │ │ ├── model.go │ │ └── user.go │ ├── pkg/ │ │ ├── auth/ │ │ │ └── auth.go │ │ ├── constvar/ │ │ │ └── constvar.go │ │ ├── errno/ │ │ │ ├── code.go │ │ │ └── errno.go │ │ ├── token/ │ │ │ └── token.go │ │ └── version/ │ │ ├── base.go │ │ ├── doc.go │ │ └── version.go │ ├── router/ │ │ ├── middleware/ │ │ │ ├── auth.go │ │ │ ├── header.go │ │ │ ├── logging.go │ │ │ └── requestid.go │ │ └── router.go │ ├── service/ │ │ └── service.go │ └── util/ │ └── util.go ├── demo15/ │ ├── Makefile │ ├── README.md │ ├── admin.sh │ ├── conf/ │ │ ├── config.yaml │ │ ├── server.crt │ │ └── server.key │ ├── config/ │ │ └── config.go │ ├── db.sql │ ├── handler/ │ │ ├── handler.go │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── list.go │ │ ├── login.go │ │ ├── update.go │ │ └── user.go │ ├── main.go │ ├── model/ │ │ ├── init.go │ │ ├── model.go │ │ └── user.go │ ├── pkg/ │ │ ├── auth/ │ │ │ └── auth.go │ │ ├── constvar/ │ │ │ └── constvar.go │ │ ├── errno/ │ │ │ ├── code.go │ │ │ └── errno.go │ │ ├── token/ │ │ │ └── token.go │ │ └── version/ │ │ ├── base.go │ │ ├── doc.go │ │ └── version.go │ ├── router/ │ │ ├── middleware/ │ │ │ ├── auth.go │ │ │ ├── header.go │ │ │ ├── logging.go │ │ │ └── requestid.go │ │ └── router.go │ ├── service/ │ │ └── service.go │ └── util/ │ ├── util.go │ └── util_test.go ├── demo16/ │ ├── Makefile │ ├── README.md │ ├── admin.sh │ ├── conf/ │ │ ├── config.yaml │ │ ├── server.crt │ │ └── server.key │ ├── config/ │ │ └── config.go │ ├── db.sql │ ├── handler/ │ │ ├── handler.go │ │ ├── sd/ │ │ │ └── check.go │ │ └── user/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── get.go │ │ ├── list.go │ │ ├── login.go │ │ ├── update.go │ │ └── user.go │ ├── main.go │ ├── model/ │ │ ├── init.go │ │ ├── model.go │ │ └── user.go │ ├── pkg/ │ │ ├── auth/ │ │ │ └── auth.go │ │ ├── constvar/ │ │ │ └── constvar.go │ │ ├── errno/ │ │ │ ├── code.go │ │ │ └── errno.go │ │ ├── token/ │ │ │ └── token.go │ │ └── version/ │ │ ├── base.go │ │ ├── doc.go │ │ └── version.go │ ├── router/ │ │ ├── middleware/ │ │ │ ├── auth.go │ │ │ ├── header.go │ │ │ ├── logging.go │ │ │ └── requestid.go │ │ └── router.go │ ├── service/ │ │ └── service.go │ └── util/ │ ├── util.go │ └── util_test.go └── demo17/ ├── Makefile ├── README.md ├── admin.sh ├── conf/ │ ├── config.yaml │ ├── server.crt │ └── server.key ├── config/ │ └── config.go ├── db.sql ├── docs/ │ ├── docs.go │ └── swagger/ │ ├── swagger.json │ └── swagger.yaml ├── handler/ │ ├── handler.go │ ├── sd/ │ │ └── check.go │ └── user/ │ ├── create.go │ ├── delete.go │ ├── get.go │ ├── list.go │ ├── login.go │ ├── update.go │ └── user.go ├── main.go ├── model/ │ ├── init.go │ ├── model.go │ └── user.go ├── pkg/ │ ├── auth/ │ │ └── auth.go │ ├── constvar/ │ │ └── constvar.go │ ├── errno/ │ │ ├── code.go │ │ └── errno.go │ ├── token/ │ │ └── token.go │ └── version/ │ ├── base.go │ ├── doc.go │ └── version.go ├── router/ │ ├── middleware/ │ │ ├── auth.go │ │ ├── header.go │ │ ├── logging.go │ │ └── requestid.go │ └── router.go ├── service/ │ └── service.go └── util/ ├── util.go └── util_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: Makefile ================================================ clean: find . -name "[._]*.s[a-w][a-z]" | xargs -i rm -f {} .PHONY: clean ================================================ FILE: README.md ================================================ ## 目录 **注意:** 此项目不再维护,如果想学习本项目可以移步:https://github.com/marmotedu/goserver **另外**:此课程已经升级为极客时间课程,课程介绍[《Go 语言项目开发实战》](https://time.geekbang.org/column/intro/100079601),建议学习该课程,更专业、内容更多。 ## apiserver_demos 项目介绍 本教程是掘金小册:[基于 Go 语言构建企业级的 RESTful API 服务](https://juejin.cn/book/6844733730678898702) 实战类教学项目,旨在让初学者花尽可能短的时间,通过尽可能详细的步骤,历经 17 个 demo,最终一步步构建出一个生产级的 API 服务器。从开发准备到 API 设计,再到 API 实现、测试和部署,每一步都详细介绍了如何去构建。通过本教程的学习,你将学到如下知识点: ![技术雷达](./docs/技术雷达.png) 知识点很多,跟着教程一节一节进行学习,你将完整的学会如何用 Go 做 API 开发。 ## 源码目录介绍 | 目录 | 介绍 | | --- | --- | | demo01 |实战:启动一个最简单的 RESTful API 服务器 | | demo02 |实战:配置文件读取 | | demo03 |实战:记录和管理 API 日志 | | demo04 |实战:初始化 MySQL 数据库并建立连接 | | demo05 |实战:自定义业务错误信息 | | demo06 |实战:读取和返回 HTTP 请求 | | demo07 |实战:用户业务逻辑处理(业务处理) | | demo08 |实战:HTTP 调用添加自定义处理逻辑 | | demo09 |实战:API 身份验证 | | demo10 |进阶:用 HTTPS 加密 API 请求 | | demo11 |进阶:用 Makefile 管理 API 项目 | | demo12 |进阶:给 API 命令增加版本功能 | | demo13 |进阶:给 API 增加启动脚本 | | demo14 |进阶:基于 Nginx 的 API 部署方案 | | demo15 |进阶:go test 测试你的代码 | | demo16 |进阶:API 性能分析 | | demo17 |进阶:生成 Swagger 在线文档 | ## 项目适宜人群 - 掌握一定 Go 语法基础,零 Go 服务器研发经验,想通过一个完整的实战,来系统学习 Go 服务器开发的同学; - 有意从事 Go 服务器开发,但尚未入门或入门尚浅的同学; - 有过 Go 服务器开发经验,但想了解某一部分构建方法的同学。 ## 你应该具备什么 - 基本的 Go 语言编程知识 - 基本的 Linux/Uinx 命令行知识 ================================================ FILE: a.go ================================================ ================================================ FILE: demo01/README.md ================================================ 实战:启动一个最简单的RESTful API服务器 ================================================ FILE: demo01/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo01/main.go ================================================ package main import ( "errors" "log" "net/http" "time" "apiserver/router" "github.com/gin-gonic/gin" ) func main() { // Create the Gin engine. g := gin.New() middlewares := []gin.HandlerFunc{} // Routes. router.Load( // Cores. g, // Middlwares. middlewares..., ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Print("The router has been deployed successfully.") }() log.Printf("Start to listening the incoming requests on http address: %s", ":8080") log.Printf(http.ListenAndServe(":8080", g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < 2; i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get("http://127.0.0.1:8080" + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Print("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo01/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo01/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo02/README.md ================================================ 实战:配置文件读取 ================================================ FILE: demo02/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 ================================================ FILE: demo02/config/config.go ================================================ package config import ( "log" "strings" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Printf("Config file changed: %s", e.Name) }) } ================================================ FILE: demo02/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo02/main.go ================================================ package main import ( "errors" "log" "net/http" "time" "apiserver/config" "apiserver/router" "github.com/gin-gonic/gin" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") ) func main() { pflag.Parse() // init config if err := config.Init(*cfg); err != nil { panic(err) } // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() middlewares := []gin.HandlerFunc{} // Routes. router.Load( // Cores. g, // Middlwares. middlewares..., ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Print("The router has been deployed successfully.") }() log.Printf("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Printf(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Print("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo02/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo02/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo03/README.md ================================================ 实战:记录和管理API日志 ================================================ FILE: demo03/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 log: writers: file,stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: false rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 ================================================ FILE: demo03/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo03/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo03/main.go ================================================ package main import ( "errors" "net/http" "time" "apiserver/config" "apiserver/router" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") ) func main() { pflag.Parse() // init config if err := config.Init(*cfg); err != nil { panic(err) } // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() middlewares := []gin.HandlerFunc{} // Routes. router.Load( // Cores. g, // Middlwares. middlewares..., ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo03/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo03/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo04/README.md ================================================ 实战:初始化Mysql数据库并建立连接 ================================================ FILE: demo04/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 log: writers: file,stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: false rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 ================================================ FILE: demo04/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo04/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo04/main.go ================================================ package main import ( "errors" "net/http" "time" "apiserver/config" "apiserver/model" "apiserver/router" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") ) func main() { pflag.Parse() // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() middlewares := []gin.HandlerFunc{} // Routes. router.Load( // Cores. g, // Middlwares. middlewares..., ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo04/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo04/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo04/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo05/README.md ================================================ 实战:自定义业务错误信息 ================================================ FILE: demo05/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 log: writers: file,stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: false rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 ================================================ FILE: demo05/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo05/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo05/handler/user/create.go ================================================ package user import ( "fmt" "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" ) // Create creates a new user account. func Create(c *gin.Context) { var r struct { Username string `json:"username"` Password string `json:"password"` } var err error if err := c.Bind(&r); err != nil { c.JSON(http.StatusOK, gin.H{"error": errno.ErrBind}) return } log.Debugf("username is: [%s], password is [%s]", r.Username, r.Password) if r.Username == "" { err = errno.New(errno.ErrUserNotFound, fmt.Errorf("username can not found in db: xx.xx.xx.xx")).Add("This is add message.") log.Errorf(err, "Get an error") } if errno.IsErrUserNotFound(err) { log.Debug("err type is ErrUserNotFound") } if r.Password == "" { err = fmt.Errorf("password is empty") } code, message := errno.DecodeErr(err) c.JSON(http.StatusOK, gin.H{"code": code, "message": message}) } ================================================ FILE: demo05/main.go ================================================ package main import ( "errors" "net/http" "time" "apiserver/config" "apiserver/model" "apiserver/router" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") ) func main() { pflag.Parse() // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() middlewares := []gin.HandlerFunc{} // Routes. router.Load( // Cores. g, // Middlwares. middlewares..., ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo05/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo05/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error."} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} // user errors ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ) ================================================ FILE: demo05/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { //err.Message = fmt.Sprintf("%s %s", err.Message, message) err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { //return err.Message = fmt.Sprintf("%s %s", err.Message, fmt.Sprintf(format, args...)) err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo05/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo05/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) u := g.Group("/v1/user") { u.POST("", user.Create) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo06/README.md ================================================ 实战:读取和返回HTTP请求 ================================================ FILE: demo06/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 log: writers: file,stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: false rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 ================================================ FILE: demo06/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo06/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo06/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo06/handler/user/create.go ================================================ package user import ( "fmt" . "apiserver/handler" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" ) // Create creates a new user account. func Create(c *gin.Context) { var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } admin2 := c.Param("username") log.Infof("URL username: %s", admin2) desc := c.Query("desc") log.Infof("URL key param desc: %s", desc) contentType := c.GetHeader("Content-Type") log.Infof("Header Content-Type: %s", contentType) log.Debugf("username is: [%s], password is [%s]", r.Username, r.Password) if r.Username == "" { SendResponse(c, errno.New(errno.ErrUserNotFound, fmt.Errorf("username can not found in db: xx.xx.xx.xx")), nil) return } if r.Password == "" { SendResponse(c, fmt.Errorf("password is empty"), nil) } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo06/handler/user/user.go ================================================ package user type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } ================================================ FILE: demo06/main.go ================================================ package main import ( "errors" "net/http" "time" "apiserver/config" "apiserver/model" "apiserver/router" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") ) func main() { pflag.Parse() // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() middlewares := []gin.HandlerFunc{} // Routes. router.Load( // Cores. g, // Middlwares. middlewares..., ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo06/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo06/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error."} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} // user errors ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ) ================================================ FILE: demo06/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { //err.Message = fmt.Sprintf("%s %s", err.Message, message) err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { //return err.Message = fmt.Sprintf("%s %s", err.Message, fmt.Sprintf(format, args...)) err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo06/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo06/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) u := g.Group("/v1/user") { u.POST("/:username", user.Create) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo07/README.md ================================================ 实战:用户业务逻辑处理(业务处理) ================================================ FILE: demo07/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 log: writers: file,stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: false rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root docker_db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root ================================================ FILE: demo07/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo07/db.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db_apiserver`; -- -- Table structure for table `tb_users` -- DROP TABLE IF EXISTS `tb_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `createdAt` timestamp NULL DEFAULT NULL, `updatedAt` timestamp NULL DEFAULT NULL, `deletedAt` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), KEY `idx_tb_users_deletedAt` (`deletedAt`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_users` -- LOCK TABLES `tb_users` WRITE; /*!40000 ALTER TABLE `tb_users` DISABLE KEYS */; INSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL); /*!40000 ALTER TABLE `tb_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-05-28 0:25:41 ================================================ FILE: demo07/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo07/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo07/handler/user/create.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Create creates a new user account. func Create(c *gin.Context) { log.Info("User Create function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } u := model.UserModel{ Username: r.Username, Password: r.Password, } // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Insert the user to the database. if err := u.Create(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo07/handler/user/delete.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Delete delete an user by the user identifier. func Delete(c *gin.Context) { userId, _ := strconv.Atoi(c.Param("id")) if err := model.DeleteUser(uint64(userId)); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo07/handler/user/get.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Get gets an user by the user identifier. func Get(c *gin.Context) { username := c.Param("username") // Get the user by the `username` from the database. user, err := model.GetUser(username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } SendResponse(c, nil, user) } ================================================ FILE: demo07/handler/user/list.go ================================================ package user import ( . "apiserver/handler" "apiserver/pkg/errno" "apiserver/service" "github.com/gin-gonic/gin" ) // List list the users in the database. func List(c *gin.Context) { var r ListRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } infos, count, err := service.ListUser(r.Username, r.Offset, r.Limit) if err != nil { SendResponse(c, err, nil) return } SendResponse(c, nil, ListResponse{ TotalCount: count, UserList: infos, }) } ================================================ FILE: demo07/handler/user/update.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Update update a exist user account info. func Update(c *gin.Context) { log.Info("Update function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) // Get the user id from the url parameter. userId, _ := strconv.Atoi(c.Param("id")) // Binding the user data. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // We update the record based on the user id. u.Id = uint64(userId) // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Save changed fields. if err := u.Update(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo07/handler/user/user.go ================================================ package user import ( "apiserver/model" ) type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } type ListRequest struct { Username string `json:"username"` Offset int `json:"offset"` Limit int `json:"limit"` } type ListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []*model.UserInfo `json:"userList"` } ================================================ FILE: demo07/main.go ================================================ package main import ( "errors" "net/http" "time" "apiserver/config" "apiserver/model" "apiserver/router" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") ) func main() { pflag.Parse() // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() middlewares := []gin.HandlerFunc{} // Routes. router.Load( // Cores. g, // Middlwares. middlewares..., ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo07/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo07/model/model.go ================================================ package model import ( "sync" "time" ) type BaseModel struct { Id uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"` CreatedAt time.Time `gorm:"column:createdAt" json:"-"` UpdatedAt time.Time `gorm:"column:updatedAt" json:"-"` DeletedAt *time.Time `gorm:"column:deletedAt" sql:"index" json:"-"` } type UserInfo struct { Id uint64 `json:"id"` Username string `json:"username"` SayHello string `json:"sayHello"` Password string `json:"password"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type UserList struct { Lock *sync.Mutex IdMap map[uint64]*UserInfo } // Token represents a JSON web token. type Token struct { Token string `json:"token"` } ================================================ FILE: demo07/model/user.go ================================================ package model import ( "fmt" "apiserver/pkg/auth" "apiserver/pkg/constvar" validator "gopkg.in/go-playground/validator.v9" ) // User represents a registered user. type UserModel struct { BaseModel Username string `json:"username" gorm:"column:username;not null" binding:"required" validate:"min=1,max=32"` Password string `json:"password" gorm:"column:password;not null" binding:"required" validate:"min=5,max=128"` } func (c *UserModel) TableName() string { return "tb_users" } // Create creates a new user account. func (u *UserModel) Create() error { return DB.Self.Create(&u).Error } // DeleteUser deletes the user by the user identifier. func DeleteUser(id uint64) error { user := UserModel{} user.BaseModel.Id = id return DB.Self.Delete(&user).Error } // Update updates an user account information. func (u *UserModel) Update() error { return DB.Self.Save(u).Error } // GetUser gets an user by the user identifier. func GetUser(username string) (*UserModel, error) { u := &UserModel{} d := DB.Self.Where("username = ?", username).First(&u) return u, d.Error } // ListUser List all users func ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) { if limit == 0 { limit = constvar.DefaultLimit } users := make([]*UserModel, 0) var count uint64 where := fmt.Sprintf("username like '%%%s%%'", username) if err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil { return users, count, err } if err := DB.Self.Where(where).Offset(offset).Limit(limit).Order("id desc").Find(&users).Error; err != nil { return users, count, err } return users, count, nil } // Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct). func (u *UserModel) Compare(pwd string) (err error) { err = auth.Compare(u.Password, pwd) return } // Encrypt the user password. func (u *UserModel) Encrypt() (err error) { u.Password, err = auth.Encrypt(u.Password) return } // Validate the fields. func (u *UserModel) Validate() error { validate := validator.New() return validate.Struct(u) } ================================================ FILE: demo07/pkg/auth/auth.go ================================================ package auth import "golang.org/x/crypto/bcrypt" // Encrypt encrypts the plain text with bcrypt. func Encrypt(source string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) return string(hashedBytes), err } // Compare compares the encrypted text with the plain text if it's the same. func Compare(hashedPassword, password string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } ================================================ FILE: demo07/pkg/constvar/constvar.go ================================================ package constvar const ( DefaultLimit = 50 ) ================================================ FILE: demo07/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error"} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} ErrDatabase = &Errno{Code: 20002, Message: "Database error."} ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} // user errors ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} ErrPasswordIncorrect = &Errno{Code: 20104, Message: "The password was incorrect."} ) ================================================ FILE: demo07/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo07/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo07/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) u := g.Group("/v1/user") { u.POST("", user.Create) u.DELETE("/:id", user.Delete) u.PUT("/:id", user.Update) u.GET("", user.List) u.GET("/:username", user.Get) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo07/service/service.go ================================================ package service import ( "fmt" "sync" "apiserver/model" "apiserver/util" ) func ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) { infos := make([]*model.UserInfo, 0) users, count, err := model.ListUser(username, offset, limit) if err != nil { return nil, count, err } ids := []uint64{} for _, user := range users { ids = append(ids, user.Id) } wg := sync.WaitGroup{} userList := model.UserList{ Lock: new(sync.Mutex), IdMap: make(map[uint64]*model.UserInfo, len(users)), } errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) go func(u *model.UserModel) { defer wg.Done() shortId, err := util.GenShortId() if err != nil { errChan <- err return } userList.Lock.Lock() defer userList.Lock.Unlock() userList.IdMap[u.Id] = &model.UserInfo{ Id: u.Id, Username: u.Username, SayHello: fmt.Sprintf("Hello %s", shortId), Password: u.Password, CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: u.UpdatedAt.Format("2006-01-02 15:04:05"), } }(u) } go func() { wg.Wait() close(finished) }() select { case <-finished: case err := <-errChan: return nil, count, err } for _, id := range ids { infos = append(infos, userList.IdMap[id]) } return infos, count, nil } ================================================ FILE: demo07/util/util.go ================================================ package util import ( "github.com/gin-gonic/gin" "github.com/teris-io/shortid" ) func GenShortId() (string, error) { return shortid.Generate() } func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") if !ok { return "" } if requestId, ok := v.(string); ok { return requestId } return "" } ================================================ FILE: demo08/README.md ================================================ 实战:HTTP调用添加自定义处理逻辑 ================================================ FILE: demo08/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 log: writers: file,stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: false rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root docker_db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root ================================================ FILE: demo08/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo08/db.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db_apiserver`; -- -- Table structure for table `tb_users` -- DROP TABLE IF EXISTS `tb_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `createdAt` timestamp NULL DEFAULT NULL, `updatedAt` timestamp NULL DEFAULT NULL, `deletedAt` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), KEY `idx_tb_users_deletedAt` (`deletedAt`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_users` -- LOCK TABLES `tb_users` WRITE; /*!40000 ALTER TABLE `tb_users` DISABLE KEYS */; INSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL); /*!40000 ALTER TABLE `tb_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-05-28 0:25:41 ================================================ FILE: demo08/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo08/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo08/handler/user/create.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Create creates a new user account. func Create(c *gin.Context) { log.Info("User Create function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } u := model.UserModel{ Username: r.Username, Password: r.Password, } // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Insert the user to the database. if err := u.Create(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo08/handler/user/delete.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Delete delete an user by the user identifier. func Delete(c *gin.Context) { userId, _ := strconv.Atoi(c.Param("id")) if err := model.DeleteUser(uint64(userId)); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo08/handler/user/get.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Get gets an user by the user identifier. func Get(c *gin.Context) { username := c.Param("username") // Get the user by the `username` from the database. user, err := model.GetUser(username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } SendResponse(c, nil, user) } ================================================ FILE: demo08/handler/user/list.go ================================================ package user import ( . "apiserver/handler" "apiserver/pkg/errno" "apiserver/service" "github.com/gin-gonic/gin" ) // List list the users in the database. func List(c *gin.Context) { var r ListRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } infos, count, err := service.ListUser(r.Username, r.Offset, r.Limit) if err != nil { SendResponse(c, err, nil) return } SendResponse(c, nil, ListResponse{ TotalCount: count, UserList: infos, }) } ================================================ FILE: demo08/handler/user/update.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Update update a exist user account info. func Update(c *gin.Context) { log.Info("Update function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) // Get the user id from the url parameter. userId, _ := strconv.Atoi(c.Param("id")) // Binding the user data. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // We update the record based on the user id. u.Id = uint64(userId) // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Save changed fields. if err := u.Update(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo08/handler/user/user.go ================================================ package user import ( "apiserver/model" ) type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } type ListRequest struct { Username string `json:"username"` Offset int `json:"offset"` Limit int `json:"limit"` } type ListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []*model.UserInfo `json:"userList"` } ================================================ FILE: demo08/main.go ================================================ package main import ( "errors" "net/http" "time" "apiserver/config" "apiserver/model" "apiserver/router" "apiserver/router/middleware" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") ) func main() { pflag.Parse() // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() // Routes. router.Load( // Cores. g, // Middlwares. middleware.Logging(), middleware.RequestId(), ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo08/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo08/model/model.go ================================================ package model import ( "sync" "time" ) type BaseModel struct { Id uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"` CreatedAt time.Time `gorm:"column:createdAt" json:"-"` UpdatedAt time.Time `gorm:"column:updatedAt" json:"-"` DeletedAt *time.Time `gorm:"column:deletedAt" sql:"index" json:"-"` } type UserInfo struct { Id uint64 `json:"id"` Username string `json:"username"` SayHello string `json:"sayHello"` Password string `json:"password"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type UserList struct { Lock *sync.Mutex IdMap map[uint64]*UserInfo } // Token represents a JSON web token. type Token struct { Token string `json:"token"` } ================================================ FILE: demo08/model/user.go ================================================ package model import ( "fmt" "apiserver/pkg/auth" "apiserver/pkg/constvar" validator "gopkg.in/go-playground/validator.v9" ) // User represents a registered user. type UserModel struct { BaseModel Username string `json:"username" gorm:"column:username;not null" binding:"required" validate:"min=1,max=32"` Password string `json:"password" gorm:"column:password;not null" binding:"required" validate:"min=5,max=128"` } func (c *UserModel) TableName() string { return "tb_users" } // Create creates a new user account. func (u *UserModel) Create() error { return DB.Self.Create(&u).Error } // DeleteUser deletes the user by the user identifier. func DeleteUser(id uint64) error { user := UserModel{} user.BaseModel.Id = id return DB.Self.Delete(&user).Error } // Update updates an user account information. func (u *UserModel) Update() error { return DB.Self.Save(u).Error } // GetUser gets an user by the user identifier. func GetUser(username string) (*UserModel, error) { u := &UserModel{} d := DB.Self.Where("username = ?", username).First(&u) return u, d.Error } // ListUser List all users func ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) { if limit == 0 { limit = constvar.DefaultLimit } users := make([]*UserModel, 0) var count uint64 where := fmt.Sprintf("username like '%%%s%%'", username) if err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil { return users, count, err } if err := DB.Self.Where(where).Offset(offset).Limit(limit).Order("id desc").Find(&users).Error; err != nil { return users, count, err } return users, count, nil } // Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct). func (u *UserModel) Compare(pwd string) (err error) { err = auth.Compare(u.Password, pwd) return } // Encrypt the user password. func (u *UserModel) Encrypt() (err error) { u.Password, err = auth.Encrypt(u.Password) return } // Validate the fields. func (u *UserModel) Validate() error { validate := validator.New() return validate.Struct(u) } ================================================ FILE: demo08/pkg/auth/auth.go ================================================ package auth import "golang.org/x/crypto/bcrypt" // Encrypt encrypts the plain text with bcrypt. func Encrypt(source string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) return string(hashedBytes), err } // Compare compares the encrypted text with the plain text if it's the same. func Compare(hashedPassword, password string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } ================================================ FILE: demo08/pkg/constvar/constvar.go ================================================ package constvar const ( DefaultLimit = 50 ) ================================================ FILE: demo08/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error"} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} ErrDatabase = &Errno{Code: 20002, Message: "Database error."} ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} // user errors ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} ErrPasswordIncorrect = &Errno{Code: 20104, Message: "The password was incorrect."} ) ================================================ FILE: demo08/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo08/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo08/router/middleware/logging.go ================================================ package middleware import ( "bytes" "encoding/json" "io/ioutil" "regexp" "time" "apiserver/handler" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/willf/pad" ) type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) } // Logging is a middleware function that logs the each request. func Logging() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now().UTC() path := c.Request.URL.Path reg := regexp.MustCompile("(/v1/user|/login)") if !reg.MatchString(path) { return } // Skip for the health check requests. if path == "/sd/health" || path == "/sd/ram" || path == "/sd/cpu" || path == "/sd/disk" { return } // Read the Body content var bodyBytes []byte if c.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(c.Request.Body) } // Restore the io.ReadCloser to its original state c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // The basic informations. method := c.Request.Method ip := c.ClientIP() //log.Debugf("New request come in, path: %s, Method: %s, body `%s`", path, method, string(bodyBytes)) blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: c.Writer, } c.Writer = blw // Continue. c.Next() // Calculates the latency. end := time.Now().UTC() latency := end.Sub(start) code, message := -1, "" // get code and message var response handler.Response if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { log.Errorf(err, "response body can not unmarshal to model.Response struct, body: `%s`", blw.body.Bytes()) code = errno.InternalServerError.Code message = err.Error() } else { code = response.Code message = response.Message } log.Infof("%-13s | %-12s | %s %s | {code: %d, message: %s}", latency, ip, pad.Right(method, 5, ""), path, code, message) } } ================================================ FILE: demo08/router/middleware/requestid.go ================================================ package middleware import ( "github.com/gin-gonic/gin" "github.com/satori/go.uuid" ) func RequestId() gin.HandlerFunc { return func(c *gin.Context) { // Check for incoming header, use it if exists requestId := c.Request.Header.Get("X-Request-Id") // Create request id with UUID4 if requestId == "" { u4, _ := uuid.NewV4() requestId = u4.String() } // Expose it for use in the application c.Set("X-Request-Id", requestId) // Set X-Request-Id header c.Writer.Header().Set("X-Request-Id", requestId) c.Next() } } ================================================ FILE: demo08/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) u := g.Group("/v1/user") { u.POST("", user.Create) u.DELETE("/:id", user.Delete) u.PUT("/:id", user.Update) u.GET("", user.List) u.GET("/:username", user.Get) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo08/service/service.go ================================================ package service import ( "fmt" "sync" "apiserver/model" "apiserver/util" ) func ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) { infos := make([]*model.UserInfo, 0) users, count, err := model.ListUser(username, offset, limit) if err != nil { return nil, count, err } ids := []uint64{} for _, user := range users { ids = append(ids, user.Id) } wg := sync.WaitGroup{} userList := model.UserList{ Lock: new(sync.Mutex), IdMap: make(map[uint64]*model.UserInfo, len(users)), } errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) go func(u *model.UserModel) { defer wg.Done() shortId, err := util.GenShortId() if err != nil { errChan <- err return } userList.Lock.Lock() defer userList.Lock.Unlock() userList.IdMap[u.Id] = &model.UserInfo{ Id: u.Id, Username: u.Username, SayHello: fmt.Sprintf("Hello %s", shortId), Password: u.Password, CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: u.UpdatedAt.Format("2006-01-02 15:04:05"), } }(u) } go func() { wg.Wait() close(finished) }() select { case <-finished: case err := <-errChan: return nil, count, err } for _, id := range ids { infos = append(infos, userList.IdMap[id]) } return infos, count, nil } ================================================ FILE: demo08/util/util.go ================================================ package util import ( "github.com/gin-gonic/gin" "github.com/teris-io/shortid" ) func GenShortId() (string, error) { return shortid.Generate() } func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") if !ok { return "" } if requestId, ok := v.(string); ok { return requestId } return "" } ================================================ FILE: demo09/README.md ================================================ 实战:API身份验证 ================================================ FILE: demo09/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 jwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5 log: writers: file,stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: false rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root docker_db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root ================================================ FILE: demo09/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo09/db.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db_apiserver`; -- -- Table structure for table `tb_users` -- DROP TABLE IF EXISTS `tb_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `createdAt` timestamp NULL DEFAULT NULL, `updatedAt` timestamp NULL DEFAULT NULL, `deletedAt` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), KEY `idx_tb_users_deletedAt` (`deletedAt`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_users` -- LOCK TABLES `tb_users` WRITE; /*!40000 ALTER TABLE `tb_users` DISABLE KEYS */; INSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL); /*!40000 ALTER TABLE `tb_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-05-28 0:25:41 ================================================ FILE: demo09/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo09/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo09/handler/user/create.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Create creates a new user account. func Create(c *gin.Context) { log.Info("User Create function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } u := model.UserModel{ Username: r.Username, Password: r.Password, } // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Insert the user to the database. if err := u.Create(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo09/handler/user/delete.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Delete delete an user by the user identifier. func Delete(c *gin.Context) { userId, _ := strconv.Atoi(c.Param("id")) if err := model.DeleteUser(uint64(userId)); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo09/handler/user/get.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Get gets an user by the user identifier. func Get(c *gin.Context) { username := c.Param("username") // Get the user by the `username` from the database. user, err := model.GetUser(username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } SendResponse(c, nil, user) } ================================================ FILE: demo09/handler/user/list.go ================================================ package user import ( . "apiserver/handler" "apiserver/pkg/errno" "apiserver/service" "github.com/gin-gonic/gin" ) // List list the users in the database. func List(c *gin.Context) { var r ListRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } infos, count, err := service.ListUser(r.Username, r.Offset, r.Limit) if err != nil { SendResponse(c, err, nil) return } SendResponse(c, nil, ListResponse{ TotalCount: count, UserList: infos, }) } ================================================ FILE: demo09/handler/user/login.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/auth" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) // Login generates the authentication token // if the password was matched with the specified account. func Login(c *gin.Context) { // Binding the data with the user struct. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // Get the user information by the login username. d, err := model.GetUser(u.Username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } // Compare the login password with the user password. if err := auth.Compare(d.Password, u.Password); err != nil { SendResponse(c, errno.ErrPasswordIncorrect, nil) return } // Sign the json web token. t, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, "") if err != nil { SendResponse(c, errno.ErrToken, nil) return } SendResponse(c, nil, model.Token{Token: t}) } ================================================ FILE: demo09/handler/user/update.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Update update a exist user account info. func Update(c *gin.Context) { log.Info("Update function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) // Get the user id from the url parameter. userId, _ := strconv.Atoi(c.Param("id")) // Binding the user data. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // We update the record based on the user id. u.Id = uint64(userId) // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Save changed fields. if err := u.Update(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo09/handler/user/user.go ================================================ package user import ( "apiserver/model" ) type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } type ListRequest struct { Username string `json:"username"` Offset int `json:"offset"` Limit int `json:"limit"` } type ListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []*model.UserInfo `json:"userList"` } ================================================ FILE: demo09/main.go ================================================ package main import ( "errors" "net/http" "time" "apiserver/config" "apiserver/model" "apiserver/router" "apiserver/router/middleware" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") ) func main() { pflag.Parse() // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() // Routes. router.Load( // Cores. g, // Middlwares. middleware.Logging(), middleware.RequestId(), ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo09/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo09/model/model.go ================================================ package model import ( "sync" "time" ) type BaseModel struct { Id uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"` CreatedAt time.Time `gorm:"column:createdAt" json:"-"` UpdatedAt time.Time `gorm:"column:updatedAt" json:"-"` DeletedAt *time.Time `gorm:"column:deletedAt" sql:"index" json:"-"` } type UserInfo struct { Id uint64 `json:"id"` Username string `json:"username"` SayHello string `json:"sayHello"` Password string `json:"password"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type UserList struct { Lock *sync.Mutex IdMap map[uint64]*UserInfo } // Token represents a JSON web token. type Token struct { Token string `json:"token"` } ================================================ FILE: demo09/model/user.go ================================================ package model import ( "fmt" "apiserver/pkg/auth" "apiserver/pkg/constvar" validator "gopkg.in/go-playground/validator.v9" ) // User represents a registered user. type UserModel struct { BaseModel Username string `json:"username" gorm:"column:username;not null" binding:"required" validate:"min=1,max=32"` Password string `json:"password" gorm:"column:password;not null" binding:"required" validate:"min=5,max=128"` } func (c *UserModel) TableName() string { return "tb_users" } // Create creates a new user account. func (u *UserModel) Create() error { return DB.Self.Create(&u).Error } // DeleteUser deletes the user by the user identifier. func DeleteUser(id uint64) error { user := UserModel{} user.BaseModel.Id = id return DB.Self.Delete(&user).Error } // Update updates an user account information. func (u *UserModel) Update() error { return DB.Self.Save(u).Error } // GetUser gets an user by the user identifier. func GetUser(username string) (*UserModel, error) { u := &UserModel{} d := DB.Self.Where("username = ?", username).First(&u) return u, d.Error } // ListUser List all users func ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) { if limit == 0 { limit = constvar.DefaultLimit } users := make([]*UserModel, 0) var count uint64 where := fmt.Sprintf("username like '%%%s%%'", username) if err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil { return users, count, err } if err := DB.Self.Where(where).Offset(offset).Limit(limit).Order("id desc").Find(&users).Error; err != nil { return users, count, err } return users, count, nil } // Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct). func (u *UserModel) Compare(pwd string) (err error) { err = auth.Compare(u.Password, pwd) return } // Encrypt the user password. func (u *UserModel) Encrypt() (err error) { u.Password, err = auth.Encrypt(u.Password) return } // Validate the fields. func (u *UserModel) Validate() error { validate := validator.New() return validate.Struct(u) } ================================================ FILE: demo09/pkg/auth/auth.go ================================================ package auth import "golang.org/x/crypto/bcrypt" // Encrypt encrypts the plain text with bcrypt. func Encrypt(source string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) return string(hashedBytes), err } // Compare compares the encrypted text with the plain text if it's the same. func Compare(hashedPassword, password string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } ================================================ FILE: demo09/pkg/constvar/constvar.go ================================================ package constvar const ( DefaultLimit = 50 ) ================================================ FILE: demo09/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error"} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} ErrDatabase = &Errno{Code: 20002, Message: "Database error."} ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} // user errors ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} ErrPasswordIncorrect = &Errno{Code: 20104, Message: "The password was incorrect."} ) ================================================ FILE: demo09/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo09/pkg/token/token.go ================================================ package token import ( "errors" "fmt" "time" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) var ( // ErrMissingHeader means the `Authorization` header was empty. ErrMissingHeader = errors.New("The length of the `Authorization` header is zero.") ) // Context is the context of the JSON web token. type Context struct { ID uint64 Username string } // secretFunc validates the secret format. func secretFunc(secret string) jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { // Make sure the `alg` is what we except. if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(secret), nil } } // Parse validates the token with the specified secret, // and returns the context if the token was valid. func Parse(tokenString string, secret string) (*Context, error) { ctx := &Context{} // Parse the token. token, err := jwt.Parse(tokenString, secretFunc(secret)) // Parse error. if err != nil { return ctx, err // Read the token if it's valid. } else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { ctx.ID = uint64(claims["id"].(float64)) ctx.Username = claims["username"].(string) return ctx, nil // Other errors. } else { return ctx, err } } // ParseRequest gets the token from the header and // pass it to the Parse function to parses the token. func ParseRequest(c *gin.Context) (*Context, error) { header := c.Request.Header.Get("Authorization") // Load the jwt secret from config secret := viper.GetString("jwt_secret") if len(header) == 0 { return &Context{}, ErrMissingHeader } var t string // Parse the header to get the token part. fmt.Sscanf(header, "Bearer %s", &t) return Parse(t, secret) } // Sign signs the context with the specified secret. func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) { // Load the jwt secret from the Gin config if the secret isn't specified. if secret == "" { secret = viper.GetString("jwt_secret") } // The token content. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "id": c.ID, "username": c.Username, "nbf": time.Now().Unix(), "iat": time.Now().Unix(), }) // Sign the token with the specified secret. tokenString, err = token.SignedString([]byte(secret)) return } ================================================ FILE: demo09/router/middleware/auth.go ================================================ package middleware import ( "apiserver/handler" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Parse the json web token. if _, err := token.ParseRequest(c); err != nil { handler.SendResponse(c, errno.ErrTokenInvalid, nil) c.Abort() return } c.Next() } } ================================================ FILE: demo09/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo09/router/middleware/logging.go ================================================ package middleware import ( "bytes" "encoding/json" "io/ioutil" "regexp" "time" "apiserver/handler" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/willf/pad" ) type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) } // Logging is a middleware function that logs the each request. func Logging() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now().UTC() path := c.Request.URL.Path reg := regexp.MustCompile("(/v1/user|/login)") if !reg.MatchString(path) { return } // Skip for the health check requests. if path == "/sd/health" || path == "/sd/ram" || path == "/sd/cpu" || path == "/sd/disk" { return } // Read the Body content var bodyBytes []byte if c.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(c.Request.Body) } // Restore the io.ReadCloser to its original state c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // The basic informations. method := c.Request.Method ip := c.ClientIP() //log.Debugf("New request come in, path: %s, Method: %s, body `%s`", path, method, string(bodyBytes)) blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: c.Writer, } c.Writer = blw // Continue. c.Next() // Calculates the latency. end := time.Now().UTC() latency := end.Sub(start) code, message := -1, "" // get code and message var response handler.Response if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { log.Errorf(err, "response body can not unmarshal to model.Response struct, body: `%s`", blw.body.Bytes()) code = errno.InternalServerError.Code message = err.Error() } else { code = response.Code message = response.Message } log.Infof("%-13s | %-12s | %s %s | {code: %d, message: %s}", latency, ip, pad.Right(method, 5, ""), path, code, message) } } ================================================ FILE: demo09/router/middleware/requestid.go ================================================ package middleware import ( "github.com/gin-gonic/gin" "github.com/satori/go.uuid" ) func RequestId() gin.HandlerFunc { return func(c *gin.Context) { // Check for incoming header, use it if exists requestId := c.Request.Header.Get("X-Request-Id") // Create request id with UUID4 if requestId == "" { u4, _ := uuid.NewV4() requestId = u4.String() } // Expose it for use in the application c.Set("X-Request-Id", requestId) // Set X-Request-Id header c.Writer.Header().Set("X-Request-Id", requestId) c.Next() } } ================================================ FILE: demo09/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // api for authentication functionalities g.POST("/login", user.Login) // The user handlers, requiring authentication u := g.Group("/v1/user") u.Use(middleware.AuthMiddleware()) { u.POST("", user.Create) u.DELETE("/:id", user.Delete) u.PUT("/:id", user.Update) u.GET("", user.List) u.GET("/:username", user.Get) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo09/service/service.go ================================================ package service import ( "fmt" "sync" "apiserver/model" "apiserver/util" ) func ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) { infos := make([]*model.UserInfo, 0) users, count, err := model.ListUser(username, offset, limit) if err != nil { return nil, count, err } ids := []uint64{} for _, user := range users { ids = append(ids, user.Id) } wg := sync.WaitGroup{} userList := model.UserList{ Lock: new(sync.Mutex), IdMap: make(map[uint64]*model.UserInfo, len(users)), } errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) go func(u *model.UserModel) { defer wg.Done() shortId, err := util.GenShortId() if err != nil { errChan <- err return } userList.Lock.Lock() defer userList.Lock.Unlock() userList.IdMap[u.Id] = &model.UserInfo{ Id: u.Id, Username: u.Username, SayHello: fmt.Sprintf("Hello %s", shortId), Password: u.Password, CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: u.UpdatedAt.Format("2006-01-02 15:04:05"), } }(u) } go func() { wg.Wait() close(finished) }() select { case <-finished: case err := <-errChan: return nil, count, err } for _, id := range ids { infos = append(infos, userList.IdMap[id]) } return infos, count, nil } ================================================ FILE: demo09/util/util.go ================================================ package util import ( "github.com/gin-gonic/gin" "github.com/teris-io/shortid" ) func GenShortId() (string, error) { return shortid.Generate() } func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") if !ok { return "" } if requestId, ok := v.(string); ok { return requestId } return "" } ================================================ FILE: demo10/README.md ================================================ 进阶:用HTTPS加密API请求 ================================================ FILE: demo10/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 jwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5 tls: addr: :8081 cert: conf/server.crt key: conf/server.key log: writers: stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: true rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root docker_db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root ================================================ FILE: demo10/conf/server.crt ================================================ -----BEGIN CERTIFICATE----- MIID2TCCAsGgAwIBAgIJAMQRszo28YxYMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx GzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNTI5MzdaFw0y ODA2MDEwNTI5MzdaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD VQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU MRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALw6CzJlFxNg79rSbFa/ Qn1IThDOb+MKOOpQjeWCSrKFTrWnU36BouUN7b9VYEn9+GpcFoyaThK5HsJX4wyd 65yTCct3WieuegdpbttA5w8vcHbUNwvfk9WTBbxXbweZrVajsGRhg1Y+8B2UM/DG dzwrkO3woKaTrYlo7kkVFegODUhE7+KkEt/B9hiLf8WWk9fDjh8VVq0mXRQXhuIR OfJ1UFb7CEdAoQa99wS0sc2hYW4QAkElgVbCAK+w2H1sPl48xaIVhsgeS/4BR1Om EsykygsFtU8jwO4J/c/e8seekNFPssUBc/kFHQXp333H/zMhFLQfYcEZW/wPTV8R C2ECAwEAAaNQME4wHQYDVR0OBBYEFCSzcWsMsL3gGOUnlWxSUZZOrud3MB8GA1Ud IwQYMBaAFCSzcWsMsL3gGOUnlWxSUZZOrud3MAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQEFBQADggEBAK8GS3jy4Og6WHB68BPCc2odBjAlug91otn0PNtpfp431fWw bpviOqRtr6aFqJ8SwdVtWynjAXTTeKZNJB0XNf7HnPMC15A+L4MwtUH9SPQ3abeW XLQTGl0Q5sgLPR9jADQ3gEgoO4cgKMNy97YhemkQZ1PpVhH9VGObebObcpPRvtQL 5+TurehVL1hYsrQ5e6q1VUZg/DJuYT096t238vYU5GimLgv8d4GSXA/PbAeTBzd6 1mofLFAyp2eJkjOZgo8OgNVSQ3aRqUluoD/8qSjnBhr9GoANCuBFJMphqPqMxYtZ cBM/rpG8PFiF5gCjeU/6rt+39w9QEST7y583dVk= -----END CERTIFICATE----- ================================================ FILE: demo10/conf/server.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8OgsyZRcTYO/a 0mxWv0J9SE4Qzm/jCjjqUI3lgkqyhU61p1N+gaLlDe2/VWBJ/fhqXBaMmk4SuR7C V+MMneuckwnLd1onrnoHaW7bQOcPL3B21DcL35PVkwW8V28Hma1Wo7BkYYNWPvAd lDPwxnc8K5Dt8KCmk62JaO5JFRXoDg1IRO/ipBLfwfYYi3/FlpPXw44fFVatJl0U F4biETnydVBW+whHQKEGvfcEtLHNoWFuEAJBJYFWwgCvsNh9bD5ePMWiFYbIHkv+ AUdTphLMpMoLBbVPI8DuCf3P3vLHnpDRT7LFAXP5BR0F6d99x/8zIRS0H2HBGVv8 D01fEQthAgMBAAECggEAVjU/a5VhPD7pnA9ED3cJvNeg6ZGjLRlBeA/s7XD/RURJ CGnak9ZMBRycB0XTFBB99ji3Gy6RE4I11EzscJrjjpLJqabAY+xFd5+SZlkTeqD/ oW0QyR9dVjRALELfV1vLSCMwZslCnf21e9ak82Hyulw5xMCw05pPoN+uQ0qk/eKn iOPnWhMLg2o2XX1C4ScYOVF0lY9RVyOIGEl6pW97QOlNM3M2D02iUytgg44Y0DSU q2vo2dcroNGzr8vEb1JzFbiXBeG1EBKWhB9boswhZ78Wer+KWwWYaoJcK6BgHRq+ MPRU36LarxLT47reoq1yeN5fM3NmaYiGKroHbGPwAQKBgQDzipn70+HIOU+vXyoa 6eQJDOfTgXjtEh1jbozus+PIgYo1ciFo9awmRGvDelItF2zOtToCU7AmzM/XNhiU QNQvoV5fYRUfou0wU9m/BtiOi2rzOWgZmPsuc4tO5ptk7lNuqfaJ9b3pH0ie4UgO WA0U2gwvZOG9hUbJU5YaFpwbCQKBgQDF2wuzql8sb/Ml6G2sGqg6RvRyO1N71hS3 oqmLxY/2Nr9RO6KvIGw1tAMoxPAyB/erjs0X5UVF3XuKE4bmZMqHBrsVe3f8SA97 igk9gFGTGI51tiLADnLp8uXKW1/Wd6GwhM1fxPagiASExhNU/TwZgmwGdDMMyeMW UJdW+x+LmQKBgQCVVqljxaKOv64AUO+lv0SI1DQX+y2m2dPRlAmxmfeUjPKuIUUh cnxUnuIh5REc+19KRdDDeoPq1u6f/lkGF9bFOkN/Yy2rz6F4YAKG4/DJP+6eJNaT 07461rlW8YvaUVYx5uD56gnBOOC0JFqmCRJEdgzAxzCxoVctvyas6q5g2QKBgB3p g9dhxom9UxFEFnCShyRoXcR3W6O5NeCdYuySrbUXic0KKwo26KUl1eRwAbBOrA7v w+n864Aof+jcEuT6D/Rh/B6/T+CANHcE42i84ZhPehopsw8+H/lmk38IWXDfHT7G lRYJfQ/AAI7iM0ICFvf0U8iWALHKQ963yGmKBbbhAoGBALTA/DHcxzNummVrvZh2 0jlWOBySt3q9Nq5umsT3Q27cfgubqnxJDr574xlw2rFay07FdinSDb4alQUz7k4E he3IfqcBzUmEsw4fL0uC/KOj1kHbIQVEstZtFBS2e3mxlEm+xPQCNWB45enWmzyb lS3DYiIrrNZ95tjw4FYiwlw2 -----END PRIVATE KEY----- ================================================ FILE: demo10/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo10/db.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db_apiserver`; -- -- Table structure for table `tb_users` -- DROP TABLE IF EXISTS `tb_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `createdAt` timestamp NULL DEFAULT NULL, `updatedAt` timestamp NULL DEFAULT NULL, `deletedAt` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), KEY `idx_tb_users_deletedAt` (`deletedAt`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_users` -- LOCK TABLES `tb_users` WRITE; /*!40000 ALTER TABLE `tb_users` DISABLE KEYS */; INSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL); /*!40000 ALTER TABLE `tb_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-05-28 0:25:41 ================================================ FILE: demo10/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo10/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo10/handler/user/create.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Create creates a new user account. func Create(c *gin.Context) { log.Info("User Create function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } u := model.UserModel{ Username: r.Username, Password: r.Password, } // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Insert the user to the database. if err := u.Create(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo10/handler/user/delete.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Delete delete an user by the user identifier. func Delete(c *gin.Context) { userId, _ := strconv.Atoi(c.Param("id")) if err := model.DeleteUser(uint64(userId)); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo10/handler/user/get.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Get gets an user by the user identifier. func Get(c *gin.Context) { username := c.Param("username") // Get the user by the `username` from the database. user, err := model.GetUser(username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } SendResponse(c, nil, user) } ================================================ FILE: demo10/handler/user/list.go ================================================ package user import ( . "apiserver/handler" "apiserver/pkg/errno" "apiserver/service" "github.com/gin-gonic/gin" ) // List list the users in the database. func List(c *gin.Context) { var r ListRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } infos, count, err := service.ListUser(r.Username, r.Offset, r.Limit) if err != nil { SendResponse(c, err, nil) return } SendResponse(c, nil, ListResponse{ TotalCount: count, UserList: infos, }) } ================================================ FILE: demo10/handler/user/login.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/auth" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) // Login generates the authentication token // if the password was matched with the specified account. func Login(c *gin.Context) { // Binding the data with the user struct. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // Get the user information by the login username. d, err := model.GetUser(u.Username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } // Compare the login password with the user password. if err := auth.Compare(d.Password, u.Password); err != nil { SendResponse(c, errno.ErrPasswordIncorrect, nil) return } // Sign the json web token. t, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, "") if err != nil { SendResponse(c, errno.ErrToken, nil) return } SendResponse(c, nil, model.Token{Token: t}) } ================================================ FILE: demo10/handler/user/update.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Update update a exist user account info. func Update(c *gin.Context) { log.Info("Update function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) // Get the user id from the url parameter. userId, _ := strconv.Atoi(c.Param("id")) // Binding the user data. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // We update the record based on the user id. u.Id = uint64(userId) // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Save changed fields. if err := u.Update(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo10/handler/user/user.go ================================================ package user import ( "apiserver/model" ) type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } type ListRequest struct { Username string `json:"username"` Offset int `json:"offset"` Limit int `json:"limit"` } type ListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []*model.UserInfo `json:"userList"` } ================================================ FILE: demo10/main.go ================================================ package main import ( "errors" "net/http" "time" "apiserver/config" "apiserver/model" "apiserver/router" "apiserver/router/middleware" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") ) func main() { pflag.Parse() // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() // Routes. router.Load( // Cores. g, // Middlwares. middleware.Logging(), middleware.RequestId(), ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() // Start to listening the incoming requests. cert := viper.GetString("tls.cert") key := viper.GetString("tls.key") if cert != "" && key != "" { go func() { log.Infof("Start to listening the incoming requests on https address: %s", viper.GetString("tls.addr")) log.Info(http.ListenAndServeTLS(viper.GetString("tls.addr"), cert, key, g).Error()) }() } log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo10/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo10/model/model.go ================================================ package model import ( "sync" "time" ) type BaseModel struct { Id uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"` CreatedAt time.Time `gorm:"column:createdAt" json:"-"` UpdatedAt time.Time `gorm:"column:updatedAt" json:"-"` DeletedAt *time.Time `gorm:"column:deletedAt" sql:"index" json:"-"` } type UserInfo struct { Id uint64 `json:"id"` Username string `json:"username"` SayHello string `json:"sayHello"` Password string `json:"password"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type UserList struct { Lock *sync.Mutex IdMap map[uint64]*UserInfo } // Token represents a JSON web token. type Token struct { Token string `json:"token"` } ================================================ FILE: demo10/model/user.go ================================================ package model import ( "fmt" "apiserver/pkg/auth" "apiserver/pkg/constvar" validator "gopkg.in/go-playground/validator.v9" ) // User represents a registered user. type UserModel struct { BaseModel Username string `json:"username" gorm:"column:username;not null" binding:"required" validate:"min=1,max=32"` Password string `json:"password" gorm:"column:password;not null" binding:"required" validate:"min=5,max=128"` } func (c *UserModel) TableName() string { return "tb_users" } // Create creates a new user account. func (u *UserModel) Create() error { return DB.Self.Create(&u).Error } // DeleteUser deletes the user by the user identifier. func DeleteUser(id uint64) error { user := UserModel{} user.BaseModel.Id = id return DB.Self.Delete(&user).Error } // Update updates an user account information. func (u *UserModel) Update() error { return DB.Self.Save(u).Error } // GetUser gets an user by the user identifier. func GetUser(username string) (*UserModel, error) { u := &UserModel{} d := DB.Self.Where("username = ?", username).First(&u) return u, d.Error } // ListUser List all users func ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) { if limit == 0 { limit = constvar.DefaultLimit } users := make([]*UserModel, 0) var count uint64 where := fmt.Sprintf("username like '%%%s%%'", username) if err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil { return users, count, err } if err := DB.Self.Where(where).Offset(offset).Limit(limit).Order("id desc").Find(&users).Error; err != nil { return users, count, err } return users, count, nil } // Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct). func (u *UserModel) Compare(pwd string) (err error) { err = auth.Compare(u.Password, pwd) return } // Encrypt the user password. func (u *UserModel) Encrypt() (err error) { u.Password, err = auth.Encrypt(u.Password) return } // Validate the fields. func (u *UserModel) Validate() error { validate := validator.New() return validate.Struct(u) } ================================================ FILE: demo10/pkg/auth/auth.go ================================================ package auth import "golang.org/x/crypto/bcrypt" // Encrypt encrypts the plain text with bcrypt. func Encrypt(source string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) return string(hashedBytes), err } // Compare compares the encrypted text with the plain text if it's the same. func Compare(hashedPassword, password string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } ================================================ FILE: demo10/pkg/constvar/constvar.go ================================================ package constvar const ( DefaultLimit = 50 ) ================================================ FILE: demo10/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error"} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} ErrDatabase = &Errno{Code: 20002, Message: "Database error."} ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} // user errors ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} ErrPasswordIncorrect = &Errno{Code: 20104, Message: "The password was incorrect."} ) ================================================ FILE: demo10/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo10/pkg/token/token.go ================================================ package token import ( "errors" "fmt" "time" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) var ( // ErrMissingHeader means the `Authorization` header was empty. ErrMissingHeader = errors.New("The length of the `Authorization` header is zero.") ) // Context is the context of the JSON web token. type Context struct { ID uint64 Username string } // secretFunc validates the secret format. func secretFunc(secret string) jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { // Make sure the `alg` is what we except. if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(secret), nil } } // Parse validates the token with the specified secret, // and returns the context if the token was valid. func Parse(tokenString string, secret string) (*Context, error) { ctx := &Context{} // Parse the token. token, err := jwt.Parse(tokenString, secretFunc(secret)) // Parse error. if err != nil { return ctx, err // Read the token if it's valid. } else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { ctx.ID = uint64(claims["id"].(float64)) ctx.Username = claims["username"].(string) return ctx, nil // Other errors. } else { return ctx, err } } // ParseRequest gets the token from the header and // pass it to the Parse function to parses the token. func ParseRequest(c *gin.Context) (*Context, error) { header := c.Request.Header.Get("Authorization") // Load the jwt secret from config secret := viper.GetString("jwt_secret") if len(header) == 0 { return &Context{}, ErrMissingHeader } var t string // Parse the header to get the token part. fmt.Sscanf(header, "Bearer %s", &t) return Parse(t, secret) } // Sign signs the context with the specified secret. func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) { // Load the jwt secret from the Gin config if the secret isn't specified. if secret == "" { secret = viper.GetString("jwt_secret") } // The token content. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "id": c.ID, "username": c.Username, "nbf": time.Now().Unix(), "iat": time.Now().Unix(), }) // Sign the token with the specified secret. tokenString, err = token.SignedString([]byte(secret)) return } ================================================ FILE: demo10/router/middleware/auth.go ================================================ package middleware import ( "apiserver/handler" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Parse the json web token. if _, err := token.ParseRequest(c); err != nil { handler.SendResponse(c, errno.ErrTokenInvalid, nil) c.Abort() return } c.Next() } } ================================================ FILE: demo10/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo10/router/middleware/logging.go ================================================ package middleware import ( "bytes" "encoding/json" "io/ioutil" "regexp" "time" "apiserver/handler" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/willf/pad" ) type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) } // Logging is a middleware function that logs the each request. func Logging() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now().UTC() path := c.Request.URL.Path reg := regexp.MustCompile("(/v1/user|/login)") if !reg.MatchString(path) { return } // Skip for the health check requests. if path == "/sd/health" || path == "/sd/ram" || path == "/sd/cpu" || path == "/sd/disk" { return } // Read the Body content var bodyBytes []byte if c.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(c.Request.Body) } // Restore the io.ReadCloser to its original state c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // The basic informations. method := c.Request.Method ip := c.ClientIP() //log.Debugf("New request come in, path: %s, Method: %s, body `%s`", path, method, string(bodyBytes)) blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: c.Writer, } c.Writer = blw // Continue. c.Next() // Calculates the latency. end := time.Now().UTC() latency := end.Sub(start) code, message := -1, "" // get code and message var response handler.Response if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { log.Errorf(err, "response body can not unmarshal to model.Response struct, body: `%s`", blw.body.Bytes()) code = errno.InternalServerError.Code message = err.Error() } else { code = response.Code message = response.Message } log.Infof("%-13s | %-12s | %s %s | {code: %d, message: %s}", latency, ip, pad.Right(method, 5, ""), path, code, message) } } ================================================ FILE: demo10/router/middleware/requestid.go ================================================ package middleware import ( "github.com/gin-gonic/gin" "github.com/satori/go.uuid" ) func RequestId() gin.HandlerFunc { return func(c *gin.Context) { // Check for incoming header, use it if exists requestId := c.Request.Header.Get("X-Request-Id") // Create request id with UUID4 if requestId == "" { u4, _ := uuid.NewV4() requestId = u4.String() } // Expose it for use in the application c.Set("X-Request-Id", requestId) // Set X-Request-Id header c.Writer.Header().Set("X-Request-Id", requestId) c.Next() } } ================================================ FILE: demo10/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // api for authentication functionalities g.POST("/login", user.Login) // The user handlers, requiring authentication u := g.Group("/v1/user") u.Use(middleware.AuthMiddleware()) { u.POST("", user.Create) u.DELETE("/:id", user.Delete) u.PUT("/:id", user.Update) u.GET("", user.List) u.GET("/:username", user.Get) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo10/service/service.go ================================================ package service import ( "fmt" "sync" "apiserver/model" "apiserver/util" ) func ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) { infos := make([]*model.UserInfo, 0) users, count, err := model.ListUser(username, offset, limit) if err != nil { return nil, count, err } ids := []uint64{} for _, user := range users { ids = append(ids, user.Id) } wg := sync.WaitGroup{} userList := model.UserList{ Lock: new(sync.Mutex), IdMap: make(map[uint64]*model.UserInfo, len(users)), } errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) go func(u *model.UserModel) { defer wg.Done() shortId, err := util.GenShortId() if err != nil { errChan <- err return } userList.Lock.Lock() defer userList.Lock.Unlock() userList.IdMap[u.Id] = &model.UserInfo{ Id: u.Id, Username: u.Username, SayHello: fmt.Sprintf("Hello %s", shortId), Password: u.Password, CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: u.UpdatedAt.Format("2006-01-02 15:04:05"), } }(u) } go func() { wg.Wait() close(finished) }() select { case <-finished: case err := <-errChan: return nil, count, err } for _, id := range ids { infos = append(infos, userList.IdMap[id]) } return infos, count, nil } ================================================ FILE: demo10/util/util.go ================================================ package util import ( "github.com/gin-gonic/gin" "github.com/teris-io/shortid" ) func GenShortId() (string, error) { return shortid.Generate() } func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") if !ok { return "" } if requestId, ok := v.(string); ok { return requestId } return "" } ================================================ FILE: demo11/Makefile ================================================ all: gotool @go build -v . clean: rm -f apiserver find . -name "[._]*.s[a-w][a-z]" | xargs -i rm -f {} gotool: gofmt -w . go tool vet . |& grep -v vendor;true ca: openssl req -new -nodes -x509 -out conf/server.crt -keyout conf/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=127.0.0.1/emailAddress=xxxxx@qq.com" help: @echo "make - compile the source code" @echo "make clean - remove binary file and vim swp files" @echo "make gotool - run go tool 'fmt' and 'vet'" @echo "make ca - generate ca files" .PHONY: clean gotool ca help ================================================ FILE: demo11/README.md ================================================ 进阶:用Makefile管理API项目 ================================================ FILE: demo11/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 jwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5 tls: addr: :8081 cert: conf/server.crt key: conf/server.key log: writers: stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: true rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root docker_db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root ================================================ FILE: demo11/conf/server.crt ================================================ -----BEGIN CERTIFICATE----- MIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx GzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y ODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD VQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU MRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG 42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN q/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm yDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9 iaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM /nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk or8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud IwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz ANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel AnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD BgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6 8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B +8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g= -----END CERTIFICATE----- ================================================ FILE: demo11/conf/server.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv K5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB xxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt 8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my QKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls /BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc rmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK g59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA 2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4 NxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ fYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg tOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW Tkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt BZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV xpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe hmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl zRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8 Q3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW AOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd UOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F jd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds K1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg peNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2 4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/ MhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/ 2eBi4LlGO5stoeD63h9Ten0= -----END PRIVATE KEY----- ================================================ FILE: demo11/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo11/db.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db_apiserver`; -- -- Table structure for table `tb_users` -- DROP TABLE IF EXISTS `tb_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `createdAt` timestamp NULL DEFAULT NULL, `updatedAt` timestamp NULL DEFAULT NULL, `deletedAt` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), KEY `idx_tb_users_deletedAt` (`deletedAt`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_users` -- LOCK TABLES `tb_users` WRITE; /*!40000 ALTER TABLE `tb_users` DISABLE KEYS */; INSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL); /*!40000 ALTER TABLE `tb_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-05-28 0:25:41 ================================================ FILE: demo11/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo11/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo11/handler/user/create.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Create creates a new user account. func Create(c *gin.Context) { log.Info("User Create function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } u := model.UserModel{ Username: r.Username, Password: r.Password, } // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Insert the user to the database. if err := u.Create(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo11/handler/user/delete.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Delete delete an user by the user identifier. func Delete(c *gin.Context) { userId, _ := strconv.Atoi(c.Param("id")) if err := model.DeleteUser(uint64(userId)); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo11/handler/user/get.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Get gets an user by the user identifier. func Get(c *gin.Context) { username := c.Param("username") // Get the user by the `username` from the database. user, err := model.GetUser(username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } SendResponse(c, nil, user) } ================================================ FILE: demo11/handler/user/list.go ================================================ package user import ( . "apiserver/handler" "apiserver/pkg/errno" "apiserver/service" "github.com/gin-gonic/gin" ) // List list the users in the database. func List(c *gin.Context) { var r ListRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } infos, count, err := service.ListUser(r.Username, r.Offset, r.Limit) if err != nil { SendResponse(c, err, nil) return } SendResponse(c, nil, ListResponse{ TotalCount: count, UserList: infos, }) } ================================================ FILE: demo11/handler/user/login.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/auth" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) // Login generates the authentication token // if the password was matched with the specified account. func Login(c *gin.Context) { // Binding the data with the user struct. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // Get the user information by the login username. d, err := model.GetUser(u.Username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } // Compare the login password with the user password. if err := auth.Compare(d.Password, u.Password); err != nil { SendResponse(c, errno.ErrPasswordIncorrect, nil) return } // Sign the json web token. t, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, "") if err != nil { SendResponse(c, errno.ErrToken, nil) return } SendResponse(c, nil, model.Token{Token: t}) } ================================================ FILE: demo11/handler/user/update.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Update update a exist user account info. func Update(c *gin.Context) { log.Info("Update function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) // Get the user id from the url parameter. userId, _ := strconv.Atoi(c.Param("id")) // Binding the user data. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // We update the record based on the user id. u.Id = uint64(userId) // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Save changed fields. if err := u.Update(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo11/handler/user/user.go ================================================ package user import ( "apiserver/model" ) type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } type ListRequest struct { Username string `json:"username"` Offset int `json:"offset"` Limit int `json:"limit"` } type ListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []*model.UserInfo `json:"userList"` } ================================================ FILE: demo11/main.go ================================================ package main import ( "errors" "net/http" "time" "apiserver/config" "apiserver/model" "apiserver/router" "apiserver/router/middleware" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") ) func main() { pflag.Parse() // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() // Routes. router.Load( // Cores. g, // Middlwares. middleware.Logging(), middleware.RequestId(), ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() // Start to listening the incoming requests. cert := viper.GetString("tls.cert") key := viper.GetString("tls.key") if cert != "" && key != "" { go func() { log.Infof("Start to listening the incoming requests on https address: %s", viper.GetString("tls.addr")) log.Info(http.ListenAndServeTLS(viper.GetString("tls.addr"), cert, key, g).Error()) }() } log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo11/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo11/model/model.go ================================================ package model import ( "sync" "time" ) type BaseModel struct { Id uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"` CreatedAt time.Time `gorm:"column:createdAt" json:"-"` UpdatedAt time.Time `gorm:"column:updatedAt" json:"-"` DeletedAt *time.Time `gorm:"column:deletedAt" sql:"index" json:"-"` } type UserInfo struct { Id uint64 `json:"id"` Username string `json:"username"` SayHello string `json:"sayHello"` Password string `json:"password"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type UserList struct { Lock *sync.Mutex IdMap map[uint64]*UserInfo } // Token represents a JSON web token. type Token struct { Token string `json:"token"` } ================================================ FILE: demo11/model/user.go ================================================ package model import ( "fmt" "apiserver/pkg/auth" "apiserver/pkg/constvar" validator "gopkg.in/go-playground/validator.v9" ) // User represents a registered user. type UserModel struct { BaseModel Username string `json:"username" gorm:"column:username;not null" binding:"required" validate:"min=1,max=32"` Password string `json:"password" gorm:"column:password;not null" binding:"required" validate:"min=5,max=128"` } func (c *UserModel) TableName() string { return "tb_users" } // Create creates a new user account. func (u *UserModel) Create() error { return DB.Self.Create(&u).Error } // DeleteUser deletes the user by the user identifier. func DeleteUser(id uint64) error { user := UserModel{} user.BaseModel.Id = id return DB.Self.Delete(&user).Error } // Update updates an user account information. func (u *UserModel) Update() error { return DB.Self.Save(u).Error } // GetUser gets an user by the user identifier. func GetUser(username string) (*UserModel, error) { u := &UserModel{} d := DB.Self.Where("username = ?", username).First(&u) return u, d.Error } // ListUser List all users func ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) { if limit == 0 { limit = constvar.DefaultLimit } users := make([]*UserModel, 0) var count uint64 where := fmt.Sprintf("username like '%%%s%%'", username) if err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil { return users, count, err } if err := DB.Self.Where(where).Offset(offset).Limit(limit).Order("id desc").Find(&users).Error; err != nil { return users, count, err } return users, count, nil } // Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct). func (u *UserModel) Compare(pwd string) (err error) { err = auth.Compare(u.Password, pwd) return } // Encrypt the user password. func (u *UserModel) Encrypt() (err error) { u.Password, err = auth.Encrypt(u.Password) return } // Validate the fields. func (u *UserModel) Validate() error { validate := validator.New() return validate.Struct(u) } ================================================ FILE: demo11/pkg/auth/auth.go ================================================ package auth import "golang.org/x/crypto/bcrypt" // Encrypt encrypts the plain text with bcrypt. func Encrypt(source string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) return string(hashedBytes), err } // Compare compares the encrypted text with the plain text if it's the same. func Compare(hashedPassword, password string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } ================================================ FILE: demo11/pkg/constvar/constvar.go ================================================ package constvar const ( DefaultLimit = 50 ) ================================================ FILE: demo11/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error"} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} ErrDatabase = &Errno{Code: 20002, Message: "Database error."} ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} // user errors ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} ErrPasswordIncorrect = &Errno{Code: 20104, Message: "The password was incorrect."} ) ================================================ FILE: demo11/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo11/pkg/token/token.go ================================================ package token import ( "errors" "fmt" "time" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) var ( // ErrMissingHeader means the `Authorization` header was empty. ErrMissingHeader = errors.New("The length of the `Authorization` header is zero.") ) // Context is the context of the JSON web token. type Context struct { ID uint64 Username string } // secretFunc validates the secret format. func secretFunc(secret string) jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { // Make sure the `alg` is what we except. if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(secret), nil } } // Parse validates the token with the specified secret, // and returns the context if the token was valid. func Parse(tokenString string, secret string) (*Context, error) { ctx := &Context{} // Parse the token. token, err := jwt.Parse(tokenString, secretFunc(secret)) // Parse error. if err != nil { return ctx, err // Read the token if it's valid. } else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { ctx.ID = uint64(claims["id"].(float64)) ctx.Username = claims["username"].(string) return ctx, nil // Other errors. } else { return ctx, err } } // ParseRequest gets the token from the header and // pass it to the Parse function to parses the token. func ParseRequest(c *gin.Context) (*Context, error) { header := c.Request.Header.Get("Authorization") // Load the jwt secret from config secret := viper.GetString("jwt_secret") if len(header) == 0 { return &Context{}, ErrMissingHeader } var t string // Parse the header to get the token part. fmt.Sscanf(header, "Bearer %s", &t) return Parse(t, secret) } // Sign signs the context with the specified secret. func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) { // Load the jwt secret from the Gin config if the secret isn't specified. if secret == "" { secret = viper.GetString("jwt_secret") } // The token content. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "id": c.ID, "username": c.Username, "nbf": time.Now().Unix(), "iat": time.Now().Unix(), }) // Sign the token with the specified secret. tokenString, err = token.SignedString([]byte(secret)) return } ================================================ FILE: demo11/router/middleware/auth.go ================================================ package middleware import ( "apiserver/handler" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Parse the json web token. if _, err := token.ParseRequest(c); err != nil { handler.SendResponse(c, errno.ErrTokenInvalid, nil) c.Abort() return } c.Next() } } ================================================ FILE: demo11/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo11/router/middleware/logging.go ================================================ package middleware import ( "bytes" "encoding/json" "io/ioutil" "regexp" "time" "apiserver/handler" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/willf/pad" ) type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) } // Logging is a middleware function that logs the each request. func Logging() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now().UTC() path := c.Request.URL.Path reg := regexp.MustCompile("(/v1/user|/login)") if !reg.MatchString(path) { return } // Skip for the health check requests. if path == "/sd/health" || path == "/sd/ram" || path == "/sd/cpu" || path == "/sd/disk" { return } // Read the Body content var bodyBytes []byte if c.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(c.Request.Body) } // Restore the io.ReadCloser to its original state c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // The basic informations. method := c.Request.Method ip := c.ClientIP() //log.Debugf("New request come in, path: %s, Method: %s, body `%s`", path, method, string(bodyBytes)) blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: c.Writer, } c.Writer = blw // Continue. c.Next() // Calculates the latency. end := time.Now().UTC() latency := end.Sub(start) code, message := -1, "" // get code and message var response handler.Response if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { log.Errorf(err, "response body can not unmarshal to model.Response struct, body: `%s`", blw.body.Bytes()) code = errno.InternalServerError.Code message = err.Error() } else { code = response.Code message = response.Message } log.Infof("%-13s | %-12s | %s %s | {code: %d, message: %s}", latency, ip, pad.Right(method, 5, ""), path, code, message) } } ================================================ FILE: demo11/router/middleware/requestid.go ================================================ package middleware import ( "github.com/gin-gonic/gin" "github.com/satori/go.uuid" ) func RequestId() gin.HandlerFunc { return func(c *gin.Context) { // Check for incoming header, use it if exists requestId := c.Request.Header.Get("X-Request-Id") // Create request id with UUID4 if requestId == "" { u4, _ := uuid.NewV4() requestId = u4.String() } // Expose it for use in the application c.Set("X-Request-Id", requestId) // Set X-Request-Id header c.Writer.Header().Set("X-Request-Id", requestId) c.Next() } } ================================================ FILE: demo11/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // api for authentication functionalities g.POST("/login", user.Login) // The user handlers, requiring authentication u := g.Group("/v1/user") u.Use(middleware.AuthMiddleware()) { u.POST("", user.Create) u.DELETE("/:id", user.Delete) u.PUT("/:id", user.Update) u.GET("", user.List) u.GET("/:username", user.Get) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo11/service/service.go ================================================ package service import ( "fmt" "sync" "apiserver/model" "apiserver/util" ) func ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) { infos := make([]*model.UserInfo, 0) users, count, err := model.ListUser(username, offset, limit) if err != nil { return nil, count, err } ids := []uint64{} for _, user := range users { ids = append(ids, user.Id) } wg := sync.WaitGroup{} userList := model.UserList{ Lock: new(sync.Mutex), IdMap: make(map[uint64]*model.UserInfo, len(users)), } errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) go func(u *model.UserModel) { defer wg.Done() shortId, err := util.GenShortId() if err != nil { errChan <- err return } userList.Lock.Lock() defer userList.Lock.Unlock() userList.IdMap[u.Id] = &model.UserInfo{ Id: u.Id, Username: u.Username, SayHello: fmt.Sprintf("Hello %s", shortId), Password: u.Password, CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: u.UpdatedAt.Format("2006-01-02 15:04:05"), } }(u) } go func() { wg.Wait() close(finished) }() select { case <-finished: case err := <-errChan: return nil, count, err } for _, id := range ids { infos = append(infos, userList.IdMap[id]) } return infos, count, nil } ================================================ FILE: demo11/util/util.go ================================================ package util import ( "github.com/gin-gonic/gin" "github.com/teris-io/shortid" ) func GenShortId() (string, error) { return shortid.Generate() } func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") if !ok { return "" } if requestId, ok := v.(string); ok { return requestId } return "" } ================================================ FILE: demo12/Makefile ================================================ SHELL := /bin/bash BASEDIR = $(shell pwd) # build with verison infos versionDir = "apiserver/pkg/version" gitTag = $(shell if [ "`git describe --tags --abbrev=0 2>/dev/null`" != "" ];then git describe --tags --abbrev=0; else git log --pretty=format:'%h' -n 1; fi) buildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z) gitCommit = $(shell git log --pretty=format:'%H' -n 1) gitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi) ldflags="-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}" all: gotool @go build -v -ldflags ${ldflags} . clean: rm -f apiserver find . -name "[._]*.s[a-w][a-z]" | xargs -i rm -f {} gotool: gofmt -w . go tool vet . |& grep -v vendor;true ca: openssl req -new -nodes -x509 -out conf/server.crt -keyout conf/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=127.0.0.1/emailAddress=xxxxx@qq.com" help: @echo "make - compile the source code" @echo "make clean - remove binary file and vim swp files" @echo "make gotool - run go tool 'fmt' and 'vet'" @echo "make ca - generate ca files" .PHONY: clean gotool ca help ================================================ FILE: demo12/README.md ================================================ 进阶:给API命令增加版本功能 ================================================ FILE: demo12/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 jwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5 tls: addr: :8081 cert: conf/server.crt key: conf/server.key log: writers: stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: true rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root docker_db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root ================================================ FILE: demo12/conf/server.crt ================================================ -----BEGIN CERTIFICATE----- MIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx GzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y ODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD VQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU MRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG 42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN q/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm yDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9 iaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM /nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk or8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud IwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz ANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel AnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD BgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6 8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B +8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g= -----END CERTIFICATE----- ================================================ FILE: demo12/conf/server.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv K5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB xxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt 8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my QKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls /BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc rmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK g59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA 2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4 NxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ fYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg tOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW Tkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt BZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV xpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe hmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl zRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8 Q3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW AOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd UOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F jd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds K1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg peNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2 4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/ MhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/ 2eBi4LlGO5stoeD63h9Ten0= -----END PRIVATE KEY----- ================================================ FILE: demo12/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo12/db.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db_apiserver`; -- -- Table structure for table `tb_users` -- DROP TABLE IF EXISTS `tb_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `createdAt` timestamp NULL DEFAULT NULL, `updatedAt` timestamp NULL DEFAULT NULL, `deletedAt` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), KEY `idx_tb_users_deletedAt` (`deletedAt`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_users` -- LOCK TABLES `tb_users` WRITE; /*!40000 ALTER TABLE `tb_users` DISABLE KEYS */; INSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL); /*!40000 ALTER TABLE `tb_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-05-28 0:25:41 ================================================ FILE: demo12/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo12/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo12/handler/user/create.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Create creates a new user account. func Create(c *gin.Context) { log.Info("User Create function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } u := model.UserModel{ Username: r.Username, Password: r.Password, } // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Insert the user to the database. if err := u.Create(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo12/handler/user/delete.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Delete delete an user by the user identifier. func Delete(c *gin.Context) { userId, _ := strconv.Atoi(c.Param("id")) if err := model.DeleteUser(uint64(userId)); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo12/handler/user/get.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Get gets an user by the user identifier. func Get(c *gin.Context) { username := c.Param("username") // Get the user by the `username` from the database. user, err := model.GetUser(username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } SendResponse(c, nil, user) } ================================================ FILE: demo12/handler/user/list.go ================================================ package user import ( . "apiserver/handler" "apiserver/pkg/errno" "apiserver/service" "github.com/gin-gonic/gin" ) // List list the users in the database. func List(c *gin.Context) { var r ListRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } infos, count, err := service.ListUser(r.Username, r.Offset, r.Limit) if err != nil { SendResponse(c, err, nil) return } SendResponse(c, nil, ListResponse{ TotalCount: count, UserList: infos, }) } ================================================ FILE: demo12/handler/user/login.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/auth" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) // Login generates the authentication token // if the password was matched with the specified account. func Login(c *gin.Context) { // Binding the data with the user struct. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // Get the user information by the login username. d, err := model.GetUser(u.Username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } // Compare the login password with the user password. if err := auth.Compare(d.Password, u.Password); err != nil { SendResponse(c, errno.ErrPasswordIncorrect, nil) return } // Sign the json web token. t, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, "") if err != nil { SendResponse(c, errno.ErrToken, nil) return } SendResponse(c, nil, model.Token{Token: t}) } ================================================ FILE: demo12/handler/user/update.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Update update a exist user account info. func Update(c *gin.Context) { log.Info("Update function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) // Get the user id from the url parameter. userId, _ := strconv.Atoi(c.Param("id")) // Binding the user data. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // We update the record based on the user id. u.Id = uint64(userId) // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Save changed fields. if err := u.Update(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo12/handler/user/user.go ================================================ package user import ( "apiserver/model" ) type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } type ListRequest struct { Username string `json:"username"` Offset int `json:"offset"` Limit int `json:"limit"` } type ListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []*model.UserInfo `json:"userList"` } ================================================ FILE: demo12/main.go ================================================ package main import ( "encoding/json" "errors" "fmt" "net/http" "os" "time" "apiserver/config" "apiserver/model" v "apiserver/pkg/version" "apiserver/router" "apiserver/router/middleware" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") version = pflag.BoolP("version", "v", false, "show version info.") ) func main() { pflag.Parse() if *version { v := v.Get() marshalled, err := json.MarshalIndent(&v, "", " ") if err != nil { fmt.Printf("%v\n", err) os.Exit(1) } fmt.Println(string(marshalled)) return } // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() // Routes. router.Load( // Cores. g, // Middlwares. middleware.Logging(), middleware.RequestId(), ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() // Start to listening the incoming requests. cert := viper.GetString("tls.cert") key := viper.GetString("tls.key") if cert != "" && key != "" { go func() { log.Infof("Start to listening the incoming requests on https address: %s", viper.GetString("tls.addr")) log.Info(http.ListenAndServeTLS(viper.GetString("tls.addr"), cert, key, g).Error()) }() } log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo12/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo12/model/model.go ================================================ package model import ( "sync" "time" ) type BaseModel struct { Id uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"` CreatedAt time.Time `gorm:"column:createdAt" json:"-"` UpdatedAt time.Time `gorm:"column:updatedAt" json:"-"` DeletedAt *time.Time `gorm:"column:deletedAt" sql:"index" json:"-"` } type UserInfo struct { Id uint64 `json:"id"` Username string `json:"username"` SayHello string `json:"sayHello"` Password string `json:"password"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type UserList struct { Lock *sync.Mutex IdMap map[uint64]*UserInfo } // Token represents a JSON web token. type Token struct { Token string `json:"token"` } ================================================ FILE: demo12/model/user.go ================================================ package model import ( "fmt" "apiserver/pkg/auth" "apiserver/pkg/constvar" validator "gopkg.in/go-playground/validator.v9" ) // User represents a registered user. type UserModel struct { BaseModel Username string `json:"username" gorm:"column:username;not null" binding:"required" validate:"min=1,max=32"` Password string `json:"password" gorm:"column:password;not null" binding:"required" validate:"min=5,max=128"` } func (c *UserModel) TableName() string { return "tb_users" } // Create creates a new user account. func (u *UserModel) Create() error { return DB.Self.Create(&u).Error } // DeleteUser deletes the user by the user identifier. func DeleteUser(id uint64) error { user := UserModel{} user.BaseModel.Id = id return DB.Self.Delete(&user).Error } // Update updates an user account information. func (u *UserModel) Update() error { return DB.Self.Save(u).Error } // GetUser gets an user by the user identifier. func GetUser(username string) (*UserModel, error) { u := &UserModel{} d := DB.Self.Where("username = ?", username).First(&u) return u, d.Error } // ListUser List all users func ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) { if limit == 0 { limit = constvar.DefaultLimit } users := make([]*UserModel, 0) var count uint64 where := fmt.Sprintf("username like '%%%s%%'", username) if err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil { return users, count, err } if err := DB.Self.Where(where).Offset(offset).Limit(limit).Order("id desc").Find(&users).Error; err != nil { return users, count, err } return users, count, nil } // Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct). func (u *UserModel) Compare(pwd string) (err error) { err = auth.Compare(u.Password, pwd) return } // Encrypt the user password. func (u *UserModel) Encrypt() (err error) { u.Password, err = auth.Encrypt(u.Password) return } // Validate the fields. func (u *UserModel) Validate() error { validate := validator.New() return validate.Struct(u) } ================================================ FILE: demo12/pkg/auth/auth.go ================================================ package auth import "golang.org/x/crypto/bcrypt" // Encrypt encrypts the plain text with bcrypt. func Encrypt(source string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) return string(hashedBytes), err } // Compare compares the encrypted text with the plain text if it's the same. func Compare(hashedPassword, password string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } ================================================ FILE: demo12/pkg/constvar/constvar.go ================================================ package constvar const ( DefaultLimit = 50 ) ================================================ FILE: demo12/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error"} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} ErrDatabase = &Errno{Code: 20002, Message: "Database error."} ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} // user errors ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} ErrPasswordIncorrect = &Errno{Code: 20104, Message: "The password was incorrect."} ) ================================================ FILE: demo12/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo12/pkg/token/token.go ================================================ package token import ( "errors" "fmt" "time" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) var ( // ErrMissingHeader means the `Authorization` header was empty. ErrMissingHeader = errors.New("The length of the `Authorization` header is zero.") ) // Context is the context of the JSON web token. type Context struct { ID uint64 Username string } // secretFunc validates the secret format. func secretFunc(secret string) jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { // Make sure the `alg` is what we except. if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(secret), nil } } // Parse validates the token with the specified secret, // and returns the context if the token was valid. func Parse(tokenString string, secret string) (*Context, error) { ctx := &Context{} // Parse the token. token, err := jwt.Parse(tokenString, secretFunc(secret)) // Parse error. if err != nil { return ctx, err // Read the token if it's valid. } else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { ctx.ID = uint64(claims["id"].(float64)) ctx.Username = claims["username"].(string) return ctx, nil // Other errors. } else { return ctx, err } } // ParseRequest gets the token from the header and // pass it to the Parse function to parses the token. func ParseRequest(c *gin.Context) (*Context, error) { header := c.Request.Header.Get("Authorization") // Load the jwt secret from config secret := viper.GetString("jwt_secret") if len(header) == 0 { return &Context{}, ErrMissingHeader } var t string // Parse the header to get the token part. fmt.Sscanf(header, "Bearer %s", &t) return Parse(t, secret) } // Sign signs the context with the specified secret. func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) { // Load the jwt secret from the Gin config if the secret isn't specified. if secret == "" { secret = viper.GetString("jwt_secret") } // The token content. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "id": c.ID, "username": c.Username, "nbf": time.Now().Unix(), "iat": time.Now().Unix(), }) // Sign the token with the specified secret. tokenString, err = token.SignedString([]byte(secret)) return } ================================================ FILE: demo12/pkg/version/base.go ================================================ package version var ( gitTag string = "" gitCommit string = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty" buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') ) ================================================ FILE: demo12/pkg/version/doc.go ================================================ package version ================================================ FILE: demo12/pkg/version/version.go ================================================ package version import ( "fmt" "runtime" ) // Info contains versioning information. type Info struct { GitTag string `json:"gitTag"` GitCommit string `json:"gitCommit"` GitTreeState string `json:"gitTreeState"` BuildDate string `json:"buildDate"` GoVersion string `json:"goVersion"` Compiler string `json:"compiler"` Platform string `json:"platform"` } // String returns info as a human-friendly version string. func (info Info) String() string { return info.GitTag } func Get() Info { return Info{ GitTag: gitTag, GitCommit: gitCommit, GitTreeState: gitTreeState, BuildDate: buildDate, GoVersion: runtime.Version(), Compiler: runtime.Compiler, Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), } } ================================================ FILE: demo12/router/middleware/auth.go ================================================ package middleware import ( "apiserver/handler" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Parse the json web token. if _, err := token.ParseRequest(c); err != nil { handler.SendResponse(c, errno.ErrTokenInvalid, nil) c.Abort() return } c.Next() } } ================================================ FILE: demo12/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo12/router/middleware/logging.go ================================================ package middleware import ( "bytes" "encoding/json" "io/ioutil" "regexp" "time" "apiserver/handler" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/willf/pad" ) type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) } // Logging is a middleware function that logs the each request. func Logging() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now().UTC() path := c.Request.URL.Path reg := regexp.MustCompile("(/v1/user|/login)") if !reg.MatchString(path) { return } // Skip for the health check requests. if path == "/sd/health" || path == "/sd/ram" || path == "/sd/cpu" || path == "/sd/disk" { return } // Read the Body content var bodyBytes []byte if c.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(c.Request.Body) } // Restore the io.ReadCloser to its original state c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // The basic informations. method := c.Request.Method ip := c.ClientIP() //log.Debugf("New request come in, path: %s, Method: %s, body `%s`", path, method, string(bodyBytes)) blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: c.Writer, } c.Writer = blw // Continue. c.Next() // Calculates the latency. end := time.Now().UTC() latency := end.Sub(start) code, message := -1, "" // get code and message var response handler.Response if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { log.Errorf(err, "response body can not unmarshal to model.Response struct, body: `%s`", blw.body.Bytes()) code = errno.InternalServerError.Code message = err.Error() } else { code = response.Code message = response.Message } log.Infof("%-13s | %-12s | %s %s | {code: %d, message: %s}", latency, ip, pad.Right(method, 5, ""), path, code, message) } } ================================================ FILE: demo12/router/middleware/requestid.go ================================================ package middleware import ( "github.com/gin-gonic/gin" "github.com/satori/go.uuid" ) func RequestId() gin.HandlerFunc { return func(c *gin.Context) { // Check for incoming header, use it if exists requestId := c.Request.Header.Get("X-Request-Id") // Create request id with UUID4 if requestId == "" { u4, _ := uuid.NewV4() requestId = u4.String() } // Expose it for use in the application c.Set("X-Request-Id", requestId) // Set X-Request-Id header c.Writer.Header().Set("X-Request-Id", requestId) c.Next() } } ================================================ FILE: demo12/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // api for authentication functionalities g.POST("/login", user.Login) // The user handlers, requiring authentication u := g.Group("/v1/user") u.Use(middleware.AuthMiddleware()) { u.POST("", user.Create) u.DELETE("/:id", user.Delete) u.PUT("/:id", user.Update) u.GET("", user.List) u.GET("/:username", user.Get) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo12/service/service.go ================================================ package service import ( "fmt" "sync" "apiserver/model" "apiserver/util" ) func ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) { infos := make([]*model.UserInfo, 0) users, count, err := model.ListUser(username, offset, limit) if err != nil { return nil, count, err } ids := []uint64{} for _, user := range users { ids = append(ids, user.Id) } wg := sync.WaitGroup{} userList := model.UserList{ Lock: new(sync.Mutex), IdMap: make(map[uint64]*model.UserInfo, len(users)), } errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) go func(u *model.UserModel) { defer wg.Done() shortId, err := util.GenShortId() if err != nil { errChan <- err return } userList.Lock.Lock() defer userList.Lock.Unlock() userList.IdMap[u.Id] = &model.UserInfo{ Id: u.Id, Username: u.Username, SayHello: fmt.Sprintf("Hello %s", shortId), Password: u.Password, CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: u.UpdatedAt.Format("2006-01-02 15:04:05"), } }(u) } go func() { wg.Wait() close(finished) }() select { case <-finished: case err := <-errChan: return nil, count, err } for _, id := range ids { infos = append(infos, userList.IdMap[id]) } return infos, count, nil } ================================================ FILE: demo12/util/util.go ================================================ package util import ( "github.com/gin-gonic/gin" "github.com/teris-io/shortid" ) func GenShortId() (string, error) { return shortid.Generate() } func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") if !ok { return "" } if requestId, ok := v.(string); ok { return requestId } return "" } ================================================ FILE: demo13/Makefile ================================================ SHELL := /bin/bash BASEDIR = $(shell pwd) # build with verison infos versionDir = "apiserver/pkg/version" gitTag = $(shell if [ "`git describe --tags --abbrev=0 2>/dev/null`" != "" ];then git describe --tags --abbrev=0; else git log --pretty=format:'%h' -n 1; fi) buildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z) gitCommit = $(shell git log --pretty=format:'%H' -n 1) gitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi) ldflags="-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}" all: gotool @go build -v -ldflags ${ldflags} . clean: rm -f apiserver find . -name "[._]*.s[a-w][a-z]" | xargs -i rm -f {} gotool: gofmt -w . go tool vet . |& grep -v vendor;true ca: openssl req -new -nodes -x509 -out conf/server.crt -keyout conf/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=127.0.0.1/emailAddress=xxxxx@qq.com" help: @echo "make - compile the source code" @echo "make clean - remove binary file and vim swp files" @echo "make gotool - run go tool 'fmt' and 'vet'" @echo "make ca - generate ca files" .PHONY: clean gotool ca help ================================================ FILE: demo13/README.md ================================================ 进阶:给API增加启动脚本 ================================================ FILE: demo13/admin.sh ================================================ #!/bin/bash SERVER="apiserver" BASE_DIR=$PWD INTERVAL=2 # 命令行参数,需要手动指定 ARGS="" function start() { if [ "`pgrep $SERVER -u $UID`" != "" ];then echo "$SERVER already running" exit 1 fi nohup $BASE_DIR/$SERVER $ARGS server &>/dev/null & echo "sleeping..." && sleep $INTERVAL # check status if [ "`pgrep $SERVER -u $UID`" == "" ];then echo "$SERVER start failed" exit 1 fi } function status() { if [ "`pgrep $SERVER -u $UID`" != "" ];then echo $SERVER is running else echo $SERVER is not running fi } function stop() { if [ "`pgrep $SERVER -u $UID`" != "" ];then kill -9 `pgrep $SERVER -u $UID` fi echo "sleeping..." && sleep $INTERVAL if [ "`pgrep $SERVER -u $UID`" != "" ];then echo "$SERVER stop failed" exit 1 fi } case "$1" in 'start') start ;; 'stop') stop ;; 'status') status ;; 'restart') stop && start ;; *) echo "usage: $0 {start|stop|restart|status}" exit 1 ;; esac ================================================ FILE: demo13/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 jwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5 tls: addr: :8081 cert: conf/server.crt key: conf/server.key log: writers: stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: true rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root docker_db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root ================================================ FILE: demo13/conf/server.crt ================================================ -----BEGIN CERTIFICATE----- MIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx GzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y ODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD VQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU MRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG 42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN q/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm yDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9 iaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM /nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk or8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud IwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz ANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel AnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD BgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6 8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B +8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g= -----END CERTIFICATE----- ================================================ FILE: demo13/conf/server.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv K5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB xxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt 8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my QKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls /BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc rmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK g59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA 2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4 NxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ fYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg tOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW Tkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt BZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV xpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe hmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl zRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8 Q3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW AOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd UOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F jd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds K1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg peNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2 4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/ MhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/ 2eBi4LlGO5stoeD63h9Ten0= -----END PRIVATE KEY----- ================================================ FILE: demo13/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo13/db.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db_apiserver`; -- -- Table structure for table `tb_users` -- DROP TABLE IF EXISTS `tb_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `createdAt` timestamp NULL DEFAULT NULL, `updatedAt` timestamp NULL DEFAULT NULL, `deletedAt` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), KEY `idx_tb_users_deletedAt` (`deletedAt`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_users` -- LOCK TABLES `tb_users` WRITE; /*!40000 ALTER TABLE `tb_users` DISABLE KEYS */; INSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL); /*!40000 ALTER TABLE `tb_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-05-28 0:25:41 ================================================ FILE: demo13/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo13/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo13/handler/user/create.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Create creates a new user account. func Create(c *gin.Context) { log.Info("User Create function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } u := model.UserModel{ Username: r.Username, Password: r.Password, } // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Insert the user to the database. if err := u.Create(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo13/handler/user/delete.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Delete delete an user by the user identifier. func Delete(c *gin.Context) { userId, _ := strconv.Atoi(c.Param("id")) if err := model.DeleteUser(uint64(userId)); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo13/handler/user/get.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Get gets an user by the user identifier. func Get(c *gin.Context) { username := c.Param("username") // Get the user by the `username` from the database. user, err := model.GetUser(username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } SendResponse(c, nil, user) } ================================================ FILE: demo13/handler/user/list.go ================================================ package user import ( . "apiserver/handler" "apiserver/pkg/errno" "apiserver/service" "github.com/gin-gonic/gin" ) // List list the users in the database. func List(c *gin.Context) { var r ListRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } infos, count, err := service.ListUser(r.Username, r.Offset, r.Limit) if err != nil { SendResponse(c, err, nil) return } SendResponse(c, nil, ListResponse{ TotalCount: count, UserList: infos, }) } ================================================ FILE: demo13/handler/user/login.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/auth" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) // Login generates the authentication token // if the password was matched with the specified account. func Login(c *gin.Context) { // Binding the data with the user struct. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // Get the user information by the login username. d, err := model.GetUser(u.Username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } // Compare the login password with the user password. if err := auth.Compare(d.Password, u.Password); err != nil { SendResponse(c, errno.ErrPasswordIncorrect, nil) return } // Sign the json web token. t, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, "") if err != nil { SendResponse(c, errno.ErrToken, nil) return } SendResponse(c, nil, model.Token{Token: t}) } ================================================ FILE: demo13/handler/user/update.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Update update a exist user account info. func Update(c *gin.Context) { log.Info("Update function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) // Get the user id from the url parameter. userId, _ := strconv.Atoi(c.Param("id")) // Binding the user data. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // We update the record based on the user id. u.Id = uint64(userId) // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Save changed fields. if err := u.Update(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo13/handler/user/user.go ================================================ package user import ( "apiserver/model" ) type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } type ListRequest struct { Username string `json:"username"` Offset int `json:"offset"` Limit int `json:"limit"` } type ListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []*model.UserInfo `json:"userList"` } ================================================ FILE: demo13/main.go ================================================ package main import ( "encoding/json" "errors" "fmt" "net/http" "os" "time" "apiserver/config" "apiserver/model" v "apiserver/pkg/version" "apiserver/router" "apiserver/router/middleware" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") version = pflag.BoolP("version", "v", false, "show version info.") ) func main() { pflag.Parse() if *version { v := v.Get() marshalled, err := json.MarshalIndent(&v, "", " ") if err != nil { fmt.Printf("%v\n", err) os.Exit(1) } fmt.Println(string(marshalled)) return } // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() // Routes. router.Load( // Cores. g, // Middlwares. middleware.Logging(), middleware.RequestId(), ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() // Start to listening the incoming requests. cert := viper.GetString("tls.cert") key := viper.GetString("tls.key") if cert != "" && key != "" { go func() { log.Infof("Start to listening the incoming requests on https address: %s", viper.GetString("tls.addr")) log.Info(http.ListenAndServeTLS(viper.GetString("tls.addr"), cert, key, g).Error()) }() } log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo13/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo13/model/model.go ================================================ package model import ( "sync" "time" ) type BaseModel struct { Id uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"` CreatedAt time.Time `gorm:"column:createdAt" json:"-"` UpdatedAt time.Time `gorm:"column:updatedAt" json:"-"` DeletedAt *time.Time `gorm:"column:deletedAt" sql:"index" json:"-"` } type UserInfo struct { Id uint64 `json:"id"` Username string `json:"username"` SayHello string `json:"sayHello"` Password string `json:"password"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type UserList struct { Lock *sync.Mutex IdMap map[uint64]*UserInfo } // Token represents a JSON web token. type Token struct { Token string `json:"token"` } ================================================ FILE: demo13/model/user.go ================================================ package model import ( "fmt" "apiserver/pkg/auth" "apiserver/pkg/constvar" validator "gopkg.in/go-playground/validator.v9" ) // User represents a registered user. type UserModel struct { BaseModel Username string `json:"username" gorm:"column:username;not null" binding:"required" validate:"min=1,max=32"` Password string `json:"password" gorm:"column:password;not null" binding:"required" validate:"min=5,max=128"` } func (c *UserModel) TableName() string { return "tb_users" } // Create creates a new user account. func (u *UserModel) Create() error { return DB.Self.Create(&u).Error } // DeleteUser deletes the user by the user identifier. func DeleteUser(id uint64) error { user := UserModel{} user.BaseModel.Id = id return DB.Self.Delete(&user).Error } // Update updates an user account information. func (u *UserModel) Update() error { return DB.Self.Save(u).Error } // GetUser gets an user by the user identifier. func GetUser(username string) (*UserModel, error) { u := &UserModel{} d := DB.Self.Where("username = ?", username).First(&u) return u, d.Error } // ListUser List all users func ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) { if limit == 0 { limit = constvar.DefaultLimit } users := make([]*UserModel, 0) var count uint64 where := fmt.Sprintf("username like '%%%s%%'", username) if err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil { return users, count, err } if err := DB.Self.Where(where).Offset(offset).Limit(limit).Order("id desc").Find(&users).Error; err != nil { return users, count, err } return users, count, nil } // Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct). func (u *UserModel) Compare(pwd string) (err error) { err = auth.Compare(u.Password, pwd) return } // Encrypt the user password. func (u *UserModel) Encrypt() (err error) { u.Password, err = auth.Encrypt(u.Password) return } // Validate the fields. func (u *UserModel) Validate() error { validate := validator.New() return validate.Struct(u) } ================================================ FILE: demo13/pkg/auth/auth.go ================================================ package auth import "golang.org/x/crypto/bcrypt" // Encrypt encrypts the plain text with bcrypt. func Encrypt(source string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) return string(hashedBytes), err } // Compare compares the encrypted text with the plain text if it's the same. func Compare(hashedPassword, password string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } ================================================ FILE: demo13/pkg/constvar/constvar.go ================================================ package constvar const ( DefaultLimit = 50 ) ================================================ FILE: demo13/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error"} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} ErrDatabase = &Errno{Code: 20002, Message: "Database error."} ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} // user errors ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} ErrPasswordIncorrect = &Errno{Code: 20104, Message: "The password was incorrect."} ) ================================================ FILE: demo13/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo13/pkg/token/token.go ================================================ package token import ( "errors" "fmt" "time" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) var ( // ErrMissingHeader means the `Authorization` header was empty. ErrMissingHeader = errors.New("The length of the `Authorization` header is zero.") ) // Context is the context of the JSON web token. type Context struct { ID uint64 Username string } // secretFunc validates the secret format. func secretFunc(secret string) jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { // Make sure the `alg` is what we except. if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(secret), nil } } // Parse validates the token with the specified secret, // and returns the context if the token was valid. func Parse(tokenString string, secret string) (*Context, error) { ctx := &Context{} // Parse the token. token, err := jwt.Parse(tokenString, secretFunc(secret)) // Parse error. if err != nil { return ctx, err // Read the token if it's valid. } else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { ctx.ID = uint64(claims["id"].(float64)) ctx.Username = claims["username"].(string) return ctx, nil // Other errors. } else { return ctx, err } } // ParseRequest gets the token from the header and // pass it to the Parse function to parses the token. func ParseRequest(c *gin.Context) (*Context, error) { header := c.Request.Header.Get("Authorization") // Load the jwt secret from config secret := viper.GetString("jwt_secret") if len(header) == 0 { return &Context{}, ErrMissingHeader } var t string // Parse the header to get the token part. fmt.Sscanf(header, "Bearer %s", &t) return Parse(t, secret) } // Sign signs the context with the specified secret. func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) { // Load the jwt secret from the Gin config if the secret isn't specified. if secret == "" { secret = viper.GetString("jwt_secret") } // The token content. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "id": c.ID, "username": c.Username, "nbf": time.Now().Unix(), "iat": time.Now().Unix(), }) // Sign the token with the specified secret. tokenString, err = token.SignedString([]byte(secret)) return } ================================================ FILE: demo13/pkg/version/base.go ================================================ package version var ( gitTag string = "" gitCommit string = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty" buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') ) ================================================ FILE: demo13/pkg/version/doc.go ================================================ package version ================================================ FILE: demo13/pkg/version/version.go ================================================ package version import ( "fmt" "runtime" ) // Info contains versioning information. type Info struct { GitTag string `json:"gitTag"` GitCommit string `json:"gitCommit"` GitTreeState string `json:"gitTreeState"` BuildDate string `json:"buildDate"` GoVersion string `json:"goVersion"` Compiler string `json:"compiler"` Platform string `json:"platform"` } // String returns info as a human-friendly version string. func (info Info) String() string { return info.GitTag } func Get() Info { return Info{ GitTag: gitTag, GitCommit: gitCommit, GitTreeState: gitTreeState, BuildDate: buildDate, GoVersion: runtime.Version(), Compiler: runtime.Compiler, Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), } } ================================================ FILE: demo13/router/middleware/auth.go ================================================ package middleware import ( "apiserver/handler" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Parse the json web token. if _, err := token.ParseRequest(c); err != nil { handler.SendResponse(c, errno.ErrTokenInvalid, nil) c.Abort() return } c.Next() } } ================================================ FILE: demo13/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo13/router/middleware/logging.go ================================================ package middleware import ( "bytes" "encoding/json" "io/ioutil" "regexp" "time" "apiserver/handler" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/willf/pad" ) type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) } // Logging is a middleware function that logs the each request. func Logging() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now().UTC() path := c.Request.URL.Path reg := regexp.MustCompile("(/v1/user|/login)") if !reg.MatchString(path) { return } // Skip for the health check requests. if path == "/sd/health" || path == "/sd/ram" || path == "/sd/cpu" || path == "/sd/disk" { return } // Read the Body content var bodyBytes []byte if c.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(c.Request.Body) } // Restore the io.ReadCloser to its original state c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // The basic informations. method := c.Request.Method ip := c.ClientIP() //log.Debugf("New request come in, path: %s, Method: %s, body `%s`", path, method, string(bodyBytes)) blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: c.Writer, } c.Writer = blw // Continue. c.Next() // Calculates the latency. end := time.Now().UTC() latency := end.Sub(start) code, message := -1, "" // get code and message var response handler.Response if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { log.Errorf(err, "response body can not unmarshal to model.Response struct, body: `%s`", blw.body.Bytes()) code = errno.InternalServerError.Code message = err.Error() } else { code = response.Code message = response.Message } log.Infof("%-13s | %-12s | %s %s | {code: %d, message: %s}", latency, ip, pad.Right(method, 5, ""), path, code, message) } } ================================================ FILE: demo13/router/middleware/requestid.go ================================================ package middleware import ( "github.com/gin-gonic/gin" "github.com/satori/go.uuid" ) func RequestId() gin.HandlerFunc { return func(c *gin.Context) { // Check for incoming header, use it if exists requestId := c.Request.Header.Get("X-Request-Id") // Create request id with UUID4 if requestId == "" { u4, _ := uuid.NewV4() requestId = u4.String() } // Expose it for use in the application c.Set("X-Request-Id", requestId) // Set X-Request-Id header c.Writer.Header().Set("X-Request-Id", requestId) c.Next() } } ================================================ FILE: demo13/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // api for authentication functionalities g.POST("/login", user.Login) // The user handlers, requiring authentication u := g.Group("/v1/user") u.Use(middleware.AuthMiddleware()) { u.POST("", user.Create) u.DELETE("/:id", user.Delete) u.PUT("/:id", user.Update) u.GET("", user.List) u.GET("/:username", user.Get) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo13/service/service.go ================================================ package service import ( "fmt" "sync" "apiserver/model" "apiserver/util" ) func ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) { infos := make([]*model.UserInfo, 0) users, count, err := model.ListUser(username, offset, limit) if err != nil { return nil, count, err } ids := []uint64{} for _, user := range users { ids = append(ids, user.Id) } wg := sync.WaitGroup{} userList := model.UserList{ Lock: new(sync.Mutex), IdMap: make(map[uint64]*model.UserInfo, len(users)), } errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) go func(u *model.UserModel) { defer wg.Done() shortId, err := util.GenShortId() if err != nil { errChan <- err return } userList.Lock.Lock() defer userList.Lock.Unlock() userList.IdMap[u.Id] = &model.UserInfo{ Id: u.Id, Username: u.Username, SayHello: fmt.Sprintf("Hello %s", shortId), Password: u.Password, CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: u.UpdatedAt.Format("2006-01-02 15:04:05"), } }(u) } go func() { wg.Wait() close(finished) }() select { case <-finished: case err := <-errChan: return nil, count, err } for _, id := range ids { infos = append(infos, userList.IdMap[id]) } return infos, count, nil } ================================================ FILE: demo13/util/util.go ================================================ package util import ( "github.com/gin-gonic/gin" "github.com/teris-io/shortid" ) func GenShortId() (string, error) { return shortid.Generate() } func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") if !ok { return "" } if requestId, ok := v.(string); ok { return requestId } return "" } ================================================ FILE: demo14/Makefile ================================================ SHELL := /bin/bash BASEDIR = $(shell pwd) # build with verison infos versionDir = "apiserver/pkg/version" gitTag = $(shell if [ "`git describe --tags --abbrev=0 2>/dev/null`" != "" ];then git describe --tags --abbrev=0; else git log --pretty=format:'%h' -n 1; fi) buildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z) gitCommit = $(shell git log --pretty=format:'%H' -n 1) gitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi) ldflags="-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}" all: gotool @go build -v -ldflags ${ldflags} . clean: rm -f apiserver find . -name "[._]*.s[a-w][a-z]" | xargs -i rm -f {} gotool: gofmt -w . go tool vet . |& grep -v vendor;true ca: openssl req -new -nodes -x509 -out conf/server.crt -keyout conf/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=127.0.0.1/emailAddress=xxxxx@qq.com" help: @echo "make - compile the source code" @echo "make clean - remove binary file and vim swp files" @echo "make gotool - run go tool 'fmt' and 'vet'" @echo "make ca - generate ca files" .PHONY: clean gotool ca help ================================================ FILE: demo14/README.md ================================================ 进阶:基于Nginx的API部署方案 ================================================ FILE: demo14/admin.sh ================================================ #!/bin/bash SERVER="apiserver" BASE_DIR=$PWD INTERVAL=2 # 命令行参数,需要手动指定 ARGS="" function start() { if [ "`pgrep $SERVER -u $UID`" != "" ];then echo "$SERVER already running" exit 1 fi nohup $BASE_DIR/$SERVER $ARGS server &>/dev/null & echo "sleeping..." && sleep $INTERVAL # check status if [ "`pgrep $SERVER -u $UID`" == "" ];then echo "$SERVER start failed" exit 1 fi } function status() { if [ "`pgrep $SERVER -u $UID`" != "" ];then echo $SERVER is running else echo $SERVER is not running fi } function stop() { if [ "`pgrep $SERVER -u $UID`" != "" ];then kill -9 `pgrep $SERVER -u $UID` fi echo "sleeping..." && sleep $INTERVAL if [ "`pgrep $SERVER -u $UID`" != "" ];then echo "$SERVER stop failed" exit 1 fi } case "$1" in 'start') start ;; 'stop') stop ;; 'status') status ;; 'restart') stop && start ;; *) echo "usage: $0 {start|stop|restart|status}" exit 1 ;; esac ================================================ FILE: demo14/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 jwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5 tls: addr: :8081 cert: conf/server.crt key: conf/server.key log: writers: stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: true rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root docker_db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root ================================================ FILE: demo14/conf/server.crt ================================================ -----BEGIN CERTIFICATE----- MIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx GzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y ODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD VQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU MRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG 42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN q/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm yDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9 iaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM /nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk or8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud IwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz ANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel AnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD BgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6 8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B +8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g= -----END CERTIFICATE----- ================================================ FILE: demo14/conf/server.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv K5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB xxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt 8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my QKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls /BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc rmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK g59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA 2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4 NxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ fYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg tOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW Tkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt BZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV xpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe hmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl zRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8 Q3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW AOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd UOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F jd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds K1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg peNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2 4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/ MhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/ 2eBi4LlGO5stoeD63h9Ten0= -----END PRIVATE KEY----- ================================================ FILE: demo14/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo14/db.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db_apiserver`; -- -- Table structure for table `tb_users` -- DROP TABLE IF EXISTS `tb_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `createdAt` timestamp NULL DEFAULT NULL, `updatedAt` timestamp NULL DEFAULT NULL, `deletedAt` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), KEY `idx_tb_users_deletedAt` (`deletedAt`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_users` -- LOCK TABLES `tb_users` WRITE; /*!40000 ALTER TABLE `tb_users` DISABLE KEYS */; INSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL); /*!40000 ALTER TABLE `tb_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-05-28 0:25:41 ================================================ FILE: demo14/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo14/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo14/handler/user/create.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Create creates a new user account. func Create(c *gin.Context) { log.Info("User Create function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } u := model.UserModel{ Username: r.Username, Password: r.Password, } // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Insert the user to the database. if err := u.Create(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo14/handler/user/delete.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Delete delete an user by the user identifier. func Delete(c *gin.Context) { userId, _ := strconv.Atoi(c.Param("id")) if err := model.DeleteUser(uint64(userId)); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo14/handler/user/get.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Get gets an user by the user identifier. func Get(c *gin.Context) { username := c.Param("username") // Get the user by the `username` from the database. user, err := model.GetUser(username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } SendResponse(c, nil, user) } ================================================ FILE: demo14/handler/user/list.go ================================================ package user import ( . "apiserver/handler" "apiserver/pkg/errno" "apiserver/service" "github.com/gin-gonic/gin" "github.com/lexkong/log" ) // List list the users in the database. func List(c *gin.Context) { log.Info("List function called.") var r ListRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } infos, count, err := service.ListUser(r.Username, r.Offset, r.Limit) if err != nil { SendResponse(c, err, nil) return } SendResponse(c, nil, ListResponse{ TotalCount: count, UserList: infos, }) } ================================================ FILE: demo14/handler/user/login.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/auth" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) // Login generates the authentication token // if the password was matched with the specified account. func Login(c *gin.Context) { // Binding the data with the user struct. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // Get the user information by the login username. d, err := model.GetUser(u.Username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } // Compare the login password with the user password. if err := auth.Compare(d.Password, u.Password); err != nil { SendResponse(c, errno.ErrPasswordIncorrect, nil) return } // Sign the json web token. t, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, "") if err != nil { SendResponse(c, errno.ErrToken, nil) return } SendResponse(c, nil, model.Token{Token: t}) } ================================================ FILE: demo14/handler/user/update.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Update update a exist user account info. func Update(c *gin.Context) { log.Info("Update function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) // Get the user id from the url parameter. userId, _ := strconv.Atoi(c.Param("id")) // Binding the user data. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // We update the record based on the user id. u.Id = uint64(userId) // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Save changed fields. if err := u.Update(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo14/handler/user/user.go ================================================ package user import ( "apiserver/model" ) type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } type ListRequest struct { Username string `json:"username"` Offset int `json:"offset"` Limit int `json:"limit"` } type ListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []*model.UserInfo `json:"userList"` } ================================================ FILE: demo14/main.go ================================================ package main import ( "encoding/json" "errors" "fmt" "net/http" "os" "time" "apiserver/config" "apiserver/model" v "apiserver/pkg/version" "apiserver/router" "apiserver/router/middleware" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") version = pflag.BoolP("version", "v", false, "show version info.") ) func main() { pflag.Parse() if *version { v := v.Get() marshalled, err := json.MarshalIndent(&v, "", " ") if err != nil { fmt.Printf("%v\n", err) os.Exit(1) } fmt.Println(string(marshalled)) return } // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() // Routes. router.Load( // Cores. g, // Middlwares. middleware.Logging(), middleware.RequestId(), ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() // Start to listening the incoming requests. cert := viper.GetString("tls.cert") key := viper.GetString("tls.key") if cert != "" && key != "" { go func() { log.Infof("Start to listening the incoming requests on https address: %s", viper.GetString("tls.addr")) log.Info(http.ListenAndServeTLS(viper.GetString("tls.addr"), cert, key, g).Error()) }() } log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo14/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo14/model/model.go ================================================ package model import ( "sync" "time" ) type BaseModel struct { Id uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"` CreatedAt time.Time `gorm:"column:createdAt" json:"-"` UpdatedAt time.Time `gorm:"column:updatedAt" json:"-"` DeletedAt *time.Time `gorm:"column:deletedAt" sql:"index" json:"-"` } type UserInfo struct { Id uint64 `json:"id"` Username string `json:"username"` SayHello string `json:"sayHello"` Password string `json:"password"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type UserList struct { Lock *sync.Mutex IdMap map[uint64]*UserInfo } // Token represents a JSON web token. type Token struct { Token string `json:"token"` } ================================================ FILE: demo14/model/user.go ================================================ package model import ( "fmt" "apiserver/pkg/auth" "apiserver/pkg/constvar" validator "gopkg.in/go-playground/validator.v9" ) // User represents a registered user. type UserModel struct { BaseModel Username string `json:"username" gorm:"column:username;not null" binding:"required" validate:"min=1,max=32"` Password string `json:"password" gorm:"column:password;not null" binding:"required" validate:"min=5,max=128"` } func (c *UserModel) TableName() string { return "tb_users" } // Create creates a new user account. func (u *UserModel) Create() error { return DB.Self.Create(&u).Error } // DeleteUser deletes the user by the user identifier. func DeleteUser(id uint64) error { user := UserModel{} user.BaseModel.Id = id return DB.Self.Delete(&user).Error } // Update updates an user account information. func (u *UserModel) Update() error { return DB.Self.Save(u).Error } // GetUser gets an user by the user identifier. func GetUser(username string) (*UserModel, error) { u := &UserModel{} d := DB.Self.Where("username = ?", username).First(&u) return u, d.Error } // ListUser List all users func ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) { if limit == 0 { limit = constvar.DefaultLimit } users := make([]*UserModel, 0) var count uint64 where := fmt.Sprintf("username like '%%%s%%'", username) if err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil { return users, count, err } if err := DB.Self.Where(where).Offset(offset).Limit(limit).Order("id desc").Find(&users).Error; err != nil { return users, count, err } return users, count, nil } // Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct). func (u *UserModel) Compare(pwd string) (err error) { err = auth.Compare(u.Password, pwd) return } // Encrypt the user password. func (u *UserModel) Encrypt() (err error) { u.Password, err = auth.Encrypt(u.Password) return } // Validate the fields. func (u *UserModel) Validate() error { validate := validator.New() return validate.Struct(u) } ================================================ FILE: demo14/pkg/auth/auth.go ================================================ package auth import "golang.org/x/crypto/bcrypt" // Encrypt encrypts the plain text with bcrypt. func Encrypt(source string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) return string(hashedBytes), err } // Compare compares the encrypted text with the plain text if it's the same. func Compare(hashedPassword, password string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } ================================================ FILE: demo14/pkg/constvar/constvar.go ================================================ package constvar const ( DefaultLimit = 50 ) ================================================ FILE: demo14/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error"} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} ErrDatabase = &Errno{Code: 20002, Message: "Database error."} ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} // user errors ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} ErrPasswordIncorrect = &Errno{Code: 20104, Message: "The password was incorrect."} ) ================================================ FILE: demo14/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo14/pkg/token/token.go ================================================ package token import ( "errors" "fmt" "time" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) var ( // ErrMissingHeader means the `Authorization` header was empty. ErrMissingHeader = errors.New("The length of the `Authorization` header is zero.") ) // Context is the context of the JSON web token. type Context struct { ID uint64 Username string } // secretFunc validates the secret format. func secretFunc(secret string) jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { // Make sure the `alg` is what we except. if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(secret), nil } } // Parse validates the token with the specified secret, // and returns the context if the token was valid. func Parse(tokenString string, secret string) (*Context, error) { ctx := &Context{} // Parse the token. token, err := jwt.Parse(tokenString, secretFunc(secret)) // Parse error. if err != nil { return ctx, err // Read the token if it's valid. } else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { ctx.ID = uint64(claims["id"].(float64)) ctx.Username = claims["username"].(string) return ctx, nil // Other errors. } else { return ctx, err } } // ParseRequest gets the token from the header and // pass it to the Parse function to parses the token. func ParseRequest(c *gin.Context) (*Context, error) { header := c.Request.Header.Get("Authorization") // Load the jwt secret from config secret := viper.GetString("jwt_secret") if len(header) == 0 { return &Context{}, ErrMissingHeader } var t string // Parse the header to get the token part. fmt.Sscanf(header, "Bearer %s", &t) return Parse(t, secret) } // Sign signs the context with the specified secret. func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) { // Load the jwt secret from the Gin config if the secret isn't specified. if secret == "" { secret = viper.GetString("jwt_secret") } // The token content. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "id": c.ID, "username": c.Username, "nbf": time.Now().Unix(), "iat": time.Now().Unix(), }) // Sign the token with the specified secret. tokenString, err = token.SignedString([]byte(secret)) return } ================================================ FILE: demo14/pkg/version/base.go ================================================ package version var ( gitTag string = "" gitCommit string = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty" buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') ) ================================================ FILE: demo14/pkg/version/doc.go ================================================ package version ================================================ FILE: demo14/pkg/version/version.go ================================================ package version import ( "fmt" "runtime" ) // Info contains versioning information. type Info struct { GitTag string `json:"gitTag"` GitCommit string `json:"gitCommit"` GitTreeState string `json:"gitTreeState"` BuildDate string `json:"buildDate"` GoVersion string `json:"goVersion"` Compiler string `json:"compiler"` Platform string `json:"platform"` } // String returns info as a human-friendly version string. func (info Info) String() string { return info.GitTag } func Get() Info { return Info{ GitTag: gitTag, GitCommit: gitCommit, GitTreeState: gitTreeState, BuildDate: buildDate, GoVersion: runtime.Version(), Compiler: runtime.Compiler, Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), } } ================================================ FILE: demo14/router/middleware/auth.go ================================================ package middleware import ( "apiserver/handler" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Parse the json web token. if _, err := token.ParseRequest(c); err != nil { handler.SendResponse(c, errno.ErrTokenInvalid, nil) c.Abort() return } c.Next() } } ================================================ FILE: demo14/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo14/router/middleware/logging.go ================================================ package middleware import ( "bytes" "encoding/json" "io/ioutil" "regexp" "time" "apiserver/handler" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/willf/pad" ) type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) } // Logging is a middleware function that logs the each request. func Logging() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now().UTC() path := c.Request.URL.Path reg := regexp.MustCompile("(/v1/user|/login)") if !reg.MatchString(path) { return } // Skip for the health check requests. if path == "/sd/health" || path == "/sd/ram" || path == "/sd/cpu" || path == "/sd/disk" { return } // Read the Body content var bodyBytes []byte if c.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(c.Request.Body) } // Restore the io.ReadCloser to its original state c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // The basic informations. method := c.Request.Method ip := c.ClientIP() //log.Debugf("New request come in, path: %s, Method: %s, body `%s`", path, method, string(bodyBytes)) blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: c.Writer, } c.Writer = blw // Continue. c.Next() // Calculates the latency. end := time.Now().UTC() latency := end.Sub(start) code, message := -1, "" // get code and message var response handler.Response if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { log.Errorf(err, "response body can not unmarshal to model.Response struct, body: `%s`", blw.body.Bytes()) code = errno.InternalServerError.Code message = err.Error() } else { code = response.Code message = response.Message } log.Infof("%-13s | %-12s | %s %s | {code: %d, message: %s}", latency, ip, pad.Right(method, 5, ""), path, code, message) } } ================================================ FILE: demo14/router/middleware/requestid.go ================================================ package middleware import ( "github.com/gin-gonic/gin" "github.com/satori/go.uuid" ) func RequestId() gin.HandlerFunc { return func(c *gin.Context) { // Check for incoming header, use it if exists requestId := c.Request.Header.Get("X-Request-Id") // Create request id with UUID4 if requestId == "" { u4, _ := uuid.NewV4() requestId = u4.String() } // Expose it for use in the application c.Set("X-Request-Id", requestId) // Set X-Request-Id header c.Writer.Header().Set("X-Request-Id", requestId) c.Next() } } ================================================ FILE: demo14/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // api for authentication functionalities g.POST("/login", user.Login) // The user handlers, requiring authentication u := g.Group("/v1/user") u.Use(middleware.AuthMiddleware()) { u.POST("", user.Create) u.DELETE("/:id", user.Delete) u.PUT("/:id", user.Update) u.GET("", user.List) u.GET("/:username", user.Get) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo14/service/service.go ================================================ package service import ( "fmt" "sync" "apiserver/model" "apiserver/util" ) func ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) { infos := make([]*model.UserInfo, 0) users, count, err := model.ListUser(username, offset, limit) if err != nil { return nil, count, err } ids := []uint64{} for _, user := range users { ids = append(ids, user.Id) } wg := sync.WaitGroup{} userList := model.UserList{ Lock: new(sync.Mutex), IdMap: make(map[uint64]*model.UserInfo, len(users)), } errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) go func(u *model.UserModel) { defer wg.Done() shortId, err := util.GenShortId() if err != nil { errChan <- err return } userList.Lock.Lock() defer userList.Lock.Unlock() userList.IdMap[u.Id] = &model.UserInfo{ Id: u.Id, Username: u.Username, SayHello: fmt.Sprintf("Hello %s", shortId), Password: u.Password, CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: u.UpdatedAt.Format("2006-01-02 15:04:05"), } }(u) } go func() { wg.Wait() close(finished) }() select { case <-finished: case err := <-errChan: return nil, count, err } for _, id := range ids { infos = append(infos, userList.IdMap[id]) } return infos, count, nil } ================================================ FILE: demo14/util/util.go ================================================ package util import ( "github.com/gin-gonic/gin" "github.com/teris-io/shortid" ) func GenShortId() (string, error) { return shortid.Generate() } func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") if !ok { return "" } if requestId, ok := v.(string); ok { return requestId } return "" } ================================================ FILE: demo15/Makefile ================================================ SHELL := /bin/bash BASEDIR = $(shell pwd) # build with verison infos versionDir = "apiserver/pkg/version" gitTag = $(shell if [ "`git describe --tags --abbrev=0 2>/dev/null`" != "" ];then git describe --tags --abbrev=0; else git log --pretty=format:'%h' -n 1; fi) buildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z) gitCommit = $(shell git log --pretty=format:'%H' -n 1) gitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi) ldflags="-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}" all: gotool @go build -v -ldflags ${ldflags} . clean: rm -f apiserver find . -name "[._]*.s[a-w][a-z]" | xargs -i rm -f {} gotool: gofmt -w . go tool vet . |& grep -v vendor;true ca: openssl req -new -nodes -x509 -out conf/server.crt -keyout conf/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=127.0.0.1/emailAddress=xxxxx@qq.com" help: @echo "make - compile the source code" @echo "make clean - remove binary file and vim swp files" @echo "make gotool - run go tool 'fmt' and 'vet'" @echo "make ca - generate ca files" .PHONY: clean gotool ca help ================================================ FILE: demo15/README.md ================================================ 进阶:go test测试你的代码 ================================================ FILE: demo15/admin.sh ================================================ #!/bin/bash SERVER="apiserver" BASE_DIR=$PWD INTERVAL=2 # 命令行参数,需要手动指定 ARGS="" function start() { if [ "`pgrep $SERVER -u $UID`" != "" ];then echo "$SERVER already running" exit 1 fi nohup $BASE_DIR/$SERVER $ARGS server &>/dev/null & echo "sleeping..." && sleep $INTERVAL # check status if [ "`pgrep $SERVER -u $UID`" == "" ];then echo "$SERVER start failed" exit 1 fi } function status() { if [ "`pgrep $SERVER -u $UID`" != "" ];then echo $SERVER is running else echo $SERVER is not running fi } function stop() { if [ "`pgrep $SERVER -u $UID`" != "" ];then kill -9 `pgrep $SERVER -u $UID` fi echo "sleeping..." && sleep $INTERVAL if [ "`pgrep $SERVER -u $UID`" != "" ];then echo "$SERVER stop failed" exit 1 fi } case "$1" in 'start') start ;; 'stop') stop ;; 'status') status ;; 'restart') stop && start ;; *) echo "usage: $0 {start|stop|restart|status}" exit 1 ;; esac ================================================ FILE: demo15/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 jwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5 tls: addr: :8081 cert: conf/server.crt key: conf/server.key log: writers: stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: true rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root docker_db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root ================================================ FILE: demo15/conf/server.crt ================================================ -----BEGIN CERTIFICATE----- MIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx GzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y ODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD VQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU MRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG 42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN q/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm yDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9 iaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM /nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk or8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud IwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz ANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel AnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD BgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6 8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B +8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g= -----END CERTIFICATE----- ================================================ FILE: demo15/conf/server.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv K5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB xxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt 8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my QKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls /BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc rmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK g59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA 2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4 NxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ fYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg tOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW Tkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt BZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV xpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe hmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl zRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8 Q3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW AOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd UOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F jd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds K1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg peNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2 4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/ MhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/ 2eBi4LlGO5stoeD63h9Ten0= -----END PRIVATE KEY----- ================================================ FILE: demo15/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo15/db.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db_apiserver`; -- -- Table structure for table `tb_users` -- DROP TABLE IF EXISTS `tb_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `createdAt` timestamp NULL DEFAULT NULL, `updatedAt` timestamp NULL DEFAULT NULL, `deletedAt` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), KEY `idx_tb_users_deletedAt` (`deletedAt`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_users` -- LOCK TABLES `tb_users` WRITE; /*!40000 ALTER TABLE `tb_users` DISABLE KEYS */; INSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL); /*!40000 ALTER TABLE `tb_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-05-28 0:25:41 ================================================ FILE: demo15/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo15/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo15/handler/user/create.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Create creates a new user account. func Create(c *gin.Context) { log.Info("User Create function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } u := model.UserModel{ Username: r.Username, Password: r.Password, } // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Insert the user to the database. if err := u.Create(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo15/handler/user/delete.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Delete delete an user by the user identifier. func Delete(c *gin.Context) { userId, _ := strconv.Atoi(c.Param("id")) if err := model.DeleteUser(uint64(userId)); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo15/handler/user/get.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Get gets an user by the user identifier. func Get(c *gin.Context) { username := c.Param("username") // Get the user by the `username` from the database. user, err := model.GetUser(username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } SendResponse(c, nil, user) } ================================================ FILE: demo15/handler/user/list.go ================================================ package user import ( . "apiserver/handler" "apiserver/pkg/errno" "apiserver/service" "github.com/gin-gonic/gin" "github.com/lexkong/log" ) // List list the users in the database. func List(c *gin.Context) { log.Info("List function called.") var r ListRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } infos, count, err := service.ListUser(r.Username, r.Offset, r.Limit) if err != nil { SendResponse(c, err, nil) return } SendResponse(c, nil, ListResponse{ TotalCount: count, UserList: infos, }) } ================================================ FILE: demo15/handler/user/login.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/auth" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) // Login generates the authentication token // if the password was matched with the specified account. func Login(c *gin.Context) { // Binding the data with the user struct. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // Get the user information by the login username. d, err := model.GetUser(u.Username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } // Compare the login password with the user password. if err := auth.Compare(d.Password, u.Password); err != nil { SendResponse(c, errno.ErrPasswordIncorrect, nil) return } // Sign the json web token. t, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, "") if err != nil { SendResponse(c, errno.ErrToken, nil) return } SendResponse(c, nil, model.Token{Token: t}) } ================================================ FILE: demo15/handler/user/update.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Update update a exist user account info. func Update(c *gin.Context) { log.Info("Update function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) // Get the user id from the url parameter. userId, _ := strconv.Atoi(c.Param("id")) // Binding the user data. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // We update the record based on the user id. u.Id = uint64(userId) // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Save changed fields. if err := u.Update(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo15/handler/user/user.go ================================================ package user import ( "apiserver/model" ) type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } type ListRequest struct { Username string `json:"username"` Offset int `json:"offset"` Limit int `json:"limit"` } type ListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []*model.UserInfo `json:"userList"` } ================================================ FILE: demo15/main.go ================================================ package main import ( "encoding/json" "errors" "fmt" "net/http" "os" "time" "apiserver/config" "apiserver/model" v "apiserver/pkg/version" "apiserver/router" "apiserver/router/middleware" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") version = pflag.BoolP("version", "v", false, "show version info.") ) func main() { pflag.Parse() if *version { v := v.Get() marshalled, err := json.MarshalIndent(&v, "", " ") if err != nil { fmt.Printf("%v\n", err) os.Exit(1) } fmt.Println(string(marshalled)) return } // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() // Routes. router.Load( // Cores. g, // Middlwares. middleware.Logging(), middleware.RequestId(), ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() // Start to listening the incoming requests. cert := viper.GetString("tls.cert") key := viper.GetString("tls.key") if cert != "" && key != "" { go func() { log.Infof("Start to listening the incoming requests on https address: %s", viper.GetString("tls.addr")) log.Info(http.ListenAndServeTLS(viper.GetString("tls.addr"), cert, key, g).Error()) }() } log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo15/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo15/model/model.go ================================================ package model import ( "sync" "time" ) type BaseModel struct { Id uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"` CreatedAt time.Time `gorm:"column:createdAt" json:"-"` UpdatedAt time.Time `gorm:"column:updatedAt" json:"-"` DeletedAt *time.Time `gorm:"column:deletedAt" sql:"index" json:"-"` } type UserInfo struct { Id uint64 `json:"id"` Username string `json:"username"` SayHello string `json:"sayHello"` Password string `json:"password"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type UserList struct { Lock *sync.Mutex IdMap map[uint64]*UserInfo } // Token represents a JSON web token. type Token struct { Token string `json:"token"` } ================================================ FILE: demo15/model/user.go ================================================ package model import ( "fmt" "apiserver/pkg/auth" "apiserver/pkg/constvar" validator "gopkg.in/go-playground/validator.v9" ) // User represents a registered user. type UserModel struct { BaseModel Username string `json:"username" gorm:"column:username;not null" binding:"required" validate:"min=1,max=32"` Password string `json:"password" gorm:"column:password;not null" binding:"required" validate:"min=5,max=128"` } func (c *UserModel) TableName() string { return "tb_users" } // Create creates a new user account. func (u *UserModel) Create() error { return DB.Self.Create(&u).Error } // DeleteUser deletes the user by the user identifier. func DeleteUser(id uint64) error { user := UserModel{} user.BaseModel.Id = id return DB.Self.Delete(&user).Error } // Update updates an user account information. func (u *UserModel) Update() error { return DB.Self.Save(u).Error } // GetUser gets an user by the user identifier. func GetUser(username string) (*UserModel, error) { u := &UserModel{} d := DB.Self.Where("username = ?", username).First(&u) return u, d.Error } // ListUser List all users func ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) { if limit == 0 { limit = constvar.DefaultLimit } users := make([]*UserModel, 0) var count uint64 where := fmt.Sprintf("username like '%%%s%%'", username) if err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil { return users, count, err } if err := DB.Self.Where(where).Offset(offset).Limit(limit).Order("id desc").Find(&users).Error; err != nil { return users, count, err } return users, count, nil } // Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct). func (u *UserModel) Compare(pwd string) (err error) { err = auth.Compare(u.Password, pwd) return } // Encrypt the user password. func (u *UserModel) Encrypt() (err error) { u.Password, err = auth.Encrypt(u.Password) return } // Validate the fields. func (u *UserModel) Validate() error { validate := validator.New() return validate.Struct(u) } ================================================ FILE: demo15/pkg/auth/auth.go ================================================ package auth import "golang.org/x/crypto/bcrypt" // Encrypt encrypts the plain text with bcrypt. func Encrypt(source string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) return string(hashedBytes), err } // Compare compares the encrypted text with the plain text if it's the same. func Compare(hashedPassword, password string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } ================================================ FILE: demo15/pkg/constvar/constvar.go ================================================ package constvar const ( DefaultLimit = 50 ) ================================================ FILE: demo15/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error"} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} ErrDatabase = &Errno{Code: 20002, Message: "Database error."} ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} // user errors ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} ErrPasswordIncorrect = &Errno{Code: 20104, Message: "The password was incorrect."} ) ================================================ FILE: demo15/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo15/pkg/token/token.go ================================================ package token import ( "errors" "fmt" "time" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) var ( // ErrMissingHeader means the `Authorization` header was empty. ErrMissingHeader = errors.New("The length of the `Authorization` header is zero.") ) // Context is the context of the JSON web token. type Context struct { ID uint64 Username string } // secretFunc validates the secret format. func secretFunc(secret string) jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { // Make sure the `alg` is what we except. if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(secret), nil } } // Parse validates the token with the specified secret, // and returns the context if the token was valid. func Parse(tokenString string, secret string) (*Context, error) { ctx := &Context{} // Parse the token. token, err := jwt.Parse(tokenString, secretFunc(secret)) // Parse error. if err != nil { return ctx, err // Read the token if it's valid. } else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { ctx.ID = uint64(claims["id"].(float64)) ctx.Username = claims["username"].(string) return ctx, nil // Other errors. } else { return ctx, err } } // ParseRequest gets the token from the header and // pass it to the Parse function to parses the token. func ParseRequest(c *gin.Context) (*Context, error) { header := c.Request.Header.Get("Authorization") // Load the jwt secret from config secret := viper.GetString("jwt_secret") if len(header) == 0 { return &Context{}, ErrMissingHeader } var t string // Parse the header to get the token part. fmt.Sscanf(header, "Bearer %s", &t) return Parse(t, secret) } // Sign signs the context with the specified secret. func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) { // Load the jwt secret from the Gin config if the secret isn't specified. if secret == "" { secret = viper.GetString("jwt_secret") } // The token content. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "id": c.ID, "username": c.Username, "nbf": time.Now().Unix(), "iat": time.Now().Unix(), }) // Sign the token with the specified secret. tokenString, err = token.SignedString([]byte(secret)) return } ================================================ FILE: demo15/pkg/version/base.go ================================================ package version var ( gitTag string = "" gitCommit string = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty" buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') ) ================================================ FILE: demo15/pkg/version/doc.go ================================================ package version ================================================ FILE: demo15/pkg/version/version.go ================================================ package version import ( "fmt" "runtime" ) // Info contains versioning information. type Info struct { GitTag string `json:"gitTag"` GitCommit string `json:"gitCommit"` GitTreeState string `json:"gitTreeState"` BuildDate string `json:"buildDate"` GoVersion string `json:"goVersion"` Compiler string `json:"compiler"` Platform string `json:"platform"` } // String returns info as a human-friendly version string. func (info Info) String() string { return info.GitTag } func Get() Info { return Info{ GitTag: gitTag, GitCommit: gitCommit, GitTreeState: gitTreeState, BuildDate: buildDate, GoVersion: runtime.Version(), Compiler: runtime.Compiler, Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), } } ================================================ FILE: demo15/router/middleware/auth.go ================================================ package middleware import ( "apiserver/handler" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Parse the json web token. if _, err := token.ParseRequest(c); err != nil { handler.SendResponse(c, errno.ErrTokenInvalid, nil) c.Abort() return } c.Next() } } ================================================ FILE: demo15/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo15/router/middleware/logging.go ================================================ package middleware import ( "bytes" "encoding/json" "io/ioutil" "regexp" "time" "apiserver/handler" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/willf/pad" ) type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) } // Logging is a middleware function that logs the each request. func Logging() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now().UTC() path := c.Request.URL.Path reg := regexp.MustCompile("(/v1/user|/login)") if !reg.MatchString(path) { return } // Skip for the health check requests. if path == "/sd/health" || path == "/sd/ram" || path == "/sd/cpu" || path == "/sd/disk" { return } // Read the Body content var bodyBytes []byte if c.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(c.Request.Body) } // Restore the io.ReadCloser to its original state c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // The basic informations. method := c.Request.Method ip := c.ClientIP() //log.Debugf("New request come in, path: %s, Method: %s, body `%s`", path, method, string(bodyBytes)) blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: c.Writer, } c.Writer = blw // Continue. c.Next() // Calculates the latency. end := time.Now().UTC() latency := end.Sub(start) code, message := -1, "" // get code and message var response handler.Response if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { log.Errorf(err, "response body can not unmarshal to model.Response struct, body: `%s`", blw.body.Bytes()) code = errno.InternalServerError.Code message = err.Error() } else { code = response.Code message = response.Message } log.Infof("%-13s | %-12s | %s %s | {code: %d, message: %s}", latency, ip, pad.Right(method, 5, ""), path, code, message) } } ================================================ FILE: demo15/router/middleware/requestid.go ================================================ package middleware import ( "github.com/gin-gonic/gin" "github.com/satori/go.uuid" ) func RequestId() gin.HandlerFunc { return func(c *gin.Context) { // Check for incoming header, use it if exists requestId := c.Request.Header.Get("X-Request-Id") // Create request id with UUID4 if requestId == "" { u4, _ := uuid.NewV4() requestId = u4.String() } // Expose it for use in the application c.Set("X-Request-Id", requestId) // Set X-Request-Id header c.Writer.Header().Set("X-Request-Id", requestId) c.Next() } } ================================================ FILE: demo15/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // api for authentication functionalities g.POST("/login", user.Login) // The user handlers, requiring authentication u := g.Group("/v1/user") u.Use(middleware.AuthMiddleware()) { u.POST("", user.Create) u.DELETE("/:id", user.Delete) u.PUT("/:id", user.Update) u.GET("", user.List) u.GET("/:username", user.Get) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo15/service/service.go ================================================ package service import ( "fmt" "sync" "apiserver/model" "apiserver/util" ) func ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) { infos := make([]*model.UserInfo, 0) users, count, err := model.ListUser(username, offset, limit) if err != nil { return nil, count, err } ids := []uint64{} for _, user := range users { ids = append(ids, user.Id) } wg := sync.WaitGroup{} userList := model.UserList{ Lock: new(sync.Mutex), IdMap: make(map[uint64]*model.UserInfo, len(users)), } errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) go func(u *model.UserModel) { defer wg.Done() shortId, err := util.GenShortId() if err != nil { errChan <- err return } userList.Lock.Lock() defer userList.Lock.Unlock() userList.IdMap[u.Id] = &model.UserInfo{ Id: u.Id, Username: u.Username, SayHello: fmt.Sprintf("Hello %s", shortId), Password: u.Password, CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: u.UpdatedAt.Format("2006-01-02 15:04:05"), } }(u) } go func() { wg.Wait() close(finished) }() select { case <-finished: case err := <-errChan: return nil, count, err } for _, id := range ids { infos = append(infos, userList.IdMap[id]) } return infos, count, nil } ================================================ FILE: demo15/util/util.go ================================================ package util import ( "github.com/gin-gonic/gin" "github.com/teris-io/shortid" ) func GenShortId() (string, error) { return shortid.Generate() } func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") if !ok { return "" } if requestId, ok := v.(string); ok { return requestId } return "" } ================================================ FILE: demo15/util/util_test.go ================================================ package util import ( "testing" ) func TestGenShortId(t *testing.T) { shortId, err := GenShortId() if shortId == "" || err != nil { t.Error("GenShortId failed!") } t.Log("GenShortId test pass") } func BenchmarkGenShortId(b *testing.B) { for i := 0; i < b.N; i++ { GenShortId() } } func BenchmarkGenShortIdTimeConsuming(b *testing.B) { b.StopTimer() //调用该函数停止压力测试的时间计数 shortId, err := GenShortId() if shortId == "" || err != nil { b.Error(err) } b.StartTimer() //重新开始时间 for i := 0; i < b.N; i++ { GenShortId() } } ================================================ FILE: demo16/Makefile ================================================ SHELL := /bin/bash BASEDIR = $(shell pwd) # build with verison infos versionDir = "apiserver/pkg/version" gitTag = $(shell if [ "`git describe --tags --abbrev=0 2>/dev/null`" != "" ];then git describe --tags --abbrev=0; else git log --pretty=format:'%h' -n 1; fi) buildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z) gitCommit = $(shell git log --pretty=format:'%H' -n 1) gitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi) ldflags="-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}" all: gotool @go build -v -ldflags ${ldflags} . clean: rm -f apiserver find . -name "[._]*.s[a-w][a-z]" | xargs -i rm -f {} gotool: gofmt -w . go tool vet . |& grep -v vendor;true ca: openssl req -new -nodes -x509 -out conf/server.crt -keyout conf/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=127.0.0.1/emailAddress=xxxxx@qq.com" help: @echo "make - compile the source code" @echo "make clean - remove binary file and vim swp files" @echo "make gotool - run go tool 'fmt' and 'vet'" @echo "make ca - generate ca files" .PHONY: clean gotool ca help ================================================ FILE: demo16/README.md ================================================ 进阶:API性能分析 ================================================ FILE: demo16/admin.sh ================================================ #!/bin/bash SERVER="apiserver" BASE_DIR=$PWD INTERVAL=2 # 命令行参数,需要手动指定 ARGS="" function start() { if [ "`pgrep $SERVER -u $UID`" != "" ];then echo "$SERVER already running" exit 1 fi nohup $BASE_DIR/$SERVER $ARGS server &>/dev/null & echo "sleeping..." && sleep $INTERVAL # check status if [ "`pgrep $SERVER -u $UID`" == "" ];then echo "$SERVER start failed" exit 1 fi } function status() { if [ "`pgrep $SERVER -u $UID`" != "" ];then echo $SERVER is running else echo $SERVER is not running fi } function stop() { if [ "`pgrep $SERVER -u $UID`" != "" ];then kill -9 `pgrep $SERVER -u $UID` fi echo "sleeping..." && sleep $INTERVAL if [ "`pgrep $SERVER -u $UID`" != "" ];then echo "$SERVER stop failed" exit 1 fi } case "$1" in 'start') start ;; 'stop') stop ;; 'status') status ;; 'restart') stop && start ;; *) echo "usage: $0 {start|stop|restart|status}" exit 1 ;; esac ================================================ FILE: demo16/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 jwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5 tls: addr: :8081 cert: conf/server.crt key: conf/server.key log: writers: stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: true rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root docker_db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root ================================================ FILE: demo16/conf/server.crt ================================================ -----BEGIN CERTIFICATE----- MIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx GzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y ODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD VQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU MRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG 42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN q/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm yDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9 iaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM /nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk or8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud IwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz ANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel AnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD BgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6 8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B +8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g= -----END CERTIFICATE----- ================================================ FILE: demo16/conf/server.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv K5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB xxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt 8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my QKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls /BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc rmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK g59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA 2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4 NxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ fYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg tOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW Tkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt BZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV xpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe hmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl zRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8 Q3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW AOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd UOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F jd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds K1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg peNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2 4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/ MhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/ 2eBi4LlGO5stoeD63h9Ten0= -----END PRIVATE KEY----- ================================================ FILE: demo16/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo16/db.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db_apiserver`; -- -- Table structure for table `tb_users` -- DROP TABLE IF EXISTS `tb_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `createdAt` timestamp NULL DEFAULT NULL, `updatedAt` timestamp NULL DEFAULT NULL, `deletedAt` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), KEY `idx_tb_users_deletedAt` (`deletedAt`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_users` -- LOCK TABLES `tb_users` WRITE; /*!40000 ALTER TABLE `tb_users` DISABLE KEYS */; INSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL); /*!40000 ALTER TABLE `tb_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-05-28 0:25:41 ================================================ FILE: demo16/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo16/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // HealthCheck shows `OK` as the ping-pong result. func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // DiskCheck checks the disk usage. func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // CPUCheck checks the cpu usage. func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // RAMCheck checks the disk usage. func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo16/handler/user/create.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Create creates a new user account. func Create(c *gin.Context) { log.Info("User Create function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } u := model.UserModel{ Username: r.Username, Password: r.Password, } // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Insert the user to the database. if err := u.Create(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo16/handler/user/delete.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Delete delete an user by the user identifier. func Delete(c *gin.Context) { userId, _ := strconv.Atoi(c.Param("id")) if err := model.DeleteUser(uint64(userId)); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo16/handler/user/get.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // Get gets an user by the user identifier. func Get(c *gin.Context) { username := c.Param("username") // Get the user by the `username` from the database. user, err := model.GetUser(username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } SendResponse(c, nil, user) } ================================================ FILE: demo16/handler/user/list.go ================================================ package user import ( . "apiserver/handler" "apiserver/pkg/errno" "apiserver/service" "github.com/gin-gonic/gin" "github.com/lexkong/log" ) // List list the users in the database. func List(c *gin.Context) { log.Info("List function called.") var r ListRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } infos, count, err := service.ListUser(r.Username, r.Offset, r.Limit) if err != nil { SendResponse(c, err, nil) return } SendResponse(c, nil, ListResponse{ TotalCount: count, UserList: infos, }) } ================================================ FILE: demo16/handler/user/login.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/auth" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) // Login generates the authentication token // if the password was matched with the specified account. func Login(c *gin.Context) { // Binding the data with the user struct. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // Get the user information by the login username. d, err := model.GetUser(u.Username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } // Compare the login password with the user password. if err := auth.Compare(d.Password, u.Password); err != nil { SendResponse(c, errno.ErrPasswordIncorrect, nil) return } // Sign the json web token. t, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, "") if err != nil { SendResponse(c, errno.ErrToken, nil) return } SendResponse(c, nil, model.Token{Token: t}) } ================================================ FILE: demo16/handler/user/update.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // Update update a exist user account info. func Update(c *gin.Context) { log.Info("Update function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) // Get the user id from the url parameter. userId, _ := strconv.Atoi(c.Param("id")) // Binding the user data. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // We update the record based on the user id. u.Id = uint64(userId) // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Save changed fields. if err := u.Update(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo16/handler/user/user.go ================================================ package user import ( "apiserver/model" ) type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } type ListRequest struct { Username string `json:"username"` Offset int `json:"offset"` Limit int `json:"limit"` } type ListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []*model.UserInfo `json:"userList"` } ================================================ FILE: demo16/main.go ================================================ package main import ( "encoding/json" "errors" "fmt" "net/http" _ "net/http/pprof" "os" "time" "apiserver/config" "apiserver/model" v "apiserver/pkg/version" "apiserver/router" "apiserver/router/middleware" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") version = pflag.BoolP("version", "v", false, "show version info.") ) func main() { pflag.Parse() if *version { v := v.Get() marshalled, err := json.MarshalIndent(&v, "", " ") if err != nil { fmt.Printf("%v\n", err) os.Exit(1) } fmt.Println(string(marshalled)) return } // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() // Routes. router.Load( // Cores. g, // Middlwares. middleware.Logging(), middleware.RequestId(), ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() // Start to listening the incoming requests. cert := viper.GetString("tls.cert") key := viper.GetString("tls.key") if cert != "" && key != "" { go func() { log.Infof("Start to listening the incoming requests on https address: %s", viper.GetString("tls.addr")) log.Info(http.ListenAndServeTLS(viper.GetString("tls.addr"), cert, key, g).Error()) }() } log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo16/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo16/model/model.go ================================================ package model import ( "sync" "time" ) type BaseModel struct { Id uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"` CreatedAt time.Time `gorm:"column:createdAt" json:"-"` UpdatedAt time.Time `gorm:"column:updatedAt" json:"-"` DeletedAt *time.Time `gorm:"column:deletedAt" sql:"index" json:"-"` } type UserInfo struct { Id uint64 `json:"id"` Username string `json:"username"` SayHello string `json:"sayHello"` Password string `json:"password"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type UserList struct { Lock *sync.Mutex IdMap map[uint64]*UserInfo } // Token represents a JSON web token. type Token struct { Token string `json:"token"` } ================================================ FILE: demo16/model/user.go ================================================ package model import ( "fmt" "apiserver/pkg/auth" "apiserver/pkg/constvar" validator "gopkg.in/go-playground/validator.v9" ) // User represents a registered user. type UserModel struct { BaseModel Username string `json:"username" gorm:"column:username;not null" binding:"required" validate:"min=1,max=32"` Password string `json:"password" gorm:"column:password;not null" binding:"required" validate:"min=5,max=128"` } func (c *UserModel) TableName() string { return "tb_users" } // Create creates a new user account. func (u *UserModel) Create() error { return DB.Self.Create(&u).Error } // DeleteUser deletes the user by the user identifier. func DeleteUser(id uint64) error { user := UserModel{} user.BaseModel.Id = id return DB.Self.Delete(&user).Error } // Update updates an user account information. func (u *UserModel) Update() error { return DB.Self.Save(u).Error } // GetUser gets an user by the user identifier. func GetUser(username string) (*UserModel, error) { u := &UserModel{} d := DB.Self.Where("username = ?", username).First(&u) return u, d.Error } // ListUser List all users func ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) { if limit == 0 { limit = constvar.DefaultLimit } users := make([]*UserModel, 0) var count uint64 where := fmt.Sprintf("username like '%%%s%%'", username) if err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil { return users, count, err } if err := DB.Self.Where(where).Offset(offset).Limit(limit).Order("id desc").Find(&users).Error; err != nil { return users, count, err } return users, count, nil } // Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct). func (u *UserModel) Compare(pwd string) (err error) { err = auth.Compare(u.Password, pwd) return } // Encrypt the user password. func (u *UserModel) Encrypt() (err error) { u.Password, err = auth.Encrypt(u.Password) return } // Validate the fields. func (u *UserModel) Validate() error { validate := validator.New() return validate.Struct(u) } ================================================ FILE: demo16/pkg/auth/auth.go ================================================ package auth import "golang.org/x/crypto/bcrypt" // Encrypt encrypts the plain text with bcrypt. func Encrypt(source string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) return string(hashedBytes), err } // Compare compares the encrypted text with the plain text if it's the same. func Compare(hashedPassword, password string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } ================================================ FILE: demo16/pkg/constvar/constvar.go ================================================ package constvar const ( DefaultLimit = 50 ) ================================================ FILE: demo16/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error"} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} ErrDatabase = &Errno{Code: 20002, Message: "Database error."} ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} // user errors ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} ErrPasswordIncorrect = &Errno{Code: 20104, Message: "The password was incorrect."} ) ================================================ FILE: demo16/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo16/pkg/token/token.go ================================================ package token import ( "errors" "fmt" "time" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) var ( // ErrMissingHeader means the `Authorization` header was empty. ErrMissingHeader = errors.New("The length of the `Authorization` header is zero.") ) // Context is the context of the JSON web token. type Context struct { ID uint64 Username string } // secretFunc validates the secret format. func secretFunc(secret string) jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { // Make sure the `alg` is what we except. if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(secret), nil } } // Parse validates the token with the specified secret, // and returns the context if the token was valid. func Parse(tokenString string, secret string) (*Context, error) { ctx := &Context{} // Parse the token. token, err := jwt.Parse(tokenString, secretFunc(secret)) // Parse error. if err != nil { return ctx, err // Read the token if it's valid. } else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { ctx.ID = uint64(claims["id"].(float64)) ctx.Username = claims["username"].(string) return ctx, nil // Other errors. } else { return ctx, err } } // ParseRequest gets the token from the header and // pass it to the Parse function to parses the token. func ParseRequest(c *gin.Context) (*Context, error) { header := c.Request.Header.Get("Authorization") // Load the jwt secret from config secret := viper.GetString("jwt_secret") if len(header) == 0 { return &Context{}, ErrMissingHeader } var t string // Parse the header to get the token part. fmt.Sscanf(header, "Bearer %s", &t) return Parse(t, secret) } // Sign signs the context with the specified secret. func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) { // Load the jwt secret from the Gin config if the secret isn't specified. if secret == "" { secret = viper.GetString("jwt_secret") } // The token content. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "id": c.ID, "username": c.Username, "nbf": time.Now().Unix(), "iat": time.Now().Unix(), }) // Sign the token with the specified secret. tokenString, err = token.SignedString([]byte(secret)) return } ================================================ FILE: demo16/pkg/version/base.go ================================================ package version var ( gitTag string = "" gitCommit string = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty" buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') ) ================================================ FILE: demo16/pkg/version/doc.go ================================================ package version ================================================ FILE: demo16/pkg/version/version.go ================================================ package version import ( "fmt" "runtime" ) // Info contains versioning information. type Info struct { GitTag string `json:"gitTag"` GitCommit string `json:"gitCommit"` GitTreeState string `json:"gitTreeState"` BuildDate string `json:"buildDate"` GoVersion string `json:"goVersion"` Compiler string `json:"compiler"` Platform string `json:"platform"` } // String returns info as a human-friendly version string. func (info Info) String() string { return info.GitTag } func Get() Info { return Info{ GitTag: gitTag, GitCommit: gitCommit, GitTreeState: gitTreeState, BuildDate: buildDate, GoVersion: runtime.Version(), Compiler: runtime.Compiler, Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), } } ================================================ FILE: demo16/router/middleware/auth.go ================================================ package middleware import ( "apiserver/handler" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Parse the json web token. if _, err := token.ParseRequest(c); err != nil { handler.SendResponse(c, errno.ErrTokenInvalid, nil) c.Abort() return } c.Next() } } ================================================ FILE: demo16/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo16/router/middleware/logging.go ================================================ package middleware import ( "bytes" "encoding/json" "io/ioutil" "regexp" "time" "apiserver/handler" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/willf/pad" ) type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) } // Logging is a middleware function that logs the each request. func Logging() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now().UTC() path := c.Request.URL.Path reg := regexp.MustCompile("(/v1/user|/login)") if !reg.MatchString(path) { return } // Skip for the health check requests. if path == "/sd/health" || path == "/sd/ram" || path == "/sd/cpu" || path == "/sd/disk" { return } // Read the Body content var bodyBytes []byte if c.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(c.Request.Body) } // Restore the io.ReadCloser to its original state c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // The basic informations. method := c.Request.Method ip := c.ClientIP() //log.Debugf("New request come in, path: %s, Method: %s, body `%s`", path, method, string(bodyBytes)) blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: c.Writer, } c.Writer = blw // Continue. c.Next() // Calculates the latency. end := time.Now().UTC() latency := end.Sub(start) code, message := -1, "" // get code and message var response handler.Response if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { log.Errorf(err, "response body can not unmarshal to model.Response struct, body: `%s`", blw.body.Bytes()) code = errno.InternalServerError.Code message = err.Error() } else { code = response.Code message = response.Message } log.Infof("%-13s | %-12s | %s %s | {code: %d, message: %s}", latency, ip, pad.Right(method, 5, ""), path, code, message) } } ================================================ FILE: demo16/router/middleware/requestid.go ================================================ package middleware import ( "github.com/gin-gonic/gin" "github.com/satori/go.uuid" ) func RequestId() gin.HandlerFunc { return func(c *gin.Context) { // Check for incoming header, use it if exists requestId := c.Request.Header.Get("X-Request-Id") // Create request id with UUID4 if requestId == "" { u4, _ := uuid.NewV4() requestId = u4.String() } // Expose it for use in the application c.Set("X-Request-Id", requestId) // Set X-Request-Id header c.Writer.Header().Set("X-Request-Id", requestId) c.Next() } } ================================================ FILE: demo16/router/router.go ================================================ package router import ( "net/http" "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // pprof router pprof.Register(g) // api for authentication functionalities g.POST("/login", user.Login) // The user handlers, requiring authentication u := g.Group("/v1/user") u.Use(middleware.AuthMiddleware()) { u.POST("", user.Create) u.DELETE("/:id", user.Delete) u.PUT("/:id", user.Update) u.GET("", user.List) u.GET("/:username", user.Get) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo16/service/service.go ================================================ package service import ( "fmt" "sync" "apiserver/model" "apiserver/util" ) func ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) { infos := make([]*model.UserInfo, 0) users, count, err := model.ListUser(username, offset, limit) if err != nil { return nil, count, err } ids := []uint64{} for _, user := range users { ids = append(ids, user.Id) } wg := sync.WaitGroup{} userList := model.UserList{ Lock: new(sync.Mutex), IdMap: make(map[uint64]*model.UserInfo, len(users)), } errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) go func(u *model.UserModel) { defer wg.Done() shortId, err := util.GenShortId() if err != nil { errChan <- err return } userList.Lock.Lock() defer userList.Lock.Unlock() userList.IdMap[u.Id] = &model.UserInfo{ Id: u.Id, Username: u.Username, SayHello: fmt.Sprintf("Hello %s", shortId), Password: u.Password, CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: u.UpdatedAt.Format("2006-01-02 15:04:05"), } }(u) } go func() { wg.Wait() close(finished) }() select { case <-finished: case err := <-errChan: return nil, count, err } for _, id := range ids { infos = append(infos, userList.IdMap[id]) } return infos, count, nil } ================================================ FILE: demo16/util/util.go ================================================ package util import ( "github.com/gin-gonic/gin" "github.com/teris-io/shortid" ) func GenShortId() (string, error) { return shortid.Generate() } func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") if !ok { return "" } if requestId, ok := v.(string); ok { return requestId } return "" } ================================================ FILE: demo16/util/util_test.go ================================================ package util import ( "testing" ) func TestGenShortId(t *testing.T) { shortId, err := GenShortId() if shortId == "" || err != nil { t.Error("GenShortId failed!") } t.Log("GenShortId test pass") } func BenchmarkGenShortId(b *testing.B) { for i := 0; i < b.N; i++ { GenShortId() } } func BenchmarkGenShortIdTimeConsuming(b *testing.B) { b.StopTimer() //调用该函数停止压力测试的时间计数 shortId, err := GenShortId() if shortId == "" || err != nil { b.Error(err) } b.StartTimer() //重新开始时间 for i := 0; i < b.N; i++ { GenShortId() } } ================================================ FILE: demo17/Makefile ================================================ SHELL := /bin/bash BASEDIR = $(shell pwd) # build with verison infos versionDir = "apiserver/pkg/version" gitTag = $(shell if [ "`git describe --tags --abbrev=0 2>/dev/null`" != "" ];then git describe --tags --abbrev=0; else git log --pretty=format:'%h' -n 1; fi) buildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z) gitCommit = $(shell git log --pretty=format:'%H' -n 1) gitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi) ldflags="-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}" all: gotool @go build -v -ldflags ${ldflags} . clean: rm -f apiserver find . -name "[._]*.s[a-w][a-z]" | xargs -i rm -f {} gotool: gofmt -w . go tool vet . |& grep -v vendor;true ca: openssl req -new -nodes -x509 -out conf/server.crt -keyout conf/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=127.0.0.1/emailAddress=xxxxx@qq.com" help: @echo "make - compile the source code" @echo "make clean - remove binary file and vim swp files" @echo "make gotool - run go tool 'fmt' and 'vet'" @echo "make ca - generate ca files" .PHONY: clean gotool ca help ================================================ FILE: demo17/README.md ================================================ 进阶:生成Swagger在线文档 ================================================ FILE: demo17/admin.sh ================================================ #!/bin/bash SERVER="apiserver" BASE_DIR=$PWD INTERVAL=2 # 命令行参数,需要手动指定 ARGS="" function start() { if [ "`pgrep $SERVER -u $UID`" != "" ];then echo "$SERVER already running" exit 1 fi nohup $BASE_DIR/$SERVER $ARGS server &>/dev/null & echo "sleeping..." && sleep $INTERVAL # check status if [ "`pgrep $SERVER -u $UID`" == "" ];then echo "$SERVER start failed" exit 1 fi } function status() { if [ "`pgrep $SERVER -u $UID`" != "" ];then echo $SERVER is running else echo $SERVER is not running fi } function stop() { if [ "`pgrep $SERVER -u $UID`" != "" ];then kill -9 `pgrep $SERVER -u $UID` fi echo "sleeping..." && sleep $INTERVAL if [ "`pgrep $SERVER -u $UID`" != "" ];then echo "$SERVER stop failed" exit 1 fi } case "$1" in 'start') start ;; 'stop') stop ;; 'status') status ;; 'restart') stop && start ;; *) echo "usage: $0 {start|stop|restart|status}" exit 1 ;; esac ================================================ FILE: demo17/conf/config.yaml ================================================ runmode: debug # 开发模式, debug, release, test addr: :8080 # HTTP绑定端口 name: apiserver # API Server的名字 url: http://127.0.0.1:8080 # pingServer函数请求的API服务器的ip:port max_ping_count: 10 # pingServer函数try的次数 jwt_secret: Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5 tls: addr: :8081 cert: conf/server.crt key: conf/server.key log: writers: stdout logger_level: DEBUG logger_file: log/apiserver.log log_format_text: true rollingPolicy: size log_rotate_date: 1 log_rotate_size: 1 log_backup_count: 7 db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root docker_db: name: db_apiserver addr: 127.0.0.1:3306 username: root password: root ================================================ FILE: demo17/conf/server.crt ================================================ -----BEGIN CERTIFICATE----- MIID2TCCAsGgAwIBAgIJAI/8axTNy37vMA0GCSqGSIb3DQEBBQUAMIGCMQswCQYD VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRIwEAYDVQQDDAkxMjcuMC4wLjEx GzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNvbTAeFw0xODA2MDQwNjMwMzFaFw0y ODA2MDEwNjMwMzFaMIGCMQswCQYDVQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYD VQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklU MRIwEAYDVQQDDAkxMjcuMC4wLjExGzAZBgkqhkiG9w0BCQEWDHh4eHh4QHFxLmNv bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKHbnpmaxHokFC8rnJlG 42eL4C3tog7rffv96DrwDajdFNk8DeaNUpZ+R4X4YPecumsCsbol2dDC1IHHGJUN q/A1vRjyDptMLmzp30p1aqVPcz+j8GMXtZUflLgup+HeKZRdrsk/0SaH1C3ywUBm yDMNHUxEvoFj0x9aJoPutnkEk9miZt/kF2vxlTbKFM14/TecVr3ljNwTebJAqEw9 iaZpNXn/IQV2I2PTGlxjyT6XNsCchjMTJ9y/XS9rBeX6BFHgQsV52CW1mWz8FieM /nyF9hE/TZKCpMtHWEYIdBbrxxY0BlhMxEtyIFzTha5zltFrQJGUYz7RIlyuabjk or8CAwEAAaNQME4wHQYDVR0OBBYEFIjLBQlToqUDoyOLeUd6HeaqiiLJMB8GA1Ud IwQYMBaAFIjLBQlToqUDoyOLeUd6HeaqiiLJMAwGA1UdEwQFMAMBAf8wDQYJKoZI hvcNAQEFBQADggEBAHAqpqGNUNgYCr3JvrVULDqFKmqLpvDKfLd6kmtnFzvuZLSz ANE2RXXqQ5Ms3F0oHqZQq4aOvgvfas5dBLoToPL/ip+GibQ7O/1PsPD5bZaczUel AnmClX4ewcOEWmZx9mbYOz2C8RREvKU3MoBERrWA33LquYH0+8W8WHyMKNNF97oD BgWZ8R1t3N59ySZyHcye7ddlJUubRbdyCGrHbmziHV3Cj61NJiwS2JRvhYkc71M6 8921j63qEM6i0NusFYG/w0GM3x/OeaeY/om6x+IOhJp3vIa1JPSekVyNSCwDEc3B +8WmVFaeMMXwErSkuu8nCbqRP8J1jiKgzpG/92g= -----END CERTIFICATE----- ================================================ FILE: demo17/conf/server.key ================================================ -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCh256ZmsR6JBQv K5yZRuNni+At7aIO6337/eg68A2o3RTZPA3mjVKWfkeF+GD3nLprArG6JdnQwtSB xxiVDavwNb0Y8g6bTC5s6d9KdWqlT3M/o/BjF7WVH5S4Lqfh3imUXa7JP9Emh9Qt 8sFAZsgzDR1MRL6BY9MfWiaD7rZ5BJPZombf5Bdr8ZU2yhTNeP03nFa95YzcE3my QKhMPYmmaTV5/yEFdiNj0xpcY8k+lzbAnIYzEyfcv10vawXl+gRR4ELFedgltZls /BYnjP58hfYRP02SgqTLR1hGCHQW68cWNAZYTMRLciBc04Wuc5bRa0CRlGM+0SJc rmm45KK/AgMBAAECggEASNMWvgf7pPT8u+iEchaKFLnDqQaFZu8f5TRtu67shnDK g59YpcYqRZoVtjp17pLu8Vzp+FY1dY9jq+yXq+DV3qNfLI0kc01IiiqEE+1WiYCA 2z541y0Av1LRSDl9wcuCq8Wm8der1AlDN1VFDCPyqb2Z1AoOKQtwH2ghcjUCltn4 NxuMYOVdql+Idl/7PCEha9pW3DQvPqJVXRPvBh6tB7FFuUOIq4+EFRUWWZlyCJfQ fYxB9jcFUWmAUieV3iBajg6H6QVkj/STLSgKuSktEBM2mqyL4slHXFWRt7+NQikg tOEWnqcTC4Ei2Dyam4SBXHJ1wT8AJV4kMTrN1ZBRmQKBgQDOMcIGHyuBI1aHcvPW Tkein7bslFAKsA1LBqnw+alWzSHMKEmfWbBjeW0FQTIGEYMXwIq5JhnNcS5Kb5Qt BZgbBPDb7S80DtJnkno3iJ6hpijE3X/29lezigIuGibO5MD0Na1am2BP0iU77ifV xpYvj/j/pkdcTLM7hRDImSeNIwKBgQDI9EUFkGi6H9NZEles+MCHenVRYUBT2PXe hmtd9d5f4v3CmRsXvl4Dl/slIrJ9kuZktAck3Ocx6D0hnZcj1mwq077K7H3QqKCl zRwB11H5v/imsZ1ey0hLVPhCzr2miRPM/9oL5B2st9eotzhyRcrMn+oYZSSt79S8 Q3YDiZ7TtQKBgENSr7z79GJnvVrgR4kTagRJDZrVGgVDUjPK6zXI7mdu9rgH93HW AOeZv+TVUpX0pc7diO3G6OnRKIIZSFIi33UC+fl0ydK/fCdhBhKXwuOYsvsEL0Hd UOlICEoxM7adrfqOhBlvXdTyEkItEkiUXHkPEwe1rNsQF/05By/YAbftAoGAYB2F jd2+WZezTN0bFl58J9CIoH31eKVDJEYCwJRC4nX9jcARV0/0Q5/DvcVUvf8vN2ds K1OFOTetVZC8o6WBYxKYJRLsMosVG3h5NuA4E06grYoyjQ6J644emEWuLCNQVzLg peNb1iqweb/4vZ9oGms6WqS14IPfqpRRs+t1DikCgYEAyFtQKlLmNm/EirOMqjS2 4/zMRj4B6n8BB8ZJsVC/MkuD+4B03rY44pP324cJnqVIcug9Zoijffnh1qbtZEc/ MhNCa4a2MY0qcd4r/43cGbjqv94En7e5TgkH/79W+lbR3uBayt33RuuKStauZkf/ 2eBi4LlGO5stoeD63h9Ten0= -----END PRIVATE KEY----- ================================================ FILE: demo17/config/config.go ================================================ package config import ( "strings" "github.com/fsnotify/fsnotify" "github.com/lexkong/log" "github.com/spf13/viper" ) type Config struct { Name string } func Init(cfg string) error { c := Config{ Name: cfg, } // 初始化配置文件 if err := c.initConfig(); err != nil { return err } // 初始化日志包 c.initLog() // 监控配置文件变化并热加载程序 c.watchConfig() return nil } func (c *Config) initConfig() error { if c.Name != "" { viper.SetConfigFile(c.Name) // 如果指定了配置文件,则解析指定的配置文件 } else { viper.AddConfigPath("conf") // 如果没有指定配置文件,则解析默认的配置文件 viper.SetConfigName("config") } viper.SetConfigType("yaml") // 设置配置文件格式为YAML viper.AutomaticEnv() // 读取匹配的环境变量 viper.SetEnvPrefix("APISERVER") // 读取环境变量的前缀为APISERVER replacer := strings.NewReplacer(".", "_") viper.SetEnvKeyReplacer(replacer) if err := viper.ReadInConfig(); err != nil { // viper解析配置文件 return err } return nil } func (c *Config) initLog() { passLagerCfg := log.PassLagerCfg{ Writers: viper.GetString("log.writers"), LoggerLevel: viper.GetString("log.logger_level"), LoggerFile: viper.GetString("log.logger_file"), LogFormatText: viper.GetBool("log.log_format_text"), RollingPolicy: viper.GetString("log.rollingPolicy"), LogRotateDate: viper.GetInt("log.log_rotate_date"), LogRotateSize: viper.GetInt("log.log_rotate_size"), LogBackupCount: viper.GetInt("log.log_backup_count"), } log.InitWithConfig(&passLagerCfg) } // 监控配置文件变化并热加载程序 func (c *Config) watchConfig() { viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { log.Infof("Config file changed: %s", e.Name) }) } ================================================ FILE: demo17/db.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_apiserver` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db_apiserver`; -- -- Table structure for table `tb_users` -- DROP TABLE IF EXISTS `tb_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_users` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, `createdAt` timestamp NULL DEFAULT NULL, `updatedAt` timestamp NULL DEFAULT NULL, `deletedAt` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`), KEY `idx_tb_users_deletedAt` (`deletedAt`) ) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_users` -- LOCK TABLES `tb_users` WRITE; /*!40000 ALTER TABLE `tb_users` DISABLE KEYS */; INSERT INTO `tb_users` VALUES (0,'admin','$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG','2018-05-27 16:25:33','2018-05-27 16:25:33',NULL); /*!40000 ALTER TABLE `tb_users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-05-28 0:25:41 ================================================ FILE: demo17/docs/docs.go ================================================ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at // 2018-06-18 02:15:43.723659228 +0800 CST m=+0.040196270 package docs import ( "github.com/swaggo/swag" ) var doc = `{ "swagger": "2.0", "info": { "description": "apiserver demo", "title": "Apiserver Example API", "contact": { "name": "lkong", "url": "http://www.swagger.io/support", "email": "466701708@qq.com" }, "license": {}, "version": "1.0" }, "host": "localhost:8080", "basePath": "/v1", "paths": { "/login": { "post": { "produces": [ "application/json" ], "summary": "Login generates the authentication token", "parameters": [ { "description": "Username", "name": "username", "in": "body", "required": true, "schema": { "type": "object" } }, { "description": "Password", "name": "password", "in": "body", "required": true, "schema": { "type": "object" } } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjgwMTY5MjIsImlkIjowLCJuYmYiOjE1MjgwMTY5MjIsInVzZXJuYW1lIjoiYWRtaW4ifQ.LjxrK9DuAwAzUD8-9v43NzWBN7HXsSLfebw92DKd1JQ\"}}", "schema": { "type": "string" } } } } }, "/sd/cpu": { "get": { "description": "Checks the cpu usage", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "sd" ], "summary": "Checks the cpu usage", "responses": { "200": { "description": "CRITICAL - Load average: 1.78, 1.99, 2.02 | Cores: 2", "schema": { "type": "string" } } } } }, "/sd/disk": { "get": { "description": "Checks the disk usage", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "sd" ], "summary": "Checks the disk usage", "responses": { "200": { "description": "OK - Free space: 17233MB (16GB) / 51200MB (50GB) | Used: 33%", "schema": { "type": "string" } } } } }, "/sd/health": { "get": { "description": "Shows OK as the ping-pong result", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "sd" ], "summary": "Shows OK as the ping-pong result", "responses": { "200": { "description": "OK", "schema": { "type": "string" } } } } }, "/sd/ram": { "get": { "description": "Checks the ram usage", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "sd" ], "summary": "Checks the ram usage", "responses": { "200": { "description": "OK - Free space: 402MB (0GB) / 8192MB (8GB) | Used: 4%", "schema": { "type": "string" } } } } }, "/user": { "get": { "description": "List users", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "user" ], "summary": "List the users in the database", "parameters": [ { "description": "List users", "name": "user", "in": "body", "required": true, "schema": { "type": "object", "$ref": "#/definitions/user.ListRequest" } } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"totalCount\":1,\"userList\":[{\"id\":0,\"username\":\"admin\",\"random\":\"user 'admin' get random string 'EnqntiSig'\",\"password\":\"$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG\",\"createdAt\":\"2018-05-28 00:25:33\",\"updatedAt\":\"2018-05-28 00:25:33\"}]}}", "schema": { "type": "object", "$ref": "#/definitions/user.SwaggerListResponse" } } } }, "post": { "description": "Add a new user", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "user" ], "summary": "Add new user to the database", "parameters": [ { "description": "Create a new user", "name": "user", "in": "body", "required": true, "schema": { "type": "object", "$ref": "#/definitions/user.CreateRequest" } } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"username\":\"kong\"}}", "schema": { "type": "object", "$ref": "#/definitions/user.CreateResponse" } } } } }, "/user/{id}": { "put": { "description": "Update a user by ID", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "user" ], "summary": "Update a user info by the user identifier", "parameters": [ { "type": "integer", "description": "The user's database id index num", "name": "id", "in": "path", "required": true }, { "description": "The user info", "name": "user", "in": "body", "required": true, "schema": { "type": "object", "$ref": "#/definitions/model.UserModel" } } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":null}", "schema": { "type": "object", "$ref": "#/definitions/handler.Response" } } } }, "delete": { "description": "Delete user by ID", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "user" ], "summary": "Delete an user by the user identifier", "parameters": [ { "type": "integer", "description": "The user's database id index num", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":null}", "schema": { "type": "object", "$ref": "#/definitions/handler.Response" } } } } }, "/user/{username}": { "get": { "description": "Get an user by username", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "user" ], "summary": "Get an user by the user identifier", "parameters": [ { "type": "string", "description": "Username", "name": "username", "in": "path", "required": true } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"username\":\"kong\",\"password\":\"$2a$10$E0kwtmtLZbwW/bDQ8qI8e.eHPqhQOW9tvjwpyo/p05f/f4Qvr3OmS\"}}", "schema": { "type": "object", "$ref": "#/definitions/model.UserModel" } } } } } }, "definitions": { "handler.Response": { "type": "object", "properties": { "code": { "type": "integer" }, "data": { "type": "object" }, "message": { "type": "string" } } }, "model.UserModel": { "type": "object", "required": [ "username", "password" ], "properties": { "password": { "type": "string" }, "username": { "type": "string" } } }, "user.CreateRequest": { "type": "object", "properties": { "password": { "type": "string" }, "username": { "type": "string" } } }, "user.CreateResponse": { "type": "object", "properties": { "username": { "type": "string" } } }, "user.ListRequest": { "type": "object", "properties": { "limit": { "type": "integer" }, "offset": { "type": "integer" }, "username": { "type": "string" } } }, "user.SwaggerListResponse": { "type": "object", "properties": { "totalCount": { "type": "integer" }, "userList": { "type": "array", "items": { "type": "\u0026{model UserInfo}" } } } } } }` type s struct{} func (s *s) ReadDoc() string { return doc } func init() { swag.Register(swag.Name, &s{}) } ================================================ FILE: demo17/docs/swagger/swagger.json ================================================ { "swagger": "2.0", "info": { "description": "apiserver demo", "title": "Apiserver Example API", "contact": { "name": "lkong", "url": "http://www.swagger.io/support", "email": "466701708@qq.com" }, "license": {}, "version": "1.0" }, "host": "localhost:8080", "basePath": "/v1", "paths": { "/login": { "post": { "produces": [ "application/json" ], "summary": "Login generates the authentication token", "parameters": [ { "description": "Username", "name": "username", "in": "body", "required": true, "schema": { "type": "object" } }, { "description": "Password", "name": "password", "in": "body", "required": true, "schema": { "type": "object" } } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjgwMTY5MjIsImlkIjowLCJuYmYiOjE1MjgwMTY5MjIsInVzZXJuYW1lIjoiYWRtaW4ifQ.LjxrK9DuAwAzUD8-9v43NzWBN7HXsSLfebw92DKd1JQ\"}}", "schema": { "type": "string" } } } } }, "/sd/cpu": { "get": { "description": "Checks the cpu usage", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "sd" ], "summary": "Checks the cpu usage", "responses": { "200": { "description": "CRITICAL - Load average: 1.78, 1.99, 2.02 | Cores: 2", "schema": { "type": "string" } } } } }, "/sd/disk": { "get": { "description": "Checks the disk usage", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "sd" ], "summary": "Checks the disk usage", "responses": { "200": { "description": "OK - Free space: 17233MB (16GB) / 51200MB (50GB) | Used: 33%", "schema": { "type": "string" } } } } }, "/sd/health": { "get": { "description": "Shows OK as the ping-pong result", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "sd" ], "summary": "Shows OK as the ping-pong result", "responses": { "200": { "description": "OK", "schema": { "type": "string" } } } } }, "/sd/ram": { "get": { "description": "Checks the ram usage", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "sd" ], "summary": "Checks the ram usage", "responses": { "200": { "description": "OK - Free space: 402MB (0GB) / 8192MB (8GB) | Used: 4%", "schema": { "type": "string" } } } } }, "/user": { "get": { "description": "List users", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "user" ], "summary": "List the users in the database", "parameters": [ { "description": "List users", "name": "user", "in": "body", "required": true, "schema": { "type": "object", "$ref": "#/definitions/user.ListRequest" } } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"totalCount\":1,\"userList\":[{\"id\":0,\"username\":\"admin\",\"random\":\"user 'admin' get random string 'EnqntiSig'\",\"password\":\"$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG\",\"createdAt\":\"2018-05-28 00:25:33\",\"updatedAt\":\"2018-05-28 00:25:33\"}]}}", "schema": { "type": "object", "$ref": "#/definitions/user.SwaggerListResponse" } } } }, "post": { "description": "Add a new user", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "user" ], "summary": "Add new user to the database", "parameters": [ { "description": "Create a new user", "name": "user", "in": "body", "required": true, "schema": { "type": "object", "$ref": "#/definitions/user.CreateRequest" } } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"username\":\"kong\"}}", "schema": { "type": "object", "$ref": "#/definitions/user.CreateResponse" } } } } }, "/user/{id}": { "put": { "description": "Update a user by ID", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "user" ], "summary": "Update a user info by the user identifier", "parameters": [ { "type": "integer", "description": "The user's database id index num", "name": "id", "in": "path", "required": true }, { "description": "The user info", "name": "user", "in": "body", "required": true, "schema": { "type": "object", "$ref": "#/definitions/model.UserModel" } } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":null}", "schema": { "type": "object", "$ref": "#/definitions/handler.Response" } } } }, "delete": { "description": "Delete user by ID", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "user" ], "summary": "Delete an user by the user identifier", "parameters": [ { "type": "integer", "description": "The user's database id index num", "name": "id", "in": "path", "required": true } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":null}", "schema": { "type": "object", "$ref": "#/definitions/handler.Response" } } } } }, "/user/{username}": { "get": { "description": "Get an user by username", "consumes": [ "application/json" ], "produces": [ "application/json" ], "tags": [ "user" ], "summary": "Get an user by the user identifier", "parameters": [ { "type": "string", "description": "Username", "name": "username", "in": "path", "required": true } ], "responses": { "200": { "description": "{\"code\":0,\"message\":\"OK\",\"data\":{\"username\":\"kong\",\"password\":\"$2a$10$E0kwtmtLZbwW/bDQ8qI8e.eHPqhQOW9tvjwpyo/p05f/f4Qvr3OmS\"}}", "schema": { "type": "object", "$ref": "#/definitions/model.UserModel" } } } } } }, "definitions": { "handler.Response": { "type": "object", "properties": { "code": { "type": "integer" }, "data": { "type": "object" }, "message": { "type": "string" } } }, "model.UserModel": { "type": "object", "required": [ "username", "password" ], "properties": { "password": { "type": "string" }, "username": { "type": "string" } } }, "user.CreateRequest": { "type": "object", "properties": { "password": { "type": "string" }, "username": { "type": "string" } } }, "user.CreateResponse": { "type": "object", "properties": { "username": { "type": "string" } } }, "user.ListRequest": { "type": "object", "properties": { "limit": { "type": "integer" }, "offset": { "type": "integer" }, "username": { "type": "string" } } }, "user.SwaggerListResponse": { "type": "object", "properties": { "totalCount": { "type": "integer" }, "userList": { "type": "array", "items": { "type": "\u0026{model UserInfo}" } } } } } } ================================================ FILE: demo17/docs/swagger/swagger.yaml ================================================ basePath: /v1 definitions: handler.Response: properties: code: type: integer data: type: object message: type: string type: object model.UserModel: properties: password: type: string username: type: string required: - username - password type: object user.CreateRequest: properties: password: type: string username: type: string type: object user.CreateResponse: properties: username: type: string type: object user.ListRequest: properties: limit: type: integer offset: type: integer username: type: string type: object user.SwaggerListResponse: properties: totalCount: type: integer userList: items: type: '&{model UserInfo}' type: array type: object host: localhost:8080 info: contact: email: 466701708@qq.com name: lkong url: http://www.swagger.io/support description: apiserver demo license: {} title: Apiserver Example API version: "1.0" paths: /login: post: parameters: - description: Username in: body name: username required: true schema: type: object - description: Password in: body name: password required: true schema: type: object produces: - application/json responses: "200": description: '{"code":0,"message":"OK","data":{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjgwMTY5MjIsImlkIjowLCJuYmYiOjE1MjgwMTY5MjIsInVzZXJuYW1lIjoiYWRtaW4ifQ.LjxrK9DuAwAzUD8-9v43NzWBN7HXsSLfebw92DKd1JQ"}}' schema: type: string summary: Login generates the authentication token /sd/cpu: get: consumes: - application/json description: Checks the cpu usage produces: - application/json responses: "200": description: 'CRITICAL - Load average: 1.78, 1.99, 2.02 | Cores: 2' schema: type: string summary: Checks the cpu usage tags: - sd /sd/disk: get: consumes: - application/json description: Checks the disk usage produces: - application/json responses: "200": description: 'OK - Free space: 17233MB (16GB) / 51200MB (50GB) | Used: 33%' schema: type: string summary: Checks the disk usage tags: - sd /sd/health: get: consumes: - application/json description: Shows OK as the ping-pong result produces: - application/json responses: "200": description: OK schema: type: string summary: Shows OK as the ping-pong result tags: - sd /sd/ram: get: consumes: - application/json description: Checks the ram usage produces: - application/json responses: "200": description: 'OK - Free space: 402MB (0GB) / 8192MB (8GB) | Used: 4%' schema: type: string summary: Checks the ram usage tags: - sd /user: get: consumes: - application/json description: List users parameters: - description: List users in: body name: user required: true schema: $ref: '#/definitions/user.ListRequest' type: object produces: - application/json responses: "200": description: '{"code":0,"message":"OK","data":{"totalCount":1,"userList":[{"id":0,"username":"admin","random":"user ''admin'' get random string ''EnqntiSig''","password":"$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG","createdAt":"2018-05-28 00:25:33","updatedAt":"2018-05-28 00:25:33"}]}}' schema: $ref: '#/definitions/user.SwaggerListResponse' type: object summary: List the users in the database tags: - user post: consumes: - application/json description: Add a new user parameters: - description: Create a new user in: body name: user required: true schema: $ref: '#/definitions/user.CreateRequest' type: object produces: - application/json responses: "200": description: '{"code":0,"message":"OK","data":{"username":"kong"}}' schema: $ref: '#/definitions/user.CreateResponse' type: object summary: Add new user to the database tags: - user /user/{id}: delete: consumes: - application/json description: Delete user by ID parameters: - description: The user's database id index num in: path name: id required: true type: integer produces: - application/json responses: "200": description: '{"code":0,"message":"OK","data":null}' schema: $ref: '#/definitions/handler.Response' type: object summary: Delete an user by the user identifier tags: - user put: consumes: - application/json description: Update a user by ID parameters: - description: The user's database id index num in: path name: id required: true type: integer - description: The user info in: body name: user required: true schema: $ref: '#/definitions/model.UserModel' type: object produces: - application/json responses: "200": description: '{"code":0,"message":"OK","data":null}' schema: $ref: '#/definitions/handler.Response' type: object summary: Update a user info by the user identifier tags: - user /user/{username}: get: consumes: - application/json description: Get an user by username parameters: - description: Username in: path name: username required: true type: string produces: - application/json responses: "200": description: '{"code":0,"message":"OK","data":{"username":"kong","password":"$2a$10$E0kwtmtLZbwW/bDQ8qI8e.eHPqhQOW9tvjwpyo/p05f/f4Qvr3OmS"}}' schema: $ref: '#/definitions/model.UserModel' type: object summary: Get an user by the user identifier tags: - user swagger: "2.0" ================================================ FILE: demo17/handler/handler.go ================================================ package handler import ( "net/http" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) type Response struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data"` } func SendResponse(c *gin.Context, err error, data interface{}) { code, message := errno.DecodeErr(err) // always return http.StatusOK c.JSON(http.StatusOK, Response{ Code: code, Message: message, Data: data, }) } ================================================ FILE: demo17/handler/sd/check.go ================================================ package sd import ( "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) const ( B = 1 KB = 1024 * B MB = 1024 * KB GB = 1024 * MB ) // @Summary Shows OK as the ping-pong result // @Description Shows OK as the ping-pong result // @Tags sd // @Accept json // @Produce json // @Success 200 {string} plain "OK" // @Router /sd/health [get] func HealthCheck(c *gin.Context) { message := "OK" c.String(http.StatusOK, "\n"+message) } // @Summary Checks the disk usage // @Description Checks the disk usage // @Tags sd // @Accept json // @Produce json // @Success 200 {string} plain "OK - Free space: 17233MB (16GB) / 51200MB (50GB) | Used: 33%" // @Router /sd/disk [get] func DiskCheck(c *gin.Context) { u, _ := disk.Usage("/") usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusOK text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } // @Summary Checks the cpu usage // @Description Checks the cpu usage // @Tags sd // @Accept json // @Produce json // @Success 200 {string} plain "CRITICAL - Load average: 1.78, 1.99, 2.02 | Cores: 2" // @Router /sd/cpu [get] func CPUCheck(c *gin.Context) { cores, _ := cpu.Counts(false) a, _ := load.Avg() l1 := a.Load1 l5 := a.Load5 l15 := a.Load15 status := http.StatusOK text := "OK" if l5 >= float64(cores-1) { status = http.StatusInternalServerError text = "CRITICAL" } else if l5 >= float64(cores-2) { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Load average: %.2f, %.2f, %.2f | Cores: %d", text, l1, l5, l15, cores) c.String(status, "\n"+message) } // @Summary Checks the ram usage // @Description Checks the ram usage // @Tags sd // @Accept json // @Produce json // @Success 200 {string} plain "OK - Free space: 402MB (0GB) / 8192MB (8GB) | Used: 4%" // @Router /sd/ram [get] func RAMCheck(c *gin.Context) { u, _ := mem.VirtualMemory() usedMB := int(u.Used) / MB usedGB := int(u.Used) / GB totalMB := int(u.Total) / MB totalGB := int(u.Total) / GB usedPercent := int(u.UsedPercent) status := http.StatusOK text := "OK" if usedPercent >= 95 { status = http.StatusInternalServerError text = "CRITICAL" } else if usedPercent >= 90 { status = http.StatusTooManyRequests text = "WARNING" } message := fmt.Sprintf("%s - Free space: %dMB (%dGB) / %dMB (%dGB) | Used: %d%%", text, usedMB, usedGB, totalMB, totalGB, usedPercent) c.String(status, "\n"+message) } ================================================ FILE: demo17/handler/user/create.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // @Summary Add new user to the database // @Description Add a new user // @Tags user // @Accept json // @Produce json // @Param user body user.CreateRequest true "Create a new user" // @Success 200 {object} user.CreateResponse "{"code":0,"message":"OK","data":{"username":"kong"}}" // @Router /user [post] func Create(c *gin.Context) { log.Info("User Create function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) var r CreateRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } u := model.UserModel{ Username: r.Username, Password: r.Password, } // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Insert the user to the database. if err := u.Create(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } rsp := CreateResponse{ Username: r.Username, } // Show the user information. SendResponse(c, nil, rsp) } ================================================ FILE: demo17/handler/user/delete.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // @Summary Delete an user by the user identifier // @Description Delete user by ID // @Tags user // @Accept json // @Produce json // @Param id path uint64 true "The user's database id index num" // @Success 200 {object} handler.Response "{"code":0,"message":"OK","data":null}" // @Router /user/{id} [delete] func Delete(c *gin.Context) { userId, _ := strconv.Atoi(c.Param("id")) if err := model.DeleteUser(uint64(userId)); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo17/handler/user/get.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "github.com/gin-gonic/gin" ) // @Summary Get an user by the user identifier // @Description Get an user by username // @Tags user // @Accept json // @Produce json // @Param username path string true "Username" // @Success 200 {object} model.UserModel "{"code":0,"message":"OK","data":{"username":"kong","password":"$2a$10$E0kwtmtLZbwW/bDQ8qI8e.eHPqhQOW9tvjwpyo/p05f/f4Qvr3OmS"}}" // @Router /user/{username} [get] func Get(c *gin.Context) { username := c.Param("username") // Get the user by the `username` from the database. user, err := model.GetUser(username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } SendResponse(c, nil, user) } ================================================ FILE: demo17/handler/user/list.go ================================================ package user import ( . "apiserver/handler" "apiserver/pkg/errno" "apiserver/service" "github.com/gin-gonic/gin" "github.com/lexkong/log" ) // @Summary List the users in the database // @Description List users // @Tags user // @Accept json // @Produce json // @Param user body user.ListRequest true "List users" // @Success 200 {object} user.SwaggerListResponse "{"code":0,"message":"OK","data":{"totalCount":1,"userList":[{"id":0,"username":"admin","random":"user 'admin' get random string 'EnqntiSig'","password":"$2a$10$veGcArz47VGj7l9xN7g2iuT9TF21jLI1YGXarGzvARNdnt4inC9PG","createdAt":"2018-05-28 00:25:33","updatedAt":"2018-05-28 00:25:33"}]}}" // @Router /user [get] func List(c *gin.Context) { log.Info("List function called.") var r ListRequest if err := c.Bind(&r); err != nil { SendResponse(c, errno.ErrBind, nil) return } infos, count, err := service.ListUser(r.Username, r.Offset, r.Limit) if err != nil { SendResponse(c, err, nil) return } SendResponse(c, nil, ListResponse{ TotalCount: count, UserList: infos, }) } ================================================ FILE: demo17/handler/user/login.go ================================================ package user import ( . "apiserver/handler" "apiserver/model" "apiserver/pkg/auth" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) // @Summary Login generates the authentication token // @Produce json // @Param username body string true "Username" // @Param password body string true "Password" // @Success 200 {string} json "{"code":0,"message":"OK","data":{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjgwMTY5MjIsImlkIjowLCJuYmYiOjE1MjgwMTY5MjIsInVzZXJuYW1lIjoiYWRtaW4ifQ.LjxrK9DuAwAzUD8-9v43NzWBN7HXsSLfebw92DKd1JQ"}}" // @Router /login [post] func Login(c *gin.Context) { // Binding the data with the user struct. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // Get the user information by the login username. d, err := model.GetUser(u.Username) if err != nil { SendResponse(c, errno.ErrUserNotFound, nil) return } // Compare the login password with the user password. if err := auth.Compare(d.Password, u.Password); err != nil { SendResponse(c, errno.ErrPasswordIncorrect, nil) return } // Sign the json web token. t, err := token.Sign(c, token.Context{ID: d.Id, Username: d.Username}, "") if err != nil { SendResponse(c, errno.ErrToken, nil) return } SendResponse(c, nil, model.Token{Token: t}) } ================================================ FILE: demo17/handler/user/update.go ================================================ package user import ( "strconv" . "apiserver/handler" "apiserver/model" "apiserver/pkg/errno" "apiserver/util" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/lexkong/log/lager" ) // @Summary Update a user info by the user identifier // @Description Update a user by ID // @Tags user // @Accept json // @Produce json // @Param id path uint64 true "The user's database id index num" // @Param user body model.UserModel true "The user info" // @Success 200 {object} handler.Response "{"code":0,"message":"OK","data":null}" // @Router /user/{id} [put] func Update(c *gin.Context) { log.Info("Update function called.", lager.Data{"X-Request-Id": util.GetReqID(c)}) // Get the user id from the url parameter. userId, _ := strconv.Atoi(c.Param("id")) // Binding the user data. var u model.UserModel if err := c.Bind(&u); err != nil { SendResponse(c, errno.ErrBind, nil) return } // We update the record based on the user id. u.Id = uint64(userId) // Validate the data. if err := u.Validate(); err != nil { SendResponse(c, errno.ErrValidation, nil) return } // Encrypt the user password. if err := u.Encrypt(); err != nil { SendResponse(c, errno.ErrEncrypt, nil) return } // Save changed fields. if err := u.Update(); err != nil { SendResponse(c, errno.ErrDatabase, nil) return } SendResponse(c, nil, nil) } ================================================ FILE: demo17/handler/user/user.go ================================================ package user import ( "apiserver/model" ) type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } type CreateResponse struct { Username string `json:"username"` } type ListRequest struct { Username string `json:"username"` Offset int `json:"offset"` Limit int `json:"limit"` } type ListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []*model.UserInfo `json:"userList"` } type SwaggerListResponse struct { TotalCount uint64 `json:"totalCount"` UserList []model.UserInfo `json:"userList"` } ================================================ FILE: demo17/main.go ================================================ package main import ( "encoding/json" "errors" "fmt" "net/http" _ "net/http/pprof" "os" "time" "apiserver/config" "apiserver/model" v "apiserver/pkg/version" "apiserver/router" "apiserver/router/middleware" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/spf13/pflag" "github.com/spf13/viper" ) var ( cfg = pflag.StringP("config", "c", "", "apiserver config file path.") version = pflag.BoolP("version", "v", false, "show version info.") ) // @title Apiserver Example API // @version 1.0 // @description apiserver demo // @contact.name lkong // @contact.url http://www.swagger.io/support // @contact.email 466701708@qq.com // @host localhost:8080 // @BasePath /v1 func main() { pflag.Parse() if *version { v := v.Get() marshalled, err := json.MarshalIndent(&v, "", " ") if err != nil { fmt.Printf("%v\n", err) os.Exit(1) } fmt.Println(string(marshalled)) return } // init config if err := config.Init(*cfg); err != nil { panic(err) } // init db model.DB.Init() defer model.DB.Close() // Set gin mode. gin.SetMode(viper.GetString("runmode")) // Create the Gin engine. g := gin.New() // Routes. router.Load( // Cores. g, // Middlwares. middleware.Logging(), middleware.RequestId(), ) // Ping the server to make sure the router is working. go func() { if err := pingServer(); err != nil { log.Fatal("The router has no response, or it might took too long to start up.", err) } log.Info("The router has been deployed successfully.") }() // Start to listening the incoming requests. cert := viper.GetString("tls.cert") key := viper.GetString("tls.key") if cert != "" && key != "" { go func() { log.Infof("Start to listening the incoming requests on https address: %s", viper.GetString("tls.addr")) log.Info(http.ListenAndServeTLS(viper.GetString("tls.addr"), cert, key, g).Error()) }() } log.Infof("Start to listening the incoming requests on http address: %s", viper.GetString("addr")) log.Info(http.ListenAndServe(viper.GetString("addr"), g).Error()) } // pingServer pings the http server to make sure the router is working. func pingServer() error { for i := 0; i < viper.GetInt("max_ping_count"); i++ { // Ping the server by sending a GET request to `/health`. resp, err := http.Get(viper.GetString("url") + "/sd/health") if err == nil && resp.StatusCode == 200 { return nil } // Sleep for a second to continue the next ping. log.Info("Waiting for the router, retry in 1 second.") time.Sleep(time.Second) } return errors.New("Cannot connect to the router.") } ================================================ FILE: demo17/model/init.go ================================================ package model import ( "fmt" "github.com/lexkong/log" "github.com/spf13/viper" // MySQL driver. "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" ) type Database struct { Self *gorm.DB Docker *gorm.DB } var DB *Database func openDB(username, password, addr, name string) *gorm.DB { config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=%t&loc=%s", username, password, addr, name, true, //"Asia/Shanghai"), "Local") db, err := gorm.Open("mysql", config) if err != nil { log.Errorf(err, "Database connection failed. Database name: %s", name) } // set for db connection setupDB(db) return db } func setupDB(db *gorm.DB) { db.LogMode(viper.GetBool("gormlog")) //db.DB().SetMaxOpenConns(20000) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 db.DB().SetMaxIdleConns(0) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 } // used for cli func InitSelfDB() *gorm.DB { return openDB(viper.GetString("db.username"), viper.GetString("db.password"), viper.GetString("db.addr"), viper.GetString("db.name")) } func GetSelfDB() *gorm.DB { return InitSelfDB() } func InitDockerDB() *gorm.DB { return openDB(viper.GetString("docker_db.username"), viper.GetString("docker_db.password"), viper.GetString("docker_db.addr"), viper.GetString("docker_db.name")) } func GetDockerDB() *gorm.DB { return InitDockerDB() } func (db *Database) Init() { DB = &Database{ Self: GetSelfDB(), Docker: GetDockerDB(), } } func (db *Database) Close() { DB.Self.Close() DB.Docker.Close() } ================================================ FILE: demo17/model/model.go ================================================ package model import ( "sync" "time" ) type BaseModel struct { Id uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"-"` CreatedAt time.Time `gorm:"column:createdAt" json:"-"` UpdatedAt time.Time `gorm:"column:updatedAt" json:"-"` DeletedAt *time.Time `gorm:"column:deletedAt" sql:"index" json:"-"` } type UserInfo struct { Id uint64 `json:"id"` Username string `json:"username"` SayHello string `json:"sayHello"` Password string `json:"password"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } type UserList struct { Lock *sync.Mutex IdMap map[uint64]*UserInfo } // Token represents a JSON web token. type Token struct { Token string `json:"token"` } ================================================ FILE: demo17/model/user.go ================================================ package model import ( "fmt" "apiserver/pkg/auth" "apiserver/pkg/constvar" validator "gopkg.in/go-playground/validator.v9" ) // User represents a registered user. type UserModel struct { BaseModel Username string `json:"username" gorm:"column:username;not null" binding:"required" validate:"min=1,max=32"` Password string `json:"password" gorm:"column:password;not null" binding:"required" validate:"min=5,max=128"` } func (c *UserModel) TableName() string { return "tb_users" } // Create creates a new user account. func (u *UserModel) Create() error { return DB.Self.Create(&u).Error } // DeleteUser deletes the user by the user identifier. func DeleteUser(id uint64) error { user := UserModel{} user.BaseModel.Id = id return DB.Self.Delete(&user).Error } // Update updates an user account information. func (u *UserModel) Update() error { return DB.Self.Save(u).Error } // GetUser gets an user by the user identifier. func GetUser(username string) (*UserModel, error) { u := &UserModel{} d := DB.Self.Where("username = ?", username).First(&u) return u, d.Error } // ListUser List all users func ListUser(username string, offset, limit int) ([]*UserModel, uint64, error) { if limit == 0 { limit = constvar.DefaultLimit } users := make([]*UserModel, 0) var count uint64 where := fmt.Sprintf("username like '%%%s%%'", username) if err := DB.Self.Model(&UserModel{}).Where(where).Count(&count).Error; err != nil { return users, count, err } if err := DB.Self.Where(where).Offset(offset).Limit(limit).Order("id desc").Find(&users).Error; err != nil { return users, count, err } return users, count, nil } // Compare with the plain text password. Returns true if it's the same as the encrypted one (in the `User` struct). func (u *UserModel) Compare(pwd string) (err error) { err = auth.Compare(u.Password, pwd) return } // Encrypt the user password. func (u *UserModel) Encrypt() (err error) { u.Password, err = auth.Encrypt(u.Password) return } // Validate the fields. func (u *UserModel) Validate() error { validate := validator.New() return validate.Struct(u) } ================================================ FILE: demo17/pkg/auth/auth.go ================================================ package auth import "golang.org/x/crypto/bcrypt" // Encrypt encrypts the plain text with bcrypt. func Encrypt(source string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(source), bcrypt.DefaultCost) return string(hashedBytes), err } // Compare compares the encrypted text with the plain text if it's the same. func Compare(hashedPassword, password string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } ================================================ FILE: demo17/pkg/constvar/constvar.go ================================================ package constvar const ( DefaultLimit = 50 ) ================================================ FILE: demo17/pkg/errno/code.go ================================================ package errno var ( // Common errors OK = &Errno{Code: 0, Message: "OK"} InternalServerError = &Errno{Code: 10001, Message: "Internal server error"} ErrBind = &Errno{Code: 10002, Message: "Error occurred while binding the request body to the struct."} ErrValidation = &Errno{Code: 20001, Message: "Validation failed."} ErrDatabase = &Errno{Code: 20002, Message: "Database error."} ErrToken = &Errno{Code: 20003, Message: "Error occurred while signing the JSON web token."} // user errors ErrEncrypt = &Errno{Code: 20101, Message: "Error occurred while encrypting the user password."} ErrUserNotFound = &Errno{Code: 20102, Message: "The user was not found."} ErrTokenInvalid = &Errno{Code: 20103, Message: "The token was invalid."} ErrPasswordIncorrect = &Errno{Code: 20104, Message: "The password was incorrect."} ) ================================================ FILE: demo17/pkg/errno/errno.go ================================================ package errno import "fmt" type Errno struct { Code int Message string } func (err Errno) Error() string { return err.Message } // Err represents an error type Err struct { Code int Message string Err error } func New(errno *Errno, err error) *Err { return &Err{Code: errno.Code, Message: errno.Message, Err: err} } func (err *Err) Add(message string) error { err.Message += " " + message return err } func (err *Err) Addf(format string, args ...interface{}) error { err.Message += " " + fmt.Sprintf(format, args...) return err } func (err *Err) Error() string { return fmt.Sprintf("Err - code: %d, message: %s, error: %s", err.Code, err.Message, err.Err) } func IsErrUserNotFound(err error) bool { code, _ := DecodeErr(err) return code == ErrUserNotFound.Code } func DecodeErr(err error) (int, string) { if err == nil { return OK.Code, OK.Message } switch typed := err.(type) { case *Err: return typed.Code, typed.Message case *Errno: return typed.Code, typed.Message default: } return InternalServerError.Code, err.Error() } ================================================ FILE: demo17/pkg/token/token.go ================================================ package token import ( "errors" "fmt" "time" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" "github.com/spf13/viper" ) var ( // ErrMissingHeader means the `Authorization` header was empty. ErrMissingHeader = errors.New("The length of the `Authorization` header is zero.") ) // Context is the context of the JSON web token. type Context struct { ID uint64 Username string } // secretFunc validates the secret format. func secretFunc(secret string) jwt.Keyfunc { return func(token *jwt.Token) (interface{}, error) { // Make sure the `alg` is what we except. if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(secret), nil } } // Parse validates the token with the specified secret, // and returns the context if the token was valid. func Parse(tokenString string, secret string) (*Context, error) { ctx := &Context{} // Parse the token. token, err := jwt.Parse(tokenString, secretFunc(secret)) // Parse error. if err != nil { return ctx, err // Read the token if it's valid. } else if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { ctx.ID = uint64(claims["id"].(float64)) ctx.Username = claims["username"].(string) return ctx, nil // Other errors. } else { return ctx, err } } // ParseRequest gets the token from the header and // pass it to the Parse function to parses the token. func ParseRequest(c *gin.Context) (*Context, error) { header := c.Request.Header.Get("Authorization") // Load the jwt secret from config secret := viper.GetString("jwt_secret") if len(header) == 0 { return &Context{}, ErrMissingHeader } var t string // Parse the header to get the token part. fmt.Sscanf(header, "Bearer %s", &t) return Parse(t, secret) } // Sign signs the context with the specified secret. func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) { // Load the jwt secret from the Gin config if the secret isn't specified. if secret == "" { secret = viper.GetString("jwt_secret") } // The token content. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "id": c.ID, "username": c.Username, "nbf": time.Now().Unix(), "iat": time.Now().Unix(), }) // Sign the token with the specified secret. tokenString, err = token.SignedString([]byte(secret)) return } ================================================ FILE: demo17/pkg/version/base.go ================================================ package version var ( gitTag string = "" gitCommit string = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty" buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') ) ================================================ FILE: demo17/pkg/version/doc.go ================================================ package version ================================================ FILE: demo17/pkg/version/version.go ================================================ package version import ( "fmt" "runtime" ) // Info contains versioning information. type Info struct { GitTag string `json:"gitTag"` GitCommit string `json:"gitCommit"` GitTreeState string `json:"gitTreeState"` BuildDate string `json:"buildDate"` GoVersion string `json:"goVersion"` Compiler string `json:"compiler"` Platform string `json:"platform"` } // String returns info as a human-friendly version string. func (info Info) String() string { return info.GitTag } func Get() Info { return Info{ GitTag: gitTag, GitCommit: gitCommit, GitTreeState: gitTreeState, BuildDate: buildDate, GoVersion: runtime.Version(), Compiler: runtime.Compiler, Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), } } ================================================ FILE: demo17/router/middleware/auth.go ================================================ package middleware import ( "apiserver/handler" "apiserver/pkg/errno" "apiserver/pkg/token" "github.com/gin-gonic/gin" ) func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Parse the json web token. if _, err := token.ParseRequest(c); err != nil { handler.SendResponse(c, errno.ErrTokenInvalid, nil) c.Abort() return } c.Next() } } ================================================ FILE: demo17/router/middleware/header.go ================================================ package middleware import ( "net/http" "time" "github.com/gin-gonic/gin" ) // NoCache is a middleware function that appends headers // to prevent the client from caching the HTTP response. func NoCache(c *gin.Context) { c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) c.Next() } // Options is a middleware function that appends headers // for options requests and aborts then exits the middleware // chain and ends the request. func Options(c *gin.Context) { if c.Request.Method != "OPTIONS" { c.Next() } else { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") c.Header("Content-Type", "application/json") c.AbortWithStatus(200) } } // Secure is a middleware function that appends security // and resource access headers. func Secure(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("X-Frame-Options", "DENY") c.Header("X-Content-Type-Options", "nosniff") c.Header("X-XSS-Protection", "1; mode=block") if c.Request.TLS != nil { c.Header("Strict-Transport-Security", "max-age=31536000") } // Also consider adding Content-Security-Policy headers // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") } ================================================ FILE: demo17/router/middleware/logging.go ================================================ package middleware import ( "bytes" "encoding/json" "io/ioutil" "regexp" "time" "apiserver/handler" "apiserver/pkg/errno" "github.com/gin-gonic/gin" "github.com/lexkong/log" "github.com/willf/pad" ) type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer } func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) } // Logging is a middleware function that logs the each request. func Logging() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now().UTC() path := c.Request.URL.Path reg := regexp.MustCompile("(/v1/user|/login)") if !reg.MatchString(path) { return } // Skip for the health check requests. if path == "/sd/health" || path == "/sd/ram" || path == "/sd/cpu" || path == "/sd/disk" { return } // Read the Body content var bodyBytes []byte if c.Request.Body != nil { bodyBytes, _ = ioutil.ReadAll(c.Request.Body) } // Restore the io.ReadCloser to its original state c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // The basic informations. method := c.Request.Method ip := c.ClientIP() //log.Debugf("New request come in, path: %s, Method: %s, body `%s`", path, method, string(bodyBytes)) blw := &bodyLogWriter{ body: bytes.NewBufferString(""), ResponseWriter: c.Writer, } c.Writer = blw // Continue. c.Next() // Calculates the latency. end := time.Now().UTC() latency := end.Sub(start) code, message := -1, "" // get code and message var response handler.Response if err := json.Unmarshal(blw.body.Bytes(), &response); err != nil { log.Errorf(err, "response body can not unmarshal to model.Response struct, body: `%s`", blw.body.Bytes()) code = errno.InternalServerError.Code message = err.Error() } else { code = response.Code message = response.Message } log.Infof("%-13s | %-12s | %s %s | {code: %d, message: %s}", latency, ip, pad.Right(method, 5, ""), path, code, message) } } ================================================ FILE: demo17/router/middleware/requestid.go ================================================ package middleware import ( "github.com/gin-gonic/gin" "github.com/satori/go.uuid" ) func RequestId() gin.HandlerFunc { return func(c *gin.Context) { // Check for incoming header, use it if exists requestId := c.Request.Header.Get("X-Request-Id") // Create request id with UUID4 if requestId == "" { u4, _ := uuid.NewV4() requestId = u4.String() } // Expose it for use in the application c.Set("X-Request-Id", requestId) // Set X-Request-Id header c.Writer.Header().Set("X-Request-Id", requestId) c.Next() } } ================================================ FILE: demo17/router/router.go ================================================ package router import ( "net/http" _ "apiserver/docs" // docs is generated by Swag CLI, you have to import it. "apiserver/handler/sd" "apiserver/handler/user" "apiserver/router/middleware" "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" "github.com/swaggo/gin-swagger" "github.com/swaggo/gin-swagger/swaggerFiles" ) // Load loads the middlewares, routes, handlers. func Load(g *gin.Engine, mw ...gin.HandlerFunc) *gin.Engine { // Middlewares. g.Use(gin.Recovery()) g.Use(middleware.NoCache) g.Use(middleware.Options) g.Use(middleware.Secure) g.Use(mw...) // 404 Handler. g.NoRoute(func(c *gin.Context) { c.String(http.StatusNotFound, "The incorrect API route.") }) // swagger api docs g.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // pprof router pprof.Register(g) // api for authentication functionalities g.POST("/login", user.Login) // The user handlers, requiring authentication u := g.Group("/v1/user") u.Use(middleware.AuthMiddleware()) { u.POST("", user.Create) u.DELETE("/:id", user.Delete) u.PUT("/:id", user.Update) u.GET("", user.List) u.GET("/:username", user.Get) } // The health check handlers svcd := g.Group("/sd") { svcd.GET("/health", sd.HealthCheck) svcd.GET("/disk", sd.DiskCheck) svcd.GET("/cpu", sd.CPUCheck) svcd.GET("/ram", sd.RAMCheck) } return g } ================================================ FILE: demo17/service/service.go ================================================ package service import ( "fmt" "sync" "apiserver/model" "apiserver/util" ) func ListUser(username string, offset, limit int) ([]*model.UserInfo, uint64, error) { infos := make([]*model.UserInfo, 0) users, count, err := model.ListUser(username, offset, limit) if err != nil { return nil, count, err } ids := []uint64{} for _, user := range users { ids = append(ids, user.Id) } wg := sync.WaitGroup{} userList := model.UserList{ Lock: new(sync.Mutex), IdMap: make(map[uint64]*model.UserInfo, len(users)), } errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) go func(u *model.UserModel) { defer wg.Done() shortId, err := util.GenShortId() if err != nil { errChan <- err return } userList.Lock.Lock() defer userList.Lock.Unlock() userList.IdMap[u.Id] = &model.UserInfo{ Id: u.Id, Username: u.Username, SayHello: fmt.Sprintf("Hello %s", shortId), Password: u.Password, CreatedAt: u.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: u.UpdatedAt.Format("2006-01-02 15:04:05"), } }(u) } go func() { wg.Wait() close(finished) }() select { case <-finished: case err := <-errChan: return nil, count, err } for _, id := range ids { infos = append(infos, userList.IdMap[id]) } return infos, count, nil } ================================================ FILE: demo17/util/util.go ================================================ package util import ( "github.com/gin-gonic/gin" "github.com/teris-io/shortid" ) func GenShortId() (string, error) { return shortid.Generate() } func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") if !ok { return "" } if requestId, ok := v.(string); ok { return requestId } return "" } ================================================ FILE: demo17/util/util_test.go ================================================ package util import ( "testing" ) func TestGenShortId(t *testing.T) { shortId, err := GenShortId() if shortId == "" || err != nil { t.Error("GenShortId failed!") } t.Log("GenShortId test pass") } func BenchmarkGenShortId(b *testing.B) { for i := 0; i < b.N; i++ { GenShortId() } } func BenchmarkGenShortIdTimeConsuming(b *testing.B) { b.StopTimer() //调用该函数停止压力测试的时间计数 shortId, err := GenShortId() if shortId == "" || err != nil { b.Error(err) } b.StartTimer() //重新开始时间 for i := 0; i < b.N; i++ { GenShortId() } }